From efd8dae98911cd3add83c149a5cdfdc793fb2a65 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Thu, 6 Jun 2024 19:28:01 +0300 Subject: [PATCH 01/18] feat: POC of l2 bots unification on Scroll example --- .gitignore | 1 + l2-bridges/.gitignore | 2 + l2-bridges/Dockerfile | 24 + l2-bridges/common/abi/ERC20Short.json | 34 + l2-bridges/common/abi/OssifiableProxy.json | 28 + l2-bridges/common/abi/ProxyAdmin.json | 151 + .../abi/TransparentUpgradableProxy.json | 76 + .../common/clients/eth_provider_client.ts | 39 + l2-bridges/common/clients/l2_block_client.ts | 115 + l2-bridges/common/clients/l2_client.ts | 248 + l2-bridges/common/entity/blockDto.ts | 11 + l2-bridges/common/entity/events.ts | 12 + l2-bridges/common/services/bridge_balance.ts | 101 + l2-bridges/common/services/event_watcher.ts | 57 + l2-bridges/common/utils/constants.ts | 6 + l2-bridges/common/utils/error.ts | 17 + l2-bridges/common/utils/finding.helpers.ts | 23 + l2-bridges/common/utils/mutex.ts | 41 + l2-bridges/common/utils/time.ts | 15 + l2-bridges/common/utils/version.ts | 27 + l2-bridges/common/utils/write-version.js | 33 + l2-bridges/mantle/abi/L2ERC20TokenBridge.json | 764 +++ l2-bridges/mantle/package.json | 27 + l2-bridges/mantle/tsconfig.json | 6 + l2-bridges/package.json | 52 + l2-bridges/push-agent.js | 73 + l2-bridges/scroll/package.json | 27 + l2-bridges/scroll/src/abi/L2LidoGateway.json | 861 +++ l2-bridges/scroll/src/agent.ts | 257 + l2-bridges/scroll/src/constants.ts | 128 + l2-bridges/scroll/tsconfig.json | 6 + l2-bridges/start-agent.ts | 36 + l2-bridges/tsconfig.json | 14 + l2-bridges/yarn.lock | 4707 +++++++++++++++++ utils/write-version.js | 8 +- 35 files changed, 8023 insertions(+), 4 deletions(-) create mode 100644 l2-bridges/.gitignore create mode 100644 l2-bridges/Dockerfile create mode 100644 l2-bridges/common/abi/ERC20Short.json create mode 100644 l2-bridges/common/abi/OssifiableProxy.json create mode 100644 l2-bridges/common/abi/ProxyAdmin.json create mode 100644 l2-bridges/common/abi/TransparentUpgradableProxy.json create mode 100644 l2-bridges/common/clients/eth_provider_client.ts create mode 100644 l2-bridges/common/clients/l2_block_client.ts create mode 100644 l2-bridges/common/clients/l2_client.ts create mode 100644 l2-bridges/common/entity/blockDto.ts create mode 100644 l2-bridges/common/entity/events.ts create mode 100644 l2-bridges/common/services/bridge_balance.ts create mode 100644 l2-bridges/common/services/event_watcher.ts create mode 100644 l2-bridges/common/utils/constants.ts create mode 100644 l2-bridges/common/utils/error.ts create mode 100644 l2-bridges/common/utils/finding.helpers.ts create mode 100644 l2-bridges/common/utils/mutex.ts create mode 100644 l2-bridges/common/utils/time.ts create mode 100644 l2-bridges/common/utils/version.ts create mode 100755 l2-bridges/common/utils/write-version.js create mode 100644 l2-bridges/mantle/abi/L2ERC20TokenBridge.json create mode 100644 l2-bridges/mantle/package.json create mode 100644 l2-bridges/mantle/tsconfig.json create mode 100644 l2-bridges/package.json create mode 100644 l2-bridges/push-agent.js create mode 100644 l2-bridges/scroll/package.json create mode 100644 l2-bridges/scroll/src/abi/L2LidoGateway.json create mode 100644 l2-bridges/scroll/src/agent.ts create mode 100644 l2-bridges/scroll/src/constants.ts create mode 100644 l2-bridges/scroll/tsconfig.json create mode 100644 l2-bridges/start-agent.ts create mode 100644 l2-bridges/tsconfig.json create mode 100644 l2-bridges/yarn.lock diff --git a/.gitignore b/.gitignore index 26c6a457..81884ed8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ forta.config.json version.json .DS_Store .idea +.vscode diff --git a/l2-bridges/.gitignore b/l2-bridges/.gitignore new file mode 100644 index 00000000..fc2b7247 --- /dev/null +++ b/l2-bridges/.gitignore @@ -0,0 +1,2 @@ +common/generated +**/src/generated diff --git a/l2-bridges/Dockerfile b/l2-bridges/Dockerfile new file mode 100644 index 00000000..691f8c35 --- /dev/null +++ b/l2-bridges/Dockerfile @@ -0,0 +1,24 @@ +# Build stage: compile Typescript to Javascript +FROM node:20.9.0-alpine3.18 AS base + +FROM base as builder + +ARG L2_NETWORK_NAME +ENV L2_NETWORK_NAME ${L2_NETWORK_NAME} + +WORKDIR /app + +COPY package.json yarn.lock tsconfig.json \ + version.json \ + start-agent.ts \ + ./ + +COPY common ./common/ +COPY $L2_NETWORK_NAME ./$L2_NETWORK_NAME/ + +RUN yarn install --frozen-lockfile + +# Build app +RUN yarn build + +CMD yarn start ${L2_NETWORK_NAME} prod diff --git a/l2-bridges/common/abi/ERC20Short.json b/l2-bridges/common/abi/ERC20Short.json new file mode 100644 index 00000000..81b8dd00 --- /dev/null +++ b/l2-bridges/common/abi/ERC20Short.json @@ -0,0 +1,34 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/l2-bridges/common/abi/OssifiableProxy.json b/l2-bridges/common/abi/OssifiableProxy.json new file mode 100644 index 00000000..b73d7c16 --- /dev/null +++ b/l2-bridges/common/abi/OssifiableProxy.json @@ -0,0 +1,28 @@ +[ + { + "inputs": [], + "name": "proxy__getAdmin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proxy__getImplementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/l2-bridges/common/abi/ProxyAdmin.json b/l2-bridges/common/abi/ProxyAdmin.json new file mode 100644 index 00000000..7793e0d8 --- /dev/null +++ b/l2-bridges/common/abi/ProxyAdmin.json @@ -0,0 +1,151 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "contract ITransparentUpgradeableProxy", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "changeProxyAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ITransparentUpgradeableProxy", + "name": "proxy", + "type": "address" + } + ], + "name": "getProxyAdmin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ITransparentUpgradeableProxy", + "name": "proxy", + "type": "address" + } + ], + "name": "getProxyImplementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ITransparentUpgradeableProxy", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "upgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ITransparentUpgradeableProxy", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "implementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } +] diff --git a/l2-bridges/common/abi/TransparentUpgradableProxy.json b/l2-bridges/common/abi/TransparentUpgradableProxy.json new file mode 100644 index 00000000..27423cca --- /dev/null +++ b/l2-bridges/common/abi/TransparentUpgradableProxy.json @@ -0,0 +1,76 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_logic", + "type": "address" + }, + { + "internalType": "address", + "name": "admin_", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] diff --git a/l2-bridges/common/clients/eth_provider_client.ts b/l2-bridges/common/clients/eth_provider_client.ts new file mode 100644 index 00000000..b0a214cf --- /dev/null +++ b/l2-bridges/common/clients/eth_provider_client.ts @@ -0,0 +1,39 @@ +import { Logger } from 'winston' +import { IL1BridgeBalanceClient } from '../services/bridge_balance' +import * as E from 'fp-ts/Either' +import BigNumber from 'bignumber.js' +import { NetworkError } from '../utils/error' +import { retryAsync } from 'ts-retry' +import { ERC20Short as wStEthRunner } from '../generated' + +const DELAY_IN_500MS = 500 +const ATTEMPTS_5 = 5 + +export class ETHProvider implements IL1BridgeBalanceClient { + private readonly wStEthRunner: wStEthRunner + private readonly logger: Logger + + constructor(logger: Logger, wStEthRunner: wStEthRunner) { + this.wStEthRunner = wStEthRunner + this.logger = logger + } + + public async getWstEthBalance(l1blockNumber: number, address: string): Promise> { + try { + const out = await retryAsync( + async (): Promise => { + const [balance] = await this.wStEthRunner.functions.balanceOf(address, { + blockTag: l1blockNumber, + }) + + return balance.toString() + }, + { delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 }, + ) + + return E.right(new BigNumber(out)) + } catch (e) { + return E.left(new NetworkError(e, `Could not call wStEthRunner.functions.balanceOf`)) + } + } +} diff --git a/l2-bridges/common/clients/l2_block_client.ts b/l2-bridges/common/clients/l2_block_client.ts new file mode 100644 index 00000000..05bde037 --- /dev/null +++ b/l2-bridges/common/clients/l2_block_client.ts @@ -0,0 +1,115 @@ +import { BlockDto } from '../entity/blockDto' +import { IL2Client } from './l2_client' +import { Log } from '@ethersproject/abstract-provider' +import { Finding } from 'forta-agent' +import * as E from 'fp-ts/Either' +import { networkAlert } from '../utils/finding.helpers' +import { Logger } from 'winston' +import { elapsedTime } from '../utils/time' + + +export class L2BlockClient { + private provider: IL2Client + private logger: Logger + private cachedBlockDto: BlockDto | undefined = undefined + + constructor(provider: IL2Client, logger: Logger) { + this.provider = provider + this.logger = logger + } + + public async getL2Blocks(): Promise> { + const start = new Date().getTime() + const blocks = await this.fetchL2Blocks() + this.logger.info(elapsedTime(L2BlockClient.name + '.' + this.getL2Blocks.name, start)) + + return blocks + } + + public async getL2Logs(workingBlocks: BlockDto[]): Promise> { + const start = new Date().getTime() + const logs = await this.fetchL2Logs(workingBlocks) + this.logger.info(elapsedTime(L2BlockClient.name + '.' + this.getL2Logs.name, start)) + + return logs + } + + private async fetchL2Blocks(): Promise> { + const out: BlockDto[] = [] + + if (this.cachedBlockDto === undefined) { + const l2Block = await this.provider.getLatestL2Block() + if (E.isLeft(l2Block)) { + return E.left( + networkAlert( + l2Block.left, + `Error in ${L2BlockClient.name}.${this.getL2Blocks.name}:21`, + `Could not call l2Provider.getLatestL2Block`, + 0, + ), + ) + } + + this.cachedBlockDto = { + number: l2Block.right.number, + timestamp: l2Block.right.timestamp, + } + + out.push(this.cachedBlockDto) + } else { + const latestL2Block = await this.provider.getLatestL2Block() + if (E.isLeft(latestL2Block)) { + this.cachedBlockDto = undefined + return E.left( + networkAlert( + latestL2Block.left, + `Error in ${L2BlockClient.name}.${this.getL2Blocks.name}:39`, + `Could not call l2Provider.getLatestL2Block`, + 0, + ), + ) + } + + const l2Blocks = await this.provider.fetchL2Blocks(this.cachedBlockDto.number, latestL2Block.right.number - 1) + for (const l2Block of l2Blocks) { + out.push({ + number: l2Block.number, + timestamp: l2Block.timestamp, + }) + } + + this.cachedBlockDto = { + number: latestL2Block.right.number, + timestamp: latestL2Block.right.timestamp, + } + + // hint: we requested blocks like [cachedBlockDto.number, latestBlock.number) + // and here we do [cachedBlockDto.number, latestBlock.number] + out.push({ + number: latestL2Block.right.number, + timestamp: latestL2Block.right.timestamp, + }) + } + + return E.right(out) + } + + private async fetchL2Logs(workingL2Blocks: BlockDto[]): Promise> { + const l2Logs = await this.provider.getL2Logs( + workingL2Blocks[0].number, + workingL2Blocks[workingL2Blocks.length - 1].number, + ) + if (E.isLeft(l2Logs)) { + return E.left( + networkAlert( + l2Logs.left, + `Error in ${L2BlockClient.name}.${this.getL2Logs.name}:76`, + `Could not call l2Provider.getL2Logs`, + workingL2Blocks[workingL2Blocks.length - 1].number, + ), + ) + } + + return E.right(l2Logs.right) + } +} diff --git a/l2-bridges/common/clients/l2_client.ts b/l2-bridges/common/clients/l2_client.ts new file mode 100644 index 00000000..f89c43ea --- /dev/null +++ b/l2-bridges/common/clients/l2_client.ts @@ -0,0 +1,248 @@ +import { Block, Log } from '@ethersproject/abstract-provider' +import { ethers } from 'forta-agent' +import * as E from 'fp-ts/Either' +import { retryAsync } from 'ts-retry' +import { NetworkError } from '../utils/error' +import { Logger } from 'winston' +import { WithdrawalRecord } from '../entity/blockDto' +import { Event } from '@ethersproject/contracts' +import BigNumber from 'bignumber.js' +// import { WithdrawERC20Event } from '../generated/L2LidoGateway' +import { IL2BridgeBalanceClient } from '../services/bridge_balance' +import { ERC20Short as BridgedWstEthRunner, /*L2LidoGateway as ScrollL2BridgeRunner */ } from '../generated' + +// export abstract class IMonitorWithdrawalsClient { +// public abstract getWithdrawalEvents( +// fromBlockNumber: number, +// toBlockNumber: number, +// ): Promise> + +// public abstract getWithdrawalRecords( +// withdrawalEvents: WithdrawERC20Event[], +// ): Promise> +// } + +export abstract class IL2Client { + public abstract fetchL2Blocks(startBlock: number, endBlock: number): Promise + + public abstract getL2Logs(startBlock: number, endBlock: number): Promise> + + public abstract getLatestL2Block(): Promise> +} + +export class L2Client implements IL2Client, /*IMonitorWithdrawalsClient,*/ IL2BridgeBalanceClient { + private readonly logger: Logger + private readonly jsonRpcProvider: ethers.providers.JsonRpcProvider + // private readonly scrollL2BridgeRunner: ScrollL2BridgeRunner + private readonly withdrawalEventsFetcher: (fromBlockNumber: number, toBlockNumber: number) => Promise + private readonly bridgedWstEthRunner: BridgedWstEthRunner + + constructor( + jsonRpcProvider: ethers.providers.JsonRpcProvider, + logger: Logger, + // scrollL2BridgeRunner: ScrollL2BridgeRunner, + withdrawalEventsFetcher: (fromBlockNumber: number, toBlockNumber: number) => Promise, + bridgedWstEthRunner: BridgedWstEthRunner, + ) { + this.jsonRpcProvider = jsonRpcProvider + this.logger = logger + // this.scrollL2BridgeRunner = scrollL2BridgeRunner + this.withdrawalEventsFetcher = withdrawalEventsFetcher + this.bridgedWstEthRunner = bridgedWstEthRunner + } + + public async fetchL2Blocks(startBlock: number, endBlock: number): Promise { + const batchRequests = [] + for (let i = startBlock; i <= endBlock; i++) { + batchRequests.push({ + jsonrpc: '2.0', + method: 'eth_getBlockByNumber', + params: [`0x${i.toString(16)}`, false], + id: i, // Use a unique identifier for each request + }) + } + const formatter = new ethers.providers.Formatter() + const doRequest = (request: unknown[]): Promise => { + return retryAsync( + async (): Promise => { + const response: Response = await fetch(this.jsonRpcProvider.connection.url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(request), + }) + + if (!response.ok) { + throw new NetworkError(`Status: ${response.status}, request: ${JSON.stringify(request)}`, 'FetchBlocksErr') + } + + const result: Block[] = [] + const objects = (await response.json()) as unknown[] + + for (const obj of objects) { + if (Object.prototype.hasOwnProperty.call(obj, 'result')) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + result.push(formatter.block(obj.result)) + } else { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + throw new Error(obj.error.message) + } + } + + return result + }, + { delay: 500, maxTry: 5 }, + ) + } + + const chunkSize = 25 + const out: Block[] = [] + + let allowedExtraRequest: number = 30 + for (let i = 0; i < batchRequests.length; i += chunkSize) { + const request = batchRequests.slice(i, i + chunkSize) + try { + const blocks = await doRequest(request) + out.push(...blocks) + } catch (e) { + this.logger.warn(`${e}`) + if (allowedExtraRequest === 0) { + break + } + + batchRequests.push(...request) + allowedExtraRequest -= 1 + } + } + + return out.toSorted((a, b) => a.number - b.number) + } + + public async getL2Logs(startBlock: number, endBlock: number): Promise> { + const logs: Log[] = [] + const batchSize = 15 + + for (let i = startBlock; i <= endBlock; i += batchSize) { + const start = i + const end = Math.min(i + batchSize - 1, endBlock) + + let chunkLogs: Log[] = [] + try { + chunkLogs = await retryAsync( + async (): Promise => { + return await this.jsonRpcProvider.send('eth_getLogs', [ + { + fromBlock: `0x${start.toString(16)}`, + toBlock: `0x${end.toString(16)}`, + }, + ]) + }, + { delay: 500, maxTry: 5 }, + ) + } catch (e) { + this.logger.warn( + `Could not fetch blocks logs. cause: ${e}, startBlock: ${start}, toBlock: ${end}. Total ${end - start}`, + ) + + continue + } + + logs.push(...chunkLogs) + } + + return E.right(logs) + } + + public async getLatestL2Block(): Promise> { + try { + const out = await retryAsync( + async (): Promise => { + return await this.jsonRpcProvider.getBlock('latest') + }, + { delay: 500, maxTry: 5 }, + ) + + return E.right(out) + } catch (e) { + return E.left(new NetworkError(e, `Could not fetch latest block`)) + } + } + + public async getWithdrawalEvents( + fromBlockNumber: number, + toBlockNumber: number, + ): Promise> { + try { + const out = await retryAsync( + + async (): Promise => { + return await this.withdrawalEventsFetcher(fromBlockNumber, toBlockNumber) + // this.scrollL2BridgeRunner.queryFilter( + // this.scrollL2BridgeRunner.filters.WithdrawERC20(), + // fromBlockNumber, + // toBlockNumber, + // ) + }, + { delay: 500, maxTry: 5 }, + ) + + return E.right(out) + } catch (e) { + return E.left(new NetworkError(e, `Could not fetch l2 bridge withdrawal events`)) + } + } + + public async getWithdrawalRecords( + withdrawalEvents: L2BridgeWithdrawalEvent[], + ): Promise> { + const out: WithdrawalRecord[] = [] + + for (const withdrawEvent of withdrawalEvents) { + if (withdrawEvent.args) { + let block: Block + try { + block = await retryAsync( + async (): Promise => { + return await withdrawEvent.getBlock() + }, + { delay: 500, maxTry: 5 }, + ) + + const record: WithdrawalRecord = { + time: block.timestamp, + // TODO: args.amount might be different for L2 networks + amount: new BigNumber(String(withdrawEvent.args.amount)), + } + + out.push(record) + } catch (e) { + return E.left(new NetworkError(e, `Could not fetch block from withdrawEvent`)) + } + } + } + + return E.right(out) + } + + public async getWstEthTotalSupply(l2blockNumber: number): Promise> { + try { + const out = await retryAsync( + async (): Promise => { + const [balance] = await this.bridgedWstEthRunner.functions.totalSupply({ + blockTag: l2blockNumber, + }) + + return balance.toString() + }, + { delay: 500, maxTry: 5 }, + ) + + return E.right(new BigNumber(out)) + } catch (e) { + return E.left(new NetworkError(e, `Could not call bridgedWstEthRunner.functions.totalSupply`)) + } + } +} diff --git a/l2-bridges/common/entity/blockDto.ts b/l2-bridges/common/entity/blockDto.ts new file mode 100644 index 00000000..b7aec27c --- /dev/null +++ b/l2-bridges/common/entity/blockDto.ts @@ -0,0 +1,11 @@ +import BigNumber from 'bignumber.js' + +export type BlockDto = { + number: number + timestamp: number +} + +export type WithdrawalRecord = { + time: number + amount: BigNumber +} diff --git a/l2-bridges/common/entity/events.ts b/l2-bridges/common/entity/events.ts new file mode 100644 index 00000000..c806a8c6 --- /dev/null +++ b/l2-bridges/common/entity/events.ts @@ -0,0 +1,12 @@ +import { FindingSeverity, FindingType } from 'forta-agent' + +export type EventOfNotice = { + name: string + address: string + event: string + alertId: string + description: CallableFunction + severity: FindingSeverity + type: FindingType + uniqueKey: string +} diff --git a/l2-bridges/common/services/bridge_balance.ts b/l2-bridges/common/services/bridge_balance.ts new file mode 100644 index 00000000..c2bac492 --- /dev/null +++ b/l2-bridges/common/services/bridge_balance.ts @@ -0,0 +1,101 @@ +import { Finding, FindingSeverity, FindingType } from 'forta-agent' +import { elapsedTime } from '../utils/time' +import { Logger } from 'winston' +import BigNumber from 'bignumber.js' +import * as E from 'fp-ts/Either' +import { getUniqueKey, networkAlert } from '../utils/finding.helpers' +import { ETH_DECIMALS } from '../utils/constants' + +export abstract class IL1BridgeBalanceClient { + abstract getWstEthBalance(l1blockNumber: number, address: string): Promise> +} + +export abstract class IL2BridgeBalanceClient { + abstract getWstEthTotalSupply(l1blockNumber: number): Promise> +} + +export class BridgeBalanceSrv { + private name = `BridgeBalanceSrv` + private readonly l2NetworkName: string + private readonly logger: Logger + private readonly clientL1: IL1BridgeBalanceClient + private readonly clientL2: IL2BridgeBalanceClient + + private readonly l1BridgeAddress: string + + constructor( + l2NetworkName: string, + logger: Logger, + clientL1: IL1BridgeBalanceClient, + clientL2: IL2BridgeBalanceClient, + l1BridgeAddress: string, + ) { + this.l2NetworkName = l2NetworkName + this.logger = logger + this.clientL1 = clientL1 + this.clientL2 = clientL2 + this.l1BridgeAddress = l1BridgeAddress + } + + async handleBlock(l1BlockNumber: number, l2BlockNumbers: number[]): Promise { + const start = new Date().getTime() + + const findings = await this.handleBridgeBalanceWstETH(l1BlockNumber, l2BlockNumbers) + + this.logger.info(elapsedTime(this.name + '.' + this.handleBlock.name, start)) + return findings + } + + private async handleBridgeBalanceWstETH(l1BlockNumber: number, l2BlockNumbers: number[]): Promise { + const wstETHBalance_onL1Bridge = await this.clientL1.getWstEthBalance(l1BlockNumber, this.l1BridgeAddress) + + const out: Finding[] = [] + if (E.isLeft(wstETHBalance_onL1Bridge)) { + return [ + networkAlert( + wstETHBalance_onL1Bridge.left, + `Error in ${BridgeBalanceSrv.name}.${this.handleBridgeBalanceWstETH.name}:36`, + `Could not call clientL1.getWstEth`, + l1BlockNumber, + ), + ] + } + + for (const l2blockNumber of l2BlockNumbers) { + const wstETHTotalSupply_onL2 = await this.clientL2.getWstEthTotalSupply(l2blockNumber) + + if (E.isLeft(wstETHTotalSupply_onL2)) { + out.push( + networkAlert( + wstETHTotalSupply_onL2.left, + `Error in ${BridgeBalanceSrv.name}.${this.handleBridgeBalanceWstETH.name}:62`, + `Could not call clientL2.getWstEth`, + l2blockNumber, + ), + ) + + continue + } + + if (wstETHTotalSupply_onL2.right.isGreaterThan(wstETHBalance_onL1Bridge.right)) { + out.push( + Finding.fromObject({ + name: `🚨🚨🚨 ${this.l2NetworkName} bridge balance mismatch 🚨🚨🚨`, + description: + `Total supply of bridged wstETH is greater than balanceOf L1 bridge side!\n` + + `L2 total supply: ${wstETHTotalSupply_onL2.right.dividedBy(ETH_DECIMALS).toFixed(2)}\n` + + `L1 balanceOf: ${wstETHBalance_onL1Bridge.right.dividedBy(ETH_DECIMALS).toFixed(2)}\n\n` + + `ETH: ${l1BlockNumber}\n` + + `${this.l2NetworkName}: ${l2blockNumber}\n`, + alertId: 'BRIDGE-BALANCE-MISMATCH', + severity: FindingSeverity.Critical, + type: FindingType.Suspicious, + uniqueKey: getUniqueKey('c0563986-fce6-4036-898e-31cc9583d49e', l1BlockNumber + l2blockNumber), + }), + ) + } + } + + return out + } +} diff --git a/l2-bridges/common/services/event_watcher.ts b/l2-bridges/common/services/event_watcher.ts new file mode 100644 index 00000000..9234dafe --- /dev/null +++ b/l2-bridges/common/services/event_watcher.ts @@ -0,0 +1,57 @@ +import { EventOfNotice } from '../entity/events' +import { Log } from '@ethersproject/abstract-provider' +import { filterLog, Finding } from 'forta-agent' +import { getUniqueKey } from '../utils/finding.helpers' +import { elapsedTime } from '../utils/time' +import { Logger } from 'winston' +import { formatAddress } from 'forta-agent/dist/cli/utils' + +export class EventWatcher { + private readonly name: string + private readonly eventsToFinding: EventOfNotice[] + private readonly logger: Logger + + constructor(botName: string, events: EventOfNotice[], logger: Logger) { + this.name = botName + this.eventsToFinding = events + this.logger = logger + } + + public getName(): string { + return this.name + } + + public handleLogs(logs: Log[]): Finding[] { + const start = new Date().getTime() + const addresses: string[] = [] + + for (const log of logs) { + addresses.push(log.address) + } + + const findings: Finding[] = [] + for (const eventToFinding of this.eventsToFinding) { + const ind = addresses.indexOf(formatAddress(eventToFinding.address)) + if (ind >= 0) { + const filteredEvents = filterLog(logs, eventToFinding.event, eventToFinding.address) + + for (const event of filteredEvents) { + findings.push( + Finding.fromObject({ + name: eventToFinding.name, + description: eventToFinding.description(event.args), + alertId: eventToFinding.alertId, + severity: eventToFinding.severity, + type: eventToFinding.type, + metadata: { args: String(event.args) }, + uniqueKey: getUniqueKey(eventToFinding.uniqueKey, logs[ind].blockNumber), + }), + ) + } + } + } + + this.logger.info(elapsedTime(this.getName() + '.' + this.handleLogs.name, start)) + return findings + } +} diff --git a/l2-bridges/common/utils/constants.ts b/l2-bridges/common/utils/constants.ts new file mode 100644 index 00000000..940a50e9 --- /dev/null +++ b/l2-bridges/common/utils/constants.ts @@ -0,0 +1,6 @@ +import BigNumber from 'bignumber.js' + + +export const ETH_DECIMALS = new BigNumber(10).pow(18) +export const MAINNET_CHAIN_ID = 1 +export const DRPC_URL = 'https://eth.drpc.org/' \ No newline at end of file diff --git a/l2-bridges/common/utils/error.ts b/l2-bridges/common/utils/error.ts new file mode 100644 index 00000000..b81d7726 --- /dev/null +++ b/l2-bridges/common/utils/error.ts @@ -0,0 +1,17 @@ +export class NetworkError extends Error { + constructor(e: unknown, name?: string) { + super() + + if (name !== undefined) { + this.name = name + } + + if (e instanceof Error) { + this.stack = e.stack + this.message = e.message + this.cause = e.cause + } else { + this.message = `${e}` + } + } +} diff --git a/l2-bridges/common/utils/finding.helpers.ts b/l2-bridges/common/utils/finding.helpers.ts new file mode 100644 index 00000000..fe456796 --- /dev/null +++ b/l2-bridges/common/utils/finding.helpers.ts @@ -0,0 +1,23 @@ +import { Finding, FindingSeverity, FindingType } from 'forta-agent' + +export const NetworkErrorFinding = 'NETWORK-ERROR' + +export function networkAlert(err: Error, name: string, desc: string, blockNumber: number): Finding { + return Finding.fromObject({ + name: name, + description: desc, + alertId: NetworkErrorFinding, + severity: FindingSeverity.Unknown, + type: FindingType.Degraded, + metadata: { + stack: `${err.stack}`, + message: `${err.message}`, + name: `${err.name}`, + }, + uniqueKey: getUniqueKey(name, blockNumber), + }) +} + +export function getUniqueKey(uniqueKey: string, blockNumber: number): string { + return uniqueKey + '-' + blockNumber.toString() +} diff --git a/l2-bridges/common/utils/mutex.ts b/l2-bridges/common/utils/mutex.ts new file mode 100644 index 00000000..80923483 --- /dev/null +++ b/l2-bridges/common/utils/mutex.ts @@ -0,0 +1,41 @@ +import { Mutex, MutexInterface, withTimeout } from 'async-mutex' + +export class DataRW { + private mutex: MutexInterface + private value: T[] + + constructor(initialValue: T[]) { + this.mutex = withTimeout(new Mutex(), 100) + this.value = initialValue + } + + async read(): Promise { + await this.mutex.acquire() + try { + const out = this.value + this.value = [] + + return out + } finally { + this.mutex.release() + } + } + + async write(newValue: T[]): Promise { + await this.mutex.acquire() + try { + this.value.push(...newValue) + } finally { + this.mutex.release() + } + } + + async append(newValue: T): Promise { + await this.mutex.acquire() + try { + this.value.push(newValue) + } finally { + this.mutex.release() + } + } +} diff --git a/l2-bridges/common/utils/time.ts b/l2-bridges/common/utils/time.ts new file mode 100644 index 00000000..acd3cb72 --- /dev/null +++ b/l2-bridges/common/utils/time.ts @@ -0,0 +1,15 @@ +export function formatTime(timeInMillis: number): string { + const seconds = (timeInMillis / 1000).toFixed(3) + return `${seconds} seconds` +} + +export function elapsedTime(methodName: string, startTime: number): string { + const elapsedTime = new Date().getTime() - startTime + return `${methodName} started at ${formatTimeToHumanReadable(new Date(startTime))}. Elapsed: ${formatTime( + elapsedTime, + )}` +} + +function formatTimeToHumanReadable(date: Date): string { + return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}` +} diff --git a/l2-bridges/common/utils/version.ts b/l2-bridges/common/utils/version.ts new file mode 100644 index 00000000..a9ad02fc --- /dev/null +++ b/l2-bridges/common/utils/version.ts @@ -0,0 +1,27 @@ +import path from 'path' + +export interface Version { + desc: string + commitHash: string + commitHashShort: string + commitMsg: string + commitMsgShort: string + isWdClean: boolean +} + +export default readVersion(path.join(__dirname, '..', '..', './version.json')) + +function readVersion(versionFilePath: string): Version { + try { + return require(versionFilePath) + } catch (e) { + return { + desc: 'unknown', + commitHash: 'unknown', + commitHashShort: 'unknown', + commitMsg: 'unknown', + commitMsgShort: 'unknown', + isWdClean: false, + } + } +} diff --git a/l2-bridges/common/utils/write-version.js b/l2-bridges/common/utils/write-version.js new file mode 100755 index 00000000..9719f5c1 --- /dev/null +++ b/l2-bridges/common/utils/write-version.js @@ -0,0 +1,33 @@ +const childProcess = require("child_process"); +const fs = require("fs"); + +const commitHash = childProcess + .execSync("git rev-parse HEAD") + .toString("utf-8") + .trim(); +const commitMsg = childProcess + .execSync("git log -1 --pretty=%B") + .toString("utf-8") + .trim(); +const gitStatusOutput = childProcess + .execSync("git status --porcelain") + .toString("utf-8") + .trim(); + +const commitHashShort = commitHash.substr(0, 7); +const commitMsgShort = commitMsg.split("\n")[0]; +const isWdClean = gitStatusOutput === ""; +const commitHashSuffux = isWdClean ? "" : " [dirty]"; + +const version = { + desc: `${commitHashShort}${commitHashSuffux} (${commitMsgShort})`, + commitHash: commitHash + commitHashSuffux, + commitHashShort: commitHashShort + commitHashSuffux, + commitMsg, + commitMsgShort, + isWdClean, +}; + +console.log("Writing to version.json:", JSON.stringify(version, null, " ")); + +fs.writeFileSync("version.json", JSON.stringify(version)); diff --git a/l2-bridges/mantle/abi/L2ERC20TokenBridge.json b/l2-bridges/mantle/abi/L2ERC20TokenBridge.json new file mode 100644 index 00000000..51cf279f --- /dev/null +++ b/l2-bridges/mantle/abi/L2ERC20TokenBridge.json @@ -0,0 +1,764 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "messenger_", + "type": "address" + }, + { + "internalType": "address", + "name": "l1TokenBridge_", + "type": "address" + }, + { + "internalType": "address", + "name": "l1Token_", + "type": "address" + }, + { + "internalType": "address", + "name": "l2Token_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "ErrorAccountIsZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorAlreadyInitialized", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorDepositsDisabled", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorDepositsEnabled", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorSenderNotEOA", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorUnauthorizedMessenger", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorUnsupportedL1Token", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorUnsupportedL2Token", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorWithdrawalsDisabled", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorWithdrawalsEnabled", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorWrongCrossDomainSender", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_l1Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "_l2Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "_from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "DepositFailed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_l1Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "_l2Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "_from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "DepositFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "disabler", + "type": "address" + } + ], + "name": "DepositsDisabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "enabler", + "type": "address" + } + ], + "name": "DepositsEnabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "admin", + "type": "address" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_l1Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "_l2Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "_from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "WithdrawalInitiated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "disabler", + "type": "address" + } + ], + "name": "WithdrawalsDisabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "enabler", + "type": "address" + } + ], + "name": "WithdrawalsEnabled", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEPOSITS_DISABLER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEPOSITS_ENABLER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "WITHDRAWALS_DISABLER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "WITHDRAWALS_ENABLER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "disableDeposits", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "disableWithdrawals", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "enableDeposits", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "enableWithdrawals", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "l1Token_", + "type": "address" + }, + { + "internalType": "address", + "name": "l2Token_", + "type": "address" + }, + { + "internalType": "address", + "name": "from_", + "type": "address" + }, + { + "internalType": "address", + "name": "to_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data_", + "type": "bytes" + } + ], + "name": "finalizeDeposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "admin_", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isDepositsEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isInitialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isWithdrawalsEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l1Token", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l1TokenBridge", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l2Token", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "messenger", + "outputs": [ + { + "internalType": "contract ICrossDomainMessenger", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "l2Token_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + }, + { + "internalType": "uint32", + "name": "l1Gas_", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "data_", + "type": "bytes" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "l2Token_", + "type": "address" + }, + { + "internalType": "address", + "name": "to_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + }, + { + "internalType": "uint32", + "name": "l1Gas_", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "data_", + "type": "bytes" + } + ], + "name": "withdrawTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/l2-bridges/mantle/package.json b/l2-bridges/mantle/package.json new file mode 100644 index 00000000..3924c0e4 --- /dev/null +++ b/l2-bridges/mantle/package.json @@ -0,0 +1,27 @@ +{ + "name": "lido-l2-bridge-mantle-bot", + "displayName": "Forta Agent Starter", + "version": "0.0.1", + "description": "Lido Detection Bot for Mantle part of L2 bridge", + "repository": { + "type": "git", + "directory": "https://github.com/lidofinance/alerting-forta/tree/main/l2-bridges" + }, + "license": "MIT", + "chainIds": [ + 1 + ], + "chainSettings": { + "default": { + "shards": 1, + "target": 5 + } + }, + "scripts": { + }, + "dependencies": { + }, + "devDependencies": { + }, + "packageManager": "yarn@1.22.21" +} diff --git a/l2-bridges/mantle/tsconfig.json b/l2-bridges/mantle/tsconfig.json new file mode 100644 index 00000000..2bb8ea80 --- /dev/null +++ b/l2-bridges/mantle/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../dist/mantle/src" + } +} diff --git a/l2-bridges/package.json b/l2-bridges/package.json new file mode 100644 index 00000000..a423bc2e --- /dev/null +++ b/l2-bridges/package.json @@ -0,0 +1,52 @@ +{ + "license": "MIT", + "scripts": { + "build": "yarn generate-types && tsc", + + "start:scroll": "yarn start scroll dev", + "push:scroll": "yarn update-version && node push-agent.js scroll", + + "postinstall": "yarn generate-types", + "generate-types": "typechain --target=ethers-v5 --out-dir=./scroll/src/generated ./scroll/src/abi/* && typechain --target=ethers-v5 --out-dir=./common/generated ./common/abi/*", + "update-version": "node ./common/utils/write-version.js ", + "start": "node start-agent.ts" + }, + "dependencies": { + "@types/lodash": "^4.14.202", + "async-mutex": "^0.4.0", + "bignumber.js": "^9.1.2", + "ethers": "^5.5.1", + "forta-agent": "^0.1.48", + "fp-ts": "^2.16.1", + "lodash": "^4.17.21", + "ts-retry": "^4.2.4", + "winston": "^3.11.0" + }, + "devDependencies": { + "@ethersproject/abi": "^5.0.0", + "@ethersproject/providers": "^5.0.0", + "@jest/globals": "^29.7.0", + "@tsconfig/node20": "^20.1.2", + "@typechain/ethers-v5": "^11.1.2", + "@types/jest": "^29.5.10", + "@types/nodemon": "^1.19.0", + "@types/ws": "^8.5.10", + "@typescript-eslint/eslint-plugin": "^6.12.0", + "@typescript-eslint/parser": "^6.12.0", + "eslint": "^8.54.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-jest": "^27.6.0", + "eslint-plugin-prettier": "^5.0.1", + "husky": "^8.0.3", + "jest": "^29.7.0", + "nodemon": "^3.0.1", + "postinstall": "^0.8.0", + "prettier": "^3.1.0", + "shelljs": "^0.8.4", + "ts-generator": "^0.1.1", + "ts-jest": "^29.1.1", + "typechain": "^8.3.2", + "typescript": "^5.3.2" + }, + "packageManager": "yarn@1.22.21" +} diff --git a/l2-bridges/push-agent.js b/l2-bridges/push-agent.js new file mode 100644 index 00000000..29acc530 --- /dev/null +++ b/l2-bridges/push-agent.js @@ -0,0 +1,73 @@ +const shelljs = require("shelljs"); +const fs = require("fs"); +const path = require("path"); + +const DOCKER_ARG_L2_NETWORK_NAME = "L2_NETWORK_NAME"; + + +function parseCommandLineArgs() { + const cmdArgs = process.argv.slice(2); + if (cmdArgs.length !== 1) { + console.error(`ERROR: Must specify these arguments: `); + process.exit(1); + } + return { l2NetworkName: cmdArgs[0] }; +} + + +function assertShellResult(result, errMsg) { + if (result.code !== 0) { + throw new Error(`${errMsg}: ${result.stderr}`) + } +} + +const main = async () => { + const args = parseCommandLineArgs(); + const agentName = JSON.parse(fs.readFileSync(path.join(__dirname, args.l2NetworkName, "package.json"))).name; + + + // NB: Docker-related code below is copied from forta-agent/cli/commands/publish/upload.image.ts + + const imageRepositoryUrl = "disco.forta.network"; + const shell = shelljs; + const imageTagSuffix = undefined; + + // build the agent image + console.log('building agent image...') + const imageTag = `${agentName}-intermediate${imageTagSuffix ? `-${imageTagSuffix}` : ''}` + let buildCommand = `docker buildx build --load --platform linux/amd64 \ + --build-arg ${DOCKER_ARG_L2_NETWORK_NAME}=${args.l2NetworkName} \ + --tag ${imageTag} \ + .` + const buildResult = shell.exec(buildCommand) + assertShellResult(buildResult, 'error building agent image') + + + // push agent image to repository + console.log('pushing agent image to repository...') + const tagResult = shell.exec(`docker tag ${imageTag} ${imageRepositoryUrl}/${imageTag}`) + assertShellResult(tagResult, 'error tagging agent image') + + const pushResult = shell.exec(`docker push ${imageRepositoryUrl}/${imageTag}`) + assertShellResult(pushResult, 'error pushing agent image') + + // extract image sha256 digest from pushResult + const digestLine = pushResult.grep('sha256').toString() + const digestStartIndex = digestLine.indexOf('sha256:')+7 + const imageDigest = digestLine.substring(digestStartIndex, digestStartIndex+64) + + // pull all tagged images for digest to get ipfs CID + const pullResult = shell.exec(`docker pull -a ${imageRepositoryUrl}/${imageDigest}`) + assertShellResult(pullResult, 'error pulling tagged agent images') + + // extract image ipfs CID from pullResult + const cidLine = pullResult.grep('bafy').toString()// v1 CID begins with 'bafy' + const cidStartIndex = cidLine.indexOf('bafy') + const cidEndIndex = cidLine.indexOf(':', cidStartIndex) + const imageIpfsCid = cidLine.substring(cidStartIndex, cidEndIndex) + const imageReference = `${imageIpfsCid}@sha256:${imageDigest}` + + console.log(`\nThe docker image reference: ${imageReference}`); +} + +main() diff --git a/l2-bridges/scroll/package.json b/l2-bridges/scroll/package.json new file mode 100644 index 00000000..ca8d38ea --- /dev/null +++ b/l2-bridges/scroll/package.json @@ -0,0 +1,27 @@ +{ + "name": "lido-l2-bridge-scroll-bot", + "displayName": "Forta Agent Starter", + "version": "0.0.1", + "description": "Lido Detection Bot for Scroll part of L2 bridge", + "repository": { + "type": "git", + "directory": "https://github.com/lidofinance/alerting-forta/tree/main/l2-bridges" + }, + "license": "MIT", + "chainIds": [ + 1 + ], + "chainSettings": { + "default": { + "shards": 1, + "target": 5 + } + }, + "scripts": { + }, + "dependencies": { + }, + "devDependencies": { + }, + "packageManager": "yarn@1.22.21" +} diff --git a/l2-bridges/scroll/src/abi/L2LidoGateway.json b/l2-bridges/scroll/src/abi/L2LidoGateway.json new file mode 100644 index 00000000..e392396d --- /dev/null +++ b/l2-bridges/scroll/src/abi/L2LidoGateway.json @@ -0,0 +1,861 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_l1Token", + "type": "address" + }, + { + "internalType": "address", + "name": "_l2Token", + "type": "address" + }, + { + "internalType": "address", + "name": "_counterpart", + "type": "address" + }, + { + "internalType": "address", + "name": "_router", + "type": "address" + }, + { + "internalType": "address", + "name": "_messenger", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "ErrorAccountIsZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorCallerIsNotCounterpartGateway", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorCallerIsNotDepositsDisabler", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorCallerIsNotDepositsEnabler", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorCallerIsNotMessenger", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorCallerIsNotWithdrawalsDisabler", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorCallerIsNotWithdrawalsEnabler", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorDepositsDisabled", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorDepositsEnabled", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorNonZeroMsgValue", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorNotInDropMessageContext", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorUnsupportedL1Token", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorUnsupportedL2Token", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorWithdrawZeroAmount", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorWithdrawalsDisabled", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorWithdrawalsEnabled", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "WithdrawAndCallIsNotAllowed", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "disabler", + "type": "address" + } + ], + "name": "DepositsDisabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "enabler", + "type": "address" + } + ], + "name": "DepositsEnabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "l1Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "l2Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "FinalizeDepositERC20", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "l1Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "l2Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "WithdrawERC20", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "disabler", + "type": "address" + } + ], + "name": "WithdrawalsDisabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "enabler", + "type": "address" + } + ], + "name": "WithdrawalsEnabled", + "type": "event" + }, + { + "inputs": [], + "name": "DEPOSITS_DISABLER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEPOSITS_ENABLER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "WITHDRAWALS_DISABLER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "WITHDRAWALS_ENABLER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "counterpart", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "disableDeposits", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "disableWithdrawals", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "enableDeposits", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "enableWithdrawals", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_l1Token", + "type": "address" + }, + { + "internalType": "address", + "name": "_l2Token", + "type": "address" + }, + { + "internalType": "address", + "name": "_from", + "type": "address" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "finalizeDepositERC20", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_l2Token", + "type": "address" + } + ], + "name": "getL1ERC20Address", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_l1Token", + "type": "address" + } + ], + "name": "getL2ERC20Address", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_role", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_index", + "type": "uint256" + } + ], + "name": "getRoleMember", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_role", + "type": "bytes32" + } + ], + "name": "getRoleMemberCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_counterpart", + "type": "address" + }, + { + "internalType": "address", + "name": "_router", + "type": "address" + }, + { + "internalType": "address", + "name": "_messenger", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_depositsEnabler", + "type": "address" + }, + { + "internalType": "address", + "name": "_depositsDisabler", + "type": "address" + }, + { + "internalType": "address", + "name": "_withdrawalsEnabler", + "type": "address" + }, + { + "internalType": "address", + "name": "_withdrawalsDisabler", + "type": "address" + } + ], + "name": "initializeV2", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isDepositsEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isWithdrawalsEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l1Token", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l2Token", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "messenger", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "router", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_gasLimit", + "type": "uint256" + } + ], + "name": "withdrawERC20", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_gasLimit", + "type": "uint256" + } + ], + "name": "withdrawERC20", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "_gasLimit", + "type": "uint256" + } + ], + "name": "withdrawERC20AndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } +] diff --git a/l2-bridges/scroll/src/agent.ts b/l2-bridges/scroll/src/agent.ts new file mode 100644 index 00000000..67e32fc4 --- /dev/null +++ b/l2-bridges/scroll/src/agent.ts @@ -0,0 +1,257 @@ +import { ethers, BlockEvent, Finding, FindingSeverity, FindingType, HandleBlock } from 'forta-agent' +import * as process from 'process' +import { InitializeResponse } from 'forta-agent/dist/sdk/initialize.response' +import { Initialize } from 'forta-agent/dist/sdk/handlers' +import * as E from 'fp-ts/Either' +import VERSION from '../../common/utils/version' +import { elapsedTime } from '../../common/utils/time' +import BigNumber from 'bignumber.js' + +import { L2Client } from '../../common/clients/l2_client' +import { EventWatcher } from '../../common/services/event_watcher' +// import { getProxyAdminEvents } from './utils/events/proxy_admin_events' +// import { ProxyContractClient } from './clients/proxy_contract_client' +import { L2LidoGateway__factory } from './generated' +import { ERC20Short__factory } from '../../common/generated' +import { L2BlockClient } from '../../common/clients/l2_block_client' +// import { ProxyWatcher } from './services/proxy_watcher' +// import { MonitorWithdrawals } from './services/monitor_withdrawals' +import { DataRW } from '../../common/utils/mutex' +import * as Winston from 'winston' +// import { getGovEvents } from './utils/events/gov_events' +import { MAINNET_CHAIN_ID, DRPC_URL } from '../../common/utils/constants' +import { Constants, getBridgeEvents, L2BridgeWithdrawalEvent } from './constants' +import { Logger } from 'winston' +import { ETHProvider } from '../../common/clients/eth_provider_client' +import { BridgeBalanceSrv } from '../../common/services/bridge_balance' +import { getJsonRpcUrl } from 'forta-agent/dist/sdk/utils' +// import { BorderTime, HealthChecker, MaxNumberErrorsPerBorderTime } from './services/health-checker/health-checker.srv' + + +export type Container = { + l2Client: L2Client + // proxyWatcher: ProxyWatcher + // monitorWithdrawals: MonitorWithdrawals + blockClient: L2BlockClient + bridgeWatcher: EventWatcher + bridgeBalanceSrv: BridgeBalanceSrv + // govWatcher: EventWatcher + // proxyEventWatcher: EventWatcher + findingsRW: DataRW + logger: Logger + // healthChecker: HealthChecker +} + +export class App { + private static instance: Container + + private constructor() {} + + public static async getInstance(): Promise { + if (!App.instance) { + const logger: Winston.Logger = Winston.createLogger({ + format: Winston.format.simple(), + transports: [new Winston.transports.Console()], + }) + + + const nodeClient = new ethers.providers.JsonRpcProvider(Constants.L2_NETWORK_RPC, Constants.L2_NETWORK_ID) + const bridgedWstethRunner = ERC20Short__factory.connect(Constants.L2_WSTETH_BRIDGED.address, nodeClient) + + const l2Bridge = L2LidoGateway__factory.connect(Constants.L2_ERC20_TOKEN_GATEWAY.address, nodeClient) + const withdrawalEventsFetcher = (fromBlockNumber: number, toBlockNumber: number) => { + return l2Bridge.queryFilter( + l2Bridge.filters.WithdrawERC20(), + fromBlockNumber, + toBlockNumber, + ) + } + + const l2Client = new L2Client(nodeClient, logger, withdrawalEventsFetcher, bridgedWstethRunner) + + const bridgeEventWatcher = new EventWatcher( + 'BridgeEventWatcher', + getBridgeEvents(Constants.L2_ERC20_TOKEN_GATEWAY.address, Constants.RolesMap), + logger, + ) + // const govEventWatcher = new EventWatcher('GovEventWatcher', getGovEvents(Constants.GOV_BRIDGE_ADDRESS), logger) + // const proxyEventWatcher = new EventWatcher( + // 'ProxyEventWatcher', + // getProxyAdminEvents(Constants.L2_WSTETH_BRIDGED, Constants.L2_ERC20_TOKEN_GATEWAY), + // logger, + // ) + + // const LIDO_PROXY_CONTRACTS: ProxyContractClient[] = [ + // new ProxyContractClient( + // Constants.L2_ERC20_TOKEN_GATEWAY.name, + // Constants.L2_ERC20_TOKEN_GATEWAY.address, + // ProxyAdmin__factory.connect(Constants.L2_PROXY_ADMIN_CONTRACT_ADDRESS, nodeClient), + // ), + // new ProxyContractClient( + // Constants.L2_WSTETH_BRIDGED.name, + // Constants.L2_WSTETH_BRIDGED.address, + // ProxyAdmin__factory.connect(Constants.L2_PROXY_ADMIN_CONTRACT_ADDRESS, nodeClient), + // ), + // ] + + const blockSrv = new L2BlockClient(l2Client, logger) + // const proxyWorker = new ProxyWatcher(LIDO_PROXY_CONTRACTS, logger) + + // const monitorWithdrawals = new MonitorWithdrawals(l2Client, Constants.L2_ERC20_TOKEN_GATEWAY.address, logger) + + + const ethProvider = new ethers.providers.FallbackProvider([ + new ethers.providers.JsonRpcProvider(getJsonRpcUrl(), MAINNET_CHAIN_ID), + new ethers.providers.JsonRpcProvider(DRPC_URL, MAINNET_CHAIN_ID), + ]) + + const wstethRunner = ERC20Short__factory.connect(Constants.L1_WSTETH_ADDRESS, ethProvider) + const ethClient = new ETHProvider(logger, wstethRunner) + const bridgeBalanceSrv = new BridgeBalanceSrv(Constants.L2_NAME, logger, ethClient, l2Client, Constants.L1_ERC20_TOKEN_GATEWAY_ADDRESS) + + App.instance = { + l2Client: l2Client, + // proxyWatcher: proxyWorker, + // monitorWithdrawals: monitorWithdrawals, + blockClient: blockSrv, + bridgeWatcher: bridgeEventWatcher, + bridgeBalanceSrv: bridgeBalanceSrv, + // govWatcher: govEventWatcher, + // proxyEventWatcher: proxyEventWatcher, + findingsRW: new DataRW([]), + logger: logger, + // healthChecker: new HealthChecker(BorderTime, MaxNumberErrorsPerBorderTime), + } + } + + return App.instance + } +} + + +export function initialize(): Initialize { + type Metadata = { [key: string]: string } + + const metadata: Metadata = { + 'version.commitHash': VERSION.commitHash, + 'version.commitMsg': VERSION.commitMsg, + } + + return async function (): Promise { + const app = await App.getInstance() + + const latestL2Block = await app.l2Client.getLatestL2Block() + if (E.isLeft(latestL2Block)) { + app.logger.error(latestL2Block.left) + + process.exit(1) + } + + // const agentMeta = await app.proxyWatcher.initialize(latestL2Block.right.number) + // if (E.isLeft(agentMeta)) { + // app.logger.error(agentMeta.left) + + // process.exit(1) + // } + + // const monitorWithdrawalsInitResp = await app.monitorWithdrawals.initialize(latestL2Block.right.number) + // if (E.isLeft(monitorWithdrawalsInitResp)) { + // app.logger.error(monitorWithdrawalsInitResp.left) + + // process.exit(1) + // } + + // metadata[`${app.proxyWatcher.getName()}.lastAdmins`] = agentMeta.right.lastAdmins + // metadata[`${app.proxyWatcher.getName()}.lastImpls`] = agentMeta.right.lastImpls + // metadata[`${app.monitorWithdrawals.getName()}.currentWithdrawals`] = + // monitorWithdrawalsInitResp.right.currentWithdrawals + + // const agents: string[] = [app.proxyWatcher.getName(), app.monitorWithdrawals.getName()] + // metadata.agents = '[' + agents.toString() + ']' + metadata.agents = '[' + ']' + + await app.findingsRW.write([ + Finding.fromObject({ + name: 'Agent launched', + description: `Version: ${VERSION.desc}`, + alertId: 'LIDO-AGENT-LAUNCHED', + severity: FindingSeverity.Info, + type: FindingType.Info, + metadata, + }), + ]) + + app.logger.info('Bot initialization is done!') + } +} + +let isHandleBlockRunning: boolean = false +export const handleBlock = (): HandleBlock => { + return async function (blockEvent: BlockEvent): Promise { + const startTime = new Date().getTime() + if (isHandleBlockRunning) { + return [] + } + + isHandleBlockRunning = true + const app = await App.getInstance() + + const findings: Finding[] = [] + const findingsAsync = await app.findingsRW.read() + if (findingsAsync.length > 0) { + findings.push(...findingsAsync) + } + + const l2blocksDto = await app.blockClient.getL2Blocks() + if (E.isLeft(l2blocksDto)) { + isHandleBlockRunning = false + return [l2blocksDto.left] + } + app.logger.info( + `ETH block ${blockEvent.blockNumber.toString()}. Fetched ${Constants.L2_NAME} blocks from ${l2blocksDto.right[0].number} to ${ + l2blocksDto.right[l2blocksDto.right.length - 1].number + }. Total: ${l2blocksDto.right.length}`, + ) + + const logs = await app.blockClient.getL2Logs(l2blocksDto.right) + if (E.isLeft(logs)) { + isHandleBlockRunning = false + return [logs.left] + } + + const bridgeEventFindings = app.bridgeWatcher.handleLogs(logs.right) + // const govEventFindings = app.govWatcher.handleLogs(logs.right) + // const proxyAdminEventFindings = app.proxyEventWatcher.handleLogs(logs.right) + // const monitorWithdrawalsFindings = app.monitorWithdrawals.handleBlocks(logs.right, l2blocksDto.right) + + const l2blockNumbersSet: Set = new Set() + for (const log of logs.right) { + l2blockNumbersSet.add(new BigNumber(log.blockNumber, 10).toNumber()) + } + + const l2blockNumbers = Array.from(l2blockNumbersSet) + + const [/*proxyWatcherFindings,*/ bridgeBalanceFindings] = await Promise.all([ + // app.proxyWatcher.handleBlocks(l2blockNumbers), + app.bridgeBalanceSrv.handleBlock(blockEvent.block.number, l2blockNumbers), + ]) + + findings.push( + ...bridgeEventFindings, + ...bridgeBalanceFindings, + // ...govEventFindings, + // ...proxyAdminEventFindings, + // ...monitorWithdrawalsFindings, + // ...proxyWatcherFindings, + ) + + app.logger.info(elapsedTime('handleBlock', startTime) + '\n') + isHandleBlockRunning = false + return findings + } +} + +export default { + initialize: initialize(), + handleBlock: handleBlock(), +} diff --git a/l2-bridges/scroll/src/constants.ts b/l2-bridges/scroll/src/constants.ts new file mode 100644 index 00000000..446d3f80 --- /dev/null +++ b/l2-bridges/scroll/src/constants.ts @@ -0,0 +1,128 @@ +import { FindingSeverity, FindingType } from 'forta-agent' +import { Result } from '@ethersproject/abi/lib' +import { EventOfNotice } from '../../common/entity/events' +import { WithdrawERC20Event } from './generated/L2LidoGateway' + +export type RoleHashToName = Map +export type L2BridgeWithdrawalEvent = WithdrawERC20Event + + +export const Constants = { + L2_NAME: 'Scroll', + L2_NETWORK_RPC: 'https://rpc.scroll.io', + L2_NETWORK_ID: 534352, + SCROLL_APPROX_BLOCK_TIME_3_SECONDS: 3, + L2_PROXY_ADMIN_CONTRACT_ADDRESS: '0x8e34d07eb348716a1f0a48a507a9de8a3a6dce45', + GOV_BRIDGE_ADDRESS: '0x0c67d8d067e349669dfeab132a7c03a90594ee09', + L1_WSTETH_ADDRESS: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0', + L1_ERC20_TOKEN_GATEWAY_ADDRESS: '0x6625c6332c9f91f2d27c304e729b86db87a3f504', + L2_ERC20_TOKEN_GATEWAY: { + name: 'L2_ERC20_TOKEN_GATEWAY', + address: '0x8ae8f22226b9d789a36ac81474e633f8be2856c9', + }, + L2_WSTETH_BRIDGED: { + name: 'SCROLL_WSTETH_BRIDGED', + address: '0xf610a9dfb7c89644979b4a0f27063e9e7d7cda32', + }, + RolesMap: new Map([ + ['0x4b43b36766bde12c5e9cbbc37d15f8d1f769f08f54720ab370faeb4ce893753a', 'DEPOSITS_ENABLER_ROLE'], + ['0x63f736f21cb2943826cd50b191eb054ebbea670e4e962d0527611f830cd399d6', 'DEPOSITS_DISABLER_ROLE'], + ['0x9ab8816a3dc0b3849ec1ac00483f6ec815b07eee2fd766a353311c823ad59d0d', 'WITHDRAWALS_ENABLER_ROLE'], + ['0x94a954c0bc99227eddbc0715a62a7e1056ed8784cd719c2303b685683908857c', 'WITHDRAWALS_DISABLER_ROLE'], + ['0x0000000000000000000000000000000000000000000000000000000000000000', 'DEFAULT_ADMIN_ROLE'], + ]), +} + + +export function getBridgeEvents(l2GatewayAddress: string, RolesAddrToNameMap: RoleHashToName): EventOfNotice[] { + return [ + { + address: l2GatewayAddress, + event: 'event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)', + alertId: 'L2-BRIDGE-OWNER-CHANGED', + name: '🚨 Scroll: L2 gateway owner changed', + description: (args: Result) => + `Owner of L2LidoGateway ${l2GatewayAddress} was changed to ${args.newOwner} (detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: '136546BE-E1BF-40DA-98FB-17B741E12A35', + }, + { + address: l2GatewayAddress, + event: 'event DepositsDisabled(address indexed disabler)', + alertId: 'L2-BRIDGE-DEPOSITS-DISABLED', + name: '🚨 Scroll L2 Bridge: Deposits Disabled', + description: (args: Result) => `Deposits were disabled by ${args.disabler}`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: '7CBC6E3F-BABA-437A-9142-0C1CD8AAA827', + }, + { + address: l2GatewayAddress, + event: 'event WithdrawalsDisabled(address indexed disabler)', + alertId: 'L2-BRIDGE-WITHDRAWALS-DISABLED', + name: '🚨 Scroll L2 Bridge: Withdrawals Disabled', + description: (args: Result) => `Withdrawals were disabled by ${args.enabler}`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: 'C6DBFF28-C12D-4CEC-8087-2F0898F7AEAB', + }, + { + address: l2GatewayAddress, + event: 'event DepositsEnabled(address indexed enabler)', + alertId: 'L2-BRIDGE-DEPOSITS-ENABLED', + name: 'ℹ️ Scroll L2 Bridge: Deposits Enabled', + description: (args: Result) => `Deposits were enabled by ${args.enabler}`, + severity: FindingSeverity.Info, + type: FindingType.Info, + uniqueKey: 'EA60F6DC-9A59-4FAE-8467-521DF56813C5', + }, + { + address: l2GatewayAddress, + event: 'event WithdrawalsEnabled(address indexed enabler)', + alertId: 'L2-BRIDGE-WITHDRAWALS-ENABLED', + name: 'ℹ️ Scroll L2 Bridge: Withdrawals Enabled', + description: (args: Result) => `Withdrawals were enabled by ${args.enabler}`, + severity: FindingSeverity.Info, + type: FindingType.Info, + uniqueKey: '0CEE896B-6BDD-45C5-9ADD-46A1558F1BBC', + }, + { + address: l2GatewayAddress, + event: 'event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender)', + alertId: 'L2-BRIDGE-ROLE-GRANTED', + name: '⚠️ Scroll L2 Bridge: Role granted', + description: (args: Result) => + `Role ${args.role}(${RolesAddrToNameMap.get(args.role) || 'unknown'}) ` + + `was granted to ${args.account} by ${args.sender}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: 'F58F36AD-9811-40D7-ACD2-667A7624D85B', + }, + { + address: l2GatewayAddress, + event: 'event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender)', + alertId: 'L2-BRIDGE-ROLE-REVOKED', + name: '⚠️ Scroll L2 Bridge: Role revoked', + description: (args: Result) => + `Role ${args.role}(${RolesAddrToNameMap.get(args.role) || 'unknown'}) ` + + `was revoked to ${args.account} by ${args.sender}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: '42816CCE-24C3-4CE2-BC21-4F2202A66EFD', + }, + { + address: l2GatewayAddress, + event: 'event Initialized(uint8 version)', + alertId: 'L2-BRIDGE-INITIALIZED', + name: '🚨 Scroll L2 Bridge: (re-)initialized', + description: (args: Result) => + `Implementation of the Scroll L2 Bridge was initialized by version: ${args.version}\n` + + `NOTE: This is not the thing that should be left unacted! ` + + `Make sure that this call was made by Lido!`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: 'E42BC7A0-0715-4D55-AB9D-0A041F639B20', + }, + ] +} \ No newline at end of file diff --git a/l2-bridges/scroll/tsconfig.json b/l2-bridges/scroll/tsconfig.json new file mode 100644 index 00000000..85a4c229 --- /dev/null +++ b/l2-bridges/scroll/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../dist/scroll/src" + } +} diff --git a/l2-bridges/start-agent.ts b/l2-bridges/start-agent.ts new file mode 100644 index 00000000..89fb7372 --- /dev/null +++ b/l2-bridges/start-agent.ts @@ -0,0 +1,36 @@ +const { configureContainer } = require("forta-agent"); + +const DEV = "dev"; +const PROD = "prod"; + + +function parseCommandLineArgs() { + const cmdArgs = process.argv.slice(2); + if (cmdArgs.length !== 2) { + console.error(`ERROR: Must specify two arguments: `); + process.exit(1); + } + const l2NetworkName = cmdArgs[0]; + const runMode = cmdArgs[1]; + if (runMode !== DEV && runMode !== PROD) { + console.error(`ERROR: Invalid run mode specified: ${runMode}`); + process.exit(1); + } + return { l2NetworkName, runMode }; +} + +const main = async () => { + const args = parseCommandLineArgs(); + + const containerArgs = { contextPath: `${__dirname}/${args.l2NetworkName}` }; + const container = configureContainer(containerArgs); + let runFunction; + if (args.runMode === PROD) { + runFunction = container.resolve("runProdServer"); + } else if (args.runMode) { + runFunction = container.resolve("runLive"); + } + await runFunction(); +} + +main() diff --git a/l2-bridges/tsconfig.json b/l2-bridges/tsconfig.json new file mode 100644 index 00000000..72ba777b --- /dev/null +++ b/l2-bridges/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "@tsconfig/node20/tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "baseUrl": "." + }, + "exclude": [ + "node_modules", + "tests", + "dist", + "**/*spec.ts", + "**/*mock.ts" + ] +} diff --git a/l2-bridges/yarn.lock b/l2-bridges/yarn.lock new file mode 100644 index 00000000..eafd9ed8 --- /dev/null +++ b/l2-bridges/yarn.lock @@ -0,0 +1,4707 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" + integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== + dependencies: + "@babel/highlight" "^7.24.7" + picocolors "^1.0.0" + +"@babel/compat-data@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.7.tgz#d23bbea508c3883ba8251fb4164982c36ea577ed" + integrity sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.7.tgz#b676450141e0b52a3d43bc91da86aa608f950ac4" + integrity sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.24.7" + "@babel/helper-compilation-targets" "^7.24.7" + "@babel/helper-module-transforms" "^7.24.7" + "@babel/helpers" "^7.24.7" + "@babel/parser" "^7.24.7" + "@babel/template" "^7.24.7" + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.24.7", "@babel/generator@^7.7.2": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.7.tgz#1654d01de20ad66b4b4d99c135471bc654c55e6d" + integrity sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA== + dependencies: + "@babel/types" "^7.24.7" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + +"@babel/helper-compilation-targets@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz#4eb6c4a80d6ffeac25ab8cd9a21b5dfa48d503a9" + integrity sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg== + dependencies: + "@babel/compat-data" "^7.24.7" + "@babel/helper-validator-option" "^7.24.7" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-environment-visitor@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz#4b31ba9551d1f90781ba83491dd59cf9b269f7d9" + integrity sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ== + dependencies: + "@babel/types" "^7.24.7" + +"@babel/helper-function-name@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz#75f1e1725742f39ac6584ee0b16d94513da38dd2" + integrity sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA== + dependencies: + "@babel/template" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-hoist-variables@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz#b4ede1cde2fd89436397f30dc9376ee06b0f25ee" + integrity sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ== + dependencies: + "@babel/types" "^7.24.7" + +"@babel/helper-module-imports@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz#f2f980392de5b84c3328fc71d38bd81bbb83042b" + integrity sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-module-transforms@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz#31b6c9a2930679498db65b685b1698bfd6c7daf8" + integrity sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ== + dependencies: + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-module-imports" "^7.24.7" + "@babel/helper-simple-access" "^7.24.7" + "@babel/helper-split-export-declaration" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.8.0": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz#98c84fe6fe3d0d3ae7bfc3a5e166a46844feb2a0" + integrity sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg== + +"@babel/helper-simple-access@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz#bcade8da3aec8ed16b9c4953b74e506b51b5edb3" + integrity sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-split-export-declaration@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz#83949436890e07fa3d6873c61a96e3bbf692d856" + integrity sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA== + dependencies: + "@babel/types" "^7.24.7" + +"@babel/helper-string-parser@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz#4d2d0f14820ede3b9807ea5fc36dfc8cd7da07f2" + integrity sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg== + +"@babel/helper-validator-identifier@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" + integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== + +"@babel/helper-validator-option@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz#24c3bb77c7a425d1742eec8fb433b5a1b38e62f6" + integrity sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw== + +"@babel/helpers@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.7.tgz#aa2ccda29f62185acb5d42fb4a3a1b1082107416" + integrity sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg== + dependencies: + "@babel/template" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/highlight@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" + integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== + dependencies: + "@babel/helper-validator-identifier" "^7.24.7" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.7.tgz#9a5226f92f0c5c8ead550b750f5608e766c8ce85" + integrity sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw== + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz#39a1fa4a7e3d3d7f34e2acc6be585b718d30e02d" + integrity sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz#58d458271b4d3b6bb27ee6ac9525acbb259bad1c" + integrity sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/template@^7.24.7", "@babel/template@^7.3.3": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.7.tgz#02efcee317d0609d2c07117cb70ef8fb17ab7315" + integrity sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/parser" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/traverse@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.7.tgz#de2b900163fa741721ba382163fe46a936c40cf5" + integrity sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.24.7" + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-function-name" "^7.24.7" + "@babel/helper-hoist-variables" "^7.24.7" + "@babel/helper-split-export-declaration" "^7.24.7" + "@babel/parser" "^7.24.7" + "@babel/types" "^7.24.7" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.24.7", "@babel/types@^7.3.3": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.7.tgz#6027fe12bc1aa724cd32ab113fb7f1988f1f66f2" + integrity sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q== + dependencies: + "@babel/helper-string-parser" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@colors/colors@1.6.0", "@colors/colors@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.6.0.tgz#ec6cd237440700bc23ca23087f513c75508958b0" + integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA== + +"@dabh/diagnostics@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a" + integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA== + dependencies: + colorspace "1.1.x" + enabled "2.0.x" + kuler "^2.0.0" + +"@danieldietrich/copy@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@danieldietrich/copy/-/copy-0.4.2.tgz#c1cabfa499d8b473ba95413c446c1c1efae64d24" + integrity sha512-ZVNZIrgb2KeomfNahP77rL445ho6aQj0HHqU6hNlQ61o4rhvca+NS+ePj0d82zQDq2UPk1mjVZBTXgP+ErsDgw== + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": + version "4.10.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.1.tgz#361461e5cb3845d874e61731c11cfedd664d83a0" + integrity sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" + integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== + +"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.0", "@ethersproject/abi@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" + integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/abstract-provider@5.7.0", "@ethersproject/abstract-provider@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz#b0a8550f88b6bf9d51f90e4795d48294630cb9ef" + integrity sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + +"@ethersproject/abstract-signer@5.7.0", "@ethersproject/abstract-signer@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz#13f4f32117868452191a4649723cb086d2b596b2" + integrity sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + +"@ethersproject/address@5.7.0", "@ethersproject/address@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.7.0.tgz#19b56c4d74a3b0a46bfdbb6cfcc0a153fc697f37" + integrity sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + +"@ethersproject/base64@5.7.0", "@ethersproject/base64@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.7.0.tgz#ac4ee92aa36c1628173e221d0d01f53692059e1c" + integrity sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ== + dependencies: + "@ethersproject/bytes" "^5.7.0" + +"@ethersproject/basex@5.7.0", "@ethersproject/basex@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.7.0.tgz#97034dc7e8938a8ca943ab20f8a5e492ece4020b" + integrity sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + +"@ethersproject/bignumber@5.7.0", "@ethersproject/bignumber@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.7.0.tgz#e2f03837f268ba655ffba03a57853e18a18dc9c2" + integrity sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + bn.js "^5.2.1" + +"@ethersproject/bytes@5.7.0", "@ethersproject/bytes@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.7.0.tgz#a00f6ea8d7e7534d6d87f47188af1148d71f155d" + integrity sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/constants@5.7.0", "@ethersproject/constants@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.7.0.tgz#df80a9705a7e08984161f09014ea012d1c75295e" + integrity sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA== + dependencies: + "@ethersproject/bignumber" "^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== + dependencies: + "@ethersproject/abi" "^5.7.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + +"@ethersproject/hash@5.7.0", "@ethersproject/hash@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.7.0.tgz#eb7aca84a588508369562e16e514b539ba5240a7" + integrity sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/hdnode@5.7.0", "@ethersproject/hdnode@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.7.0.tgz#e627ddc6b466bc77aebf1a6b9e47405ca5aef9cf" + integrity sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wordlists" "^5.7.0" + +"@ethersproject/json-wallets@5.7.0", "@ethersproject/json-wallets@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz#5e3355287b548c32b368d91014919ebebddd5360" + integrity sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hdnode" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + aes-js "3.0.0" + scrypt-js "3.0.1" + +"@ethersproject/keccak256@5.7.0", "@ethersproject/keccak256@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.7.0.tgz#3186350c6e1cd6aba7940384ec7d6d9db01f335a" + integrity sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + js-sha3 "0.8.0" + +"@ethersproject/logger@5.7.0", "@ethersproject/logger@^5.7.0": + version "5.7.0" + 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": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.1.tgz#118e1a981d757d45ccea6bb58d9fd3d9db14ead6" + integrity sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/pbkdf2@5.7.0", "@ethersproject/pbkdf2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz#d2267d0a1f6e123f3771007338c47cccd83d3102" + integrity sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + +"@ethersproject/properties@5.7.0", "@ethersproject/properties@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.7.0.tgz#a6e12cb0439b878aaf470f1902a176033067ed30" + integrity sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.0.0": + version "5.7.2" + resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" + integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + bech32 "1.1.4" + ws "7.4.6" + +"@ethersproject/random@5.7.0", "@ethersproject/random@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.7.0.tgz#af19dcbc2484aae078bb03656ec05df66253280c" + integrity sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/rlp@5.7.0", "@ethersproject/rlp@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.7.0.tgz#de39e4d5918b9d74d46de93af80b7685a9c21304" + integrity sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/sha2@5.7.0", "@ethersproject/sha2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.7.0.tgz#9a5f7a7824ef784f7f7680984e593a800480c9fb" + integrity sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + hash.js "1.1.7" + +"@ethersproject/signing-key@5.7.0", "@ethersproject/signing-key@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.7.0.tgz#06b2df39411b00bc57c7c09b01d1e41cf1b16ab3" + integrity sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + bn.js "^5.2.1" + elliptic "6.5.4" + hash.js "1.1.7" + +"@ethersproject/solidity@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.7.0.tgz#5e9c911d8a2acce2a5ebb48a5e2e0af20b631cb8" + integrity sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/strings@5.7.0", "@ethersproject/strings@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.7.0.tgz#54c9d2a7c57ae8f1205c88a9d3a56471e14d5ed2" + integrity sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.7.0.tgz#91318fc24063e057885a6af13fdb703e1f993d3b" + integrity sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + +"@ethersproject/units@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.7.0.tgz#637b563d7e14f42deeee39245275d477aae1d8b1" + integrity sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/wallet@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.7.0.tgz#4e5d0790d96fe21d61d38fb40324e6c7ef350b2d" + integrity sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/hdnode" "^5.7.0" + "@ethersproject/json-wallets" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wordlists" "^5.7.0" + +"@ethersproject/web@5.7.1", "@ethersproject/web@^5.7.0": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.1.tgz#de1f285b373149bee5928f4eb7bcb87ee5fbb4ae" + integrity sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w== + dependencies: + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/wordlists@5.7.0", "@ethersproject/wordlists@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.7.0.tgz#8fb2c07185d68c3e09eb3bfd6e779ba2774627f5" + integrity sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@grpc/grpc-js@^1.3.6": + version "1.10.8" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.10.8.tgz#99787785cd8335be861afd1cd485ae9f058e4484" + integrity sha512-vYVqYzHicDqyKB+NQhAc54I1QWCBLCrYG6unqOIcBTHx+7x8C9lcoLj3KVJXs2VB4lUbpWY+Kk9NipcbXYWmvg== + dependencies: + "@grpc/proto-loader" "^0.7.13" + "@js-sdsl/ordered-map" "^4.4.2" + +"@grpc/proto-loader@^0.6.4": + version "0.6.13" + resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.6.13.tgz#008f989b72a40c60c96cd4088522f09b05ac66bc" + integrity sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g== + dependencies: + "@types/long" "^4.0.1" + lodash.camelcase "^4.3.0" + long "^4.0.0" + protobufjs "^6.11.3" + yargs "^16.2.0" + +"@grpc/proto-loader@^0.7.13": + version "0.7.13" + resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.13.tgz#f6a44b2b7c9f7b609f5748c6eac2d420e37670cf" + integrity sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw== + dependencies: + lodash.camelcase "^4.3.0" + long "^5.0.0" + protobufjs "^7.2.5" + yargs "^17.7.2" + +"@humanwhocodes/config-array@^0.11.14": + version "0.11.14" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" + integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== + dependencies: + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@js-sdsl/ordered-map@^4.4.2": + version "4.4.2" + resolved "https://registry.yarnpkg.com/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz#9299f82874bab9e4c7f9c48d865becbfe8d6907c" + integrity sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@pkgr/core@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" + integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== + +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@tsconfig/node20@^20.1.2": + version "20.1.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node20/-/node20-20.1.4.tgz#3457d42eddf12d3bde3976186ab0cd22b85df928" + integrity sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg== + +"@typechain/ethers-v5@^11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-11.1.2.tgz#82510c1744f37a2f906b9e0532ac18c0b74ffe69" + integrity sha512-ID6pqWkao54EuUQa0P5RgjvfA3MYqxUQKpbGKERbsjBW5Ra7EIXvbMlPp2pcP5IAdUkyMCFYsP2SN5q7mPdLDQ== + dependencies: + lodash "^4.17.15" + ts-essentials "^7.0.1" + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.8" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" + integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.20.6" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7" + integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg== + dependencies: + "@babel/types" "^7.20.7" + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^29.5.10": + version "29.5.12" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.12.tgz#7f7dc6eb4cf246d2474ed78744b05d06ce025544" + integrity sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/json-schema@^7.0.12", "@types/json-schema@^7.0.9": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/lodash@^4.14.202": + version "4.17.4" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.4.tgz#0303b64958ee070059e3a7184048a55159fe20b7" + integrity sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ== + +"@types/long@^4.0.1": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" + integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== + +"@types/mkdirp@^0.5.2": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-0.5.2.tgz#503aacfe5cc2703d5484326b1b27efa67a339c1f" + integrity sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg== + dependencies: + "@types/node" "*" + +"@types/node@*", "@types/node@>=13.7.0": + version "20.14.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.2.tgz#a5f4d2bcb4b6a87bffcaa717718c5a0f208f4a18" + integrity sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q== + dependencies: + undici-types "~5.26.4" + +"@types/nodemon@^1.19.0": + version "1.19.6" + resolved "https://registry.yarnpkg.com/@types/nodemon/-/nodemon-1.19.6.tgz#1c14bac51dfd3d354e2b5046949f925a742412c4" + integrity sha512-vjKuaQOLUA5EY2zkUmWG1ipXbKt9Wd+H/0SiIuHVeH4cHtt6509iRUGH9ZR0iqgUrtj3BrP9KqoTuV3ZCbQcYA== + dependencies: + "@types/node" "*" + +"@types/prettier@^2.1.1": + version "2.7.3" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f" + integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== + +"@types/resolve@^0.0.8": + version "0.0.8" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" + integrity sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ== + dependencies: + "@types/node" "*" + +"@types/semver@^7.3.12", "@types/semver@^7.5.0": + version "7.5.8" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" + integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/triple-beam@^1.3.2": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.5.tgz#74fef9ffbaa198eb8b588be029f38b00299caa2c" + integrity sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw== + +"@types/uuid@^8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== + +"@types/ws@^8.5.10": + version "8.5.10" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" + integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== + dependencies: + "@types/node" "*" + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.32" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.32.tgz#030774723a2f7faafebf645f4e5a48371dca6229" + integrity sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^6.12.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz#30830c1ca81fd5f3c2714e524c4303e0194f9cd3" + integrity sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA== + dependencies: + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/type-utils" "6.21.0" + "@typescript-eslint/utils" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/parser@^6.12.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b" + integrity sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ== + dependencies: + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/typescript-estree" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" + integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== + dependencies: + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" + +"@typescript-eslint/scope-manager@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz#ea8a9bfc8f1504a6ac5d59a6df308d3a0630a2b1" + integrity sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg== + dependencies: + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + +"@typescript-eslint/type-utils@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz#6473281cfed4dacabe8004e8521cee0bd9d4c01e" + integrity sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag== + dependencies: + "@typescript-eslint/typescript-estree" "6.21.0" + "@typescript-eslint/utils" "6.21.0" + debug "^4.3.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/types@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" + integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== + +"@typescript-eslint/types@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d" + integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg== + +"@typescript-eslint/typescript-estree@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" + integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== + dependencies: + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/typescript-estree@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz#c47ae7901db3b8bddc3ecd73daff2d0895688c46" + integrity sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ== + dependencies: + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + minimatch "9.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/utils@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.21.0.tgz#4714e7a6b39e773c1c8e97ec587f520840cd8134" + integrity sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/typescript-estree" "6.21.0" + semver "^7.5.4" + +"@typescript-eslint/utils@^5.10.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" + integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" + eslint-scope "^5.1.1" + semver "^7.3.7" + +"@typescript-eslint/visitor-keys@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" + integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== + dependencies: + "@typescript-eslint/types" "5.62.0" + eslint-visitor-keys "^3.3.0" + +"@typescript-eslint/visitor-keys@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz#87a99d077aa507e20e238b11d56cc26ade45fe47" + integrity sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A== + dependencies: + "@typescript-eslint/types" "6.21.0" + eslint-visitor-keys "^3.4.1" + +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.9.0: + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + +aes-js@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" + integrity sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +anymatch@^3.0.3, anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-back@^3.0.1, array-back@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0" + integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q== + +array-back@^4.0.1, array-back@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e" + integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +asn1.js@^4.10.1: + version "4.10.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" + integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +async-mutex@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.4.1.tgz#bccf55b96f2baf8df90ed798cb5544a1f6ee4c2c" + integrity sha512-WfoBo4E/TbCX1G95XTjbWTE3X2XLG0m1Xbv2cwOtuPdyH9CZvnaA5nCt1ucjaKEgW2A5IF71hxrRhr83Je5xjA== + dependencies: + tslib "^2.4.0" + +async-retry@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.3.tgz#0e7f36c04d8478e7a58bdbed80cedf977785f280" + integrity sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw== + dependencies: + retry "0.13.1" + +async@^3.2.3: + version "3.2.5" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" + integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== + +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== + +awilix@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/awilix/-/awilix-4.3.4.tgz#aeecc662efa96256981af3bc6243eb201c8b4a4f" + integrity sha512-NgRwUPxUnoK+OTRa2fXcRQVFPOPQXlwCN1FJPkhO3IHKQJHokhdVpDfgz9L3VZTcA1iSaOFE3N/Q/5P7lIDqig== + dependencies: + camel-case "^4.1.2" + glob "^7.1.6" + +axios@^1.6.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" + integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-arraybuffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc" + integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bech32@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" + integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== + +bignumber.js@^9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" + integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + +bn.js@^5.0.0, bn.js@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +brorand@^1.0.1, brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== + +browserify-aes@^1.0.4, browserify-aes@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + +browserify-cipher@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" + integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== + dependencies: + browserify-aes "^1.0.4" + browserify-des "^1.0.0" + evp_bytestokey "^1.0.0" + +browserify-des@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" + integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== + dependencies: + cipher-base "^1.0.1" + des.js "^1.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +browserify-rsa@^4.0.0, browserify-rsa@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" + integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== + dependencies: + bn.js "^5.0.0" + randombytes "^2.0.1" + +browserify-sign@^4.0.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.3.tgz#7afe4c01ec7ee59a89a558a4b75bd85ae62d4208" + integrity sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw== + dependencies: + bn.js "^5.2.1" + browserify-rsa "^4.1.0" + create-hash "^1.2.0" + create-hmac "^1.1.7" + elliptic "^6.5.5" + hash-base "~3.0" + inherits "^2.0.4" + parse-asn1 "^5.1.7" + readable-stream "^2.3.8" + safe-buffer "^5.2.1" + +browserslist@^4.22.2: + version "4.23.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" + integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== + dependencies: + caniuse-lite "^1.0.30001587" + electron-to-chromium "^1.4.668" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== + +buffer@6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camel-case@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" + integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== + dependencies: + pascal-case "^3.1.2" + tslib "^2.0.3" + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001587: + version "1.0.30001629" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001629.tgz#907a36f4669031bd8a1a8dbc2fa08b29e0db297e" + integrity sha512-c3dl911slnQhmxUIT4HhYzT7wnBK/XYpGnYLOj4nJBaRiw52Ibe7YxlDaAeRECvA786zCuExhxIUJ2K7nHMrBw== + +chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +chokidar@^3.5.2: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +cjs-module-lexer@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz#c485341ae8fd999ca4ee5af2d7a1c9ae01e0099c" + integrity sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + +color-convert@^1.9.0, color-convert@^1.9.3: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.6.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^3.1.3: + version "3.2.1" + resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" + integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== + dependencies: + color-convert "^1.9.3" + color-string "^1.6.0" + +colorspace@1.1.x: + version "1.1.4" + resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243" + integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w== + dependencies: + color "^3.1.3" + text-hex "1.0.x" + +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" + +command-line-args@^5.1.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.2.1.tgz#c44c32e437a57d7c51157696893c5909e9cec42e" + integrity sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg== + dependencies: + array-back "^3.1.0" + find-replace "^3.0.0" + lodash.camelcase "^4.3.0" + typical "^4.0.0" + +command-line-usage@^6.1.0: + version "6.1.3" + resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-6.1.3.tgz#428fa5acde6a838779dfa30e44686f4b6761d957" + integrity sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw== + dependencies: + array-back "^4.0.2" + chalk "^2.4.2" + table-layout "^1.0.2" + typical "^5.2.0" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +create-ecdh@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" + integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== + dependencies: + bn.js "^4.1.0" + elliptic "^6.5.3" + +create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + +create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypto-browserify@3.12.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" + integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== + dependencies: + browserify-cipher "^1.0.0" + browserify-sign "^4.0.0" + create-ecdh "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.0" + diffie-hellman "^5.0.0" + inherits "^2.0.1" + pbkdf2 "^3.0.3" + public-encrypt "^4.0.0" + randombytes "^2.0.0" + randomfill "^1.0.3" + +debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" + +dedent@^1.0.0: + version "1.5.3" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" + integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== + +deep-extend@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +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== + +des.js@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.1.0.tgz#1d37f5766f3bbff4ee9638e871a8768c173b81da" + integrity sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg== + dependencies: + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +diffie-hellman@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== + dependencies: + bn.js "^4.1.0" + miller-rabin "^4.0.0" + randombytes "^2.0.0" + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +electron-to-chromium@^1.4.668: + version "1.4.791" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.791.tgz#ba84e037cbf4df4eb23061076426e78e6122e389" + integrity sha512-6FlqP0NSWvxFf1v+gHu+LCn5wjr1pmkj5nPr7BsxPnj41EDR4EWhK/KmQN0ytHUqgTR1lkpHRYxvHBLZFQtkKw== + +elliptic@6.5.4: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + +elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.5: + version "6.5.5" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.5.tgz#c715e09f78b6923977610d4c2346d6ce22e6dded" + integrity sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +enabled@2.0.x: + version "2.0.0" + resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" + integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +escalade@^3.1.1, escalade@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-prettier@^9.0.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz#31af3d94578645966c082fcb71a5846d3c94867f" + integrity sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw== + +eslint-plugin-jest@^27.6.0: + version "27.9.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-27.9.0.tgz#7c98a33605e1d8b8442ace092b60e9919730000b" + integrity sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug== + dependencies: + "@typescript-eslint/utils" "^5.10.0" + +eslint-plugin-prettier@^5.0.1: + version "5.1.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz#17cfade9e732cef32b5f5be53bd4e07afd8e67e1" + integrity sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw== + dependencies: + prettier-linter-helpers "^1.0.0" + synckit "^0.8.6" + +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.54.0: + version "8.57.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" + integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.0" + "@humanwhocodes/config-array" "^0.11.14" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +ethers@^5.5.1: + version "5.7.2" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" + integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== + dependencies: + "@ethersproject/abi" "5.7.0" + "@ethersproject/abstract-provider" "5.7.0" + "@ethersproject/abstract-signer" "5.7.0" + "@ethersproject/address" "5.7.0" + "@ethersproject/base64" "5.7.0" + "@ethersproject/basex" "5.7.0" + "@ethersproject/bignumber" "5.7.0" + "@ethersproject/bytes" "5.7.0" + "@ethersproject/constants" "5.7.0" + "@ethersproject/contracts" "5.7.0" + "@ethersproject/hash" "5.7.0" + "@ethersproject/hdnode" "5.7.0" + "@ethersproject/json-wallets" "5.7.0" + "@ethersproject/keccak256" "5.7.0" + "@ethersproject/logger" "5.7.0" + "@ethersproject/networks" "5.7.1" + "@ethersproject/pbkdf2" "5.7.0" + "@ethersproject/properties" "5.7.0" + "@ethersproject/providers" "5.7.2" + "@ethersproject/random" "5.7.0" + "@ethersproject/rlp" "5.7.0" + "@ethersproject/sha2" "5.7.0" + "@ethersproject/signing-key" "5.7.0" + "@ethersproject/solidity" "5.7.0" + "@ethersproject/strings" "5.7.0" + "@ethersproject/transactions" "5.7.0" + "@ethersproject/units" "5.7.0" + "@ethersproject/wallet" "5.7.0" + "@ethersproject/web" "5.7.1" + "@ethersproject/wordlists" "5.7.0" + +evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + +fast-glob@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fast-safe-stringify@^2.0.6: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" + integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +fecha@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" + integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-replace@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38" + integrity sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ== + dependencies: + array-back "^3.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== + +fn.name@1.x.x: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" + integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== + +follow-redirects@^1.15.6: + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== + +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" + +forta-agent@^0.1.48: + version "0.1.48" + resolved "https://registry.yarnpkg.com/forta-agent/-/forta-agent-0.1.48.tgz#d2660eb0c8db3daf554079176836ba5bec8d3d4f" + integrity sha512-fk3mar7/Avqg/4OHFmgv01ww/azr1XM+g5KcSnwvNxZy3KDMi7aFp1jAjPCsBjs8ZyVcR03ITUlbtFpRVgZB4Q== + dependencies: + "@grpc/grpc-js" "^1.3.6" + "@grpc/proto-loader" "^0.6.4" + "@types/uuid" "^8.3.4" + async-retry "^1.3.3" + awilix "^4.3.4" + axios "^1.6.2" + base64-arraybuffer "^1.0.2" + ethers "^5.5.1" + flat-cache "^3.0.4" + form-data "^4.0.0" + jsonc "^2.0.0" + keythereum "^1.2.0" + lodash "^4.17.21" + murmurhash3js "^3.0.1" + n-readlines "^1.0.1" + prompts "^2.4.1" + python-shell "^3.0.0" + sha3 "^2.1.4" + shelljs "^0.8.4" + uuid "^8.3.2" + yargs "^17.0.1" + +fp-ts@^2.16.1: + version "2.16.6" + resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.16.6.tgz#9d63c5b2a06355d627ae94c37a5cffda5c455d24" + integrity sha512-v7w209VPj4L6pPn/ftFRJu31Oa8QagwcVw7BZmLCUWU4AQoc954rX9ogSIahDf67Pg+GjPbkW/Kn9XWnlWJG0g== + +fs-extra@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2, fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@7.1.7: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.0.0, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^8.0.3: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hash-base@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== + dependencies: + inherits "^2.0.4" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + +hash-base@~3.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" + integrity sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +hasown@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +husky@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" + integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== + +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== + +ignore@^5.2.0, ignore@^5.2.4: + version "5.3.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +interpret@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-core-module@^2.13.0: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz#91655936cf7380e4e473383081e38478b69993b1" + integrity sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + +jest-util@^29.0.0, jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + +js-sha3@0.8.0, js-sha3@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonc@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/jsonc/-/jsonc-2.0.0.tgz#9e2a25100d164a9bb864c57517563717fa882551" + integrity sha512-B281bLCT2TRMQa+AQUQY5AGcqSOXBOKaYGP4wDzoA/+QswUfN8sODektbPEs9Baq7LGKun5jQbNFpzwGuVYKhw== + dependencies: + fast-safe-stringify "^2.0.6" + graceful-fs "^4.1.15" + mkdirp "^0.5.1" + parse-json "^4.0.0" + strip-bom "^4.0.0" + strip-json-comments "^3.0.1" + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + +keccak@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.1.tgz#ae30a0e94dbe43414f741375cff6d64c8bea0bff" + integrity sha512-epq90L9jlFWCW7+pQa6JOnKn2Xgl2mtI664seYR6MHskvI9agt7AnDqmAlp9TqU4/caMYbA08Hi5DMZAl5zdkA== + dependencies: + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + +keythereum@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/keythereum/-/keythereum-1.2.0.tgz#3e6c2d7451ef4fde011f8008348c194f7e58c86a" + integrity sha512-u3XnjIruOmjIvJ4tH1Wdr2y0X8+z8BZTQ+dqJuDMyLvNWw6VnH9XKtt0yauSE+96Bq97h6CPm4w5LbW3i28x0g== + dependencies: + crypto-browserify "3.12.0" + keccak "3.0.1" + scrypt-js "3.0.1" + secp256k1 "4.0.2" + sjcl "1.0.6" + uuid "3.0.0" + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +kuler@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" + integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== + +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash@^4.17.15, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +logform@^2.3.2, logform@^2.4.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/logform/-/logform-2.6.0.tgz#8c82a983f05d6eaeb2d75e3decae7a768b2bf9b5" + integrity sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ== + dependencies: + "@colors/colors" "1.6.0" + "@types/triple-beam" "^1.3.2" + fecha "^4.2.0" + ms "^2.1.1" + safe-stable-stringify "^2.3.1" + triple-beam "^1.3.0" + +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + +long@^5.0.0: + version "5.2.3" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" + integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== + +lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@1.x: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" + integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +miller-rabin@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" + integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== + dependencies: + bn.js "^4.0.0" + brorand "^1.0.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" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +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" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== + +minimatch@9.0.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +mkdirp@^0.5.1: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +murmurhash3js@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/murmurhash3js/-/murmurhash3js-3.0.1.tgz#3e983e5b47c2a06f43a713174e7e435ca044b998" + integrity sha512-KL8QYUaxq7kUbcl0Yto51rMcYt7E/4N4BG3/c96Iqw1PQrTRspu8Cpx4TZ4Nunib1d4bEkIH3gjCYlP2RLBdow== + +n-readlines@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/n-readlines/-/n-readlines-1.0.1.tgz#bbb7364d38bc31a170a199f986fcacfa76b95f6e" + integrity sha512-z4SyAIVgMy7CkgsoNw7YVz40v0g4+WWvvqy8+ZdHrCtgevcEO758WQyrYcw3XPxcLxF+//RszTz/rO48nzD0wQ== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + dependencies: + lower-case "^2.0.2" + tslib "^2.0.3" + +node-addon-api@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" + integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== + +node-gyp-build@^4.2.0: + version "4.8.1" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.1.tgz#976d3ad905e71b76086f4f0b0d3637fe79b6cda5" + integrity sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + +nodemon@^3.0.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.1.3.tgz#dcce9ee0aa7d19cd4dcd576ae9a0456d9078b286" + integrity sha512-m4Vqs+APdKzDFpuaL9F9EVOF85+h070FnkHVEoU4+rmT6Vw0bmNl7s61VEkY/cJkL7RCv1p4urnUDUMrS5rk2w== + dependencies: + chokidar "^3.5.2" + debug "^4" + ignore-by-default "^1.0.1" + minimatch "^3.1.2" + pstree.remy "^1.1.8" + semver "^7.5.3" + simple-update-notifier "^2.0.0" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.5" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +one-time@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" + integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== + dependencies: + fn.name "1.x.x" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-asn1@^5.0.0, parse-asn1@^5.1.7: + version "5.1.7" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.7.tgz#73cdaaa822125f9647165625eb45f8a051d2df06" + integrity sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg== + dependencies: + asn1.js "^4.10.1" + browserify-aes "^1.2.0" + evp_bytestokey "^1.0.3" + hash-base "~3.0" + pbkdf2 "^3.1.2" + safe-buffer "^5.2.1" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw== + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +pascal-case@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" + integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pbkdf2@^3.0.3, pbkdf2@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" + integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +picocolors@^1.0.0, picocolors@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" + integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +postinstall@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/postinstall/-/postinstall-0.8.0.tgz#4acd1d9113831055a35e8670cefb2bf57aa7f87b" + integrity sha512-onh5cnUw4ue+iBzwoyHZNfih1iopqm5abfc/0vK/A9QyYVPxCbLW0DxwrRpHFZ2/Fs5Uo7j4TiaVDNWriq0HIg== + dependencies: + "@danieldietrich/copy" "^0.4.2" + glob "^8.0.3" + minimist "^1.2.6" + resolve-from "^5.0.0" + resolve-pkg "^2.0.0" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^2.1.2, prettier@^2.3.1: + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + +prettier@^3.1.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.1.tgz#e68935518dd90bb7ec4821ba970e68f8de16e1ac" + integrity sha512-7CAwy5dRsxs8PHXT3twixW9/OEll8MLE0VRPCJyl7CkS6VHGPSlsVaWTiASPTyGyYRyApxlaWTzwUxVNrhcwDg== + +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +prompts@^2.0.1, prompts@^2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +protobufjs@^6.11.3: + version "6.11.4" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.4.tgz#29a412c38bf70d89e537b6d02d904a6f448173aa" + integrity sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.1" + "@types/node" ">=13.7.0" + long "^4.0.0" + +protobufjs@^7.2.5: + version "7.3.0" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.3.0.tgz#a32ec0422c039798c41a0700306a6e305b9cb32c" + integrity sha512-YWD03n3shzV9ImZRX3ccbjqLxj7NokGN0V/ESiBV5xWqrommYHYiihuIyavq03pWSGqlyvYUFmfoMKd+1rPA/g== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + +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== + +pstree.remy@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + +public-encrypt@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" + integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== + dependencies: + bn.js "^4.1.0" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + parse-asn1 "^5.0.0" + randombytes "^2.0.1" + safe-buffer "^5.1.2" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + +python-shell@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/python-shell/-/python-shell-3.0.1.tgz#c3d3b11536e6ebdb8d6a2602482f7180d940bb13" + integrity sha512-TWeotuxe1auhXa5bGRScxnc2J+0r41NBntSa6RYZtMBLtAEsvCboKrEbW6DvASosWQepVkhZZlT3B5Ei766G+Q== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +randomfill@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== + dependencies: + randombytes "^2.0.5" + safe-buffer "^5.1.0" + +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +readable-stream@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.4.0, readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== + dependencies: + resolve "^1.1.6" + +reduce-flatten@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" + integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg/-/resolve-pkg-2.0.0.tgz#ac06991418a7623edc119084edc98b0e6bf05a41" + integrity sha512-+1lzwXehGCXSeryaISr6WujZzowloigEofRB+dj75y9RRa/obVcYgbHJd53tdYw8pvZj8GojXaaENws8Ktw/hQ== + dependencies: + resolve-from "^5.0.0" + +resolve.exports@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" + integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== + +resolve@^1.1.6, resolve@^1.20.0, resolve@^1.8.1: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +retry@0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-stable-stringify@^2.3.1: + version "2.4.3" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" + integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== + +scrypt-js@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" + integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== + +secp256k1@4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.2.tgz#15dd57d0f0b9fdb54ac1fa1694f40e5e9a54f4a1" + integrity sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg== + dependencies: + elliptic "^6.5.2" + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.7, semver@^7.5.3, semver@^7.5.4: + version "7.6.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" + integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== + +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +sha3@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/sha3/-/sha3-2.1.4.tgz#000fac0fe7c2feac1f48a25e7a31b52a6492cc8f" + integrity sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg== + dependencies: + buffer "6.0.3" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shelljs@^0.8.4: + version "0.8.5" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" + integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + +signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" + +simple-update-notifier@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" + integrity sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w== + dependencies: + semver "^7.5.3" + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +sjcl@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/sjcl/-/sjcl-1.0.6.tgz#6415462a63cc0d4215c49baec9d3fa0c1b53520f" + integrity sha512-oUVs+hzMSWEZ3rdeDL461QilvvEU2OL9q6T42lpVi2C5Proej9obVZ1nQeY9T96NxoMy/dqw82m33MfNNEmYJg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-trace@0.0.x: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +string-format@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string-format/-/string-format-2.0.0.tgz#f2df2e7097440d3b65de31b6d40d54c96eaffb9b" + integrity sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA== + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.0.1, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^5.3.0, supports-color@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +synckit@^0.8.6: + version "0.8.8" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.8.tgz#fe7fe446518e3d3d49f5e429f443cf08b6edfcd7" + integrity sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ== + dependencies: + "@pkgr/core" "^0.1.0" + tslib "^2.6.2" + +table-layout@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-1.0.2.tgz#c4038a1853b0136d63365a734b6931cf4fad4a04" + integrity sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A== + dependencies: + array-back "^4.0.1" + deep-extend "~0.6.0" + typical "^5.2.0" + wordwrapjs "^4.0.0" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-hex@1.0.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" + integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +touch@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.1.tgz#097a23d7b161476435e5c1344a95c0f75b4a5694" + integrity sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA== + +triple-beam@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984" + integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== + +ts-api-utils@^1.0.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" + integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== + +ts-command-line-args@^2.2.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/ts-command-line-args/-/ts-command-line-args-2.5.1.tgz#e64456b580d1d4f6d948824c274cf6fa5f45f7f0" + integrity sha512-H69ZwTw3rFHb5WYpQya40YAX2/w7Ut75uUECbgBIsLmM+BNuYnxsltfyyLMxy6sEeKxgijLTnQtLd0nKd6+IYw== + dependencies: + chalk "^4.1.0" + command-line-args "^5.1.1" + command-line-usage "^6.1.0" + string-format "^2.0.0" + +ts-essentials@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-1.0.4.tgz#ce3b5dade5f5d97cf69889c11bf7d2da8555b15a" + integrity sha512-q3N1xS4vZpRouhYHDPwO0bDW3EZ6SK9CrrDHxi/D6BPReSjpVgWIOpLS2o0gSBZm+7q/wyKp6RVM1AeeW7uyfQ== + +ts-essentials@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-7.0.3.tgz#686fd155a02133eedcc5362dc8b5056cde3e5a38" + integrity sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ== + +ts-generator@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ts-generator/-/ts-generator-0.1.1.tgz#af46f2fb88a6db1f9785977e9590e7bcd79220ab" + integrity sha512-N+ahhZxTLYu1HNTQetwWcx3so8hcYbkKBHTr4b4/YgObFTIKkOSSsaa+nal12w8mfrJAyzJfETXawbNjSfP2gQ== + dependencies: + "@types/mkdirp" "^0.5.2" + "@types/prettier" "^2.1.1" + "@types/resolve" "^0.0.8" + chalk "^2.4.1" + glob "^7.1.2" + mkdirp "^0.5.1" + prettier "^2.1.2" + resolve "^1.8.1" + ts-essentials "^1.0.0" + +ts-jest@^29.1.1: + version "29.1.4" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.4.tgz#26f8a55ce31e4d2ef7a1fd47dc7fa127e92793ef" + integrity sha512-YiHwDhSvCiItoAgsKtoLFCuakDzDsJ1DLDnSouTaTmdOcOwIkSzbLXduaQ6M5DRVhuZC/NYaaZ/mtHbWMv/S6Q== + dependencies: + bs-logger "0.x" + fast-json-stable-stringify "2.x" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "4.x" + make-error "1.x" + semver "^7.5.3" + yargs-parser "^21.0.1" + +ts-retry@^4.2.4: + version "4.2.5" + resolved "https://registry.yarnpkg.com/ts-retry/-/ts-retry-4.2.5.tgz#ee4638e66c68bb49da975aa4994d5f16bfb61bc2" + integrity sha512-dFBa4pxMBkt/bjzdBio8EwYfbAdycEAwe0KZgzlUKKwU9Wr1WErK7Hg9QLqJuDDYJXTW4KYZyXAyqYKOdO/ehA== + +tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.0.3, tslib@^2.4.0, tslib@^2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" + integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +typechain@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/typechain/-/typechain-8.3.2.tgz#1090dd8d9c57b6ef2aed3640a516bdbf01b00d73" + integrity sha512-x/sQYr5w9K7yv3es7jo4KTX05CLxOf7TRWwoHlrjRh8H82G64g+k7VuWPJlgMo6qrjfCulOdfBjiaDtmhFYD/Q== + dependencies: + "@types/prettier" "^2.1.1" + debug "^4.3.1" + fs-extra "^7.0.0" + glob "7.1.7" + js-sha3 "^0.8.0" + lodash "^4.17.15" + mkdirp "^1.0.4" + prettier "^2.3.1" + ts-command-line-args "^2.2.0" + ts-essentials "^7.0.1" + +typescript@^5.3.2: + version "5.4.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" + integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== + +typical@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" + integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw== + +typical@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066" + integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg== + +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +update-browserslist-db@^1.0.13: + version "1.0.16" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz#f6d489ed90fb2f07d67784eb3f53d7891f736356" + integrity sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ== + dependencies: + escalade "^3.1.2" + picocolors "^1.0.1" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +uuid@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.0.tgz#6728fc0459c450d796a99c31837569bdf672d728" + integrity sha512-rqE1LoOVLv3QrZMjb4NkF5UWlkurCfPyItVnFPNKDDGkHw4dQUdE4zMcLqx28+0Kcf3+bnUk4PisaiRJT4aiaQ== + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +v8-to-istanbul@^9.0.1: + version "9.2.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz#2ed7644a245cddd83d4e087b9b33b3e62dfd10ad" + integrity sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +winston-transport@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.7.0.tgz#e302e6889e6ccb7f383b926df6936a5b781bd1f0" + integrity sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg== + dependencies: + logform "^2.3.2" + readable-stream "^3.6.0" + triple-beam "^1.3.0" + +winston@^3.11.0: + version "3.13.0" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.13.0.tgz#e76c0d722f78e04838158c61adc1287201de7ce3" + integrity sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ== + dependencies: + "@colors/colors" "^1.6.0" + "@dabh/diagnostics" "^2.0.2" + async "^3.2.3" + is-stream "^2.0.0" + logform "^2.4.0" + one-time "^1.0.0" + readable-stream "^3.4.0" + safe-stable-stringify "^2.3.1" + stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.7.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +wordwrapjs@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-4.0.1.tgz#d9790bccfb110a0fc7836b5ebce0937b37a8b98f" + integrity sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA== + dependencies: + reduce-flatten "^2.0.0" + typical "^5.2.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +ws@7.4.6: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-parser@^21.0.1, yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yargs@^17.0.1, yargs@^17.3.1, yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/utils/write-version.js b/utils/write-version.js index 9719f5c1..c7a3dbd2 100755 --- a/utils/write-version.js +++ b/utils/write-version.js @@ -17,12 +17,12 @@ const gitStatusOutput = childProcess const commitHashShort = commitHash.substr(0, 7); const commitMsgShort = commitMsg.split("\n")[0]; const isWdClean = gitStatusOutput === ""; -const commitHashSuffux = isWdClean ? "" : " [dirty]"; +const commitHashSuffix = isWdClean ? "" : " [dirty]"; const version = { - desc: `${commitHashShort}${commitHashSuffux} (${commitMsgShort})`, - commitHash: commitHash + commitHashSuffux, - commitHashShort: commitHashShort + commitHashSuffux, + desc: `${commitHashShort}${commitHashSuffix} (${commitMsgShort})`, + commitHash: commitHash + commitHashSuffix, + commitHashShort: commitHashShort + commitHashSuffix, commitMsg, commitMsgShort, isWdClean, From ad5d0a72eee3e620f032290b7541046ec9940911 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Fri, 7 Jun 2024 12:02:26 +0300 Subject: [PATCH 02/18] new scroll: restore govWatcher, proxyEventWatcher, monitorWithdrawals --- l2-bridges/common/clients/l2_client.ts | 31 +-- .../common/services/monitor_withdrawals.ts | 174 ++++++++++++++++ l2-bridges/scroll/src/agent.ts | 61 +++--- l2-bridges/scroll/src/constants.ts | 194 +++++++++++++++++- 4 files changed, 406 insertions(+), 54 deletions(-) create mode 100644 l2-bridges/common/services/monitor_withdrawals.ts diff --git a/l2-bridges/common/clients/l2_client.ts b/l2-bridges/common/clients/l2_client.ts index f89c43ea..ad0d0220 100644 --- a/l2-bridges/common/clients/l2_client.ts +++ b/l2-bridges/common/clients/l2_client.ts @@ -7,20 +7,19 @@ import { Logger } from 'winston' import { WithdrawalRecord } from '../entity/blockDto' import { Event } from '@ethersproject/contracts' import BigNumber from 'bignumber.js' -// import { WithdrawERC20Event } from '../generated/L2LidoGateway' import { IL2BridgeBalanceClient } from '../services/bridge_balance' -import { ERC20Short as BridgedWstEthRunner, /*L2LidoGateway as ScrollL2BridgeRunner */ } from '../generated' +import { ERC20Short as BridgedWstEthRunner } from '../generated' -// export abstract class IMonitorWithdrawalsClient { -// public abstract getWithdrawalEvents( -// fromBlockNumber: number, -// toBlockNumber: number, -// ): Promise> +export abstract class IMonitorWithdrawalsClient { + public abstract getWithdrawalEvents( + fromBlockNumber: number, + toBlockNumber: number, + ): Promise> -// public abstract getWithdrawalRecords( -// withdrawalEvents: WithdrawERC20Event[], -// ): Promise> -// } + public abstract getWithdrawalRecords( + withdrawalEvents: L2BridgeWithdrawalEvent[], + ): Promise> +} export abstract class IL2Client { public abstract fetchL2Blocks(startBlock: number, endBlock: number): Promise @@ -30,23 +29,20 @@ export abstract class IL2Client { public abstract getLatestL2Block(): Promise> } -export class L2Client implements IL2Client, /*IMonitorWithdrawalsClient,*/ IL2BridgeBalanceClient { +export class L2Client implements IL2Client, IMonitorWithdrawalsClient, IL2BridgeBalanceClient { private readonly logger: Logger private readonly jsonRpcProvider: ethers.providers.JsonRpcProvider - // private readonly scrollL2BridgeRunner: ScrollL2BridgeRunner private readonly withdrawalEventsFetcher: (fromBlockNumber: number, toBlockNumber: number) => Promise private readonly bridgedWstEthRunner: BridgedWstEthRunner constructor( jsonRpcProvider: ethers.providers.JsonRpcProvider, logger: Logger, - // scrollL2BridgeRunner: ScrollL2BridgeRunner, withdrawalEventsFetcher: (fromBlockNumber: number, toBlockNumber: number) => Promise, bridgedWstEthRunner: BridgedWstEthRunner, ) { this.jsonRpcProvider = jsonRpcProvider this.logger = logger - // this.scrollL2BridgeRunner = scrollL2BridgeRunner this.withdrawalEventsFetcher = withdrawalEventsFetcher this.bridgedWstEthRunner = bridgedWstEthRunner } @@ -180,11 +176,6 @@ export class L2Client implements IL2Clien async (): Promise => { return await this.withdrawalEventsFetcher(fromBlockNumber, toBlockNumber) - // this.scrollL2BridgeRunner.queryFilter( - // this.scrollL2BridgeRunner.filters.WithdrawERC20(), - // fromBlockNumber, - // toBlockNumber, - // ) }, { delay: 500, maxTry: 5 }, ) diff --git a/l2-bridges/common/services/monitor_withdrawals.ts b/l2-bridges/common/services/monitor_withdrawals.ts new file mode 100644 index 00000000..089869d0 --- /dev/null +++ b/l2-bridges/common/services/monitor_withdrawals.ts @@ -0,0 +1,174 @@ +import BigNumber from 'bignumber.js' +import { filterLog, Finding, FindingSeverity, FindingType } from 'forta-agent' +import { Logger } from 'winston' +import { Log } from '@ethersproject/abstract-provider' +import * as E from 'fp-ts/Either' +import { BlockDto, WithdrawalRecord } from '../entity/blockDto' +import { IMonitorWithdrawalsClient } from '../clients/l2_client' +import { NetworkError } from '../utils/error' +import { elapsedTime } from '../utils/time' +import { getUniqueKey } from '../utils/finding.helpers' +import { ETH_DECIMALS } from '../utils/constants' +import { formatAddress } from 'forta-agent/dist/cli/utils' +import { Event } from '@ethersproject/contracts' + +// 10k wstETH +const MAX_WITHDRAWALS_SUM = 10_000 + +export type MonitorWithdrawalsInitResp = { + currentWithdrawals: string +} +export const HOURS_48 = 60 * 60 * 24 * 2 + +export class MonitorWithdrawals { + private readonly name: string = 'WithdrawalsMonitor' + + private readonly logger: Logger + private readonly l2Erc20TokenGatewayAddress: string + private readonly withdrawalsClient: IMonitorWithdrawalsClient + private readonly withdrawalInitiatedEvent: string + private readonly l2BlockAverageTime: number + + private withdrawalsStore: WithdrawalRecord[] = [] + private lastReportedTooManyWithdrawalsTimestamp = 0 + + constructor(withdrawalsClient: IMonitorWithdrawalsClient, l2Erc20TokenGatewayAddress: string, logger: Logger, withdrawalInitiatedEvent: string, l2BlockAverageTime: number) { + this.withdrawalsClient = withdrawalsClient + this.l2Erc20TokenGatewayAddress = l2Erc20TokenGatewayAddress + this.logger = logger + this.withdrawalInitiatedEvent = withdrawalInitiatedEvent + this.l2BlockAverageTime = l2BlockAverageTime + } + + public getName(): string { + return this.name + } + + public async initialize(currentBlock: number): Promise> { + const pastBlock = currentBlock - Math.ceil(HOURS_48 / this.l2BlockAverageTime) + + const withdrawalEvents = await this.withdrawalsClient.getWithdrawalEvents(pastBlock, currentBlock - 1) + if (E.isLeft(withdrawalEvents)) { + return withdrawalEvents + } + + const withdrawalRecords = await this.withdrawalsClient.getWithdrawalRecords(withdrawalEvents.right) + if (E.isLeft(withdrawalRecords)) { + return withdrawalRecords + } + + const withdrawalsSum = new BigNumber(0) + for (const wc of withdrawalRecords.right) { + withdrawalsSum.plus(wc.amount) + this.withdrawalsStore.push(wc) + } + + this.logger.info(`${MonitorWithdrawals.name} started on block ${currentBlock}`) + return E.right({ + currentWithdrawals: withdrawalsSum.div(ETH_DECIMALS).toFixed(2), + }) + } + + public handleBlocks(l2Logs: Log[], l2BlocksDto: BlockDto[]): Finding[] { + const start = new Date().getTime() + + // adds records into withdrawalsCache + const withdrawalRecords = this.getWithdrawalRecords(l2Logs, l2BlocksDto) + if (withdrawalRecords.length !== 0) { + this.logger.info(`Withdrawals count = ${withdrawalRecords.length}`) + } + this.withdrawalsStore.push(...withdrawalRecords) + + const out: Finding[] = [] + + for (const l2Block of l2BlocksDto) { + // remove withdrawals records older than MAX_WITHDRAWALS_WINDOW + const withdrawalsCache: WithdrawalRecord[] = [] + for (const wc of this.withdrawalsStore) { + if (wc.time > l2Block.timestamp - HOURS_48) { + withdrawalsCache.push(wc) + } + } + + this.withdrawalsStore = withdrawalsCache + + const withdrawalsSum = new BigNumber(0) + for (const wc of this.withdrawalsStore) { + withdrawalsSum.plus(wc.amount) + } + + // block number condition is meant to "sync" agents alerts + if (withdrawalsSum.div(ETH_DECIMALS).isGreaterThanOrEqualTo(MAX_WITHDRAWALS_SUM) && l2Block.number % 10 === 0) { + const period = + l2Block.timestamp - this.lastReportedTooManyWithdrawalsTimestamp < HOURS_48 + ? l2Block.timestamp - this.lastReportedTooManyWithdrawalsTimestamp + : HOURS_48 + + const uniqueKey = `C167F276-D519-4906-90CB-C4455E9ABBD4` + + const finding: Finding = Finding.fromObject({ + name: `⚠️ Scroll: Huge withdrawals during the last ` + `${Math.floor(period / (60 * 60))} hour(s)`, + description: + `There were withdrawals requests from L2 to L1 for the ` + + `${withdrawalsSum.div(ETH_DECIMALS).toFixed(4)} wstETH in total`, + alertId: 'HUGE-WITHDRAWALS-FROM-L2', + severity: FindingSeverity.Medium, + type: FindingType.Suspicious, + uniqueKey: getUniqueKey(uniqueKey, l2Block.number), + }) + + out.push(finding) + + this.lastReportedTooManyWithdrawalsTimestamp = l2Block.timestamp + + const tmp: WithdrawalRecord[] = [] + for (const wc of this.withdrawalsStore) { + if (wc.time > l2Block.timestamp - this.lastReportedTooManyWithdrawalsTimestamp) { + tmp.push(wc) + } + } + + this.withdrawalsStore = tmp + } + } + + this.logger.info(elapsedTime(MonitorWithdrawals.name + '.' + this.handleBlocks.name, start)) + return out + } + + private getWithdrawalRecords(l2Logs: Log[], l2BlocksDto: BlockDto[]): WithdrawalRecord[] { + const blockNumberToBlock = new Map() + const logIndexToLogs = new Map() + const addresses = new Set() + + for (const l2Log of l2Logs) { + logIndexToLogs.set(l2Log.logIndex, l2Log) + addresses.add(l2Log.address.toLowerCase()) + } + + for (const l2BlockDto of l2BlocksDto) { + blockNumberToBlock.set(l2BlockDto.number, l2BlockDto) + } + + const out: WithdrawalRecord[] = [] + if (formatAddress(this.l2Erc20TokenGatewayAddress) in addresses) { + const events = filterLog(l2Logs, this.withdrawalInitiatedEvent, formatAddress(this.l2Erc20TokenGatewayAddress)) + + for (const event of events) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + const log: Log = logIndexToLogs.get(event.logIndex) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + const blockDto: BlockDto = blockNumberToBlock.get(log.blockNumber) + + out.push({ + time: blockDto.timestamp, + amount: new BigNumber(String(event.args.amount)), + }) + } + } + + return out + } +} diff --git a/l2-bridges/scroll/src/agent.ts b/l2-bridges/scroll/src/agent.ts index 67e32fc4..b6475655 100644 --- a/l2-bridges/scroll/src/agent.ts +++ b/l2-bridges/scroll/src/agent.ts @@ -9,18 +9,16 @@ import BigNumber from 'bignumber.js' import { L2Client } from '../../common/clients/l2_client' import { EventWatcher } from '../../common/services/event_watcher' -// import { getProxyAdminEvents } from './utils/events/proxy_admin_events' // import { ProxyContractClient } from './clients/proxy_contract_client' import { L2LidoGateway__factory } from './generated' import { ERC20Short__factory } from '../../common/generated' import { L2BlockClient } from '../../common/clients/l2_block_client' // import { ProxyWatcher } from './services/proxy_watcher' -// import { MonitorWithdrawals } from './services/monitor_withdrawals' +import { MonitorWithdrawals } from '../../common/services/monitor_withdrawals' import { DataRW } from '../../common/utils/mutex' import * as Winston from 'winston' -// import { getGovEvents } from './utils/events/gov_events' import { MAINNET_CHAIN_ID, DRPC_URL } from '../../common/utils/constants' -import { Constants, getBridgeEvents, L2BridgeWithdrawalEvent } from './constants' +import { Constants, getBridgeEvents, getGovEvents, getProxyAdminEvents, L2BridgeWithdrawalEvent } from './constants' import { Logger } from 'winston' import { ETHProvider } from '../../common/clients/eth_provider_client' import { BridgeBalanceSrv } from '../../common/services/bridge_balance' @@ -31,12 +29,12 @@ import { getJsonRpcUrl } from 'forta-agent/dist/sdk/utils' export type Container = { l2Client: L2Client // proxyWatcher: ProxyWatcher - // monitorWithdrawals: MonitorWithdrawals + monitorWithdrawals: MonitorWithdrawals blockClient: L2BlockClient bridgeWatcher: EventWatcher bridgeBalanceSrv: BridgeBalanceSrv - // govWatcher: EventWatcher - // proxyEventWatcher: EventWatcher + govWatcher: EventWatcher + proxyEventWatcher: EventWatcher findingsRW: DataRW logger: Logger // healthChecker: HealthChecker @@ -66,7 +64,6 @@ export class App { toBlockNumber, ) } - const l2Client = new L2Client(nodeClient, logger, withdrawalEventsFetcher, bridgedWstethRunner) const bridgeEventWatcher = new EventWatcher( @@ -74,12 +71,12 @@ export class App { getBridgeEvents(Constants.L2_ERC20_TOKEN_GATEWAY.address, Constants.RolesMap), logger, ) - // const govEventWatcher = new EventWatcher('GovEventWatcher', getGovEvents(Constants.GOV_BRIDGE_ADDRESS), logger) - // const proxyEventWatcher = new EventWatcher( - // 'ProxyEventWatcher', - // getProxyAdminEvents(Constants.L2_WSTETH_BRIDGED, Constants.L2_ERC20_TOKEN_GATEWAY), - // logger, - // ) + const govEventWatcher = new EventWatcher('GovEventWatcher', getGovEvents(Constants.GOV_BRIDGE_ADDRESS), logger) + const proxyEventWatcher = new EventWatcher( + 'ProxyEventWatcher', + getProxyAdminEvents(Constants.L2_WSTETH_BRIDGED, Constants.L2_ERC20_TOKEN_GATEWAY), + logger, + ) // const LIDO_PROXY_CONTRACTS: ProxyContractClient[] = [ // new ProxyContractClient( @@ -97,7 +94,8 @@ export class App { const blockSrv = new L2BlockClient(l2Client, logger) // const proxyWorker = new ProxyWatcher(LIDO_PROXY_CONTRACTS, logger) - // const monitorWithdrawals = new MonitorWithdrawals(l2Client, Constants.L2_ERC20_TOKEN_GATEWAY.address, logger) + const monitorWithdrawals = new MonitorWithdrawals( + l2Client, Constants.L2_ERC20_TOKEN_GATEWAY.address, logger, Constants.withdrawalInitiatedEvent, Constants.SCROLL_APPROX_BLOCK_TIME_3_SECONDS) const ethProvider = new ethers.providers.FallbackProvider([ @@ -112,12 +110,12 @@ export class App { App.instance = { l2Client: l2Client, // proxyWatcher: proxyWorker, - // monitorWithdrawals: monitorWithdrawals, + monitorWithdrawals: monitorWithdrawals, blockClient: blockSrv, bridgeWatcher: bridgeEventWatcher, bridgeBalanceSrv: bridgeBalanceSrv, - // govWatcher: govEventWatcher, - // proxyEventWatcher: proxyEventWatcher, + govWatcher: govEventWatcher, + proxyEventWatcher: proxyEventWatcher, findingsRW: new DataRW([]), logger: logger, // healthChecker: new HealthChecker(BorderTime, MaxNumberErrorsPerBorderTime), @@ -154,21 +152,20 @@ export function initialize(): Initialize { // process.exit(1) // } - // const monitorWithdrawalsInitResp = await app.monitorWithdrawals.initialize(latestL2Block.right.number) - // if (E.isLeft(monitorWithdrawalsInitResp)) { - // app.logger.error(monitorWithdrawalsInitResp.left) + const monitorWithdrawalsInitResp = await app.monitorWithdrawals.initialize(latestL2Block.right.number) + if (E.isLeft(monitorWithdrawalsInitResp)) { + app.logger.error(monitorWithdrawalsInitResp.left) - // process.exit(1) - // } + process.exit(1) + } // metadata[`${app.proxyWatcher.getName()}.lastAdmins`] = agentMeta.right.lastAdmins // metadata[`${app.proxyWatcher.getName()}.lastImpls`] = agentMeta.right.lastImpls // metadata[`${app.monitorWithdrawals.getName()}.currentWithdrawals`] = // monitorWithdrawalsInitResp.right.currentWithdrawals - // const agents: string[] = [app.proxyWatcher.getName(), app.monitorWithdrawals.getName()] - // metadata.agents = '[' + agents.toString() + ']' - metadata.agents = '[' + ']' + const agents: string[] = [/*app.proxyWatcher.getName(),*/ app.monitorWithdrawals.getName()] + metadata.agents = '[' + agents.toString() + ']' await app.findingsRW.write([ Finding.fromObject({ @@ -220,9 +217,9 @@ export const handleBlock = (): HandleBlock => { } const bridgeEventFindings = app.bridgeWatcher.handleLogs(logs.right) - // const govEventFindings = app.govWatcher.handleLogs(logs.right) - // const proxyAdminEventFindings = app.proxyEventWatcher.handleLogs(logs.right) - // const monitorWithdrawalsFindings = app.monitorWithdrawals.handleBlocks(logs.right, l2blocksDto.right) + const govEventFindings = app.govWatcher.handleLogs(logs.right) + const proxyAdminEventFindings = app.proxyEventWatcher.handleLogs(logs.right) + const monitorWithdrawalsFindings = app.monitorWithdrawals.handleBlocks(logs.right, l2blocksDto.right) const l2blockNumbersSet: Set = new Set() for (const log of logs.right) { @@ -239,9 +236,9 @@ export const handleBlock = (): HandleBlock => { findings.push( ...bridgeEventFindings, ...bridgeBalanceFindings, - // ...govEventFindings, - // ...proxyAdminEventFindings, - // ...monitorWithdrawalsFindings, + ...govEventFindings, + ...proxyAdminEventFindings, + ...monitorWithdrawalsFindings, // ...proxyWatcherFindings, ) diff --git a/l2-bridges/scroll/src/constants.ts b/l2-bridges/scroll/src/constants.ts index 446d3f80..65ff2402 100644 --- a/l2-bridges/scroll/src/constants.ts +++ b/l2-bridges/scroll/src/constants.ts @@ -5,7 +5,10 @@ import { WithdrawERC20Event } from './generated/L2LidoGateway' export type RoleHashToName = Map export type L2BridgeWithdrawalEvent = WithdrawERC20Event - +export type ContractInfo = { + name: string + address: string +} export const Constants = { L2_NAME: 'Scroll', @@ -31,6 +34,7 @@ export const Constants = { ['0x94a954c0bc99227eddbc0715a62a7e1056ed8784cd719c2303b685683908857c', 'WITHDRAWALS_DISABLER_ROLE'], ['0x0000000000000000000000000000000000000000000000000000000000000000', 'DEFAULT_ADMIN_ROLE'], ]), + withdrawalInitiatedEvent: 'event WithdrawERC20(address indexed l1Token, address indexed l2Token, address indexed from, uint256 amount, bytes data)', } @@ -125,4 +129,190 @@ export function getBridgeEvents(l2GatewayAddress: string, RolesAddrToNameMap: Ro uniqueKey: 'E42BC7A0-0715-4D55-AB9D-0A041F639B20', }, ] -} \ No newline at end of file +} + + +export function getGovEvents(GOV_BRIDGE_ADDRESS: string): EventOfNotice[] { + return [ + { + address: GOV_BRIDGE_ADDRESS, + event: + 'event EthereumGovernanceExecutorUpdate(address oldEthereumGovernanceExecutor, address newEthereumGovernanceExecutor)', + alertId: 'GOV-BRIDGE-EXEC-UPDATED', + name: '🚨 Scroll Gov Bridge: Ethereum Governance Executor Updated', + description: (args: Result) => + `Ethereum Governance Executor was updated from ` + + `${args.oldEthereumGovernanceExecutor} to ${args.newEthereumGovernanceExecutor}`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: '73EE62B0-E0CF-4527-8E44-566D72667F22', + }, + { + address: GOV_BRIDGE_ADDRESS, + event: 'event GuardianUpdate(address oldGuardian, address newGuardian)', + alertId: 'GOV-BRIDGE-GUARDIAN-UPDATED', + name: '🚨 Scroll Gov Bridge: Guardian Updated', + description: (args: Result) => `Guardian was updated from ` + `${args.oldGuardian} to ${args.newGuardian}`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: '8C586373-5040-4BDA-8EF8-16CBE582D6B0', + }, + { + address: GOV_BRIDGE_ADDRESS, + event: 'event DelayUpdate(uint256 oldDelay, uint256 newDelay)', + alertId: 'GOV-BRIDGE-DELAY-UPDATED', + name: '⚠️ Scroll Gov Bridge: Delay Updated', + description: (args: Result) => `Delay was updated from ` + `${args.oldDelay} to ${args.newDelay}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: '073F04A8-B232-4671-A34E-D42A3729FE34', + }, + { + address: GOV_BRIDGE_ADDRESS, + event: 'event GracePeriodUpdate(uint256 oldGracePeriod, uint256 newGracePeriod)', + alertId: 'GOV-BRIDGE-GRACE-PERIOD-UPDATED', + name: '⚠️ Scroll Gov Bridge: Grace Period Updated', + description: (args: Result) => + `Grace Period was updated from ` + `${args.oldGracePeriod} to ${args.newGracePeriod}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: '26574C78-EBD1-42D3-A9C7-3E4A2976FCB7', + }, + { + address: GOV_BRIDGE_ADDRESS, + event: 'event MinimumDelayUpdate(uint256 oldMinimumDelay, uint256 newMinimumDelay)', + alertId: 'GOV-BRIDGE-MIN-DELAY-UPDATED', + name: '⚠️ Scroll Gov Bridge: Min Delay Updated', + description: (args: Result) => + `Min Delay was updated from ` + `${args.oldMinimumDelay} to ${args.newMinimumDelay}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: '35391E05-CBB4-4013-ACA1-A75F8C5D6991', + }, + { + address: GOV_BRIDGE_ADDRESS, + event: 'event MaximumDelayUpdate(uint256 oldMaximumDelay, uint256 newMaximumDelay)', + alertId: 'GOV-BRIDGE-MAX-DELAY-UPDATED', + name: '⚠️ Scroll Gov Bridge: Max Delay Updated', + description: (args: Result) => + `Max Delay was updated from ` + `${args.oldMaximumDelay} to ${args.newMaximumDelay}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: '003CCEDE-551A-4310-86A7-F8EC22135C45', + }, + { + address: GOV_BRIDGE_ADDRESS, + event: + 'event ActionsSetQueued(uint256 indexed id, address[] targets, uint256[] values, string[] signatures, bytes[] calldatas, bool[] withDelegatecalls, uint256 executionTime)', + alertId: 'GOV-BRIDGE-ACTION-SET-QUEUED', + name: 'ℹ️ Scroll Gov Bridge: Action set queued', + description: (args: Result) => `Action set ${args.id} was queued`, + severity: FindingSeverity.Info, + type: FindingType.Info, + uniqueKey: '84D309D8-AB13-4B41-A9CE-8DE4AB77E77A', + }, + { + address: GOV_BRIDGE_ADDRESS, + event: 'event ActionsSetExecuted(uint256 indexed id, address indexed initiatorExecution, bytes[] returnedData)', + alertId: 'GOV-BRIDGE-ACTION-SET-EXECUTED', + name: 'ℹ️ Scroll Gov Bridge: Action set executed', + description: (args: Result) => `Action set ${args.id} was executed`, + severity: FindingSeverity.Info, + type: FindingType.Info, + uniqueKey: '31FE6EEB-4619-4579-9C0B-58EECC3D7724', + }, + { + address: GOV_BRIDGE_ADDRESS, + event: 'event ActionsSetCanceled(uint256 indexed id)', + alertId: 'GOV-BRIDGE-ACTION-SET-CANCELED', + name: 'ℹ️ Scroll Gov Bridge: Action set canceled', + description: (args: Result) => `Action set ${args.id} was canceled`, + severity: FindingSeverity.Info, + type: FindingType.Info, + uniqueKey: '76022839-385E-4AD7-85E9-3739C1CACA09', + }, + ] +} + +export function getProxyAdminEvents(l2WstethContract: ContractInfo, l2GatewayContract: ContractInfo): EventOfNotice[] { + return [ + { + address: l2WstethContract.address, + event: 'event AdminChanged(address previousAdmin, address newAdmin)', + alertId: 'PROXY-ADMIN-CHANGED', + name: '🚨 Scroll: Proxy admin changed', + description: (args: Result) => + `Proxy admin for ${l2WstethContract.name}(${l2WstethContract.address}) ` + + `was changed from ${args.previousAdmin} to ${args.newAdmin}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: '18BA44FB-E5AC-4F7D-A556-3B49D9381B0C', + }, + { + address: l2WstethContract.address, + event: 'event Upgraded(address indexed implementation)', + alertId: 'PROXY-UPGRADED', + name: '🚨 Scroll: Proxy upgraded', + description: (args: Result) => + `Proxy for ${l2WstethContract.name}(${l2WstethContract.address}) ` + + `was updated to ${args.implementation}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: '6D0FC28D-0D3E-41D3-8F9A-2A52AFDA7543', + }, + { + address: l2WstethContract.address, + event: 'event BeaconUpgraded(address indexed beacon)', + alertId: 'PROXY-BEACON-UPGRADED', + name: '🚨 Scroll: Proxy beacon upgraded', + description: (args: Result) => + `Proxy for ${l2WstethContract.name}(${l2WstethContract.address}) ` + + `beacon was updated to ${args.beacon}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: '913345D0-B591-4699-9E5B-384C2640A9C3', + }, + { + address: l2GatewayContract.address, + event: 'event AdminChanged(address previousAdmin, address newAdmin)', + alertId: 'PROXY-ADMIN-CHANGED', + name: '🚨 Scroll: Proxy admin changed', + description: (args: Result) => + `Proxy admin for ${l2GatewayContract.name}(${l2GatewayContract.address}) ` + + `was changed from ${args.previousAdmin} to ${args.newAdmin}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: 'DE3F6E46-984B-435F-B88C-E5198386CCF6', + }, + { + address: l2GatewayContract.address, + event: 'event Upgraded(address indexed implementation)', + alertId: 'PROXY-UPGRADED', + name: '🚨 Scroll: Proxy upgraded', + description: (args: Result) => + `Proxy for ${l2GatewayContract.name}(${l2GatewayContract.address}) ` + + `was updated to ${args.implementation}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: 'CFA25A7F-69C4-45BE-8CAE-884EE8FEF5CA', + }, + { + address: l2GatewayContract.address, + event: 'event BeaconUpgraded(address indexed beacon)', + alertId: 'PROXY-BEACON-UPGRADED', + name: '🚨 Scroll: Proxy beacon upgraded', + description: (args: Result) => + `Proxy for ${l2GatewayContract.name}(${l2GatewayContract.address}) ` + + `beacon was updated to ${args.beacon}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: 'B7193990-458E-4F41-ADD9-82848D235F5B', + }, + ] +} From 1550b8ea216ce803b7167019e7f50aa4e77051a7 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Tue, 11 Jun 2024 11:23:08 +0300 Subject: [PATCH 03/18] feat: fix monitorWithdrawals + its historical test --- l2-bridges/common/clients/l2_block_client.ts | 115 --------- l2-bridges/common/clients/l2_client.ts | 225 +++++++++++------- .../common/services/monitor_withdrawals.ts | 70 ++++-- l2-bridges/common/utils/constants.ts | 6 +- l2-bridges/jest.config.js | 6 + l2-bridges/package.json | 2 + l2-bridges/push-agent.js | 2 +- l2-bridges/scroll/src/agent.ts | 30 +-- l2-bridges/scroll/src/constants.ts | 17 +- .../scroll/test/monitor_withdrawals.spec.ts | 62 +++++ 10 files changed, 291 insertions(+), 244 deletions(-) delete mode 100644 l2-bridges/common/clients/l2_block_client.ts create mode 100644 l2-bridges/jest.config.js create mode 100644 l2-bridges/scroll/test/monitor_withdrawals.spec.ts diff --git a/l2-bridges/common/clients/l2_block_client.ts b/l2-bridges/common/clients/l2_block_client.ts deleted file mode 100644 index 05bde037..00000000 --- a/l2-bridges/common/clients/l2_block_client.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { BlockDto } from '../entity/blockDto' -import { IL2Client } from './l2_client' -import { Log } from '@ethersproject/abstract-provider' -import { Finding } from 'forta-agent' -import * as E from 'fp-ts/Either' -import { networkAlert } from '../utils/finding.helpers' -import { Logger } from 'winston' -import { elapsedTime } from '../utils/time' - - -export class L2BlockClient { - private provider: IL2Client - private logger: Logger - private cachedBlockDto: BlockDto | undefined = undefined - - constructor(provider: IL2Client, logger: Logger) { - this.provider = provider - this.logger = logger - } - - public async getL2Blocks(): Promise> { - const start = new Date().getTime() - const blocks = await this.fetchL2Blocks() - this.logger.info(elapsedTime(L2BlockClient.name + '.' + this.getL2Blocks.name, start)) - - return blocks - } - - public async getL2Logs(workingBlocks: BlockDto[]): Promise> { - const start = new Date().getTime() - const logs = await this.fetchL2Logs(workingBlocks) - this.logger.info(elapsedTime(L2BlockClient.name + '.' + this.getL2Logs.name, start)) - - return logs - } - - private async fetchL2Blocks(): Promise> { - const out: BlockDto[] = [] - - if (this.cachedBlockDto === undefined) { - const l2Block = await this.provider.getLatestL2Block() - if (E.isLeft(l2Block)) { - return E.left( - networkAlert( - l2Block.left, - `Error in ${L2BlockClient.name}.${this.getL2Blocks.name}:21`, - `Could not call l2Provider.getLatestL2Block`, - 0, - ), - ) - } - - this.cachedBlockDto = { - number: l2Block.right.number, - timestamp: l2Block.right.timestamp, - } - - out.push(this.cachedBlockDto) - } else { - const latestL2Block = await this.provider.getLatestL2Block() - if (E.isLeft(latestL2Block)) { - this.cachedBlockDto = undefined - return E.left( - networkAlert( - latestL2Block.left, - `Error in ${L2BlockClient.name}.${this.getL2Blocks.name}:39`, - `Could not call l2Provider.getLatestL2Block`, - 0, - ), - ) - } - - const l2Blocks = await this.provider.fetchL2Blocks(this.cachedBlockDto.number, latestL2Block.right.number - 1) - for (const l2Block of l2Blocks) { - out.push({ - number: l2Block.number, - timestamp: l2Block.timestamp, - }) - } - - this.cachedBlockDto = { - number: latestL2Block.right.number, - timestamp: latestL2Block.right.timestamp, - } - - // hint: we requested blocks like [cachedBlockDto.number, latestBlock.number) - // and here we do [cachedBlockDto.number, latestBlock.number] - out.push({ - number: latestL2Block.right.number, - timestamp: latestL2Block.right.timestamp, - }) - } - - return E.right(out) - } - - private async fetchL2Logs(workingL2Blocks: BlockDto[]): Promise> { - const l2Logs = await this.provider.getL2Logs( - workingL2Blocks[0].number, - workingL2Blocks[workingL2Blocks.length - 1].number, - ) - if (E.isLeft(l2Logs)) { - return E.left( - networkAlert( - l2Logs.left, - `Error in ${L2BlockClient.name}.${this.getL2Logs.name}:76`, - `Could not call l2Provider.getL2Logs`, - workingL2Blocks[workingL2Blocks.length - 1].number, - ), - ) - } - - return E.right(l2Logs.right) - } -} diff --git a/l2-bridges/common/clients/l2_client.ts b/l2-bridges/common/clients/l2_client.ts index ad0d0220..75fa364e 100644 --- a/l2-bridges/common/clients/l2_client.ts +++ b/l2-bridges/common/clients/l2_client.ts @@ -1,60 +1,57 @@ import { Block, Log } from '@ethersproject/abstract-provider' -import { ethers } from 'forta-agent' +import { ethers, Finding } from 'forta-agent' import * as E from 'fp-ts/Either' import { retryAsync } from 'ts-retry' import { NetworkError } from '../utils/error' +import { elapsedTime } from '../utils/time' import { Logger } from 'winston' -import { WithdrawalRecord } from '../entity/blockDto' -import { Event } from '@ethersproject/contracts' +import { BlockDto } from '../entity/blockDto' import BigNumber from 'bignumber.js' import { IL2BridgeBalanceClient } from '../services/bridge_balance' import { ERC20Short as BridgedWstEthRunner } from '../generated' +import { networkAlert } from '../utils/finding.helpers' -export abstract class IMonitorWithdrawalsClient { - public abstract getWithdrawalEvents( - fromBlockNumber: number, - toBlockNumber: number, - ): Promise> - public abstract getWithdrawalRecords( - withdrawalEvents: L2BridgeWithdrawalEvent[], - ): Promise> -} - -export abstract class IL2Client { - public abstract fetchL2Blocks(startBlock: number, endBlock: number): Promise - - public abstract getL2Logs(startBlock: number, endBlock: number): Promise> - - public abstract getLatestL2Block(): Promise> -} - -export class L2Client implements IL2Client, IMonitorWithdrawalsClient, IL2BridgeBalanceClient { +export class L2Client implements IL2BridgeBalanceClient { private readonly logger: Logger private readonly jsonRpcProvider: ethers.providers.JsonRpcProvider - private readonly withdrawalEventsFetcher: (fromBlockNumber: number, toBlockNumber: number) => Promise private readonly bridgedWstEthRunner: BridgedWstEthRunner + private readonly maxBlocksPerGetLogsRequest: number + + private cachedBlockDto: BlockDto | undefined = undefined constructor( jsonRpcProvider: ethers.providers.JsonRpcProvider, logger: Logger, - withdrawalEventsFetcher: (fromBlockNumber: number, toBlockNumber: number) => Promise, bridgedWstEthRunner: BridgedWstEthRunner, + maxBlocksPerGetLogsRequest: number, ) { this.jsonRpcProvider = jsonRpcProvider this.logger = logger - this.withdrawalEventsFetcher = withdrawalEventsFetcher this.bridgedWstEthRunner = bridgedWstEthRunner + this.maxBlocksPerGetLogsRequest = maxBlocksPerGetLogsRequest } - public async fetchL2Blocks(startBlock: number, endBlock: number): Promise { - const batchRequests = [] + public async fetchL2Blocks(blockNumbers: Set): Promise { + return this._fetchL2Blocks(blockNumbers) + } + + public async fetchL2BlocksRange(startBlock: number, endBlock: number): Promise { + const blockNumbers = new Set() for (let i = startBlock; i <= endBlock; i++) { + blockNumbers.add(i) + } + return this._fetchL2Blocks(blockNumbers) + } + + private async _fetchL2Blocks(blockNumbers: Set): Promise { + const batchRequests = [] + for (const n of blockNumbers) { batchRequests.push({ jsonrpc: '2.0', method: 'eth_getBlockByNumber', - params: [`0x${i.toString(16)}`, false], - id: i, // Use a unique identifier for each request + params: [`0x${n.toString(16)}`, false], + id: n, // Use a unique identifier for each request }) } const formatter = new ethers.providers.Formatter() @@ -104,6 +101,7 @@ export class L2Client implements IL2Clien const blocks = await doRequest(request) out.push(...blocks) } catch (e) { + this.logger.warn(`${e}`) if (allowedExtraRequest === 0) { break @@ -117,24 +115,31 @@ export class L2Client implements IL2Clien return out.toSorted((a, b) => a.number - b.number) } - public async getL2Logs(startBlock: number, endBlock: number): Promise> { + public async getL2Logs( + startBlock: number, + endBlock: number, + address: string | undefined = undefined, + eventSignature: string | undefined = undefined, + ): Promise> + { const logs: Log[] = [] - const batchSize = 15 - + const batchSize = Math.min(endBlock - startBlock + 1, this.maxBlocksPerGetLogsRequest) for (let i = startBlock; i <= endBlock; i += batchSize) { const start = i const end = Math.min(i + batchSize - 1, endBlock) - let chunkLogs: Log[] = [] try { chunkLogs = await retryAsync( async (): Promise => { - return await this.jsonRpcProvider.send('eth_getLogs', [ - { - fromBlock: `0x${start.toString(16)}`, - toBlock: `0x${end.toString(16)}`, - }, - ]) + const params: {[key: string]: unknown} = { + fromBlock: `0x${start.toString(16)}`, + toBlock: `0x${end.toString(16)}`, + address, + } + if (eventSignature) { + params['topics'] = [eventSignature] + } + return await this.jsonRpcProvider.send('eth_getLogs', [params]) }, { delay: 500, maxTry: 5 }, ) @@ -167,73 +172,117 @@ export class L2Client implements IL2Clien } } - public async getWithdrawalEvents( - fromBlockNumber: number, - toBlockNumber: number, - ): Promise> { + public async getWstEthTotalSupply(l2blockNumber: number): Promise> { try { - const out = await retryAsync( + const out = await retryAsync( + async (): Promise => { + const [balance] = await this.bridgedWstEthRunner.functions.totalSupply({ + blockTag: l2blockNumber, + }) - async (): Promise => { - return await this.withdrawalEventsFetcher(fromBlockNumber, toBlockNumber) + return balance.toString() }, { delay: 500, maxTry: 5 }, ) - return E.right(out) + return E.right(new BigNumber(out)) } catch (e) { - return E.left(new NetworkError(e, `Could not fetch l2 bridge withdrawal events`)) + return E.left(new NetworkError(e, `Could not call bridgedWstEthRunner.functions.totalSupply`)) } } - public async getWithdrawalRecords( - withdrawalEvents: L2BridgeWithdrawalEvent[], - ): Promise> { - const out: WithdrawalRecord[] = [] - - for (const withdrawEvent of withdrawalEvents) { - if (withdrawEvent.args) { - let block: Block - try { - block = await retryAsync( - async (): Promise => { - return await withdrawEvent.getBlock() - }, - { delay: 500, maxTry: 5 }, - ) + public async getNotYetProcessedL2Blocks(): Promise> { + const start = new Date().getTime() + const blocks = await this._fetchNotYetProcessedL2Blocks() + this.logger.info(elapsedTime(this.constructor.name + '.' + this._fetchNotYetProcessedL2Blocks.name, start)) - const record: WithdrawalRecord = { - time: block.timestamp, - // TODO: args.amount might be different for L2 networks - amount: new BigNumber(String(withdrawEvent.args.amount)), - } + return blocks + } - out.push(record) - } catch (e) { - return E.left(new NetworkError(e, `Could not fetch block from withdrawEvent`)) - } - } + public async getL2LogsOrNetworkAlert(workingBlocks: BlockDto[]): Promise> { + const start = new Date().getTime() + + let result: E.Either + const logs = await this.getL2Logs( + workingBlocks[0].number, + workingBlocks[workingBlocks.length - 1].number + ) + if (E.isLeft(logs)) { + result = E.left( + networkAlert( + logs.left, + `Error in ${this.constructor.name}.${this.getL2Logs.name}:76`, + `Could not call getL2Logs`, + workingBlocks[workingBlocks.length - 1].number, + ), + ) + } else { + result = E.right(logs.right) } - return E.right(out) + this.logger.info(elapsedTime(this.constructor.name + '.' + this.getL2Logs.name, start)) + + return result } - public async getWstEthTotalSupply(l2blockNumber: number): Promise> { - try { - const out = await retryAsync( - async (): Promise => { - const [balance] = await this.bridgedWstEthRunner.functions.totalSupply({ - blockTag: l2blockNumber, - }) + private async _fetchNotYetProcessedL2Blocks(): Promise> { + const out: BlockDto[] = [] + + if (this.cachedBlockDto === undefined) { + const l2Block = await this.getLatestL2Block() + if (E.isLeft(l2Block)) { + return E.left( + networkAlert( + l2Block.left, + `Error in ${this.constructor.name}.${this._fetchNotYetProcessedL2Blocks.name}:21`, + `Could not call l2Provider.getLatestL2Block`, + 0, + ), + ) + } - return balance.toString() - }, - { delay: 500, maxTry: 5 }, - ) + this.cachedBlockDto = { + number: l2Block.right.number, + timestamp: l2Block.right.timestamp, + } - return E.right(new BigNumber(out)) - } catch (e) { - return E.left(new NetworkError(e, `Could not call bridgedWstEthRunner.functions.totalSupply`)) + out.push(this.cachedBlockDto) + } else { + const latestL2Block = await this.getLatestL2Block() + if (E.isLeft(latestL2Block)) { + this.cachedBlockDto = undefined + return E.left( + networkAlert( + latestL2Block.left, + `Error in ${this.constructor.name}.${this._fetchNotYetProcessedL2Blocks.name}:39`, + `Could not call l2Provider.getLatestL2Block`, + 0, + ), + ) + } + + const l2Blocks = await this.fetchL2BlocksRange(this.cachedBlockDto.number, latestL2Block.right.number - 1) + for (const l2Block of l2Blocks) { + out.push({ + number: l2Block.number, + timestamp: l2Block.timestamp, + }) + } + + this.cachedBlockDto = { + number: latestL2Block.right.number, + timestamp: latestL2Block.right.timestamp, + } + + // hint: we requested blocks like [cachedBlockDto.number, latestBlock.number) + // and here we do [cachedBlockDto.number, latestBlock.number] + out.push({ + number: latestL2Block.right.number, + timestamp: latestL2Block.right.timestamp, + }) } + + return E.right(out) } + } diff --git a/l2-bridges/common/services/monitor_withdrawals.ts b/l2-bridges/common/services/monitor_withdrawals.ts index 089869d0..7181cf5b 100644 --- a/l2-bridges/common/services/monitor_withdrawals.ts +++ b/l2-bridges/common/services/monitor_withdrawals.ts @@ -4,13 +4,15 @@ import { Logger } from 'winston' import { Log } from '@ethersproject/abstract-provider' import * as E from 'fp-ts/Either' import { BlockDto, WithdrawalRecord } from '../entity/blockDto' -import { IMonitorWithdrawalsClient } from '../clients/l2_client' +import { L2Client } from '../clients/l2_client' import { NetworkError } from '../utils/error' import { elapsedTime } from '../utils/time' import { getUniqueKey } from '../utils/finding.helpers' import { ETH_DECIMALS } from '../utils/constants' import { formatAddress } from 'forta-agent/dist/cli/utils' -import { Event } from '@ethersproject/contracts' +import { ethers } from 'ethers' +import { WithdrawalInfo } from '../utils/constants' +import assert from 'assert' // 10k wstETH const MAX_WITHDRAWALS_SUM = 10_000 @@ -20,24 +22,30 @@ export type MonitorWithdrawalsInitResp = { } export const HOURS_48 = 60 * 60 * 24 * 2 -export class MonitorWithdrawals { +export class MonitorWithdrawals { private readonly name: string = 'WithdrawalsMonitor' private readonly logger: Logger private readonly l2Erc20TokenGatewayAddress: string - private readonly withdrawalsClient: IMonitorWithdrawalsClient - private readonly withdrawalInitiatedEvent: string + private readonly l2Client: L2Client + private readonly withdrawalInfo: WithdrawalInfo & { eventSignature: string, eventInterface: ethers.utils.Interface } private readonly l2BlockAverageTime: number private withdrawalsStore: WithdrawalRecord[] = [] private lastReportedTooManyWithdrawalsTimestamp = 0 - constructor(withdrawalsClient: IMonitorWithdrawalsClient, l2Erc20TokenGatewayAddress: string, logger: Logger, withdrawalInitiatedEvent: string, l2BlockAverageTime: number) { - this.withdrawalsClient = withdrawalsClient + constructor(l2Client: L2Client, l2Erc20TokenGatewayAddress: string, logger: Logger, withdrawalInfo: WithdrawalInfo, l2BlockAverageTime: number) { + const eventInterface = new ethers.utils.Interface([withdrawalInfo.eventDefinition]) + this.l2Client = l2Client this.l2Erc20TokenGatewayAddress = l2Erc20TokenGatewayAddress this.logger = logger - this.withdrawalInitiatedEvent = withdrawalInitiatedEvent + this.withdrawalInfo = { + ...withdrawalInfo, + eventSignature: eventInterface.getEventTopic(withdrawalInfo.eventName), + eventInterface: eventInterface, + } this.l2BlockAverageTime = l2BlockAverageTime + } public getName(): string { @@ -45,16 +53,13 @@ export class MonitorWithdrawals { } public async initialize(currentBlock: number): Promise> { - const pastBlock = currentBlock - Math.ceil(HOURS_48 / this.l2BlockAverageTime) - - const withdrawalEvents = await this.withdrawalsClient.getWithdrawalEvents(pastBlock, currentBlock - 1) - if (E.isLeft(withdrawalEvents)) { - return withdrawalEvents - } + const start = new Date().getTime() + const startBlock = currentBlock - Math.ceil(HOURS_48 / this.l2BlockAverageTime) + const endBlock = currentBlock - 1 - const withdrawalRecords = await this.withdrawalsClient.getWithdrawalRecords(withdrawalEvents.right) + const withdrawalRecords = await this._getWithdrawalRecordsInBlockRange(startBlock, endBlock) if (E.isLeft(withdrawalRecords)) { - return withdrawalRecords + return E.left(withdrawalRecords.left) } const withdrawalsSum = new BigNumber(0) @@ -64,6 +69,7 @@ export class MonitorWithdrawals { } this.logger.info(`${MonitorWithdrawals.name} started on block ${currentBlock}`) + this.logger.info(elapsedTime(MonitorWithdrawals.name + '.' + this._getWithdrawalRecordsInBlockRange.name, start)) return E.right({ currentWithdrawals: withdrawalsSum.div(ETH_DECIMALS).toFixed(2), }) @@ -136,6 +142,36 @@ export class MonitorWithdrawals { return out } + public async _getWithdrawalRecordsInBlockRange(startBlock: number, endBlock: number): Promise> { + const withdrawalLogsE = await this.l2Client.getL2Logs(startBlock, endBlock, this.l2Erc20TokenGatewayAddress, this.withdrawalInfo.eventSignature) + if (E.isLeft(withdrawalLogsE)) { + return E.left(withdrawalLogsE.left) + } + + const blocksToRequest = new Set() + const blockNumberToTime = new Map() + const withdrawalRecordsAux: { amount: BigNumber, blockNumber: number }[] = [] + for (const log of withdrawalLogsE.right) { + const event = this.withdrawalInfo.eventInterface.parseLog(log) + // NB: log.blockNumber is actually a string, although its typescript type is number + const blockNumber = (typeof log.blockNumber === 'number') ? log.blockNumber : Number(log.blockNumber) + withdrawalRecordsAux.push({ blockNumber: blockNumber, amount: new BigNumber(String(event.args.amount)) }) + blocksToRequest.add(blockNumber) + } + const blocks = await this.l2Client.fetchL2Blocks(blocksToRequest) + for (const oneBlock of blocks) { + blockNumberToTime.set(oneBlock.number, oneBlock.timestamp) + } + const result: WithdrawalRecord[] = [] + for (const record of withdrawalRecordsAux) { + const blockTime = blockNumberToTime.get(record.blockNumber) + assert(blockTime) + result.push({ time: blockTime, amount: record.amount }) + } + + return E.right(result) + } + private getWithdrawalRecords(l2Logs: Log[], l2BlocksDto: BlockDto[]): WithdrawalRecord[] { const blockNumberToBlock = new Map() const logIndexToLogs = new Map() @@ -152,7 +188,7 @@ export class MonitorWithdrawals { const out: WithdrawalRecord[] = [] if (formatAddress(this.l2Erc20TokenGatewayAddress) in addresses) { - const events = filterLog(l2Logs, this.withdrawalInitiatedEvent, formatAddress(this.l2Erc20TokenGatewayAddress)) + const events = filterLog(l2Logs, this.withdrawalInfo.eventDefinition, formatAddress(this.l2Erc20TokenGatewayAddress)) for (const event of events) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/l2-bridges/common/utils/constants.ts b/l2-bridges/common/utils/constants.ts index 940a50e9..6a97a37f 100644 --- a/l2-bridges/common/utils/constants.ts +++ b/l2-bridges/common/utils/constants.ts @@ -1,6 +1,10 @@ import BigNumber from 'bignumber.js' +export type WithdrawalInfo = { + eventName: string, + eventDefinition: string, // e.g. "event WithdawalEvent(address indexed l1token, ...)" +} export const ETH_DECIMALS = new BigNumber(10).pow(18) export const MAINNET_CHAIN_ID = 1 -export const DRPC_URL = 'https://eth.drpc.org/' \ No newline at end of file +export const DRPC_URL = 'https://eth.drpc.org/' diff --git a/l2-bridges/jest.config.js b/l2-bridges/jest.config.js new file mode 100644 index 00000000..9dfeb5d2 --- /dev/null +++ b/l2-bridges/jest.config.js @@ -0,0 +1,6 @@ +/** @type {import("ts-jest").JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testPathIgnorePatterns: ['dist'], +} diff --git a/l2-bridges/package.json b/l2-bridges/package.json index a423bc2e..02eb5956 100644 --- a/l2-bridges/package.json +++ b/l2-bridges/package.json @@ -6,6 +6,8 @@ "start:scroll": "yarn start scroll dev", "push:scroll": "yarn update-version && node push-agent.js scroll", + "test": "jest", + "postinstall": "yarn generate-types", "generate-types": "typechain --target=ethers-v5 --out-dir=./scroll/src/generated ./scroll/src/abi/* && typechain --target=ethers-v5 --out-dir=./common/generated ./common/abi/*", "update-version": "node ./common/utils/write-version.js ", diff --git a/l2-bridges/push-agent.js b/l2-bridges/push-agent.js index 29acc530..f4fba84a 100644 --- a/l2-bridges/push-agent.js +++ b/l2-bridges/push-agent.js @@ -67,7 +67,7 @@ const main = async () => { const imageIpfsCid = cidLine.substring(cidStartIndex, cidEndIndex) const imageReference = `${imageIpfsCid}@sha256:${imageDigest}` - console.log(`\nThe docker image reference: ${imageReference}`); + console.log(`\nThe docker image reference: ${imageReference}`) } main() diff --git a/l2-bridges/scroll/src/agent.ts b/l2-bridges/scroll/src/agent.ts index b6475655..635c2b82 100644 --- a/l2-bridges/scroll/src/agent.ts +++ b/l2-bridges/scroll/src/agent.ts @@ -12,13 +12,12 @@ import { EventWatcher } from '../../common/services/event_watcher' // import { ProxyContractClient } from './clients/proxy_contract_client' import { L2LidoGateway__factory } from './generated' import { ERC20Short__factory } from '../../common/generated' -import { L2BlockClient } from '../../common/clients/l2_block_client' // import { ProxyWatcher } from './services/proxy_watcher' import { MonitorWithdrawals } from '../../common/services/monitor_withdrawals' import { DataRW } from '../../common/utils/mutex' import * as Winston from 'winston' import { MAINNET_CHAIN_ID, DRPC_URL } from '../../common/utils/constants' -import { Constants, getBridgeEvents, getGovEvents, getProxyAdminEvents, L2BridgeWithdrawalEvent } from './constants' +import { Constants, getBridgeEvents, getGovEvents, getProxyAdminEvents } from './constants' import { Logger } from 'winston' import { ETHProvider } from '../../common/clients/eth_provider_client' import { BridgeBalanceSrv } from '../../common/services/bridge_balance' @@ -27,10 +26,10 @@ import { getJsonRpcUrl } from 'forta-agent/dist/sdk/utils' export type Container = { - l2Client: L2Client + l2Client: L2Client // proxyWatcher: ProxyWatcher - monitorWithdrawals: MonitorWithdrawals - blockClient: L2BlockClient + monitorWithdrawals: MonitorWithdrawals + // blockClient: L2BlockClient bridgeWatcher: EventWatcher bridgeBalanceSrv: BridgeBalanceSrv govWatcher: EventWatcher @@ -57,14 +56,7 @@ export class App { const bridgedWstethRunner = ERC20Short__factory.connect(Constants.L2_WSTETH_BRIDGED.address, nodeClient) const l2Bridge = L2LidoGateway__factory.connect(Constants.L2_ERC20_TOKEN_GATEWAY.address, nodeClient) - const withdrawalEventsFetcher = (fromBlockNumber: number, toBlockNumber: number) => { - return l2Bridge.queryFilter( - l2Bridge.filters.WithdrawERC20(), - fromBlockNumber, - toBlockNumber, - ) - } - const l2Client = new L2Client(nodeClient, logger, withdrawalEventsFetcher, bridgedWstethRunner) + const l2Client = new L2Client(nodeClient, logger, bridgedWstethRunner, Constants.MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST) const bridgeEventWatcher = new EventWatcher( 'BridgeEventWatcher', @@ -91,11 +83,12 @@ export class App { // ), // ] - const blockSrv = new L2BlockClient(l2Client, logger) // const proxyWorker = new ProxyWatcher(LIDO_PROXY_CONTRACTS, logger) - const monitorWithdrawals = new MonitorWithdrawals( - l2Client, Constants.L2_ERC20_TOKEN_GATEWAY.address, logger, Constants.withdrawalInitiatedEvent, Constants.SCROLL_APPROX_BLOCK_TIME_3_SECONDS) + const monitorWithdrawals = new MonitorWithdrawals( + l2Client, Constants.L2_ERC20_TOKEN_GATEWAY.address, logger, Constants.withdrawalInfo, + Constants.SCROLL_APPROX_BLOCK_TIME_3_SECONDS + ) const ethProvider = new ethers.providers.FallbackProvider([ @@ -111,7 +104,6 @@ export class App { l2Client: l2Client, // proxyWatcher: proxyWorker, monitorWithdrawals: monitorWithdrawals, - blockClient: blockSrv, bridgeWatcher: bridgeEventWatcher, bridgeBalanceSrv: bridgeBalanceSrv, govWatcher: govEventWatcher, @@ -199,7 +191,7 @@ export const handleBlock = (): HandleBlock => { findings.push(...findingsAsync) } - const l2blocksDto = await app.blockClient.getL2Blocks() + const l2blocksDto = await app.l2Client.getNotYetProcessedL2Blocks() if (E.isLeft(l2blocksDto)) { isHandleBlockRunning = false return [l2blocksDto.left] @@ -210,7 +202,7 @@ export const handleBlock = (): HandleBlock => { }. Total: ${l2blocksDto.right.length}`, ) - const logs = await app.blockClient.getL2Logs(l2blocksDto.right) + const logs = await app.l2Client.getL2LogsOrNetworkAlert(l2blocksDto.right) if (E.isLeft(logs)) { isHandleBlockRunning = false return [logs.left] diff --git a/l2-bridges/scroll/src/constants.ts b/l2-bridges/scroll/src/constants.ts index 65ff2402..c8439e60 100644 --- a/l2-bridges/scroll/src/constants.ts +++ b/l2-bridges/scroll/src/constants.ts @@ -1,18 +1,19 @@ import { FindingSeverity, FindingType } from 'forta-agent' import { Result } from '@ethersproject/abi/lib' import { EventOfNotice } from '../../common/entity/events' -import { WithdrawERC20Event } from './generated/L2LidoGateway' +import { utils } from "ethers" export type RoleHashToName = Map -export type L2BridgeWithdrawalEvent = WithdrawERC20Event export type ContractInfo = { name: string address: string } + export const Constants = { L2_NAME: 'Scroll', L2_NETWORK_RPC: 'https://rpc.scroll.io', + MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST: 50_000, L2_NETWORK_ID: 534352, SCROLL_APPROX_BLOCK_TIME_3_SECONDS: 3, L2_PROXY_ADMIN_CONTRACT_ADDRESS: '0x8e34d07eb348716a1f0a48a507a9de8a3a6dce45', @@ -34,7 +35,17 @@ export const Constants = { ['0x94a954c0bc99227eddbc0715a62a7e1056ed8784cd719c2303b685683908857c', 'WITHDRAWALS_DISABLER_ROLE'], ['0x0000000000000000000000000000000000000000000000000000000000000000', 'DEFAULT_ADMIN_ROLE'], ]), - withdrawalInitiatedEvent: 'event WithdrawERC20(address indexed l1Token, address indexed l2Token, address indexed from, uint256 amount, bytes data)', + withdrawalInfo: { + eventName: 'WithdrawERC20', + eventDefinition: `event WithdrawERC20( + address indexed l1Token, + address indexed l2Token, + address indexed from, + address to, + uint256 amount, + bytes data +)`, + } } diff --git a/l2-bridges/scroll/test/monitor_withdrawals.spec.ts b/l2-bridges/scroll/test/monitor_withdrawals.spec.ts new file mode 100644 index 00000000..618d396b --- /dev/null +++ b/l2-bridges/scroll/test/monitor_withdrawals.spec.ts @@ -0,0 +1,62 @@ +import { MonitorWithdrawals } from '../../common/services/monitor_withdrawals' +import * as E from 'fp-ts/Either' +import { ethers } from 'forta-agent' +import { expect } from '@jest/globals' +import BigNumber from 'bignumber.js' +import * as Winston from 'winston' +import { ERC20Short__factory } from '../../common/generated' +import { Constants } from '../src/constants' +import { L2Client } from '../../common/clients/l2_client' +import assert from 'assert' + +const SECOND = 1000 // ms + + +describe('MonitorWithdrawals', () => { + const logger = Winston.createLogger({ + format: Winston.format.simple(), + transports: [new Winston.transports.Console()], + }) + + const nodeClient = new ethers.providers.JsonRpcProvider(Constants.L2_NETWORK_RPC, Constants.L2_NETWORK_ID) + const bridgedWstethRunner = ERC20Short__factory.connect(Constants.L2_WSTETH_BRIDGED.address, nodeClient) + + const l2Client = new L2Client(nodeClient, logger, bridgedWstethRunner, Constants.MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST) + + const monitorWithdrawals = new MonitorWithdrawals( + l2Client, Constants.L2_ERC20_TOKEN_GATEWAY.address, logger, Constants.withdrawalInfo, + Constants.SCROLL_APPROX_BLOCK_TIME_3_SECONDS + ) + + test(`getWithdrawalRecordsInBlockRange: 3 withdrawals, 3889 blocks`, async () => { + const withdrawalRecords = await monitorWithdrawals._getWithdrawalRecordsInBlockRange(6608787, 6612676) + assert(E.isRight(withdrawalRecords)) + expect(withdrawalRecords.right).toEqual([ + { + time: 1718529531, + amount: new BigNumber('2557056545136494'), + }, + { + time: 1718531871, + amount: new BigNumber('55285277863366'), + }, + { + time: 1718541199, + amount: new BigNumber('16222703281150365'), + }, + ]) + }) + + test(`getWithdrawalRecordsInBlockRange: 25 withdrawals (51_984 blocks)`, async () => { + const withdrawalRecords = await monitorWithdrawals._getWithdrawalRecordsInBlockRange(6581354, 6633338) + assert(E.isRight(withdrawalRecords)) + expect(withdrawalRecords.right).toHaveLength(25) + }) + + xtest(`getWithdrawalRecordsInBlockRange for 1_000_000 blocks`, async () => { + const endBlock = 6_633_338 + const startBlock = endBlock - 1_000_000 + const withdrawalRecords = await monitorWithdrawals._getWithdrawalRecordsInBlockRange(startBlock, endBlock) + assert(E.isRight(withdrawalRecords)) + }, 20 * SECOND) +}) From 466edde9315c5df222e85c070230a99c408dee67 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 17 Jun 2024 10:51:32 +0300 Subject: [PATCH 04/18] refactor(l2-bridges/scroll): remove leftovers of ProxyWatcher It is not needed because proxies admin and implementation changes are monitored by watching events --- l2-bridges/scroll/src/agent.ts | 43 ++++------------------------------ 1 file changed, 4 insertions(+), 39 deletions(-) diff --git a/l2-bridges/scroll/src/agent.ts b/l2-bridges/scroll/src/agent.ts index 635c2b82..46e24405 100644 --- a/l2-bridges/scroll/src/agent.ts +++ b/l2-bridges/scroll/src/agent.ts @@ -9,10 +9,7 @@ import BigNumber from 'bignumber.js' import { L2Client } from '../../common/clients/l2_client' import { EventWatcher } from '../../common/services/event_watcher' -// import { ProxyContractClient } from './clients/proxy_contract_client' -import { L2LidoGateway__factory } from './generated' import { ERC20Short__factory } from '../../common/generated' -// import { ProxyWatcher } from './services/proxy_watcher' import { MonitorWithdrawals } from '../../common/services/monitor_withdrawals' import { DataRW } from '../../common/utils/mutex' import * as Winston from 'winston' @@ -27,9 +24,7 @@ import { getJsonRpcUrl } from 'forta-agent/dist/sdk/utils' export type Container = { l2Client: L2Client - // proxyWatcher: ProxyWatcher monitorWithdrawals: MonitorWithdrawals - // blockClient: L2BlockClient bridgeWatcher: EventWatcher bridgeBalanceSrv: BridgeBalanceSrv govWatcher: EventWatcher @@ -51,11 +46,9 @@ export class App { transports: [new Winston.transports.Console()], }) - const nodeClient = new ethers.providers.JsonRpcProvider(Constants.L2_NETWORK_RPC, Constants.L2_NETWORK_ID) const bridgedWstethRunner = ERC20Short__factory.connect(Constants.L2_WSTETH_BRIDGED.address, nodeClient) - const l2Bridge = L2LidoGateway__factory.connect(Constants.L2_ERC20_TOKEN_GATEWAY.address, nodeClient) const l2Client = new L2Client(nodeClient, logger, bridgedWstethRunner, Constants.MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST) const bridgeEventWatcher = new EventWatcher( @@ -70,27 +63,11 @@ export class App { logger, ) - // const LIDO_PROXY_CONTRACTS: ProxyContractClient[] = [ - // new ProxyContractClient( - // Constants.L2_ERC20_TOKEN_GATEWAY.name, - // Constants.L2_ERC20_TOKEN_GATEWAY.address, - // ProxyAdmin__factory.connect(Constants.L2_PROXY_ADMIN_CONTRACT_ADDRESS, nodeClient), - // ), - // new ProxyContractClient( - // Constants.L2_WSTETH_BRIDGED.name, - // Constants.L2_WSTETH_BRIDGED.address, - // ProxyAdmin__factory.connect(Constants.L2_PROXY_ADMIN_CONTRACT_ADDRESS, nodeClient), - // ), - // ] - - // const proxyWorker = new ProxyWatcher(LIDO_PROXY_CONTRACTS, logger) - const monitorWithdrawals = new MonitorWithdrawals( l2Client, Constants.L2_ERC20_TOKEN_GATEWAY.address, logger, Constants.withdrawalInfo, Constants.SCROLL_APPROX_BLOCK_TIME_3_SECONDS ) - const ethProvider = new ethers.providers.FallbackProvider([ new ethers.providers.JsonRpcProvider(getJsonRpcUrl(), MAINNET_CHAIN_ID), new ethers.providers.JsonRpcProvider(DRPC_URL, MAINNET_CHAIN_ID), @@ -102,7 +79,6 @@ export class App { App.instance = { l2Client: l2Client, - // proxyWatcher: proxyWorker, monitorWithdrawals: monitorWithdrawals, bridgeWatcher: bridgeEventWatcher, bridgeBalanceSrv: bridgeBalanceSrv, @@ -137,13 +113,6 @@ export function initialize(): Initialize { process.exit(1) } - // const agentMeta = await app.proxyWatcher.initialize(latestL2Block.right.number) - // if (E.isLeft(agentMeta)) { - // app.logger.error(agentMeta.left) - - // process.exit(1) - // } - const monitorWithdrawalsInitResp = await app.monitorWithdrawals.initialize(latestL2Block.right.number) if (E.isLeft(monitorWithdrawalsInitResp)) { app.logger.error(monitorWithdrawalsInitResp.left) @@ -151,12 +120,10 @@ export function initialize(): Initialize { process.exit(1) } - // metadata[`${app.proxyWatcher.getName()}.lastAdmins`] = agentMeta.right.lastAdmins - // metadata[`${app.proxyWatcher.getName()}.lastImpls`] = agentMeta.right.lastImpls - // metadata[`${app.monitorWithdrawals.getName()}.currentWithdrawals`] = - // monitorWithdrawalsInitResp.right.currentWithdrawals + metadata[`${app.monitorWithdrawals.getName()}.currentWithdrawals`] = + monitorWithdrawalsInitResp.right.currentWithdrawals - const agents: string[] = [/*app.proxyWatcher.getName(),*/ app.monitorWithdrawals.getName()] + const agents: string[] = [app.monitorWithdrawals.getName()] metadata.agents = '[' + agents.toString() + ']' await app.findingsRW.write([ @@ -220,8 +187,7 @@ export const handleBlock = (): HandleBlock => { const l2blockNumbers = Array.from(l2blockNumbersSet) - const [/*proxyWatcherFindings,*/ bridgeBalanceFindings] = await Promise.all([ - // app.proxyWatcher.handleBlocks(l2blockNumbers), + const [bridgeBalanceFindings] = await Promise.all([ app.bridgeBalanceSrv.handleBlock(blockEvent.block.number, l2blockNumbers), ]) @@ -231,7 +197,6 @@ export const handleBlock = (): HandleBlock => { ...govEventFindings, ...proxyAdminEventFindings, ...monitorWithdrawalsFindings, - // ...proxyWatcherFindings, ) app.logger.info(elapsedTime('handleBlock', startTime) + '\n') From affdf45178c6e3087d3be3b4c20a68f3fc7c24b6 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Sun, 14 Jul 2024 21:51:43 +0300 Subject: [PATCH 05/18] refactor: move more agent.ts code to common code base --- l2-bridges/common/agent.ts | 209 +++++++ l2-bridges/common/constants.ts | 47 ++ l2-bridges/common/services/bridge_balance.ts | 2 +- .../common/services/monitor_withdrawals.ts | 14 +- l2-bridges/common/utils/constants.ts | 10 - l2-bridges/scroll/src/agent.ts | 519 +++++++++++------- l2-bridges/scroll/src/constants.ts | 329 ----------- .../scroll/test/monitor_withdrawals.spec.ts | 16 +- 8 files changed, 592 insertions(+), 554 deletions(-) create mode 100644 l2-bridges/common/agent.ts create mode 100644 l2-bridges/common/constants.ts delete mode 100644 l2-bridges/common/utils/constants.ts delete mode 100644 l2-bridges/scroll/src/constants.ts diff --git a/l2-bridges/common/agent.ts b/l2-bridges/common/agent.ts new file mode 100644 index 00000000..ba45c03b --- /dev/null +++ b/l2-bridges/common/agent.ts @@ -0,0 +1,209 @@ +import { ethers, BlockEvent, Finding, FindingSeverity, FindingType } from 'forta-agent' +import * as process from 'process' +import { InitializeResponse } from 'forta-agent/dist/sdk/initialize.response' +import { Initialize } from 'forta-agent/dist/sdk/handlers' +import * as E from 'fp-ts/Either' +import VERSION from './utils/version' +import { elapsedTime } from './utils/time' +import BigNumber from 'bignumber.js' + +import { L2Client } from './clients/l2_client' +import { EventWatcher } from './services/event_watcher' +import { ERC20Short__factory } from './generated' +import { MonitorWithdrawals } from './services/monitor_withdrawals' +import { DataRW } from './utils/mutex' +import * as Winston from 'winston' +import { Logger } from 'winston' +import { ETHProvider } from './clients/eth_provider_client' +import { BridgeBalanceSrv } from './services/bridge_balance' +import { getJsonRpcUrl } from 'forta-agent/dist/sdk/utils' +import { Constants, MAINNET_CHAIN_ID, DRPC_URL } from './constants' +// import { BorderTime, HealthChecker, MaxNumberErrorsPerBorderTime } from './services/health-checker/health-checker.srv' + + +export type Container = { + params: Constants, + l2Client: L2Client + monitorWithdrawals: MonitorWithdrawals + bridgeWatcher: EventWatcher + bridgeBalanceSrv: BridgeBalanceSrv + govWatcher: EventWatcher + proxyEventWatcher: EventWatcher + findingsRW: DataRW + logger: Logger + // healthChecker: HealthChecker +} + +export class App { + private static instance: Container + private static isHandleBlockRunning = false + + private constructor() {} + + public static async createInstance(params: Constants): Promise { + + const logger: Winston.Logger = Winston.createLogger({ + format: Winston.format.simple(), + transports: [new Winston.transports.Console()], + }) + + const nodeClient = new ethers.providers.JsonRpcProvider(params.L2_NETWORK_RPC, params.L2_NETWORK_ID) + const bridgedWstethRunner = ERC20Short__factory.connect(params.L2_WSTETH_BRIDGED.address, nodeClient) + + const l2Client = new L2Client(nodeClient, logger, bridgedWstethRunner, params.MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST) + + const bridgeEventWatcher = new EventWatcher( + 'BridgeEventWatcher', + params.getBridgeEvents(params.L2_ERC20_TOKEN_GATEWAY.address, params.RolesMap), + logger, + ) + const govEventWatcher = new EventWatcher('GovEventWatcher', params.getGovEvents(params.GOV_BRIDGE_ADDRESS), logger) + const proxyEventWatcher = new EventWatcher( + 'ProxyEventWatcher', + params.getProxyAdminEvents(params.L2_WSTETH_BRIDGED, params.L2_ERC20_TOKEN_GATEWAY), + logger, + ) + + const monitorWithdrawals = new MonitorWithdrawals( + l2Client, params.L2_ERC20_TOKEN_GATEWAY.address, logger, params.withdrawalInfo, + params.L2_APPROX_BLOCK_TIME_SECONDS + ) + + const ethProvider = new ethers.providers.FallbackProvider([ + new ethers.providers.JsonRpcProvider(getJsonRpcUrl(), MAINNET_CHAIN_ID), + new ethers.providers.JsonRpcProvider(DRPC_URL, MAINNET_CHAIN_ID), + ]) + + const wstethRunner = ERC20Short__factory.connect(params.L1_WSTETH_ADDRESS, ethProvider) + const ethClient = new ETHProvider(logger, wstethRunner) + const bridgeBalanceSrv = new BridgeBalanceSrv(params.L2_NAME, logger, ethClient, l2Client, params.L1_ERC20_TOKEN_GATEWAY_ADDRESS) + + App.instance = { + params, + l2Client, + monitorWithdrawals, + bridgeWatcher: bridgeEventWatcher, + bridgeBalanceSrv, + govWatcher: govEventWatcher, + proxyEventWatcher, + findingsRW: new DataRW([]), + logger, + // healthChecker: new HealthChecker(BorderTime, MaxNumberErrorsPerBorderTime), + } + + return App.instance + } + + public static async getInstance(): Promise { + if (!App.instance) { + throw new Error(`App instance is not created`) + } + return App.instance; + } + + public static async handleBlock(blockEvent: BlockEvent): Promise { + const app = await App.getInstance(); + + const startTime = new Date().getTime() + if (App.isHandleBlockRunning) { + return [] + } + + App.isHandleBlockRunning = true + + const findings: Finding[] = [] + const findingsAsync = await app.findingsRW.read() + if (findingsAsync.length > 0) { + findings.push(...findingsAsync) + } + + const l2blocksDto = await app.l2Client.getNotYetProcessedL2Blocks() + if (E.isLeft(l2blocksDto)) { + App.isHandleBlockRunning = false + return [l2blocksDto.left] + } + app.logger.info( + `ETH block ${blockEvent.blockNumber.toString()}. Fetched ${app.params.L2_NAME} blocks from ${l2blocksDto.right[0].number} to ${ + l2blocksDto.right[l2blocksDto.right.length - 1].number + }. Total: ${l2blocksDto.right.length}`, + ) + + const logs = await app.l2Client.getL2LogsOrNetworkAlert(l2blocksDto.right) + if (E.isLeft(logs)) { + App.isHandleBlockRunning = false + return [logs.left] + } + + const bridgeEventFindings = app.bridgeWatcher.handleLogs(logs.right) + const govEventFindings = app.govWatcher.handleLogs(logs.right) + const proxyAdminEventFindings = app.proxyEventWatcher.handleLogs(logs.right) + const monitorWithdrawalsFindings = app.monitorWithdrawals.handleBlocks(logs.right, l2blocksDto.right) + + const l2blockNumbersSet: Set = new Set() + for (const log of logs.right) { + l2blockNumbersSet.add(new BigNumber(log.blockNumber, 10).toNumber()) + } + + const l2blockNumbers = Array.from(l2blockNumbersSet) + + const [bridgeBalanceFindings] = await Promise.all([ + app.bridgeBalanceSrv.handleBlock(blockEvent.block.number, l2blockNumbers), + ]) + + findings.push( + ...bridgeEventFindings, + ...bridgeBalanceFindings, + ...govEventFindings, + ...proxyAdminEventFindings, + ...monitorWithdrawalsFindings, + ) + + app.logger.info(elapsedTime('handleBlock', startTime) + '\n') + App.isHandleBlockRunning = false + return findings + } + + public static initialize(params: Constants): Initialize { + const metadata: { [key: string]: string } = { + 'version.commitHash': VERSION.commitHash, + 'version.commitMsg': VERSION.commitMsg, + } + + return async function (): Promise { + const app = await App.createInstance(params) + + const latestL2Block = await app.l2Client.getLatestL2Block() + if (E.isLeft(latestL2Block)) { + app.logger.error(latestL2Block.left) + + process.exit(1) + } + + const monitorWithdrawalsInitResp = await app.monitorWithdrawals.initialize(latestL2Block.right.number) + if (E.isLeft(monitorWithdrawalsInitResp)) { + app.logger.error(monitorWithdrawalsInitResp.left) + + process.exit(1) + } + + metadata[`${app.monitorWithdrawals.getName()}.currentWithdrawals`] = + monitorWithdrawalsInitResp.right.currentWithdrawals + + const agents: string[] = [app.monitorWithdrawals.getName()] + metadata.agents = '[' + agents.toString() + ']' + + await app.findingsRW.write([ + Finding.fromObject({ + name: 'Agent launched', + description: `Version: ${VERSION.desc}`, + alertId: 'LIDO-AGENT-LAUNCHED', + severity: FindingSeverity.Info, + type: FindingType.Info, + metadata, + }), + ]) + + app.logger.info('Bot initialization is done!') + } + } +} diff --git a/l2-bridges/common/constants.ts b/l2-bridges/common/constants.ts new file mode 100644 index 00000000..7d3a9538 --- /dev/null +++ b/l2-bridges/common/constants.ts @@ -0,0 +1,47 @@ +import { EventOfNotice } from './entity/events' + +import BigNumber from 'bignumber.js' + +export type WithdrawalInfo = { + eventName: string, + eventDefinition: string, // e.g. "event WithdrawalEvent(address indexed l1token, ...)" +} + +export const ETH_DECIMALS = new BigNumber(10).pow(18) +export const MAINNET_CHAIN_ID = 1 +export const DRPC_URL = 'https://eth.drpc.org/' + + +export type RoleHashToName = Map +export type ContractInfo = { + name: string + address: string +} + +export type Constants = { + L2_NAME: string, + L2_NETWORK_RPC: string, + MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST: number, + L2_NETWORK_ID: number, + L2_APPROX_BLOCK_TIME_SECONDS: number, + L2_PROXY_ADMIN_CONTRACT_ADDRESS: string, + GOV_BRIDGE_ADDRESS: string, + L1_WSTETH_ADDRESS: string, + L1_ERC20_TOKEN_GATEWAY_ADDRESS: string, + L2_ERC20_TOKEN_GATEWAY: { + name: string, + address: string, + }, + L2_WSTETH_BRIDGED: { + name: string, + address: string, + }, + RolesMap: RoleHashToName, + withdrawalInfo: { + eventName: string, + eventDefinition: string, + }, + getBridgeEvents: (l2GatewayAddress: string, RolesAddrToNameMap: RoleHashToName) => EventOfNotice[], + getGovEvents: (GOV_BRIDGE_ADDRESS: string) => EventOfNotice[], + getProxyAdminEvents: (l2WstethContract: ContractInfo, l2GatewayContract: ContractInfo) => EventOfNotice[], +} diff --git a/l2-bridges/common/services/bridge_balance.ts b/l2-bridges/common/services/bridge_balance.ts index c2bac492..f3b6232c 100644 --- a/l2-bridges/common/services/bridge_balance.ts +++ b/l2-bridges/common/services/bridge_balance.ts @@ -4,7 +4,7 @@ import { Logger } from 'winston' import BigNumber from 'bignumber.js' import * as E from 'fp-ts/Either' import { getUniqueKey, networkAlert } from '../utils/finding.helpers' -import { ETH_DECIMALS } from '../utils/constants' +import { ETH_DECIMALS } from '../constants' export abstract class IL1BridgeBalanceClient { abstract getWstEthBalance(l1blockNumber: number, address: string): Promise> diff --git a/l2-bridges/common/services/monitor_withdrawals.ts b/l2-bridges/common/services/monitor_withdrawals.ts index 7181cf5b..12939655 100644 --- a/l2-bridges/common/services/monitor_withdrawals.ts +++ b/l2-bridges/common/services/monitor_withdrawals.ts @@ -8,10 +8,10 @@ import { L2Client } from '../clients/l2_client' import { NetworkError } from '../utils/error' import { elapsedTime } from '../utils/time' import { getUniqueKey } from '../utils/finding.helpers' -import { ETH_DECIMALS } from '../utils/constants' +import { ETH_DECIMALS } from '../constants' import { formatAddress } from 'forta-agent/dist/cli/utils' import { ethers } from 'ethers' -import { WithdrawalInfo } from '../utils/constants' +import { WithdrawalInfo } from '../constants' import assert from 'assert' // 10k wstETH @@ -62,16 +62,17 @@ export class MonitorWithdrawals { return E.left(withdrawalRecords.left) } - const withdrawalsSum = new BigNumber(0) + let withdrawalsSum = 0n for (const wc of withdrawalRecords.right) { - withdrawalsSum.plus(wc.amount) + withdrawalsSum += BigInt(wc.amount.toString()) this.withdrawalsStore.push(wc) } - this.logger.info(`${MonitorWithdrawals.name} started on block ${currentBlock}`) + this.logger.info(`${MonitorWithdrawals.name} started on block ${currentBlock}.` + + ` Fetched past withdrawal events in blocks range [${startBlock}, ${endBlock}]`) this.logger.info(elapsedTime(MonitorWithdrawals.name + '.' + this._getWithdrawalRecordsInBlockRange.name, start)) return E.right({ - currentWithdrawals: withdrawalsSum.div(ETH_DECIMALS).toFixed(2), + currentWithdrawals: (new BigNumber(withdrawalsSum.toString())).div(ETH_DECIMALS).toFixed(4), }) } @@ -147,7 +148,6 @@ export class MonitorWithdrawals { if (E.isLeft(withdrawalLogsE)) { return E.left(withdrawalLogsE.left) } - const blocksToRequest = new Set() const blockNumberToTime = new Map() const withdrawalRecordsAux: { amount: BigNumber, blockNumber: number }[] = [] diff --git a/l2-bridges/common/utils/constants.ts b/l2-bridges/common/utils/constants.ts deleted file mode 100644 index 6a97a37f..00000000 --- a/l2-bridges/common/utils/constants.ts +++ /dev/null @@ -1,10 +0,0 @@ -import BigNumber from 'bignumber.js' - -export type WithdrawalInfo = { - eventName: string, - eventDefinition: string, // e.g. "event WithdawalEvent(address indexed l1token, ...)" -} - -export const ETH_DECIMALS = new BigNumber(10).pow(18) -export const MAINNET_CHAIN_ID = 1 -export const DRPC_URL = 'https://eth.drpc.org/' diff --git a/l2-bridges/scroll/src/agent.ts b/l2-bridges/scroll/src/agent.ts index 46e24405..3962fbed 100644 --- a/l2-bridges/scroll/src/agent.ts +++ b/l2-bridges/scroll/src/agent.ts @@ -1,211 +1,332 @@ -import { ethers, BlockEvent, Finding, FindingSeverity, FindingType, HandleBlock } from 'forta-agent' -import * as process from 'process' -import { InitializeResponse } from 'forta-agent/dist/sdk/initialize.response' -import { Initialize } from 'forta-agent/dist/sdk/handlers' -import * as E from 'fp-ts/Either' -import VERSION from '../../common/utils/version' -import { elapsedTime } from '../../common/utils/time' -import BigNumber from 'bignumber.js' - -import { L2Client } from '../../common/clients/l2_client' -import { EventWatcher } from '../../common/services/event_watcher' -import { ERC20Short__factory } from '../../common/generated' -import { MonitorWithdrawals } from '../../common/services/monitor_withdrawals' -import { DataRW } from '../../common/utils/mutex' -import * as Winston from 'winston' -import { MAINNET_CHAIN_ID, DRPC_URL } from '../../common/utils/constants' -import { Constants, getBridgeEvents, getGovEvents, getProxyAdminEvents } from './constants' -import { Logger } from 'winston' -import { ETHProvider } from '../../common/clients/eth_provider_client' -import { BridgeBalanceSrv } from '../../common/services/bridge_balance' -import { getJsonRpcUrl } from 'forta-agent/dist/sdk/utils' -// import { BorderTime, HealthChecker, MaxNumberErrorsPerBorderTime } from './services/health-checker/health-checker.srv' - - -export type Container = { - l2Client: L2Client - monitorWithdrawals: MonitorWithdrawals - bridgeWatcher: EventWatcher - bridgeBalanceSrv: BridgeBalanceSrv - govWatcher: EventWatcher - proxyEventWatcher: EventWatcher - findingsRW: DataRW - logger: Logger - // healthChecker: HealthChecker +import { FindingSeverity, FindingType } from 'forta-agent' +import { App } from '../../common/agent' +import { Result } from '@ethersproject/abi/lib' +import { EventOfNotice } from '../../common/entity/events' +import { Constants, RoleHashToName, ContractInfo } from '../../common/constants' + + +export const scrollConstants: Constants = { + L2_NAME: 'Scroll', + L2_NETWORK_RPC: 'https://rpc.scroll.io', + MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST: 1_000, + L2_NETWORK_ID: 534352, + L2_APPROX_BLOCK_TIME_SECONDS: 3, + L2_PROXY_ADMIN_CONTRACT_ADDRESS: '0x8e34d07eb348716a1f0a48a507a9de8a3a6dce45', + GOV_BRIDGE_ADDRESS: '0x0c67d8d067e349669dfeab132a7c03a90594ee09', + L1_WSTETH_ADDRESS: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0', + L1_ERC20_TOKEN_GATEWAY_ADDRESS: '0x6625c6332c9f91f2d27c304e729b86db87a3f504', + L2_ERC20_TOKEN_GATEWAY: { + name: 'L2_ERC20_TOKEN_GATEWAY', + address: '0x8ae8f22226b9d789a36ac81474e633f8be2856c9', + }, + L2_WSTETH_BRIDGED: { + name: 'SCROLL_WSTETH_BRIDGED', + address: '0xf610a9dfb7c89644979b4a0f27063e9e7d7cda32', + }, + RolesMap: new Map([ + ['0x4b43b36766bde12c5e9cbbc37d15f8d1f769f08f54720ab370faeb4ce893753a', 'DEPOSITS_ENABLER_ROLE'], + ['0x63f736f21cb2943826cd50b191eb054ebbea670e4e962d0527611f830cd399d6', 'DEPOSITS_DISABLER_ROLE'], + ['0x9ab8816a3dc0b3849ec1ac00483f6ec815b07eee2fd766a353311c823ad59d0d', 'WITHDRAWALS_ENABLER_ROLE'], + ['0x94a954c0bc99227eddbc0715a62a7e1056ed8784cd719c2303b685683908857c', 'WITHDRAWALS_DISABLER_ROLE'], + ['0x0000000000000000000000000000000000000000000000000000000000000000', 'DEFAULT_ADMIN_ROLE'], + ]), + withdrawalInfo: { + eventName: 'WithdrawERC20', + eventDefinition: `event WithdrawERC20( + address indexed l1Token, + address indexed l2Token, + address indexed from, + address to, + uint256 amount, + bytes data +)`, + }, + getBridgeEvents, + getGovEvents, + getProxyAdminEvents, } -export class App { - private static instance: Container - - private constructor() {} - - public static async getInstance(): Promise { - if (!App.instance) { - const logger: Winston.Logger = Winston.createLogger({ - format: Winston.format.simple(), - transports: [new Winston.transports.Console()], - }) - - const nodeClient = new ethers.providers.JsonRpcProvider(Constants.L2_NETWORK_RPC, Constants.L2_NETWORK_ID) - const bridgedWstethRunner = ERC20Short__factory.connect(Constants.L2_WSTETH_BRIDGED.address, nodeClient) - - const l2Client = new L2Client(nodeClient, logger, bridgedWstethRunner, Constants.MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST) - - const bridgeEventWatcher = new EventWatcher( - 'BridgeEventWatcher', - getBridgeEvents(Constants.L2_ERC20_TOKEN_GATEWAY.address, Constants.RolesMap), - logger, - ) - const govEventWatcher = new EventWatcher('GovEventWatcher', getGovEvents(Constants.GOV_BRIDGE_ADDRESS), logger) - const proxyEventWatcher = new EventWatcher( - 'ProxyEventWatcher', - getProxyAdminEvents(Constants.L2_WSTETH_BRIDGED, Constants.L2_ERC20_TOKEN_GATEWAY), - logger, - ) - const monitorWithdrawals = new MonitorWithdrawals( - l2Client, Constants.L2_ERC20_TOKEN_GATEWAY.address, logger, Constants.withdrawalInfo, - Constants.SCROLL_APPROX_BLOCK_TIME_3_SECONDS - ) - - const ethProvider = new ethers.providers.FallbackProvider([ - new ethers.providers.JsonRpcProvider(getJsonRpcUrl(), MAINNET_CHAIN_ID), - new ethers.providers.JsonRpcProvider(DRPC_URL, MAINNET_CHAIN_ID), - ]) - - const wstethRunner = ERC20Short__factory.connect(Constants.L1_WSTETH_ADDRESS, ethProvider) - const ethClient = new ETHProvider(logger, wstethRunner) - const bridgeBalanceSrv = new BridgeBalanceSrv(Constants.L2_NAME, logger, ethClient, l2Client, Constants.L1_ERC20_TOKEN_GATEWAY_ADDRESS) - - App.instance = { - l2Client: l2Client, - monitorWithdrawals: monitorWithdrawals, - bridgeWatcher: bridgeEventWatcher, - bridgeBalanceSrv: bridgeBalanceSrv, - govWatcher: govEventWatcher, - proxyEventWatcher: proxyEventWatcher, - findingsRW: new DataRW([]), - logger: logger, - // healthChecker: new HealthChecker(BorderTime, MaxNumberErrorsPerBorderTime), - } - } - - return App.instance - } +export function getBridgeEvents(l2GatewayAddress: string, RolesAddrToNameMap: RoleHashToName): EventOfNotice[] { + return [ + { + address: l2GatewayAddress, + event: 'event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)', + alertId: 'L2-BRIDGE-OWNER-CHANGED', + name: '🚨 Scroll: L2 gateway owner changed', + description: (args: Result) => + `Owner of L2LidoGateway ${l2GatewayAddress} was changed to ${args.newOwner} (detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: '136546BE-E1BF-40DA-98FB-17B741E12A35', + }, + { + address: l2GatewayAddress, + event: 'event DepositsDisabled(address indexed disabler)', + alertId: 'L2-BRIDGE-DEPOSITS-DISABLED', + name: '🚨 Scroll L2 Bridge: Deposits Disabled', + description: (args: Result) => `Deposits were disabled by ${args.disabler}`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: '7CBC6E3F-BABA-437A-9142-0C1CD8AAA827', + }, + { + address: l2GatewayAddress, + event: 'event WithdrawalsDisabled(address indexed disabler)', + alertId: 'L2-BRIDGE-WITHDRAWALS-DISABLED', + name: '🚨 Scroll L2 Bridge: Withdrawals Disabled', + description: (args: Result) => `Withdrawals were disabled by ${args.enabler}`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: 'C6DBFF28-C12D-4CEC-8087-2F0898F7AEAB', + }, + { + address: l2GatewayAddress, + event: 'event DepositsEnabled(address indexed enabler)', + alertId: 'L2-BRIDGE-DEPOSITS-ENABLED', + name: 'ℹ️ Scroll L2 Bridge: Deposits Enabled', + description: (args: Result) => `Deposits were enabled by ${args.enabler}`, + severity: FindingSeverity.Info, + type: FindingType.Info, + uniqueKey: 'EA60F6DC-9A59-4FAE-8467-521DF56813C5', + }, + { + address: l2GatewayAddress, + event: 'event WithdrawalsEnabled(address indexed enabler)', + alertId: 'L2-BRIDGE-WITHDRAWALS-ENABLED', + name: 'ℹ️ Scroll L2 Bridge: Withdrawals Enabled', + description: (args: Result) => `Withdrawals were enabled by ${args.enabler}`, + severity: FindingSeverity.Info, + type: FindingType.Info, + uniqueKey: '0CEE896B-6BDD-45C5-9ADD-46A1558F1BBC', + }, + { + address: l2GatewayAddress, + event: 'event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender)', + alertId: 'L2-BRIDGE-ROLE-GRANTED', + name: '⚠️ Scroll L2 Bridge: Role granted', + description: (args: Result) => + `Role ${args.role}(${RolesAddrToNameMap.get(args.role) || 'unknown'}) ` + + `was granted to ${args.account} by ${args.sender}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: 'F58F36AD-9811-40D7-ACD2-667A7624D85B', + }, + { + address: l2GatewayAddress, + event: 'event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender)', + alertId: 'L2-BRIDGE-ROLE-REVOKED', + name: '⚠️ Scroll L2 Bridge: Role revoked', + description: (args: Result) => + `Role ${args.role}(${RolesAddrToNameMap.get(args.role) || 'unknown'}) ` + + `was revoked to ${args.account} by ${args.sender}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: '42816CCE-24C3-4CE2-BC21-4F2202A66EFD', + }, + { + address: l2GatewayAddress, + event: 'event Initialized(uint8 version)', + alertId: 'L2-BRIDGE-INITIALIZED', + name: '🚨 Scroll L2 Bridge: (re-)initialized', + description: (args: Result) => + `Implementation of the Scroll L2 Bridge was initialized by version: ${args.version}\n` + + `NOTE: This is not the thing that should be left unacted! ` + + `Make sure that this call was made by Lido!`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: 'E42BC7A0-0715-4D55-AB9D-0A041F639B20', + }, + ] } -export function initialize(): Initialize { - type Metadata = { [key: string]: string } - - const metadata: Metadata = { - 'version.commitHash': VERSION.commitHash, - 'version.commitMsg': VERSION.commitMsg, - } - - return async function (): Promise { - const app = await App.getInstance() - - const latestL2Block = await app.l2Client.getLatestL2Block() - if (E.isLeft(latestL2Block)) { - app.logger.error(latestL2Block.left) - - process.exit(1) - } - - const monitorWithdrawalsInitResp = await app.monitorWithdrawals.initialize(latestL2Block.right.number) - if (E.isLeft(monitorWithdrawalsInitResp)) { - app.logger.error(monitorWithdrawalsInitResp.left) - - process.exit(1) - } - - metadata[`${app.monitorWithdrawals.getName()}.currentWithdrawals`] = - monitorWithdrawalsInitResp.right.currentWithdrawals - - const agents: string[] = [app.monitorWithdrawals.getName()] - metadata.agents = '[' + agents.toString() + ']' - - await app.findingsRW.write([ - Finding.fromObject({ - name: 'Agent launched', - description: `Version: ${VERSION.desc}`, - alertId: 'LIDO-AGENT-LAUNCHED', - severity: FindingSeverity.Info, - type: FindingType.Info, - metadata, - }), - ]) - - app.logger.info('Bot initialization is done!') - } +export function getGovEvents(GOV_BRIDGE_ADDRESS: string): EventOfNotice[] { + return [ + { + address: GOV_BRIDGE_ADDRESS, + event: + 'event EthereumGovernanceExecutorUpdate(address oldEthereumGovernanceExecutor, address newEthereumGovernanceExecutor)', + alertId: 'GOV-BRIDGE-EXEC-UPDATED', + name: '🚨 Scroll Gov Bridge: Ethereum Governance Executor Updated', + description: (args: Result) => + `Ethereum Governance Executor was updated from ` + + `${args.oldEthereumGovernanceExecutor} to ${args.newEthereumGovernanceExecutor}`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: '73EE62B0-E0CF-4527-8E44-566D72667F22', + }, + { + address: GOV_BRIDGE_ADDRESS, + event: 'event GuardianUpdate(address oldGuardian, address newGuardian)', + alertId: 'GOV-BRIDGE-GUARDIAN-UPDATED', + name: '🚨 Scroll Gov Bridge: Guardian Updated', + description: (args: Result) => `Guardian was updated from ` + `${args.oldGuardian} to ${args.newGuardian}`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: '8C586373-5040-4BDA-8EF8-16CBE582D6B0', + }, + { + address: GOV_BRIDGE_ADDRESS, + event: 'event DelayUpdate(uint256 oldDelay, uint256 newDelay)', + alertId: 'GOV-BRIDGE-DELAY-UPDATED', + name: '⚠️ Scroll Gov Bridge: Delay Updated', + description: (args: Result) => `Delay was updated from ` + `${args.oldDelay} to ${args.newDelay}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: '073F04A8-B232-4671-A34E-D42A3729FE34', + }, + { + address: GOV_BRIDGE_ADDRESS, + event: 'event GracePeriodUpdate(uint256 oldGracePeriod, uint256 newGracePeriod)', + alertId: 'GOV-BRIDGE-GRACE-PERIOD-UPDATED', + name: '⚠️ Scroll Gov Bridge: Grace Period Updated', + description: (args: Result) => + `Grace Period was updated from ` + `${args.oldGracePeriod} to ${args.newGracePeriod}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: '26574C78-EBD1-42D3-A9C7-3E4A2976FCB7', + }, + { + address: GOV_BRIDGE_ADDRESS, + event: 'event MinimumDelayUpdate(uint256 oldMinimumDelay, uint256 newMinimumDelay)', + alertId: 'GOV-BRIDGE-MIN-DELAY-UPDATED', + name: '⚠️ Scroll Gov Bridge: Min Delay Updated', + description: (args: Result) => + `Min Delay was updated from ` + `${args.oldMinimumDelay} to ${args.newMinimumDelay}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: '35391E05-CBB4-4013-ACA1-A75F8C5D6991', + }, + { + address: GOV_BRIDGE_ADDRESS, + event: 'event MaximumDelayUpdate(uint256 oldMaximumDelay, uint256 newMaximumDelay)', + alertId: 'GOV-BRIDGE-MAX-DELAY-UPDATED', + name: '⚠️ Scroll Gov Bridge: Max Delay Updated', + description: (args: Result) => + `Max Delay was updated from ` + `${args.oldMaximumDelay} to ${args.newMaximumDelay}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: '003CCEDE-551A-4310-86A7-F8EC22135C45', + }, + { + address: GOV_BRIDGE_ADDRESS, + event: + 'event ActionsSetQueued(uint256 indexed id, address[] targets, uint256[] values, string[] signatures, bytes[] calldatas, bool[] withDelegatecalls, uint256 executionTime)', + alertId: 'GOV-BRIDGE-ACTION-SET-QUEUED', + name: 'ℹ️ Scroll Gov Bridge: Action set queued', + description: (args: Result) => `Action set ${args.id} was queued`, + severity: FindingSeverity.Info, + type: FindingType.Info, + uniqueKey: '84D309D8-AB13-4B41-A9CE-8DE4AB77E77A', + }, + { + address: GOV_BRIDGE_ADDRESS, + event: 'event ActionsSetExecuted(uint256 indexed id, address indexed initiatorExecution, bytes[] returnedData)', + alertId: 'GOV-BRIDGE-ACTION-SET-EXECUTED', + name: 'ℹ️ Scroll Gov Bridge: Action set executed', + description: (args: Result) => `Action set ${args.id} was executed`, + severity: FindingSeverity.Info, + type: FindingType.Info, + uniqueKey: '31FE6EEB-4619-4579-9C0B-58EECC3D7724', + }, + { + address: GOV_BRIDGE_ADDRESS, + event: 'event ActionsSetCanceled(uint256 indexed id)', + alertId: 'GOV-BRIDGE-ACTION-SET-CANCELED', + name: 'ℹ️ Scroll Gov Bridge: Action set canceled', + description: (args: Result) => `Action set ${args.id} was canceled`, + severity: FindingSeverity.Info, + type: FindingType.Info, + uniqueKey: '76022839-385E-4AD7-85E9-3739C1CACA09', + }, + ] } -let isHandleBlockRunning: boolean = false -export const handleBlock = (): HandleBlock => { - return async function (blockEvent: BlockEvent): Promise { - const startTime = new Date().getTime() - if (isHandleBlockRunning) { - return [] - } - - isHandleBlockRunning = true - const app = await App.getInstance() - - const findings: Finding[] = [] - const findingsAsync = await app.findingsRW.read() - if (findingsAsync.length > 0) { - findings.push(...findingsAsync) - } - - const l2blocksDto = await app.l2Client.getNotYetProcessedL2Blocks() - if (E.isLeft(l2blocksDto)) { - isHandleBlockRunning = false - return [l2blocksDto.left] - } - app.logger.info( - `ETH block ${blockEvent.blockNumber.toString()}. Fetched ${Constants.L2_NAME} blocks from ${l2blocksDto.right[0].number} to ${ - l2blocksDto.right[l2blocksDto.right.length - 1].number - }. Total: ${l2blocksDto.right.length}`, - ) - - const logs = await app.l2Client.getL2LogsOrNetworkAlert(l2blocksDto.right) - if (E.isLeft(logs)) { - isHandleBlockRunning = false - return [logs.left] - } - - const bridgeEventFindings = app.bridgeWatcher.handleLogs(logs.right) - const govEventFindings = app.govWatcher.handleLogs(logs.right) - const proxyAdminEventFindings = app.proxyEventWatcher.handleLogs(logs.right) - const monitorWithdrawalsFindings = app.monitorWithdrawals.handleBlocks(logs.right, l2blocksDto.right) - - const l2blockNumbersSet: Set = new Set() - for (const log of logs.right) { - l2blockNumbersSet.add(new BigNumber(log.blockNumber, 10).toNumber()) - } - - const l2blockNumbers = Array.from(l2blockNumbersSet) - - const [bridgeBalanceFindings] = await Promise.all([ - app.bridgeBalanceSrv.handleBlock(blockEvent.block.number, l2blockNumbers), - ]) - - findings.push( - ...bridgeEventFindings, - ...bridgeBalanceFindings, - ...govEventFindings, - ...proxyAdminEventFindings, - ...monitorWithdrawalsFindings, - ) - - app.logger.info(elapsedTime('handleBlock', startTime) + '\n') - isHandleBlockRunning = false - return findings - } +export function getProxyAdminEvents(l2WstethContract: ContractInfo, l2GatewayContract: ContractInfo): EventOfNotice[] { + return [ + { + address: l2WstethContract.address, + event: 'event AdminChanged(address previousAdmin, address newAdmin)', + alertId: 'PROXY-ADMIN-CHANGED', + name: '🚨 Scroll: Proxy admin changed', + description: (args: Result) => + `Proxy admin for ${l2WstethContract.name}(${l2WstethContract.address}) ` + + `was changed from ${args.previousAdmin} to ${args.newAdmin}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: '18BA44FB-E5AC-4F7D-A556-3B49D9381B0C', + }, + { + address: l2WstethContract.address, + event: 'event Upgraded(address indexed implementation)', + alertId: 'PROXY-UPGRADED', + name: '🚨 Scroll: Proxy upgraded', + description: (args: Result) => + `Proxy for ${l2WstethContract.name}(${l2WstethContract.address}) ` + + `was updated to ${args.implementation}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: '6D0FC28D-0D3E-41D3-8F9A-2A52AFDA7543', + }, + { + address: l2WstethContract.address, + event: 'event BeaconUpgraded(address indexed beacon)', + alertId: 'PROXY-BEACON-UPGRADED', + name: '🚨 Scroll: Proxy beacon upgraded', + description: (args: Result) => + `Proxy for ${l2WstethContract.name}(${l2WstethContract.address}) ` + + `beacon was updated to ${args.beacon}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: '913345D0-B591-4699-9E5B-384C2640A9C3', + }, + { + address: l2GatewayContract.address, + event: 'event AdminChanged(address previousAdmin, address newAdmin)', + alertId: 'PROXY-ADMIN-CHANGED', + name: '🚨 Scroll: Proxy admin changed', + description: (args: Result) => + `Proxy admin for ${l2GatewayContract.name}(${l2GatewayContract.address}) ` + + `was changed from ${args.previousAdmin} to ${args.newAdmin}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: 'DE3F6E46-984B-435F-B88C-E5198386CCF6', + }, + { + address: l2GatewayContract.address, + event: 'event Upgraded(address indexed implementation)', + alertId: 'PROXY-UPGRADED', + name: '🚨 Scroll: Proxy upgraded', + description: (args: Result) => + `Proxy for ${l2GatewayContract.name}(${l2GatewayContract.address}) ` + + `was updated to ${args.implementation}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: 'CFA25A7F-69C4-45BE-8CAE-884EE8FEF5CA', + }, + { + address: l2GatewayContract.address, + event: 'event BeaconUpgraded(address indexed beacon)', + alertId: 'PROXY-BEACON-UPGRADED', + name: '🚨 Scroll: Proxy beacon upgraded', + description: (args: Result) => + `Proxy for ${l2GatewayContract.name}(${l2GatewayContract.address}) ` + + `beacon was updated to ${args.beacon}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: 'B7193990-458E-4F41-ADD9-82848D235F5B', + }, + ] } export default { - initialize: initialize(), - handleBlock: handleBlock(), + initialize: App.initialize(scrollConstants), + handleBlock: App.handleBlock, } diff --git a/l2-bridges/scroll/src/constants.ts b/l2-bridges/scroll/src/constants.ts deleted file mode 100644 index c8439e60..00000000 --- a/l2-bridges/scroll/src/constants.ts +++ /dev/null @@ -1,329 +0,0 @@ -import { FindingSeverity, FindingType } from 'forta-agent' -import { Result } from '@ethersproject/abi/lib' -import { EventOfNotice } from '../../common/entity/events' -import { utils } from "ethers" - -export type RoleHashToName = Map -export type ContractInfo = { - name: string - address: string -} - - -export const Constants = { - L2_NAME: 'Scroll', - L2_NETWORK_RPC: 'https://rpc.scroll.io', - MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST: 50_000, - L2_NETWORK_ID: 534352, - SCROLL_APPROX_BLOCK_TIME_3_SECONDS: 3, - L2_PROXY_ADMIN_CONTRACT_ADDRESS: '0x8e34d07eb348716a1f0a48a507a9de8a3a6dce45', - GOV_BRIDGE_ADDRESS: '0x0c67d8d067e349669dfeab132a7c03a90594ee09', - L1_WSTETH_ADDRESS: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0', - L1_ERC20_TOKEN_GATEWAY_ADDRESS: '0x6625c6332c9f91f2d27c304e729b86db87a3f504', - L2_ERC20_TOKEN_GATEWAY: { - name: 'L2_ERC20_TOKEN_GATEWAY', - address: '0x8ae8f22226b9d789a36ac81474e633f8be2856c9', - }, - L2_WSTETH_BRIDGED: { - name: 'SCROLL_WSTETH_BRIDGED', - address: '0xf610a9dfb7c89644979b4a0f27063e9e7d7cda32', - }, - RolesMap: new Map([ - ['0x4b43b36766bde12c5e9cbbc37d15f8d1f769f08f54720ab370faeb4ce893753a', 'DEPOSITS_ENABLER_ROLE'], - ['0x63f736f21cb2943826cd50b191eb054ebbea670e4e962d0527611f830cd399d6', 'DEPOSITS_DISABLER_ROLE'], - ['0x9ab8816a3dc0b3849ec1ac00483f6ec815b07eee2fd766a353311c823ad59d0d', 'WITHDRAWALS_ENABLER_ROLE'], - ['0x94a954c0bc99227eddbc0715a62a7e1056ed8784cd719c2303b685683908857c', 'WITHDRAWALS_DISABLER_ROLE'], - ['0x0000000000000000000000000000000000000000000000000000000000000000', 'DEFAULT_ADMIN_ROLE'], - ]), - withdrawalInfo: { - eventName: 'WithdrawERC20', - eventDefinition: `event WithdrawERC20( - address indexed l1Token, - address indexed l2Token, - address indexed from, - address to, - uint256 amount, - bytes data -)`, - } -} - - -export function getBridgeEvents(l2GatewayAddress: string, RolesAddrToNameMap: RoleHashToName): EventOfNotice[] { - return [ - { - address: l2GatewayAddress, - event: 'event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)', - alertId: 'L2-BRIDGE-OWNER-CHANGED', - name: '🚨 Scroll: L2 gateway owner changed', - description: (args: Result) => - `Owner of L2LidoGateway ${l2GatewayAddress} was changed to ${args.newOwner} (detected by event)`, - severity: FindingSeverity.High, - type: FindingType.Info, - uniqueKey: '136546BE-E1BF-40DA-98FB-17B741E12A35', - }, - { - address: l2GatewayAddress, - event: 'event DepositsDisabled(address indexed disabler)', - alertId: 'L2-BRIDGE-DEPOSITS-DISABLED', - name: '🚨 Scroll L2 Bridge: Deposits Disabled', - description: (args: Result) => `Deposits were disabled by ${args.disabler}`, - severity: FindingSeverity.High, - type: FindingType.Info, - uniqueKey: '7CBC6E3F-BABA-437A-9142-0C1CD8AAA827', - }, - { - address: l2GatewayAddress, - event: 'event WithdrawalsDisabled(address indexed disabler)', - alertId: 'L2-BRIDGE-WITHDRAWALS-DISABLED', - name: '🚨 Scroll L2 Bridge: Withdrawals Disabled', - description: (args: Result) => `Withdrawals were disabled by ${args.enabler}`, - severity: FindingSeverity.High, - type: FindingType.Info, - uniqueKey: 'C6DBFF28-C12D-4CEC-8087-2F0898F7AEAB', - }, - { - address: l2GatewayAddress, - event: 'event DepositsEnabled(address indexed enabler)', - alertId: 'L2-BRIDGE-DEPOSITS-ENABLED', - name: 'ℹ️ Scroll L2 Bridge: Deposits Enabled', - description: (args: Result) => `Deposits were enabled by ${args.enabler}`, - severity: FindingSeverity.Info, - type: FindingType.Info, - uniqueKey: 'EA60F6DC-9A59-4FAE-8467-521DF56813C5', - }, - { - address: l2GatewayAddress, - event: 'event WithdrawalsEnabled(address indexed enabler)', - alertId: 'L2-BRIDGE-WITHDRAWALS-ENABLED', - name: 'ℹ️ Scroll L2 Bridge: Withdrawals Enabled', - description: (args: Result) => `Withdrawals were enabled by ${args.enabler}`, - severity: FindingSeverity.Info, - type: FindingType.Info, - uniqueKey: '0CEE896B-6BDD-45C5-9ADD-46A1558F1BBC', - }, - { - address: l2GatewayAddress, - event: 'event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender)', - alertId: 'L2-BRIDGE-ROLE-GRANTED', - name: '⚠️ Scroll L2 Bridge: Role granted', - description: (args: Result) => - `Role ${args.role}(${RolesAddrToNameMap.get(args.role) || 'unknown'}) ` + - `was granted to ${args.account} by ${args.sender}`, - severity: FindingSeverity.Medium, - type: FindingType.Info, - uniqueKey: 'F58F36AD-9811-40D7-ACD2-667A7624D85B', - }, - { - address: l2GatewayAddress, - event: 'event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender)', - alertId: 'L2-BRIDGE-ROLE-REVOKED', - name: '⚠️ Scroll L2 Bridge: Role revoked', - description: (args: Result) => - `Role ${args.role}(${RolesAddrToNameMap.get(args.role) || 'unknown'}) ` + - `was revoked to ${args.account} by ${args.sender}`, - severity: FindingSeverity.Medium, - type: FindingType.Info, - uniqueKey: '42816CCE-24C3-4CE2-BC21-4F2202A66EFD', - }, - { - address: l2GatewayAddress, - event: 'event Initialized(uint8 version)', - alertId: 'L2-BRIDGE-INITIALIZED', - name: '🚨 Scroll L2 Bridge: (re-)initialized', - description: (args: Result) => - `Implementation of the Scroll L2 Bridge was initialized by version: ${args.version}\n` + - `NOTE: This is not the thing that should be left unacted! ` + - `Make sure that this call was made by Lido!`, - severity: FindingSeverity.High, - type: FindingType.Info, - uniqueKey: 'E42BC7A0-0715-4D55-AB9D-0A041F639B20', - }, - ] -} - - -export function getGovEvents(GOV_BRIDGE_ADDRESS: string): EventOfNotice[] { - return [ - { - address: GOV_BRIDGE_ADDRESS, - event: - 'event EthereumGovernanceExecutorUpdate(address oldEthereumGovernanceExecutor, address newEthereumGovernanceExecutor)', - alertId: 'GOV-BRIDGE-EXEC-UPDATED', - name: '🚨 Scroll Gov Bridge: Ethereum Governance Executor Updated', - description: (args: Result) => - `Ethereum Governance Executor was updated from ` + - `${args.oldEthereumGovernanceExecutor} to ${args.newEthereumGovernanceExecutor}`, - severity: FindingSeverity.High, - type: FindingType.Info, - uniqueKey: '73EE62B0-E0CF-4527-8E44-566D72667F22', - }, - { - address: GOV_BRIDGE_ADDRESS, - event: 'event GuardianUpdate(address oldGuardian, address newGuardian)', - alertId: 'GOV-BRIDGE-GUARDIAN-UPDATED', - name: '🚨 Scroll Gov Bridge: Guardian Updated', - description: (args: Result) => `Guardian was updated from ` + `${args.oldGuardian} to ${args.newGuardian}`, - severity: FindingSeverity.High, - type: FindingType.Info, - uniqueKey: '8C586373-5040-4BDA-8EF8-16CBE582D6B0', - }, - { - address: GOV_BRIDGE_ADDRESS, - event: 'event DelayUpdate(uint256 oldDelay, uint256 newDelay)', - alertId: 'GOV-BRIDGE-DELAY-UPDATED', - name: '⚠️ Scroll Gov Bridge: Delay Updated', - description: (args: Result) => `Delay was updated from ` + `${args.oldDelay} to ${args.newDelay}`, - severity: FindingSeverity.Medium, - type: FindingType.Info, - uniqueKey: '073F04A8-B232-4671-A34E-D42A3729FE34', - }, - { - address: GOV_BRIDGE_ADDRESS, - event: 'event GracePeriodUpdate(uint256 oldGracePeriod, uint256 newGracePeriod)', - alertId: 'GOV-BRIDGE-GRACE-PERIOD-UPDATED', - name: '⚠️ Scroll Gov Bridge: Grace Period Updated', - description: (args: Result) => - `Grace Period was updated from ` + `${args.oldGracePeriod} to ${args.newGracePeriod}`, - severity: FindingSeverity.Medium, - type: FindingType.Info, - uniqueKey: '26574C78-EBD1-42D3-A9C7-3E4A2976FCB7', - }, - { - address: GOV_BRIDGE_ADDRESS, - event: 'event MinimumDelayUpdate(uint256 oldMinimumDelay, uint256 newMinimumDelay)', - alertId: 'GOV-BRIDGE-MIN-DELAY-UPDATED', - name: '⚠️ Scroll Gov Bridge: Min Delay Updated', - description: (args: Result) => - `Min Delay was updated from ` + `${args.oldMinimumDelay} to ${args.newMinimumDelay}`, - severity: FindingSeverity.Medium, - type: FindingType.Info, - uniqueKey: '35391E05-CBB4-4013-ACA1-A75F8C5D6991', - }, - { - address: GOV_BRIDGE_ADDRESS, - event: 'event MaximumDelayUpdate(uint256 oldMaximumDelay, uint256 newMaximumDelay)', - alertId: 'GOV-BRIDGE-MAX-DELAY-UPDATED', - name: '⚠️ Scroll Gov Bridge: Max Delay Updated', - description: (args: Result) => - `Max Delay was updated from ` + `${args.oldMaximumDelay} to ${args.newMaximumDelay}`, - severity: FindingSeverity.Medium, - type: FindingType.Info, - uniqueKey: '003CCEDE-551A-4310-86A7-F8EC22135C45', - }, - { - address: GOV_BRIDGE_ADDRESS, - event: - 'event ActionsSetQueued(uint256 indexed id, address[] targets, uint256[] values, string[] signatures, bytes[] calldatas, bool[] withDelegatecalls, uint256 executionTime)', - alertId: 'GOV-BRIDGE-ACTION-SET-QUEUED', - name: 'ℹ️ Scroll Gov Bridge: Action set queued', - description: (args: Result) => `Action set ${args.id} was queued`, - severity: FindingSeverity.Info, - type: FindingType.Info, - uniqueKey: '84D309D8-AB13-4B41-A9CE-8DE4AB77E77A', - }, - { - address: GOV_BRIDGE_ADDRESS, - event: 'event ActionsSetExecuted(uint256 indexed id, address indexed initiatorExecution, bytes[] returnedData)', - alertId: 'GOV-BRIDGE-ACTION-SET-EXECUTED', - name: 'ℹ️ Scroll Gov Bridge: Action set executed', - description: (args: Result) => `Action set ${args.id} was executed`, - severity: FindingSeverity.Info, - type: FindingType.Info, - uniqueKey: '31FE6EEB-4619-4579-9C0B-58EECC3D7724', - }, - { - address: GOV_BRIDGE_ADDRESS, - event: 'event ActionsSetCanceled(uint256 indexed id)', - alertId: 'GOV-BRIDGE-ACTION-SET-CANCELED', - name: 'ℹ️ Scroll Gov Bridge: Action set canceled', - description: (args: Result) => `Action set ${args.id} was canceled`, - severity: FindingSeverity.Info, - type: FindingType.Info, - uniqueKey: '76022839-385E-4AD7-85E9-3739C1CACA09', - }, - ] -} - -export function getProxyAdminEvents(l2WstethContract: ContractInfo, l2GatewayContract: ContractInfo): EventOfNotice[] { - return [ - { - address: l2WstethContract.address, - event: 'event AdminChanged(address previousAdmin, address newAdmin)', - alertId: 'PROXY-ADMIN-CHANGED', - name: '🚨 Scroll: Proxy admin changed', - description: (args: Result) => - `Proxy admin for ${l2WstethContract.name}(${l2WstethContract.address}) ` + - `was changed from ${args.previousAdmin} to ${args.newAdmin}` + - `\n(detected by event)`, - severity: FindingSeverity.High, - type: FindingType.Info, - uniqueKey: '18BA44FB-E5AC-4F7D-A556-3B49D9381B0C', - }, - { - address: l2WstethContract.address, - event: 'event Upgraded(address indexed implementation)', - alertId: 'PROXY-UPGRADED', - name: '🚨 Scroll: Proxy upgraded', - description: (args: Result) => - `Proxy for ${l2WstethContract.name}(${l2WstethContract.address}) ` + - `was updated to ${args.implementation}` + - `\n(detected by event)`, - severity: FindingSeverity.High, - type: FindingType.Info, - uniqueKey: '6D0FC28D-0D3E-41D3-8F9A-2A52AFDA7543', - }, - { - address: l2WstethContract.address, - event: 'event BeaconUpgraded(address indexed beacon)', - alertId: 'PROXY-BEACON-UPGRADED', - name: '🚨 Scroll: Proxy beacon upgraded', - description: (args: Result) => - `Proxy for ${l2WstethContract.name}(${l2WstethContract.address}) ` + - `beacon was updated to ${args.beacon}` + - `\n(detected by event)`, - severity: FindingSeverity.High, - type: FindingType.Info, - uniqueKey: '913345D0-B591-4699-9E5B-384C2640A9C3', - }, - { - address: l2GatewayContract.address, - event: 'event AdminChanged(address previousAdmin, address newAdmin)', - alertId: 'PROXY-ADMIN-CHANGED', - name: '🚨 Scroll: Proxy admin changed', - description: (args: Result) => - `Proxy admin for ${l2GatewayContract.name}(${l2GatewayContract.address}) ` + - `was changed from ${args.previousAdmin} to ${args.newAdmin}` + - `\n(detected by event)`, - severity: FindingSeverity.High, - type: FindingType.Info, - uniqueKey: 'DE3F6E46-984B-435F-B88C-E5198386CCF6', - }, - { - address: l2GatewayContract.address, - event: 'event Upgraded(address indexed implementation)', - alertId: 'PROXY-UPGRADED', - name: '🚨 Scroll: Proxy upgraded', - description: (args: Result) => - `Proxy for ${l2GatewayContract.name}(${l2GatewayContract.address}) ` + - `was updated to ${args.implementation}` + - `\n(detected by event)`, - severity: FindingSeverity.High, - type: FindingType.Info, - uniqueKey: 'CFA25A7F-69C4-45BE-8CAE-884EE8FEF5CA', - }, - { - address: l2GatewayContract.address, - event: 'event BeaconUpgraded(address indexed beacon)', - alertId: 'PROXY-BEACON-UPGRADED', - name: '🚨 Scroll: Proxy beacon upgraded', - description: (args: Result) => - `Proxy for ${l2GatewayContract.name}(${l2GatewayContract.address}) ` + - `beacon was updated to ${args.beacon}` + - `\n(detected by event)`, - severity: FindingSeverity.High, - type: FindingType.Info, - uniqueKey: 'B7193990-458E-4F41-ADD9-82848D235F5B', - }, - ] -} diff --git a/l2-bridges/scroll/test/monitor_withdrawals.spec.ts b/l2-bridges/scroll/test/monitor_withdrawals.spec.ts index 618d396b..c2c089ac 100644 --- a/l2-bridges/scroll/test/monitor_withdrawals.spec.ts +++ b/l2-bridges/scroll/test/monitor_withdrawals.spec.ts @@ -5,7 +5,7 @@ import { expect } from '@jest/globals' import BigNumber from 'bignumber.js' import * as Winston from 'winston' import { ERC20Short__factory } from '../../common/generated' -import { Constants } from '../src/constants' +import { scrollConstants } from '../src/agent' import { L2Client } from '../../common/clients/l2_client' import assert from 'assert' @@ -18,14 +18,14 @@ describe('MonitorWithdrawals', () => { transports: [new Winston.transports.Console()], }) - const nodeClient = new ethers.providers.JsonRpcProvider(Constants.L2_NETWORK_RPC, Constants.L2_NETWORK_ID) - const bridgedWstethRunner = ERC20Short__factory.connect(Constants.L2_WSTETH_BRIDGED.address, nodeClient) + const nodeClient = new ethers.providers.JsonRpcProvider(scrollConstants.L2_NETWORK_RPC, scrollConstants.L2_NETWORK_ID) + const bridgedWstethRunner = ERC20Short__factory.connect(scrollConstants.L2_WSTETH_BRIDGED.address, nodeClient) - const l2Client = new L2Client(nodeClient, logger, bridgedWstethRunner, Constants.MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST) + const l2Client = new L2Client(nodeClient, logger, bridgedWstethRunner, scrollConstants.MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST) const monitorWithdrawals = new MonitorWithdrawals( - l2Client, Constants.L2_ERC20_TOKEN_GATEWAY.address, logger, Constants.withdrawalInfo, - Constants.SCROLL_APPROX_BLOCK_TIME_3_SECONDS + l2Client, scrollConstants.L2_ERC20_TOKEN_GATEWAY.address, logger, scrollConstants.withdrawalInfo, + scrollConstants.L2_APPROX_BLOCK_TIME_SECONDS ) test(`getWithdrawalRecordsInBlockRange: 3 withdrawals, 3889 blocks`, async () => { @@ -45,13 +45,13 @@ describe('MonitorWithdrawals', () => { amount: new BigNumber('16222703281150365'), }, ]) - }) + }, 20 * SECOND) test(`getWithdrawalRecordsInBlockRange: 25 withdrawals (51_984 blocks)`, async () => { const withdrawalRecords = await monitorWithdrawals._getWithdrawalRecordsInBlockRange(6581354, 6633338) assert(E.isRight(withdrawalRecords)) expect(withdrawalRecords.right).toHaveLength(25) - }) + }, 40 * SECOND) xtest(`getWithdrawalRecordsInBlockRange for 1_000_000 blocks`, async () => { const endBlock = 6_633_338 From 73902464f4a4daf7dccc22082ecd910fdce4d889 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 15 Jul 2024 19:41:31 +0300 Subject: [PATCH 06/18] feat(l2-bridges): add initial mantle agent version --- l2-bridges/common/agent.ts | 4 +- l2-bridges/common/constants.ts | 19 +- .../common/services/monitor_withdrawals.ts | 5 +- l2-bridges/mantle/src/agent.ts | 405 ++++++++ .../test/monitor_withdrawals_mantle.spec.ts | 43 + l2-bridges/package.json | 5 +- l2-bridges/scroll/src/abi/L2LidoGateway.json | 861 ------------------ l2-bridges/scroll/src/agent.ts | 2 +- 8 files changed, 464 insertions(+), 880 deletions(-) create mode 100644 l2-bridges/mantle/src/agent.ts create mode 100644 l2-bridges/mantle/test/monitor_withdrawals_mantle.spec.ts delete mode 100644 l2-bridges/scroll/src/abi/L2LidoGateway.json diff --git a/l2-bridges/common/agent.ts b/l2-bridges/common/agent.ts index ba45c03b..42774891 100644 --- a/l2-bridges/common/agent.ts +++ b/l2-bridges/common/agent.ts @@ -17,7 +17,7 @@ import { Logger } from 'winston' import { ETHProvider } from './clients/eth_provider_client' import { BridgeBalanceSrv } from './services/bridge_balance' import { getJsonRpcUrl } from 'forta-agent/dist/sdk/utils' -import { Constants, MAINNET_CHAIN_ID, DRPC_URL } from './constants' +import { Constants, MAINNET_CHAIN_ID, DRPC_URL, L1_WSTETH_ADDRESS } from './constants' // import { BorderTime, HealthChecker, MaxNumberErrorsPerBorderTime } from './services/health-checker/health-checker.srv' @@ -74,7 +74,7 @@ export class App { new ethers.providers.JsonRpcProvider(DRPC_URL, MAINNET_CHAIN_ID), ]) - const wstethRunner = ERC20Short__factory.connect(params.L1_WSTETH_ADDRESS, ethProvider) + const wstethRunner = ERC20Short__factory.connect(L1_WSTETH_ADDRESS, ethProvider) const ethClient = new ETHProvider(logger, wstethRunner) const bridgeBalanceSrv = new BridgeBalanceSrv(params.L2_NAME, logger, ethClient, l2Client, params.L1_ERC20_TOKEN_GATEWAY_ADDRESS) diff --git a/l2-bridges/common/constants.ts b/l2-bridges/common/constants.ts index 7d3a9538..8ae76b85 100644 --- a/l2-bridges/common/constants.ts +++ b/l2-bridges/common/constants.ts @@ -5,12 +5,13 @@ import BigNumber from 'bignumber.js' export type WithdrawalInfo = { eventName: string, eventDefinition: string, // e.g. "event WithdrawalEvent(address indexed l1token, ...)" + amountFieldName: string, // e.g. "amount", according to the name of the field in the withdrawal event } export const ETH_DECIMALS = new BigNumber(10).pow(18) export const MAINNET_CHAIN_ID = 1 export const DRPC_URL = 'https://eth.drpc.org/' - +export const L1_WSTETH_ADDRESS = '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0' export type RoleHashToName = Map export type ContractInfo = { @@ -26,21 +27,11 @@ export type Constants = { L2_APPROX_BLOCK_TIME_SECONDS: number, L2_PROXY_ADMIN_CONTRACT_ADDRESS: string, GOV_BRIDGE_ADDRESS: string, - L1_WSTETH_ADDRESS: string, L1_ERC20_TOKEN_GATEWAY_ADDRESS: string, - L2_ERC20_TOKEN_GATEWAY: { - name: string, - address: string, - }, - L2_WSTETH_BRIDGED: { - name: string, - address: string, - }, + L2_ERC20_TOKEN_GATEWAY: ContractInfo, + L2_WSTETH_BRIDGED: ContractInfo, RolesMap: RoleHashToName, - withdrawalInfo: { - eventName: string, - eventDefinition: string, - }, + withdrawalInfo: WithdrawalInfo, getBridgeEvents: (l2GatewayAddress: string, RolesAddrToNameMap: RoleHashToName) => EventOfNotice[], getGovEvents: (GOV_BRIDGE_ADDRESS: string) => EventOfNotice[], getProxyAdminEvents: (l2WstethContract: ContractInfo, l2GatewayContract: ContractInfo) => EventOfNotice[], diff --git a/l2-bridges/common/services/monitor_withdrawals.ts b/l2-bridges/common/services/monitor_withdrawals.ts index 12939655..04f6b7d2 100644 --- a/l2-bridges/common/services/monitor_withdrawals.ts +++ b/l2-bridges/common/services/monitor_withdrawals.ts @@ -155,7 +155,10 @@ export class MonitorWithdrawals { const event = this.withdrawalInfo.eventInterface.parseLog(log) // NB: log.blockNumber is actually a string, although its typescript type is number const blockNumber = (typeof log.blockNumber === 'number') ? log.blockNumber : Number(log.blockNumber) - withdrawalRecordsAux.push({ blockNumber: blockNumber, amount: new BigNumber(String(event.args.amount)) }) + withdrawalRecordsAux.push({ + blockNumber: blockNumber, + amount: new BigNumber(String(event.args[this.withdrawalInfo.amountFieldName])) + }) blocksToRequest.add(blockNumber) } const blocks = await this.l2Client.fetchL2Blocks(blocksToRequest) diff --git a/l2-bridges/mantle/src/agent.ts b/l2-bridges/mantle/src/agent.ts new file mode 100644 index 00000000..683c4385 --- /dev/null +++ b/l2-bridges/mantle/src/agent.ts @@ -0,0 +1,405 @@ +import { FindingSeverity, FindingType } from 'forta-agent' +import { App } from '../../common/agent' +import { Result } from '@ethersproject/abi/lib' +import { EventOfNotice } from '../../common/entity/events' +import { Constants, RoleHashToName, ContractInfo } from '../../common/constants' + + +export const mantleConstants: Constants = { + L2_NAME: 'Mantle', + L2_NETWORK_RPC: 'https://rpc.mantle.xyz', + MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST: 10_000, + L2_NETWORK_ID: 5000, + L2_APPROX_BLOCK_TIME_SECONDS: 2, + L2_PROXY_ADMIN_CONTRACT_ADDRESS: '0x8e34d07eb348716a1f0a48a507a9de8a3a6dce45', // TODO + GOV_BRIDGE_ADDRESS: '0x3a7b055bf88cdc59d20d0245809c6e6b3c5819dd', + L1_ERC20_TOKEN_GATEWAY_ADDRESS: '0x2D001d79E5aF5F65a939781FE228B267a8Ed468B', + L2_ERC20_TOKEN_GATEWAY: { + name: 'L2_ERC20_TOKEN_GATEWAY', + address: '0x9c46560D6209743968cC24150893631A39AfDe4d', + }, + L2_WSTETH_BRIDGED: { + name: 'MANTLE_WSTETH_BRIDGED', + address: '0x458ed78EB972a369799fb278c0243b25e5242A83', + }, + RolesMap: new Map([ + ['0x4b43b36766bde12c5e9cbbc37d15f8d1f769f08f54720ab370faeb4ce893753a', 'DEPOSITS_ENABLER_ROLE'], + ['0x63f736f21cb2943826cd50b191eb054ebbea670e4e962d0527611f830cd399d6', 'DEPOSITS_DISABLER_ROLE'], + ['0x9ab8816a3dc0b3849ec1ac00483f6ec815b07eee2fd766a353311c823ad59d0d', 'WITHDRAWALS_ENABLER_ROLE'], + ['0x94a954c0bc99227eddbc0715a62a7e1056ed8784cd719c2303b685683908857c', 'WITHDRAWALS_DISABLER_ROLE'], + ['0x0000000000000000000000000000000000000000000000000000000000000000', 'DEFAULT_ADMIN_ROLE'], + ]), + withdrawalInfo: { + eventName: 'WithdrawalInitiated', + eventDefinition: `event WithdrawalInitiated( + address indexed _l1Token, + address indexed _l2Token, + address indexed _from, + address _to, + uint256 _amount, + bytes _data +)`, + amountFieldName: "_amount", + }, + getBridgeEvents, + getGovEvents, + getProxyAdminEvents, +} + + +export function getBridgeEvents( + L2_ERC20_TOKEN_GATEWAY_ADDRESS: string, + RolesAddrToNameMap: RoleHashToName, +): EventOfNotice[] { + const uniqueKeys = [ + 'be8452bb-c4c6-4526-9489-b04626ec4c4d', + 'e3e767b1-de01-4695-84c7-5654567cf501', + 'ff634c6e-e42c-4432-80e8-b1b4133c7478', + '3da97319-97cc-4124-85fd-96253be17368', + '8f775e16-a0f0-4232-a83d-6741825cd0e5', + '077579cd-d178-422e-ab76-3f3e3bf6c533', + 'cae6f704-391f-4862-9ae4-6b4dae289cc8', + 'f3e7baf3-f8cb-48bf-821a-3fec05222497', + ] + + return [ + { + address: L2_ERC20_TOKEN_GATEWAY_ADDRESS, + event: 'event Initialized(address indexed admin)', + alertId: 'L2-BRIDGE-IMPLEMENTATION-INITIALIZED', + name: '🚨🚨🚨 Mantle L2 Bridge: Implementation initialized', + description: (args: Result) => + `Implementation of the Mantle L2 Bridge was initialized by ${args.admin}\n` + + `NOTE: This is not the thing that should be left unacted! ` + + `Make sure that this call was made by Lido!`, + severity: FindingSeverity.Critical, + type: FindingType.Info, + uniqueKey: uniqueKeys[0], + }, + { + address: L2_ERC20_TOKEN_GATEWAY_ADDRESS, + event: 'event DepositsDisabled(address indexed disabler)', + alertId: 'L2-BRIDGE-DEPOSITS-DISABLED', + name: '🚨 Mantle L2 Bridge: Deposits Disabled', + description: (args: Result) => `Deposits were disabled by ${args.disabler}`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[1], + }, + { + address: L2_ERC20_TOKEN_GATEWAY_ADDRESS, + event: + 'event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole)', + alertId: 'L2-BRIDGE-ROLE-ADMIN-CHANGED', + name: '🚨 Mantle L2 Bridge: Role Admin changed', + description: (args: Result) => + `Role Admin for role ${args.role}(${RolesAddrToNameMap.get(args.role) || 'unknown'}) ` + + `was changed from ${args.previousAdminRole} to ${args.newAdminRole}`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[2], + }, + { + address: L2_ERC20_TOKEN_GATEWAY_ADDRESS, + event: 'event WithdrawalsDisabled(address indexed disabler)', + alertId: 'L2-BRIDGE-WITHDRAWALS-DISABLED', + name: '🚨 Mantle L2 Bridge: Withdrawals Disabled', + description: (args: Result) => `Withdrawals were disabled by ${args.enabler}`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[3], + }, + { + address: L2_ERC20_TOKEN_GATEWAY_ADDRESS, + event: 'event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender)', + alertId: 'L2-BRIDGE-ROLE-GRANTED', + name: '⚠️ Mantle L2 Bridge: Role granted', + description: (args: Result) => + `Role ${args.role}(${RolesAddrToNameMap.get(args.role) || 'unknown'}) ` + + `was granted to ${args.account} by ${args.sender}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: uniqueKeys[4], + }, + { + address: L2_ERC20_TOKEN_GATEWAY_ADDRESS, + event: 'event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender)', + alertId: 'L2-BRIDGE-ROLE-REVOKED', + name: '⚠️ Mantle L2 Bridge: Role revoked', + description: (args: Result) => + `Role ${args.role}(${RolesAddrToNameMap.get(args.role) || 'unknown'}) ` + + `was revoked to ${args.account} by ${args.sender}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: uniqueKeys[5], + }, + { + address: L2_ERC20_TOKEN_GATEWAY_ADDRESS, + event: 'event DepositsEnabled(address indexed enabler)', + alertId: 'L2-BRIDGE-DEPOSITS-ENABLED', + name: 'ℹ️ Mantle L2 Bridge: Deposits Enabled', + description: (args: Result) => `Deposits were enabled by ${args.enabler}`, + severity: FindingSeverity.Info, + type: FindingType.Info, + uniqueKey: uniqueKeys[6], + }, + { + address: L2_ERC20_TOKEN_GATEWAY_ADDRESS, + event: 'event WithdrawalsEnabled(address indexed enabler)', + alertId: 'L2-BRIDGE-WITHDRAWALS-ENABLED', + name: 'ℹ️ Mantle L2 Bridge: Withdrawals Enabled', + description: (args: Result) => `Withdrawals were enabled by ${args.enabler}`, + severity: FindingSeverity.Info, + type: FindingType.Info, + uniqueKey: uniqueKeys[7], + }, + ] +} + + + + +export function getGovEvents(GOV_BRIDGE_ADDRESS: string): EventOfNotice[] { + const uniqueKeys = [ + '0a9a066e-233d-4d00-af58-84b685a42729', + 'a2224ced-9745-45c3-90d2-d95f66f57442', + 'c897edd7-a322-47e6-9470-7a4ff3e260e1', + 'a268ead2-28bf-4b65-959b-8627d91ca8ec', + '70df46a0-787f-4dc1-98f7-fff387a60ec0', + 'fc487e28-f617-4cce-885a-4f5ae67ce283', + '6ce3c4b3-1c8f-4cee-98d0-a1e0c44d74d8', + 'fd8271cf-5419-4379-87e3-64cbac2c4fc7', + '0554e1b6-2a3f-4f4e-88e7-5b74c23fdc25', + ] + + return [ + { + address: GOV_BRIDGE_ADDRESS, + event: + 'event EthereumGovernanceExecutorUpdate(address oldEthereumGovernanceExecutor, address newEthereumGovernanceExecutor)', + alertId: 'GOV-BRIDGE-EXEC-UPDATED', + name: '🚨 Mantle Gov Bridge: Ethereum Governance Executor Updated', + description: (args: Result) => + `Ethereum Governance Executor was updated from ` + + `${args.oldEthereumGovernanceExecutor} to ${args.newEthereumGovernanceExecutor}`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[0], + }, + { + address: GOV_BRIDGE_ADDRESS, + event: 'event GuardianUpdate(address oldGuardian, address newGuardian)', + alertId: 'GOV-BRIDGE-GUARDIAN-UPDATED', + name: '🚨 Mantle Gov Bridge: Guardian Updated', + description: (args: Result) => `Guardian was updated from ` + `${args.oldGuardian} to ${args.newGuardian}`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[1], + }, + { + address: GOV_BRIDGE_ADDRESS, + event: 'event DelayUpdate(uint256 oldDelay, uint256 newDelay)', + alertId: 'GOV-BRIDGE-DELAY-UPDATED', + name: '⚠️ Mantle Gov Bridge: Delay Updated', + description: (args: Result) => `Delay was updated from ` + `${args.oldDelay} to ${args.newDelay}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: uniqueKeys[2], + }, + { + address: GOV_BRIDGE_ADDRESS, + event: 'event GracePeriodUpdate(uint256 oldGracePeriod, uint256 newGracePeriod)', + alertId: 'GOV-BRIDGE-GRACE-PERIOD-UPDATED', + name: '⚠️ Mantle Gov Bridge: Grace Period Updated', + description: (args: Result) => + `Grace Period was updated from ` + `${args.oldGracePeriod} to ${args.newGracePeriod}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: uniqueKeys[3], + }, + { + address: GOV_BRIDGE_ADDRESS, + event: 'event MinimumDelayUpdate(uint256 oldMinimumDelay, uint256 newMinimumDelay)', + alertId: 'GOV-BRIDGE-MIN-DELAY-UPDATED', + name: '⚠️ Mantle Gov Bridge: Min Delay Updated', + description: (args: Result) => + `Min Delay was updated from ` + `${args.oldMinimumDelay} to ${args.newMinimumDelay}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: uniqueKeys[4], + }, + { + address: GOV_BRIDGE_ADDRESS, + event: 'event MaximumDelayUpdate(uint256 oldMaximumDelay, uint256 newMaximumDelay)', + alertId: 'GOV-BRIDGE-MAX-DELAY-UPDATED', + name: '⚠️ Mantle Gov Bridge: Max Delay Updated', + description: (args: Result) => + `Max Delay was updated from ` + `${args.oldMaximumDelay} to ${args.newMaximumDelay}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: uniqueKeys[5], + }, + { + address: GOV_BRIDGE_ADDRESS, + event: + 'event ActionsSetQueued(uint256 indexed id, address[] targets, uint256[] values, string[] signatures, bytes[] calldatas, bool[] withDelegatecalls, uint256 executionTime)', + alertId: 'GOV-BRIDGE-ACTION-SET-QUEUED', + name: 'ℹ️ Mantle Gov Bridge: Action set queued', + description: (args: Result) => `Action set ${args.id} was queued`, + severity: FindingSeverity.Info, + type: FindingType.Info, + uniqueKey: uniqueKeys[6], + }, + { + address: GOV_BRIDGE_ADDRESS, + event: 'event ActionsSetExecuted(uint256 indexed id, address indexed initiatorExecution, bytes[] returnedData)', + alertId: 'GOV-BRIDGE-ACTION-SET-EXECUTED', + name: 'ℹ️ Mantle Gov Bridge: Action set executed', + description: (args: Result) => `Action set ${args.id} was executed`, + severity: FindingSeverity.Info, + type: FindingType.Info, + uniqueKey: uniqueKeys[7], + }, + { + address: GOV_BRIDGE_ADDRESS, + event: 'event ActionsSetCanceled(uint256 indexed id)', + alertId: 'GOV-BRIDGE-ACTION-SET-CANCELED', + name: 'ℹ️ Mantle Gov Bridge: Action set canceled', + description: (args: Result) => `Action set ${args.id} was canceled`, + severity: FindingSeverity.Info, + type: FindingType.Info, + uniqueKey: uniqueKeys[8], + }, + ] +} + + +export function getProxyAdminEvents( + MANTLE_WST_ETH_BRIDGED_ADDRESS: ContractInfo, + L2_ERC20_TOKEN_GATEWAY_ADDRESS: ContractInfo, +): EventOfNotice[] { + const uniqueKeys = [ + '82b39d98-a156-4be2-be48-81a0d237c53a', + '44367f0e-dbe2-4cb0-b256-1af2c9a38d9f', + '85bcbe60-df81-46ec-b54a-4f667f6a238d', + 'e719527e-99ee-4345-aa6f-c815d7d4a1b1', + 'e449ed63-f96b-4df8-96a2-f6643e4bc679', + 'cef80661-e44b-47d4-8682-a78d41316953', + '525a056c-6099-4c02-8fdb-e9ced4a17fbb', + 'e926bca1-8446-4ef7-b610-43748b3fcc91', + ] + + return [ + { + address: MANTLE_WST_ETH_BRIDGED_ADDRESS.address, + event: 'event ProxyOssified()', + alertId: 'PROXY-OSSIFIED', + name: '🚨 Mantle: Proxy ossified', + // eslint-disable-next-line @typescript-eslint/no-unused-vars + description: (args: Result) => + `Proxy for ${MANTLE_WST_ETH_BRIDGED_ADDRESS.name}(${MANTLE_WST_ETH_BRIDGED_ADDRESS.address}) was ossified` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[0], + }, + { + address: MANTLE_WST_ETH_BRIDGED_ADDRESS.address, + event: 'event AdminChanged(address previousAdmin, address newAdmin)', + alertId: 'PROXY-ADMIN-CHANGED', + name: '🚨 Mantle: Proxy admin changed', + description: (args: Result) => + `Proxy admin for ${MANTLE_WST_ETH_BRIDGED_ADDRESS.name}(${MANTLE_WST_ETH_BRIDGED_ADDRESS.address}) ` + + `was changed from ${args.previousAdmin} to ${args.newAdmin}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[1], + }, + { + address: MANTLE_WST_ETH_BRIDGED_ADDRESS.address, + event: 'event Upgraded(address indexed implementation)', + alertId: 'PROXY-UPGRADED', + name: '🚨 Mantle: Proxy upgraded', + description: (args: Result) => + `Proxy for ${MANTLE_WST_ETH_BRIDGED_ADDRESS.name}(${MANTLE_WST_ETH_BRIDGED_ADDRESS.address}) ` + + `was updated to ${args.implementation}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[2], + }, + { + address: MANTLE_WST_ETH_BRIDGED_ADDRESS.address, + event: 'event BeaconUpgraded(address indexed beacon)', + alertId: 'PROXY-BEACON-UPGRADED', + name: '🚨 Mantle: Proxy beacon upgraded', + description: (args: Result) => + `Proxy for ${MANTLE_WST_ETH_BRIDGED_ADDRESS.name}(${MANTLE_WST_ETH_BRIDGED_ADDRESS.address}) ` + + `beacon was updated to ${args.beacon}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[3], + }, + + { + address: L2_ERC20_TOKEN_GATEWAY_ADDRESS.address, + event: 'event ProxyOssified()', + alertId: 'PROXY-OSSIFIED', + name: '🚨 Mantle: Proxy ossified', + // eslint-disable-next-line @typescript-eslint/no-unused-vars + description: (args: Result) => + `Proxy for ${L2_ERC20_TOKEN_GATEWAY_ADDRESS.name}(${L2_ERC20_TOKEN_GATEWAY_ADDRESS.address}) was ossified` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[4], + }, + { + address: L2_ERC20_TOKEN_GATEWAY_ADDRESS.address, + event: 'event AdminChanged(address previousAdmin, address newAdmin)', + alertId: 'PROXY-ADMIN-CHANGED', + name: '🚨 Mantle: Proxy admin changed', + description: (args: Result) => + `Proxy admin for ${L2_ERC20_TOKEN_GATEWAY_ADDRESS.name}(${L2_ERC20_TOKEN_GATEWAY_ADDRESS.address}) ` + + `was changed from ${args.previousAdmin} to ${args.newAdmin}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[5], + }, + { + address: L2_ERC20_TOKEN_GATEWAY_ADDRESS.address, + event: 'event Upgraded(address indexed implementation)', + alertId: 'PROXY-UPGRADED', + name: '🚨 Mantle: Proxy upgraded', + description: (args: Result) => + `Proxy for ${L2_ERC20_TOKEN_GATEWAY_ADDRESS.name}(${L2_ERC20_TOKEN_GATEWAY_ADDRESS.address}) ` + + `was updated to ${args.implementation}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[6], + }, + { + address: L2_ERC20_TOKEN_GATEWAY_ADDRESS.address, + event: 'event BeaconUpgraded(address indexed beacon)', + alertId: 'PROXY-BEACON-UPGRADED', + name: '🚨 Mantle: Proxy beacon upgraded', + description: (args: Result) => + `Proxy for ${L2_ERC20_TOKEN_GATEWAY_ADDRESS.name}(${L2_ERC20_TOKEN_GATEWAY_ADDRESS.address}) ` + + `beacon was updated to ${args.beacon}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[7], + }, + ] +} + + +export default { + initialize: App.initialize(mantleConstants), + handleBlock: App.handleBlock, +} diff --git a/l2-bridges/mantle/test/monitor_withdrawals_mantle.spec.ts b/l2-bridges/mantle/test/monitor_withdrawals_mantle.spec.ts new file mode 100644 index 00000000..0fef674a --- /dev/null +++ b/l2-bridges/mantle/test/monitor_withdrawals_mantle.spec.ts @@ -0,0 +1,43 @@ +import { MonitorWithdrawals } from '../../common/services/monitor_withdrawals' +import * as E from 'fp-ts/Either' +import { ethers } from 'forta-agent' +import { expect } from '@jest/globals' +import BigNumber from 'bignumber.js' +import * as Winston from 'winston' +import { ERC20Short__factory } from '../../common/generated' +import { mantleConstants } from '../src/agent' +import { L2Client } from '../../common/clients/l2_client' +import assert from 'assert' + +const SECOND = 1000 // ms + + +describe('MonitorWithdrawals', () => { + const logger = Winston.createLogger({ + format: Winston.format.simple(), + transports: [new Winston.transports.Console()], + }) + + const nodeClient = new ethers.providers.JsonRpcProvider(mantleConstants.L2_NETWORK_RPC, mantleConstants.L2_NETWORK_ID) + const bridgedWstethRunner = ERC20Short__factory.connect(mantleConstants.L2_WSTETH_BRIDGED.address, nodeClient) + const l2Client = new L2Client(nodeClient, logger, bridgedWstethRunner, mantleConstants.MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST) + const monitorWithdrawals = new MonitorWithdrawals( + l2Client, mantleConstants.L2_ERC20_TOKEN_GATEWAY.address, logger, mantleConstants.withdrawalInfo, + mantleConstants.L2_APPROX_BLOCK_TIME_SECONDS + ) + + test(`getWithdrawalRecordsInBlockRange: 2 withdrawals, 1_023_599 blocks`, async () => { + const withdrawalRecords = await monitorWithdrawals._getWithdrawalRecordsInBlockRange(64995881, 66019480) + assert(E.isRight(withdrawalRecords)) + expect(withdrawalRecords.right).toEqual([ + { + time: 1718122074, + amount: new BigNumber('337496585884301715'), + }, + { + time: 1720169272, + amount: new BigNumber('2107828861318592904'), + }, + ]) + }, 20 * SECOND) +}) diff --git a/l2-bridges/package.json b/l2-bridges/package.json index 02eb5956..2356e042 100644 --- a/l2-bridges/package.json +++ b/l2-bridges/package.json @@ -6,10 +6,13 @@ "start:scroll": "yarn start scroll dev", "push:scroll": "yarn update-version && node push-agent.js scroll", + "start:mantle": "yarn start mantle dev", + "push:mantle": "yarn update-version && node push-agent.js mantle", + "test": "jest", "postinstall": "yarn generate-types", - "generate-types": "typechain --target=ethers-v5 --out-dir=./scroll/src/generated ./scroll/src/abi/* && typechain --target=ethers-v5 --out-dir=./common/generated ./common/abi/*", + "generate-types": "typechain --target=ethers-v5 --out-dir=./common/generated ./common/abi/*", "update-version": "node ./common/utils/write-version.js ", "start": "node start-agent.ts" }, diff --git a/l2-bridges/scroll/src/abi/L2LidoGateway.json b/l2-bridges/scroll/src/abi/L2LidoGateway.json deleted file mode 100644 index e392396d..00000000 --- a/l2-bridges/scroll/src/abi/L2LidoGateway.json +++ /dev/null @@ -1,861 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "address", - "name": "_l1Token", - "type": "address" - }, - { - "internalType": "address", - "name": "_l2Token", - "type": "address" - }, - { - "internalType": "address", - "name": "_counterpart", - "type": "address" - }, - { - "internalType": "address", - "name": "_router", - "type": "address" - }, - { - "internalType": "address", - "name": "_messenger", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "ErrorAccountIsZeroAddress", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorCallerIsNotCounterpartGateway", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorCallerIsNotDepositsDisabler", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorCallerIsNotDepositsEnabler", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorCallerIsNotMessenger", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorCallerIsNotWithdrawalsDisabler", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorCallerIsNotWithdrawalsEnabler", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorDepositsDisabled", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorDepositsEnabled", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorNonZeroMsgValue", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorNotInDropMessageContext", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorUnsupportedL1Token", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorUnsupportedL2Token", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorWithdrawZeroAmount", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorWithdrawalsDisabled", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorWithdrawalsEnabled", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorZeroAddress", - "type": "error" - }, - { - "inputs": [], - "name": "WithdrawAndCallIsNotAllowed", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "disabler", - "type": "address" - } - ], - "name": "DepositsDisabled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "enabler", - "type": "address" - } - ], - "name": "DepositsEnabled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "l1Token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "l2Token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "FinalizeDepositERC20", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - } - ], - "name": "RoleGranted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - } - ], - "name": "RoleRevoked", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "l1Token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "l2Token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "WithdrawERC20", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "disabler", - "type": "address" - } - ], - "name": "WithdrawalsDisabled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "enabler", - "type": "address" - } - ], - "name": "WithdrawalsEnabled", - "type": "event" - }, - { - "inputs": [], - "name": "DEPOSITS_DISABLER_ROLE", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "DEPOSITS_ENABLER_ROLE", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "WITHDRAWALS_DISABLER_ROLE", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "WITHDRAWALS_ENABLER_ROLE", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "counterpart", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "disableDeposits", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "disableWithdrawals", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "enableDeposits", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "enableWithdrawals", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_l1Token", - "type": "address" - }, - { - "internalType": "address", - "name": "_l2Token", - "type": "address" - }, - { - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "_data", - "type": "bytes" - } - ], - "name": "finalizeDepositERC20", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_l2Token", - "type": "address" - } - ], - "name": "getL1ERC20Address", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_l1Token", - "type": "address" - } - ], - "name": "getL2ERC20Address", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "_role", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "_index", - "type": "uint256" - } - ], - "name": "getRoleMember", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "_role", - "type": "bytes32" - } - ], - "name": "getRoleMemberCount", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "_role", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "_account", - "type": "address" - } - ], - "name": "grantRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "_role", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "_account", - "type": "address" - } - ], - "name": "hasRole", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_counterpart", - "type": "address" - }, - { - "internalType": "address", - "name": "_router", - "type": "address" - }, - { - "internalType": "address", - "name": "_messenger", - "type": "address" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_depositsEnabler", - "type": "address" - }, - { - "internalType": "address", - "name": "_depositsDisabler", - "type": "address" - }, - { - "internalType": "address", - "name": "_withdrawalsEnabler", - "type": "address" - }, - { - "internalType": "address", - "name": "_withdrawalsDisabler", - "type": "address" - } - ], - "name": "initializeV2", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "isDepositsEnabled", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "isWithdrawalsEnabled", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "l1Token", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "l2Token", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "messenger", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "_role", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "_account", - "type": "address" - } - ], - "name": "revokeRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "router", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_gasLimit", - "type": "uint256" - } - ], - "name": "withdrawERC20", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_token", - "type": "address" - }, - { - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_gasLimit", - "type": "uint256" - } - ], - "name": "withdrawERC20", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_token", - "type": "address" - }, - { - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "_data", - "type": "bytes" - }, - { - "internalType": "uint256", - "name": "_gasLimit", - "type": "uint256" - } - ], - "name": "withdrawERC20AndCall", - "outputs": [], - "stateMutability": "payable", - "type": "function" - } -] diff --git a/l2-bridges/scroll/src/agent.ts b/l2-bridges/scroll/src/agent.ts index 3962fbed..136814b1 100644 --- a/l2-bridges/scroll/src/agent.ts +++ b/l2-bridges/scroll/src/agent.ts @@ -13,7 +13,6 @@ export const scrollConstants: Constants = { L2_APPROX_BLOCK_TIME_SECONDS: 3, L2_PROXY_ADMIN_CONTRACT_ADDRESS: '0x8e34d07eb348716a1f0a48a507a9de8a3a6dce45', GOV_BRIDGE_ADDRESS: '0x0c67d8d067e349669dfeab132a7c03a90594ee09', - L1_WSTETH_ADDRESS: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0', L1_ERC20_TOKEN_GATEWAY_ADDRESS: '0x6625c6332c9f91f2d27c304e729b86db87a3f504', L2_ERC20_TOKEN_GATEWAY: { name: 'L2_ERC20_TOKEN_GATEWAY', @@ -40,6 +39,7 @@ export const scrollConstants: Constants = { uint256 amount, bytes data )`, + amountFieldName: "amount", }, getBridgeEvents, getGovEvents, From ec0e69d02305a862e631ccb1cb86f10cedc23afe Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 15 Jul 2024 19:42:30 +0300 Subject: [PATCH 07/18] chore(mantle): remove unused abi file --- l2-bridges/mantle/abi/L2ERC20TokenBridge.json | 764 ------------------ 1 file changed, 764 deletions(-) delete mode 100644 l2-bridges/mantle/abi/L2ERC20TokenBridge.json diff --git a/l2-bridges/mantle/abi/L2ERC20TokenBridge.json b/l2-bridges/mantle/abi/L2ERC20TokenBridge.json deleted file mode 100644 index 51cf279f..00000000 --- a/l2-bridges/mantle/abi/L2ERC20TokenBridge.json +++ /dev/null @@ -1,764 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "address", - "name": "messenger_", - "type": "address" - }, - { - "internalType": "address", - "name": "l1TokenBridge_", - "type": "address" - }, - { - "internalType": "address", - "name": "l1Token_", - "type": "address" - }, - { - "internalType": "address", - "name": "l2Token_", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "ErrorAccountIsZeroAddress", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorAlreadyInitialized", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorDepositsDisabled", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorDepositsEnabled", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorSenderNotEOA", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorUnauthorizedMessenger", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorUnsupportedL1Token", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorUnsupportedL2Token", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorWithdrawalsDisabled", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorWithdrawalsEnabled", - "type": "error" - }, - { - "inputs": [], - "name": "ErrorWrongCrossDomainSender", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_l1Token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_l2Token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "_data", - "type": "bytes" - } - ], - "name": "DepositFailed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_l1Token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_l2Token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "_data", - "type": "bytes" - } - ], - "name": "DepositFinalized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "disabler", - "type": "address" - } - ], - "name": "DepositsDisabled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "enabler", - "type": "address" - } - ], - "name": "DepositsEnabled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "admin", - "type": "address" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "previousAdminRole", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "newAdminRole", - "type": "bytes32" - } - ], - "name": "RoleAdminChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - } - ], - "name": "RoleGranted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - } - ], - "name": "RoleRevoked", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_l1Token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_l2Token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "_data", - "type": "bytes" - } - ], - "name": "WithdrawalInitiated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "disabler", - "type": "address" - } - ], - "name": "WithdrawalsDisabled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "enabler", - "type": "address" - } - ], - "name": "WithdrawalsEnabled", - "type": "event" - }, - { - "inputs": [], - "name": "DEFAULT_ADMIN_ROLE", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "DEPOSITS_DISABLER_ROLE", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "DEPOSITS_ENABLER_ROLE", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "WITHDRAWALS_DISABLER_ROLE", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "WITHDRAWALS_ENABLER_ROLE", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "disableDeposits", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "disableWithdrawals", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "enableDeposits", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "enableWithdrawals", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "l1Token_", - "type": "address" - }, - { - "internalType": "address", - "name": "l2Token_", - "type": "address" - }, - { - "internalType": "address", - "name": "from_", - "type": "address" - }, - { - "internalType": "address", - "name": "to_", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount_", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data_", - "type": "bytes" - } - ], - "name": "finalizeDeposit", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - } - ], - "name": "getRoleAdmin", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "grantRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "hasRole", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "admin_", - "type": "address" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "isDepositsEnabled", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "isInitialized", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "isWithdrawalsEnabled", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "l1Token", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "l1TokenBridge", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "l2Token", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "messenger", - "outputs": [ - { - "internalType": "contract ICrossDomainMessenger", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "renounceRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "revokeRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes4", - "name": "interfaceId", - "type": "bytes4" - } - ], - "name": "supportsInterface", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "l2Token_", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount_", - "type": "uint256" - }, - { - "internalType": "uint32", - "name": "l1Gas_", - "type": "uint32" - }, - { - "internalType": "bytes", - "name": "data_", - "type": "bytes" - } - ], - "name": "withdraw", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "l2Token_", - "type": "address" - }, - { - "internalType": "address", - "name": "to_", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount_", - "type": "uint256" - }, - { - "internalType": "uint32", - "name": "l1Gas_", - "type": "uint32" - }, - { - "internalType": "bytes", - "name": "data_", - "type": "bytes" - } - ], - "name": "withdrawTo", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] From 49a2021863debb2ae0397f39c688077cfbd89be2 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Tue, 16 Jul 2024 17:47:32 +0300 Subject: [PATCH 08/18] refactor: store events in constants object right away --- l2-bridges/common/agent.ts | 14 ++--- l2-bridges/common/constants.ts | 37 ++++++++++---- l2-bridges/mantle/src/agent.ts | 93 +++++++++++++++------------------- l2-bridges/scroll/src/agent.ts | 49 +++++++++--------- 4 files changed, 96 insertions(+), 97 deletions(-) diff --git a/l2-bridges/common/agent.ts b/l2-bridges/common/agent.ts index 42774891..b0c6bd24 100644 --- a/l2-bridges/common/agent.ts +++ b/l2-bridges/common/agent.ts @@ -52,17 +52,9 @@ export class App { const l2Client = new L2Client(nodeClient, logger, bridgedWstethRunner, params.MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST) - const bridgeEventWatcher = new EventWatcher( - 'BridgeEventWatcher', - params.getBridgeEvents(params.L2_ERC20_TOKEN_GATEWAY.address, params.RolesMap), - logger, - ) - const govEventWatcher = new EventWatcher('GovEventWatcher', params.getGovEvents(params.GOV_BRIDGE_ADDRESS), logger) - const proxyEventWatcher = new EventWatcher( - 'ProxyEventWatcher', - params.getProxyAdminEvents(params.L2_WSTETH_BRIDGED, params.L2_ERC20_TOKEN_GATEWAY), - logger, - ) + const bridgeEventWatcher = new EventWatcher('BridgeEventWatcher', params.bridgeEvents, logger) + const govEventWatcher = new EventWatcher('GovEventWatcher', params.govEvents, logger) + const proxyEventWatcher = new EventWatcher('ProxyEventWatcher', params.proxyAdminEvents, logger) const monitorWithdrawals = new MonitorWithdrawals( l2Client, params.L2_ERC20_TOKEN_GATEWAY.address, logger, params.withdrawalInfo, diff --git a/l2-bridges/common/constants.ts b/l2-bridges/common/constants.ts index 8ae76b85..808832b0 100644 --- a/l2-bridges/common/constants.ts +++ b/l2-bridges/common/constants.ts @@ -2,23 +2,38 @@ import { EventOfNotice } from './entity/events' import BigNumber from 'bignumber.js' -export type WithdrawalInfo = { - eventName: string, - eventDefinition: string, // e.g. "event WithdrawalEvent(address indexed l1token, ...)" - amountFieldName: string, // e.g. "amount", according to the name of the field in the withdrawal event -} export const ETH_DECIMALS = new BigNumber(10).pow(18) export const MAINNET_CHAIN_ID = 1 export const DRPC_URL = 'https://eth.drpc.org/' export const L1_WSTETH_ADDRESS = '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0' +export const DEFAULT_ROLES_MAP: RoleHashToName = new Map([ + ['0x4b43b36766bde12c5e9cbbc37d15f8d1f769f08f54720ab370faeb4ce893753a', 'DEPOSITS_ENABLER_ROLE'], + ['0x63f736f21cb2943826cd50b191eb054ebbea670e4e962d0527611f830cd399d6', 'DEPOSITS_DISABLER_ROLE'], + ['0x9ab8816a3dc0b3849ec1ac00483f6ec815b07eee2fd766a353311c823ad59d0d', 'WITHDRAWALS_ENABLER_ROLE'], + ['0x94a954c0bc99227eddbc0715a62a7e1056ed8784cd719c2303b685683908857c', 'WITHDRAWALS_DISABLER_ROLE'], + ['0x0000000000000000000000000000000000000000000000000000000000000000', 'DEFAULT_ADMIN_ROLE'], +]); export type RoleHashToName = Map + export type ContractInfo = { name: string address: string } +export type TransparentProxyInfo = { + name: string + address: string + proxyAdminAddress: string +} + +export type WithdrawalInfo = { + eventName: string, + eventDefinition: string, // e.g. "event WithdrawalEvent(address indexed l1token, ...)" + amountFieldName: string, // e.g. "amount", according to the name of the field in the withdrawal event +} + export type Constants = { L2_NAME: string, L2_NETWORK_RPC: string, @@ -26,13 +41,13 @@ export type Constants = { L2_NETWORK_ID: number, L2_APPROX_BLOCK_TIME_SECONDS: number, L2_PROXY_ADMIN_CONTRACT_ADDRESS: string, - GOV_BRIDGE_ADDRESS: string, + govExecutor: string | TransparentProxyInfo, L1_ERC20_TOKEN_GATEWAY_ADDRESS: string, L2_ERC20_TOKEN_GATEWAY: ContractInfo, - L2_WSTETH_BRIDGED: ContractInfo, - RolesMap: RoleHashToName, + L2_WSTETH_BRIDGED: ContractInfo | TransparentProxyInfo, + rolesMap: RoleHashToName, withdrawalInfo: WithdrawalInfo, - getBridgeEvents: (l2GatewayAddress: string, RolesAddrToNameMap: RoleHashToName) => EventOfNotice[], - getGovEvents: (GOV_BRIDGE_ADDRESS: string) => EventOfNotice[], - getProxyAdminEvents: (l2WstethContract: ContractInfo, l2GatewayContract: ContractInfo) => EventOfNotice[], + bridgeEvents: EventOfNotice[], + govEvents: EventOfNotice[], + proxyAdminEvents: EventOfNotice[], } diff --git a/l2-bridges/mantle/src/agent.ts b/l2-bridges/mantle/src/agent.ts index 683c4385..5c5b13fd 100644 --- a/l2-bridges/mantle/src/agent.ts +++ b/l2-bridges/mantle/src/agent.ts @@ -2,7 +2,7 @@ import { FindingSeverity, FindingType } from 'forta-agent' import { App } from '../../common/agent' import { Result } from '@ethersproject/abi/lib' import { EventOfNotice } from '../../common/entity/events' -import { Constants, RoleHashToName, ContractInfo } from '../../common/constants' +import { Constants, RoleHashToName, ContractInfo, DEFAULT_ROLES_MAP } from '../../common/constants' export const mantleConstants: Constants = { @@ -12,7 +12,7 @@ export const mantleConstants: Constants = { L2_NETWORK_ID: 5000, L2_APPROX_BLOCK_TIME_SECONDS: 2, L2_PROXY_ADMIN_CONTRACT_ADDRESS: '0x8e34d07eb348716a1f0a48a507a9de8a3a6dce45', // TODO - GOV_BRIDGE_ADDRESS: '0x3a7b055bf88cdc59d20d0245809c6e6b3c5819dd', + govExecutor: '0x3a7b055bf88cdc59d20d0245809c6e6b3c5819dd', L1_ERC20_TOKEN_GATEWAY_ADDRESS: '0x2D001d79E5aF5F65a939781FE228B267a8Ed468B', L2_ERC20_TOKEN_GATEWAY: { name: 'L2_ERC20_TOKEN_GATEWAY', @@ -22,13 +22,7 @@ export const mantleConstants: Constants = { name: 'MANTLE_WSTETH_BRIDGED', address: '0x458ed78EB972a369799fb278c0243b25e5242A83', }, - RolesMap: new Map([ - ['0x4b43b36766bde12c5e9cbbc37d15f8d1f769f08f54720ab370faeb4ce893753a', 'DEPOSITS_ENABLER_ROLE'], - ['0x63f736f21cb2943826cd50b191eb054ebbea670e4e962d0527611f830cd399d6', 'DEPOSITS_DISABLER_ROLE'], - ['0x9ab8816a3dc0b3849ec1ac00483f6ec815b07eee2fd766a353311c823ad59d0d', 'WITHDRAWALS_ENABLER_ROLE'], - ['0x94a954c0bc99227eddbc0715a62a7e1056ed8784cd719c2303b685683908857c', 'WITHDRAWALS_DISABLER_ROLE'], - ['0x0000000000000000000000000000000000000000000000000000000000000000', 'DEFAULT_ADMIN_ROLE'], - ]), + rolesMap: DEFAULT_ROLES_MAP, withdrawalInfo: { eventName: 'WithdrawalInitiated', eventDefinition: `event WithdrawalInitiated( @@ -41,16 +35,19 @@ export const mantleConstants: Constants = { )`, amountFieldName: "_amount", }, - getBridgeEvents, - getGovEvents, - getProxyAdminEvents, + bridgeEvents: [], + govEvents: [], + proxyAdminEvents: [], } +mantleConstants.bridgeEvents = getBridgeEvents(mantleConstants.L2_ERC20_TOKEN_GATEWAY.address, mantleConstants.rolesMap); +mantleConstants.govEvents = getGovEvents(mantleConstants.govExecutor as string); +mantleConstants.proxyAdminEvents = getProxyAdminEvents( + mantleConstants.L2_WSTETH_BRIDGED as ContractInfo, + mantleConstants.L2_ERC20_TOKEN_GATEWAY +); -export function getBridgeEvents( - L2_ERC20_TOKEN_GATEWAY_ADDRESS: string, - RolesAddrToNameMap: RoleHashToName, -): EventOfNotice[] { +function getBridgeEvents(l2TokenBridgeAddress: string, rolesMap: RoleHashToName): EventOfNotice[] { const uniqueKeys = [ 'be8452bb-c4c6-4526-9489-b04626ec4c4d', 'e3e767b1-de01-4695-84c7-5654567cf501', @@ -64,7 +61,7 @@ export function getBridgeEvents( return [ { - address: L2_ERC20_TOKEN_GATEWAY_ADDRESS, + address: l2TokenBridgeAddress, event: 'event Initialized(address indexed admin)', alertId: 'L2-BRIDGE-IMPLEMENTATION-INITIALIZED', name: '🚨🚨🚨 Mantle L2 Bridge: Implementation initialized', @@ -77,7 +74,7 @@ export function getBridgeEvents( uniqueKey: uniqueKeys[0], }, { - address: L2_ERC20_TOKEN_GATEWAY_ADDRESS, + address: l2TokenBridgeAddress, event: 'event DepositsDisabled(address indexed disabler)', alertId: 'L2-BRIDGE-DEPOSITS-DISABLED', name: '🚨 Mantle L2 Bridge: Deposits Disabled', @@ -87,20 +84,20 @@ export function getBridgeEvents( uniqueKey: uniqueKeys[1], }, { - address: L2_ERC20_TOKEN_GATEWAY_ADDRESS, + address: l2TokenBridgeAddress, event: 'event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole)', alertId: 'L2-BRIDGE-ROLE-ADMIN-CHANGED', name: '🚨 Mantle L2 Bridge: Role Admin changed', description: (args: Result) => - `Role Admin for role ${args.role}(${RolesAddrToNameMap.get(args.role) || 'unknown'}) ` + + `Role Admin for role ${args.role}(${rolesMap.get(args.role) || 'unknown'}) ` + `was changed from ${args.previousAdminRole} to ${args.newAdminRole}`, severity: FindingSeverity.High, type: FindingType.Info, uniqueKey: uniqueKeys[2], }, { - address: L2_ERC20_TOKEN_GATEWAY_ADDRESS, + address: l2TokenBridgeAddress, event: 'event WithdrawalsDisabled(address indexed disabler)', alertId: 'L2-BRIDGE-WITHDRAWALS-DISABLED', name: '🚨 Mantle L2 Bridge: Withdrawals Disabled', @@ -110,31 +107,31 @@ export function getBridgeEvents( uniqueKey: uniqueKeys[3], }, { - address: L2_ERC20_TOKEN_GATEWAY_ADDRESS, + address: l2TokenBridgeAddress, event: 'event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender)', alertId: 'L2-BRIDGE-ROLE-GRANTED', name: '⚠️ Mantle L2 Bridge: Role granted', description: (args: Result) => - `Role ${args.role}(${RolesAddrToNameMap.get(args.role) || 'unknown'}) ` + + `Role ${args.role}(${rolesMap.get(args.role) || 'unknown'}) ` + `was granted to ${args.account} by ${args.sender}`, severity: FindingSeverity.Medium, type: FindingType.Info, uniqueKey: uniqueKeys[4], }, { - address: L2_ERC20_TOKEN_GATEWAY_ADDRESS, + address: l2TokenBridgeAddress, event: 'event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender)', alertId: 'L2-BRIDGE-ROLE-REVOKED', name: '⚠️ Mantle L2 Bridge: Role revoked', description: (args: Result) => - `Role ${args.role}(${RolesAddrToNameMap.get(args.role) || 'unknown'}) ` + + `Role ${args.role}(${rolesMap.get(args.role) || 'unknown'}) ` + `was revoked to ${args.account} by ${args.sender}`, severity: FindingSeverity.Medium, type: FindingType.Info, uniqueKey: uniqueKeys[5], }, { - address: L2_ERC20_TOKEN_GATEWAY_ADDRESS, + address: l2TokenBridgeAddress, event: 'event DepositsEnabled(address indexed enabler)', alertId: 'L2-BRIDGE-DEPOSITS-ENABLED', name: 'ℹ️ Mantle L2 Bridge: Deposits Enabled', @@ -144,7 +141,7 @@ export function getBridgeEvents( uniqueKey: uniqueKeys[6], }, { - address: L2_ERC20_TOKEN_GATEWAY_ADDRESS, + address: l2TokenBridgeAddress, event: 'event WithdrawalsEnabled(address indexed enabler)', alertId: 'L2-BRIDGE-WITHDRAWALS-ENABLED', name: 'ℹ️ Mantle L2 Bridge: Withdrawals Enabled', @@ -156,10 +153,7 @@ export function getBridgeEvents( ] } - - - -export function getGovEvents(GOV_BRIDGE_ADDRESS: string): EventOfNotice[] { +function getGovEvents(GOV_BRIDGE_ADDRESS: string): EventOfNotice[] { const uniqueKeys = [ '0a9a066e-233d-4d00-af58-84b685a42729', 'a2224ced-9745-45c3-90d2-d95f66f57442', @@ -274,10 +268,7 @@ export function getGovEvents(GOV_BRIDGE_ADDRESS: string): EventOfNotice[] { } -export function getProxyAdminEvents( - MANTLE_WST_ETH_BRIDGED_ADDRESS: ContractInfo, - L2_ERC20_TOKEN_GATEWAY_ADDRESS: ContractInfo, -): EventOfNotice[] { +function getProxyAdminEvents(l2Wsteth: ContractInfo, l2TokenBridge: ContractInfo): EventOfNotice[] { const uniqueKeys = [ '82b39d98-a156-4be2-be48-81a0d237c53a', '44367f0e-dbe2-4cb0-b256-1af2c9a38d9f', @@ -291,25 +282,25 @@ export function getProxyAdminEvents( return [ { - address: MANTLE_WST_ETH_BRIDGED_ADDRESS.address, + address: l2Wsteth.address, event: 'event ProxyOssified()', alertId: 'PROXY-OSSIFIED', name: '🚨 Mantle: Proxy ossified', // eslint-disable-next-line @typescript-eslint/no-unused-vars description: (args: Result) => - `Proxy for ${MANTLE_WST_ETH_BRIDGED_ADDRESS.name}(${MANTLE_WST_ETH_BRIDGED_ADDRESS.address}) was ossified` + + `Proxy for ${l2Wsteth.name}(${l2Wsteth.address}) was ossified` + `\n(detected by event)`, severity: FindingSeverity.High, type: FindingType.Info, uniqueKey: uniqueKeys[0], }, { - address: MANTLE_WST_ETH_BRIDGED_ADDRESS.address, + address: l2Wsteth.address, event: 'event AdminChanged(address previousAdmin, address newAdmin)', alertId: 'PROXY-ADMIN-CHANGED', name: '🚨 Mantle: Proxy admin changed', description: (args: Result) => - `Proxy admin for ${MANTLE_WST_ETH_BRIDGED_ADDRESS.name}(${MANTLE_WST_ETH_BRIDGED_ADDRESS.address}) ` + + `Proxy admin for ${l2Wsteth.name}(${l2Wsteth.address}) ` + `was changed from ${args.previousAdmin} to ${args.newAdmin}` + `\n(detected by event)`, severity: FindingSeverity.High, @@ -317,12 +308,12 @@ export function getProxyAdminEvents( uniqueKey: uniqueKeys[1], }, { - address: MANTLE_WST_ETH_BRIDGED_ADDRESS.address, + address: l2Wsteth.address, event: 'event Upgraded(address indexed implementation)', alertId: 'PROXY-UPGRADED', name: '🚨 Mantle: Proxy upgraded', description: (args: Result) => - `Proxy for ${MANTLE_WST_ETH_BRIDGED_ADDRESS.name}(${MANTLE_WST_ETH_BRIDGED_ADDRESS.address}) ` + + `Proxy for ${l2Wsteth.name}(${l2Wsteth.address}) ` + `was updated to ${args.implementation}` + `\n(detected by event)`, severity: FindingSeverity.High, @@ -330,12 +321,12 @@ export function getProxyAdminEvents( uniqueKey: uniqueKeys[2], }, { - address: MANTLE_WST_ETH_BRIDGED_ADDRESS.address, + address: l2Wsteth.address, event: 'event BeaconUpgraded(address indexed beacon)', alertId: 'PROXY-BEACON-UPGRADED', name: '🚨 Mantle: Proxy beacon upgraded', description: (args: Result) => - `Proxy for ${MANTLE_WST_ETH_BRIDGED_ADDRESS.name}(${MANTLE_WST_ETH_BRIDGED_ADDRESS.address}) ` + + `Proxy for ${l2Wsteth.name}(${l2Wsteth.address}) ` + `beacon was updated to ${args.beacon}` + `\n(detected by event)`, severity: FindingSeverity.High, @@ -344,25 +335,25 @@ export function getProxyAdminEvents( }, { - address: L2_ERC20_TOKEN_GATEWAY_ADDRESS.address, + address: l2TokenBridge.address, event: 'event ProxyOssified()', alertId: 'PROXY-OSSIFIED', name: '🚨 Mantle: Proxy ossified', // eslint-disable-next-line @typescript-eslint/no-unused-vars description: (args: Result) => - `Proxy for ${L2_ERC20_TOKEN_GATEWAY_ADDRESS.name}(${L2_ERC20_TOKEN_GATEWAY_ADDRESS.address}) was ossified` + + `Proxy for ${l2TokenBridge.name}(${l2TokenBridge.address}) was ossified` + `\n(detected by event)`, severity: FindingSeverity.High, type: FindingType.Info, uniqueKey: uniqueKeys[4], }, { - address: L2_ERC20_TOKEN_GATEWAY_ADDRESS.address, + address: l2TokenBridge.address, event: 'event AdminChanged(address previousAdmin, address newAdmin)', alertId: 'PROXY-ADMIN-CHANGED', name: '🚨 Mantle: Proxy admin changed', description: (args: Result) => - `Proxy admin for ${L2_ERC20_TOKEN_GATEWAY_ADDRESS.name}(${L2_ERC20_TOKEN_GATEWAY_ADDRESS.address}) ` + + `Proxy admin for ${l2TokenBridge.name}(${l2TokenBridge.address}) ` + `was changed from ${args.previousAdmin} to ${args.newAdmin}` + `\n(detected by event)`, severity: FindingSeverity.High, @@ -370,12 +361,12 @@ export function getProxyAdminEvents( uniqueKey: uniqueKeys[5], }, { - address: L2_ERC20_TOKEN_GATEWAY_ADDRESS.address, + address: l2TokenBridge.address, event: 'event Upgraded(address indexed implementation)', alertId: 'PROXY-UPGRADED', name: '🚨 Mantle: Proxy upgraded', description: (args: Result) => - `Proxy for ${L2_ERC20_TOKEN_GATEWAY_ADDRESS.name}(${L2_ERC20_TOKEN_GATEWAY_ADDRESS.address}) ` + + `Proxy for ${l2TokenBridge.name}(${l2TokenBridge.address}) ` + `was updated to ${args.implementation}` + `\n(detected by event)`, severity: FindingSeverity.High, @@ -383,12 +374,12 @@ export function getProxyAdminEvents( uniqueKey: uniqueKeys[6], }, { - address: L2_ERC20_TOKEN_GATEWAY_ADDRESS.address, + address: l2TokenBridge.address, event: 'event BeaconUpgraded(address indexed beacon)', alertId: 'PROXY-BEACON-UPGRADED', name: '🚨 Mantle: Proxy beacon upgraded', description: (args: Result) => - `Proxy for ${L2_ERC20_TOKEN_GATEWAY_ADDRESS.name}(${L2_ERC20_TOKEN_GATEWAY_ADDRESS.address}) ` + + `Proxy for ${l2TokenBridge.name}(${l2TokenBridge.address}) ` + `beacon was updated to ${args.beacon}` + `\n(detected by event)`, severity: FindingSeverity.High, diff --git a/l2-bridges/scroll/src/agent.ts b/l2-bridges/scroll/src/agent.ts index 136814b1..2506e6a4 100644 --- a/l2-bridges/scroll/src/agent.ts +++ b/l2-bridges/scroll/src/agent.ts @@ -2,7 +2,7 @@ import { FindingSeverity, FindingType } from 'forta-agent' import { App } from '../../common/agent' import { Result } from '@ethersproject/abi/lib' import { EventOfNotice } from '../../common/entity/events' -import { Constants, RoleHashToName, ContractInfo } from '../../common/constants' +import { Constants, RoleHashToName, ContractInfo, DEFAULT_ROLES_MAP } from '../../common/constants' export const scrollConstants: Constants = { @@ -12,7 +12,7 @@ export const scrollConstants: Constants = { L2_NETWORK_ID: 534352, L2_APPROX_BLOCK_TIME_SECONDS: 3, L2_PROXY_ADMIN_CONTRACT_ADDRESS: '0x8e34d07eb348716a1f0a48a507a9de8a3a6dce45', - GOV_BRIDGE_ADDRESS: '0x0c67d8d067e349669dfeab132a7c03a90594ee09', + govExecutor: '0x0c67d8d067e349669dfeab132a7c03a90594ee09', L1_ERC20_TOKEN_GATEWAY_ADDRESS: '0x6625c6332c9f91f2d27c304e729b86db87a3f504', L2_ERC20_TOKEN_GATEWAY: { name: 'L2_ERC20_TOKEN_GATEWAY', @@ -22,13 +22,7 @@ export const scrollConstants: Constants = { name: 'SCROLL_WSTETH_BRIDGED', address: '0xf610a9dfb7c89644979b4a0f27063e9e7d7cda32', }, - RolesMap: new Map([ - ['0x4b43b36766bde12c5e9cbbc37d15f8d1f769f08f54720ab370faeb4ce893753a', 'DEPOSITS_ENABLER_ROLE'], - ['0x63f736f21cb2943826cd50b191eb054ebbea670e4e962d0527611f830cd399d6', 'DEPOSITS_DISABLER_ROLE'], - ['0x9ab8816a3dc0b3849ec1ac00483f6ec815b07eee2fd766a353311c823ad59d0d', 'WITHDRAWALS_ENABLER_ROLE'], - ['0x94a954c0bc99227eddbc0715a62a7e1056ed8784cd719c2303b685683908857c', 'WITHDRAWALS_DISABLER_ROLE'], - ['0x0000000000000000000000000000000000000000000000000000000000000000', 'DEFAULT_ADMIN_ROLE'], - ]), + rolesMap: DEFAULT_ROLES_MAP, withdrawalInfo: { eventName: 'WithdrawERC20', eventDefinition: `event WithdrawERC20( @@ -41,13 +35,19 @@ export const scrollConstants: Constants = { )`, amountFieldName: "amount", }, - getBridgeEvents, - getGovEvents, - getProxyAdminEvents, + bridgeEvents: [], + govEvents: [], + proxyAdminEvents: [], } +scrollConstants.bridgeEvents = getBridgeEvents(scrollConstants.L2_ERC20_TOKEN_GATEWAY.address, scrollConstants.rolesMap); +scrollConstants.govEvents = getGovEvents(scrollConstants.govExecutor as string); +scrollConstants.proxyAdminEvents = getProxyAdminEvents( + scrollConstants.L2_WSTETH_BRIDGED as ContractInfo, + scrollConstants.L2_ERC20_TOKEN_GATEWAY +); -export function getBridgeEvents(l2GatewayAddress: string, RolesAddrToNameMap: RoleHashToName): EventOfNotice[] { +function getBridgeEvents(l2GatewayAddress: string, RolesAddrToNameMap: RoleHashToName): EventOfNotice[] { return [ { address: l2GatewayAddress, @@ -141,10 +141,10 @@ export function getBridgeEvents(l2GatewayAddress: string, RolesAddrToNameMap: Ro } -export function getGovEvents(GOV_BRIDGE_ADDRESS: string): EventOfNotice[] { +function getGovEvents(govExecutorAddress: string): EventOfNotice[] { return [ { - address: GOV_BRIDGE_ADDRESS, + address: govExecutorAddress, event: 'event EthereumGovernanceExecutorUpdate(address oldEthereumGovernanceExecutor, address newEthereumGovernanceExecutor)', alertId: 'GOV-BRIDGE-EXEC-UPDATED', @@ -157,7 +157,7 @@ export function getGovEvents(GOV_BRIDGE_ADDRESS: string): EventOfNotice[] { uniqueKey: '73EE62B0-E0CF-4527-8E44-566D72667F22', }, { - address: GOV_BRIDGE_ADDRESS, + address: govExecutorAddress, event: 'event GuardianUpdate(address oldGuardian, address newGuardian)', alertId: 'GOV-BRIDGE-GUARDIAN-UPDATED', name: '🚨 Scroll Gov Bridge: Guardian Updated', @@ -167,7 +167,7 @@ export function getGovEvents(GOV_BRIDGE_ADDRESS: string): EventOfNotice[] { uniqueKey: '8C586373-5040-4BDA-8EF8-16CBE582D6B0', }, { - address: GOV_BRIDGE_ADDRESS, + address: govExecutorAddress, event: 'event DelayUpdate(uint256 oldDelay, uint256 newDelay)', alertId: 'GOV-BRIDGE-DELAY-UPDATED', name: '⚠️ Scroll Gov Bridge: Delay Updated', @@ -177,7 +177,7 @@ export function getGovEvents(GOV_BRIDGE_ADDRESS: string): EventOfNotice[] { uniqueKey: '073F04A8-B232-4671-A34E-D42A3729FE34', }, { - address: GOV_BRIDGE_ADDRESS, + address: govExecutorAddress, event: 'event GracePeriodUpdate(uint256 oldGracePeriod, uint256 newGracePeriod)', alertId: 'GOV-BRIDGE-GRACE-PERIOD-UPDATED', name: '⚠️ Scroll Gov Bridge: Grace Period Updated', @@ -188,7 +188,7 @@ export function getGovEvents(GOV_BRIDGE_ADDRESS: string): EventOfNotice[] { uniqueKey: '26574C78-EBD1-42D3-A9C7-3E4A2976FCB7', }, { - address: GOV_BRIDGE_ADDRESS, + address: govExecutorAddress, event: 'event MinimumDelayUpdate(uint256 oldMinimumDelay, uint256 newMinimumDelay)', alertId: 'GOV-BRIDGE-MIN-DELAY-UPDATED', name: '⚠️ Scroll Gov Bridge: Min Delay Updated', @@ -199,7 +199,7 @@ export function getGovEvents(GOV_BRIDGE_ADDRESS: string): EventOfNotice[] { uniqueKey: '35391E05-CBB4-4013-ACA1-A75F8C5D6991', }, { - address: GOV_BRIDGE_ADDRESS, + address: govExecutorAddress, event: 'event MaximumDelayUpdate(uint256 oldMaximumDelay, uint256 newMaximumDelay)', alertId: 'GOV-BRIDGE-MAX-DELAY-UPDATED', name: '⚠️ Scroll Gov Bridge: Max Delay Updated', @@ -210,7 +210,7 @@ export function getGovEvents(GOV_BRIDGE_ADDRESS: string): EventOfNotice[] { uniqueKey: '003CCEDE-551A-4310-86A7-F8EC22135C45', }, { - address: GOV_BRIDGE_ADDRESS, + address: govExecutorAddress, event: 'event ActionsSetQueued(uint256 indexed id, address[] targets, uint256[] values, string[] signatures, bytes[] calldatas, bool[] withDelegatecalls, uint256 executionTime)', alertId: 'GOV-BRIDGE-ACTION-SET-QUEUED', @@ -221,7 +221,7 @@ export function getGovEvents(GOV_BRIDGE_ADDRESS: string): EventOfNotice[] { uniqueKey: '84D309D8-AB13-4B41-A9CE-8DE4AB77E77A', }, { - address: GOV_BRIDGE_ADDRESS, + address: govExecutorAddress, event: 'event ActionsSetExecuted(uint256 indexed id, address indexed initiatorExecution, bytes[] returnedData)', alertId: 'GOV-BRIDGE-ACTION-SET-EXECUTED', name: 'ℹ️ Scroll Gov Bridge: Action set executed', @@ -231,7 +231,7 @@ export function getGovEvents(GOV_BRIDGE_ADDRESS: string): EventOfNotice[] { uniqueKey: '31FE6EEB-4619-4579-9C0B-58EECC3D7724', }, { - address: GOV_BRIDGE_ADDRESS, + address: govExecutorAddress, event: 'event ActionsSetCanceled(uint256 indexed id)', alertId: 'GOV-BRIDGE-ACTION-SET-CANCELED', name: 'ℹ️ Scroll Gov Bridge: Action set canceled', @@ -243,7 +243,8 @@ export function getGovEvents(GOV_BRIDGE_ADDRESS: string): EventOfNotice[] { ] } -export function getProxyAdminEvents(l2WstethContract: ContractInfo, l2GatewayContract: ContractInfo): EventOfNotice[] { + +function getProxyAdminEvents(l2WstethContract: ContractInfo, l2GatewayContract: ContractInfo): EventOfNotice[] { return [ { address: l2WstethContract.address, From 86c3a602f1512b695c2c7f6cf9cefa5703f51dbb Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Tue, 16 Jul 2024 17:49:37 +0300 Subject: [PATCH 09/18] refactor: rename monitor_withdrawals test files --- ...hdrawals_mantle.spec.ts => monitor_withdrawals.mantle.spec.ts} | 0 ...tor_withdrawals.spec.ts => monitor_withdrawals.scroll.spec.ts} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename l2-bridges/mantle/test/{monitor_withdrawals_mantle.spec.ts => monitor_withdrawals.mantle.spec.ts} (100%) rename l2-bridges/scroll/test/{monitor_withdrawals.spec.ts => monitor_withdrawals.scroll.spec.ts} (100%) diff --git a/l2-bridges/mantle/test/monitor_withdrawals_mantle.spec.ts b/l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts similarity index 100% rename from l2-bridges/mantle/test/monitor_withdrawals_mantle.spec.ts rename to l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts diff --git a/l2-bridges/scroll/test/monitor_withdrawals.spec.ts b/l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts similarity index 100% rename from l2-bridges/scroll/test/monitor_withdrawals.spec.ts rename to l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts From a6c3d79bf47be198b2e3665d1e364f0053d8db12 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Fri, 26 Jul 2024 21:40:51 +0300 Subject: [PATCH 10/18] fix(l2,monitor_withdrawals): fix HUGE-WITHDRAWALS-FROM-L2 alert text --- l2-bridges/common/agent.ts | 9 ++-- l2-bridges/common/constants.ts | 27 +++++++++- .../common/services/monitor_withdrawals.ts | 49 +++++++++---------- l2-bridges/mantle/src/agent.ts | 11 +++-- l2-bridges/scroll/src/agent.ts | 8 ++- 5 files changed, 65 insertions(+), 39 deletions(-) diff --git a/l2-bridges/common/agent.ts b/l2-bridges/common/agent.ts index b0c6bd24..e2593429 100644 --- a/l2-bridges/common/agent.ts +++ b/l2-bridges/common/agent.ts @@ -56,10 +56,7 @@ export class App { const govEventWatcher = new EventWatcher('GovEventWatcher', params.govEvents, logger) const proxyEventWatcher = new EventWatcher('ProxyEventWatcher', params.proxyAdminEvents, logger) - const monitorWithdrawals = new MonitorWithdrawals( - l2Client, params.L2_ERC20_TOKEN_GATEWAY.address, logger, params.withdrawalInfo, - params.L2_APPROX_BLOCK_TIME_SECONDS - ) + const monitorWithdrawals = new MonitorWithdrawals(l2Client, logger, params) const ethProvider = new ethers.providers.FallbackProvider([ new ethers.providers.JsonRpcProvider(getJsonRpcUrl(), MAINNET_CHAIN_ID), @@ -90,11 +87,11 @@ export class App { if (!App.instance) { throw new Error(`App instance is not created`) } - return App.instance; + return App.instance } public static async handleBlock(blockEvent: BlockEvent): Promise { - const app = await App.getInstance(); + const app = await App.getInstance() const startTime = new Date().getTime() if (App.isHandleBlockRunning) { diff --git a/l2-bridges/common/constants.ts b/l2-bridges/common/constants.ts index 808832b0..22b274a9 100644 --- a/l2-bridges/common/constants.ts +++ b/l2-bridges/common/constants.ts @@ -1,5 +1,6 @@ +import { FindingSeverity, FindingType, Finding } from 'forta-agent' import { EventOfNotice } from './entity/events' - +import { getUniqueKey } from './utils/finding.helpers' import BigNumber from 'bignumber.js' @@ -13,7 +14,22 @@ export const DEFAULT_ROLES_MAP: RoleHashToName = new Map([ ['0x9ab8816a3dc0b3849ec1ac00483f6ec815b07eee2fd766a353311c823ad59d0d', 'WITHDRAWALS_ENABLER_ROLE'], ['0x94a954c0bc99227eddbc0715a62a7e1056ed8784cd719c2303b685683908857c', 'WITHDRAWALS_DISABLER_ROLE'], ['0x0000000000000000000000000000000000000000000000000000000000000000', 'DEFAULT_ADMIN_ROLE'], -]); +]) + +export const getHugeWithdrawalsFromL2AlertFactory = (l2Name: string, uniqueKey: string) => { + return (params: HugeWithdrawalsFromL2AlertParams) => { + return Finding.fromObject({ + name: `⚠️ ${l2Name}: Huge withdrawals during the last ` + `${Math.floor(params.period / (60 * 60))} hour(s)`, + description: + `There were withdrawals requests from L2 to L1 for the ` + + `${params.withdrawalsSum.div(ETH_DECIMALS).toFixed(4)} wstETH in total`, + alertId: 'HUGE-WITHDRAWALS-FROM-L2', + severity: FindingSeverity.Medium, + type: FindingType.Suspicious, + uniqueKey: getUniqueKey(uniqueKey, params.l2BlockNumber), + }) + } +} export type RoleHashToName = Map @@ -34,6 +50,12 @@ export type WithdrawalInfo = { amountFieldName: string, // e.g. "amount", according to the name of the field in the withdrawal event } +export type HugeWithdrawalsFromL2AlertParams = { + period: number, + l2BlockNumber: number, + withdrawalsSum: BigNumber, +} + export type Constants = { L2_NAME: string, L2_NETWORK_RPC: string, @@ -50,4 +72,5 @@ export type Constants = { bridgeEvents: EventOfNotice[], govEvents: EventOfNotice[], proxyAdminEvents: EventOfNotice[], + getHugeWithdrawalsFromL2Alert: (params: HugeWithdrawalsFromL2AlertParams) => Finding, } diff --git a/l2-bridges/common/services/monitor_withdrawals.ts b/l2-bridges/common/services/monitor_withdrawals.ts index 04f6b7d2..02181886 100644 --- a/l2-bridges/common/services/monitor_withdrawals.ts +++ b/l2-bridges/common/services/monitor_withdrawals.ts @@ -7,45 +7,55 @@ import { BlockDto, WithdrawalRecord } from '../entity/blockDto' import { L2Client } from '../clients/l2_client' import { NetworkError } from '../utils/error' import { elapsedTime } from '../utils/time' -import { getUniqueKey } from '../utils/finding.helpers' import { ETH_DECIMALS } from '../constants' import { formatAddress } from 'forta-agent/dist/cli/utils' import { ethers } from 'ethers' -import { WithdrawalInfo } from '../constants' +import { WithdrawalInfo, ContractInfo, HugeWithdrawalsFromL2AlertParams } from '../constants' import assert from 'assert' // 10k wstETH const MAX_WITHDRAWALS_SUM = 10_000 +const HOURS_48 = 60 * 60 * 24 * 2 * 6 export type MonitorWithdrawalsInitResp = { - currentWithdrawals: string + currentWithdrawals: string, +} + +export type WithdrawalConstants = { + L2_NAME: string, + withdrawalInfo: WithdrawalInfo, + L2_APPROX_BLOCK_TIME_SECONDS: number, + L2_ERC20_TOKEN_GATEWAY: ContractInfo, + getHugeWithdrawalsFromL2Alert: (params: HugeWithdrawalsFromL2AlertParams) => Finding, } -export const HOURS_48 = 60 * 60 * 24 * 2 export class MonitorWithdrawals { private readonly name: string = 'WithdrawalsMonitor' private readonly logger: Logger private readonly l2Erc20TokenGatewayAddress: string + private readonly networkName: string private readonly l2Client: L2Client private readonly withdrawalInfo: WithdrawalInfo & { eventSignature: string, eventInterface: ethers.utils.Interface } private readonly l2BlockAverageTime: number + private readonly constants: WithdrawalConstants private withdrawalsStore: WithdrawalRecord[] = [] private lastReportedTooManyWithdrawalsTimestamp = 0 - constructor(l2Client: L2Client, l2Erc20TokenGatewayAddress: string, logger: Logger, withdrawalInfo: WithdrawalInfo, l2BlockAverageTime: number) { - const eventInterface = new ethers.utils.Interface([withdrawalInfo.eventDefinition]) + constructor(l2Client: L2Client, logger: Logger, withdrawalConstants: WithdrawalConstants) { + const eventInterface = new ethers.utils.Interface([withdrawalConstants.withdrawalInfo.eventDefinition]) + this.constants = withdrawalConstants this.l2Client = l2Client - this.l2Erc20TokenGatewayAddress = l2Erc20TokenGatewayAddress + this.l2Erc20TokenGatewayAddress = withdrawalConstants.L2_ERC20_TOKEN_GATEWAY.address this.logger = logger this.withdrawalInfo = { - ...withdrawalInfo, - eventSignature: eventInterface.getEventTopic(withdrawalInfo.eventName), + ...withdrawalConstants.withdrawalInfo, + eventSignature: eventInterface.getEventTopic(withdrawalConstants.withdrawalInfo.eventName), eventInterface: eventInterface, } - this.l2BlockAverageTime = l2BlockAverageTime - + this.l2BlockAverageTime = withdrawalConstants.L2_APPROX_BLOCK_TIME_SECONDS + this.networkName = withdrawalConstants.L2_NAME } public getName(): string { @@ -111,20 +121,9 @@ export class MonitorWithdrawals { ? l2Block.timestamp - this.lastReportedTooManyWithdrawalsTimestamp : HOURS_48 - const uniqueKey = `C167F276-D519-4906-90CB-C4455E9ABBD4` - - const finding: Finding = Finding.fromObject({ - name: `⚠️ Scroll: Huge withdrawals during the last ` + `${Math.floor(period / (60 * 60))} hour(s)`, - description: - `There were withdrawals requests from L2 to L1 for the ` + - `${withdrawalsSum.div(ETH_DECIMALS).toFixed(4)} wstETH in total`, - alertId: 'HUGE-WITHDRAWALS-FROM-L2', - severity: FindingSeverity.Medium, - type: FindingType.Suspicious, - uniqueKey: getUniqueKey(uniqueKey, l2Block.number), - }) - - out.push(finding) + out.push(this.constants.getHugeWithdrawalsFromL2Alert({ + period, withdrawalsSum, l2BlockNumber: l2Block.number + })) this.lastReportedTooManyWithdrawalsTimestamp = l2Block.timestamp diff --git a/l2-bridges/mantle/src/agent.ts b/l2-bridges/mantle/src/agent.ts index 5c5b13fd..413a64e6 100644 --- a/l2-bridges/mantle/src/agent.ts +++ b/l2-bridges/mantle/src/agent.ts @@ -2,16 +2,16 @@ import { FindingSeverity, FindingType } from 'forta-agent' import { App } from '../../common/agent' import { Result } from '@ethersproject/abi/lib' import { EventOfNotice } from '../../common/entity/events' -import { Constants, RoleHashToName, ContractInfo, DEFAULT_ROLES_MAP } from '../../common/constants' - +import { Constants, RoleHashToName, ContractInfo, DEFAULT_ROLES_MAP, getHugeWithdrawalsFromL2AlertFactory } from '../../common/constants' +const L2_NAME = 'Mantle' export const mantleConstants: Constants = { - L2_NAME: 'Mantle', + L2_NAME: L2_NAME, L2_NETWORK_RPC: 'https://rpc.mantle.xyz', MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST: 10_000, L2_NETWORK_ID: 5000, L2_APPROX_BLOCK_TIME_SECONDS: 2, - L2_PROXY_ADMIN_CONTRACT_ADDRESS: '0x8e34d07eb348716a1f0a48a507a9de8a3a6dce45', // TODO + L2_PROXY_ADMIN_CONTRACT_ADDRESS: '0x8e34d07eb348716a1f0a48a507a9de8a3a6dce45', govExecutor: '0x3a7b055bf88cdc59d20d0245809c6e6b3c5819dd', L1_ERC20_TOKEN_GATEWAY_ADDRESS: '0x2D001d79E5aF5F65a939781FE228B267a8Ed468B', L2_ERC20_TOKEN_GATEWAY: { @@ -38,6 +38,9 @@ export const mantleConstants: Constants = { bridgeEvents: [], govEvents: [], proxyAdminEvents: [], + getHugeWithdrawalsFromL2Alert: getHugeWithdrawalsFromL2AlertFactory( + L2_NAME, `51F04709-7E86-4FB3-B53C-24C53C99DA24` + ), } mantleConstants.bridgeEvents = getBridgeEvents(mantleConstants.L2_ERC20_TOKEN_GATEWAY.address, mantleConstants.rolesMap); mantleConstants.govEvents = getGovEvents(mantleConstants.govExecutor as string); diff --git a/l2-bridges/scroll/src/agent.ts b/l2-bridges/scroll/src/agent.ts index 2506e6a4..9a505a72 100644 --- a/l2-bridges/scroll/src/agent.ts +++ b/l2-bridges/scroll/src/agent.ts @@ -2,11 +2,12 @@ import { FindingSeverity, FindingType } from 'forta-agent' import { App } from '../../common/agent' import { Result } from '@ethersproject/abi/lib' import { EventOfNotice } from '../../common/entity/events' -import { Constants, RoleHashToName, ContractInfo, DEFAULT_ROLES_MAP } from '../../common/constants' +import { Constants, RoleHashToName, ContractInfo, DEFAULT_ROLES_MAP, getHugeWithdrawalsFromL2AlertFactory } from '../../common/constants' +const L2_NAME = 'Scroll' export const scrollConstants: Constants = { - L2_NAME: 'Scroll', + L2_NAME: L2_NAME, L2_NETWORK_RPC: 'https://rpc.scroll.io', MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST: 1_000, L2_NETWORK_ID: 534352, @@ -38,6 +39,9 @@ export const scrollConstants: Constants = { bridgeEvents: [], govEvents: [], proxyAdminEvents: [], + getHugeWithdrawalsFromL2Alert: getHugeWithdrawalsFromL2AlertFactory( + L2_NAME, `E1F5563E-C44D-4ACA-825E-F5A771B9D8C0` + ) } scrollConstants.bridgeEvents = getBridgeEvents(scrollConstants.L2_ERC20_TOKEN_GATEWAY.address, scrollConstants.rolesMap); scrollConstants.govEvents = getGovEvents(scrollConstants.govExecutor as string); From 204d2b28c2dfd29aca889ddda7a3ee2e3f4050cc Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Fri, 26 Jul 2024 21:43:08 +0300 Subject: [PATCH 11/18] feat(l2 unified): add mvp for ZkSync --- l2-bridges/package.json | 3 + l2-bridges/zksync/package.json | 27 ++ l2-bridges/zksync/src/agent.ts | 407 ++++++++++++++++++ .../test/monitor_withdrawals.zksync.spec.ts | 36 ++ l2-bridges/zksync/tsconfig.json | 6 + 5 files changed, 479 insertions(+) create mode 100644 l2-bridges/zksync/package.json create mode 100644 l2-bridges/zksync/src/agent.ts create mode 100644 l2-bridges/zksync/test/monitor_withdrawals.zksync.spec.ts create mode 100644 l2-bridges/zksync/tsconfig.json diff --git a/l2-bridges/package.json b/l2-bridges/package.json index 2356e042..c37541ff 100644 --- a/l2-bridges/package.json +++ b/l2-bridges/package.json @@ -9,6 +9,9 @@ "start:mantle": "yarn start mantle dev", "push:mantle": "yarn update-version && node push-agent.js mantle", + "start:zksync": "yarn start zksync dev", + "push:zksync": "yarn update-version && node push-agent.js zksync", + "test": "jest", "postinstall": "yarn generate-types", diff --git a/l2-bridges/zksync/package.json b/l2-bridges/zksync/package.json new file mode 100644 index 00000000..0cdf7d9a --- /dev/null +++ b/l2-bridges/zksync/package.json @@ -0,0 +1,27 @@ +{ + "name": "lido-l2-bridge-zksync-bot", + "displayName": "Forta Agent Starter", + "version": "0.0.1", + "description": "Lido Detection Bot for ZkSync part of L2 bridge", + "repository": { + "type": "git", + "directory": "https://github.com/lidofinance/alerting-forta/tree/main/l2-bridges" + }, + "license": "MIT", + "chainIds": [ + 1 + ], + "chainSettings": { + "default": { + "shards": 1, + "target": 5 + } + }, + "scripts": { + }, + "dependencies": { + }, + "devDependencies": { + }, + "packageManager": "yarn@1.22.21" +} diff --git a/l2-bridges/zksync/src/agent.ts b/l2-bridges/zksync/src/agent.ts new file mode 100644 index 00000000..3564e6f2 --- /dev/null +++ b/l2-bridges/zksync/src/agent.ts @@ -0,0 +1,407 @@ +import { FindingSeverity, FindingType } from 'forta-agent' +import { App } from '../../common/agent' +import { Result } from '@ethersproject/abi/lib' +import { EventOfNotice } from '../../common/entity/events' +import { Constants, RoleHashToName, DEFAULT_ROLES_MAP, TransparentProxyInfo, getHugeWithdrawalsFromL2AlertFactory } from '../../common/constants' + +const L2_NAME = 'ZkSync' +const L2_PROXY_ADMIN_CONTRACT_ADDRESS = '0xbd80e505ecc49bae2cc86094a78fa0e2db28b52a'; +export const zksyncConstants: Constants = { + L2_NAME: L2_NAME, + L2_NETWORK_RPC: 'https://mainnet.era.zksync.io', + MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST: 50_000, + L2_NETWORK_ID: 324, + L2_APPROX_BLOCK_TIME_SECONDS: 1, // see info at https://zksync.blockscout.com/ + L2_PROXY_ADMIN_CONTRACT_ADDRESS, + govExecutor: { + name: 'ZKSYNC_GOV_EXECUTOR', + address: '0x139ee25dcad405d2a038e7a67f9ffdbf0f573f3c', + proxyAdminAddress: L2_PROXY_ADMIN_CONTRACT_ADDRESS, + }, + L1_ERC20_TOKEN_GATEWAY_ADDRESS: '0x41527b2d03844db6b0945f25702cb958b6d55989', + L2_ERC20_TOKEN_GATEWAY: { + name: 'L2_ERC20_TOKEN_GATEWAY', + address: '0xe1d6a50e7101c8f8db77352897ee3f1ac53f782b', + }, + L2_WSTETH_BRIDGED: { + name: 'ZKSYNC_WSTETH_BRIDGED', + address: '0x703b52F2b28fEbcB60E1372858AF5b18849FE867', + proxyAdminAddress: L2_PROXY_ADMIN_CONTRACT_ADDRESS, + }, + rolesMap: DEFAULT_ROLES_MAP, + withdrawalInfo: { + eventName: 'WithdrawalInitiated', + eventDefinition: `event WithdrawalInitiated( + address indexed l2Sender, + address indexed l1Receiver, + address indexed l2Token, + uint256 amount +);`, + amountFieldName: "amount", + }, + bridgeEvents: [], + govEvents: [], + proxyAdminEvents: [], + getHugeWithdrawalsFromL2Alert: getHugeWithdrawalsFromL2AlertFactory( + L2_NAME, `C167F276-D519-4906-90CB-C4455E9ABBD4` + ) +} +zksyncConstants.bridgeEvents = getBridgeEvents(zksyncConstants.L2_ERC20_TOKEN_GATEWAY.address, zksyncConstants.rolesMap); +zksyncConstants.govEvents = getGovEvents((zksyncConstants.govExecutor as TransparentProxyInfo).address); +zksyncConstants.proxyAdminEvents = getProxyAdminEvents( + zksyncConstants.L2_WSTETH_BRIDGED as TransparentProxyInfo, + zksyncConstants.govExecutor as TransparentProxyInfo, +); + + +function getBridgeEvents( + l2TokenBridgeAddress: string, + rolesAddrToNameMap: RoleHashToName, +): EventOfNotice[] { + const uniqueKeys = [ + 'f760df18-5dfc-4237-a752-b5654a123c48', + 'e0e57c09-17f0-4e37-8308-1d2e666d7fdb', + 'd1b7de38-d0b1-4b52-996a-ca4b32b4af2d', + '36a0ce4b-0d5d-42b0-a40b-cdf8275cc8b4', + 'bf4843ce-ee15-4e6e-9d8c-c7c6507ce908', + 'a5f4581f-5059-4d1c-b77d-85546b7fb464', + 'e20e7508-7df1-4c5d-964d-b3364cc2465e', + '9600882e-bbda-4d11-a50a-345f96e13d11', + ] + + return [ + { + address: l2TokenBridgeAddress, + event: + 'event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole)', + alertId: 'L2-BRIDGE-ROLE-ADMIN-CHANGED', + name: '🚨 ZkSync L2 Bridge: Role Admin changed', + description: (args: Result) => + `Role Admin for role ${args.role}(${rolesAddrToNameMap.get(args.role) || 'unknown'}) ` + + `was changed from ${args.previousAdminRole} to ${args.newAdminRole}`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[0], + }, + { + address: l2TokenBridgeAddress, + event: 'event WithdrawalsDisabled(address indexed disabler)', + alertId: 'L2-BRIDGE-WITHDRAWALS-DISABLED', + name: '🚨 ZkSync L2 Bridge: Withdrawals Disabled', + description: (args: Result) => `Withdrawals were disabled by ${args.enabler}`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[6], + }, + { + address: l2TokenBridgeAddress, + event: 'event Initialized(address indexed admin)', + alertId: 'L2-BRIDGE-IMPLEMENTATION-INITIALIZED', + name: '🚨 ZkSync L2 Bridge: Implementation initialized', + description: (args: Result) => + `Implementation of the ZkSync L2 Bridge was initialized by ${args.admin}\n` + + `NOTE: This is not the thing that should be left unacted! ` + + `Make sure that this call was made by Lido!`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[7], + }, + { + address: l2TokenBridgeAddress, + event: 'event DepositsDisabled(address indexed disabler)', + alertId: 'L2-BRIDGE-DEPOSITS-DISABLED', + name: '🚨 ZkSync L2 Bridge: Deposits Disabled', + description: (args: Result) => `Deposits were disabled by ${args.disabler}`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[4], + }, + { + address: l2TokenBridgeAddress, + event: 'event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender)', + alertId: 'L2-BRIDGE-ROLE-GRANTED', + name: '⚠️ ZkSync L2 Bridge: Role granted', + description: (args: Result) => + `Role ${args.role}(${rolesAddrToNameMap.get(args.role) || 'unknown'}) ` + + `was granted to ${args.account} by ${args.sender}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: uniqueKeys[1], + }, + { + address: l2TokenBridgeAddress, + event: 'event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender)', + alertId: 'L2-BRIDGE-ROLE-REVOKED', + name: '⚠️ ZkSync L2 Bridge: Role revoked', + description: (args: Result) => + `Role ${args.role}(${rolesAddrToNameMap.get(args.role) || 'unknown'}) ` + + `was revoked to ${args.account} by ${args.sender}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: uniqueKeys[2], + }, + { + address: l2TokenBridgeAddress, + event: 'event DepositsEnabled(address indexed enabler)', + alertId: 'L2-BRIDGE-DEPOSITS-ENABLED', + name: 'ℹ️ ZkSync L2 Bridge: Deposits Enabled', + description: (args: Result) => `Deposits were enabled by ${args.enabler}`, + severity: FindingSeverity.Info, + type: FindingType.Info, + uniqueKey: uniqueKeys[3], + }, + { + address: l2TokenBridgeAddress, + event: 'event WithdrawalsEnabled(address indexed enabler)', + alertId: 'L2-BRIDGE-WITHDRAWALS-ENABLED', + name: 'ℹ️ ZkSync L2 Bridge: Withdrawals Enabled', + description: (args: Result) => `Withdrawals were enabled by ${args.enabler}`, + severity: FindingSeverity.Info, + type: FindingType.Info, + uniqueKey: uniqueKeys[5], + }, + ] +} + + +function getGovEvents(zksyncGovExecutorAddress: string): EventOfNotice[] { + const uniqueKeys = [ + '0a4af7aa-698e-47b8-adff-ed17cb85c8d4', + '35c68be6-1704-4e39-b3ee-dd83eb1da978', + '798b6597-ed63-46d3-9bbc-52422ae7ca15', + 'a87a0cde-b07b-4453-9bb1-9460cc5c2bad', + '6600c65d-b062-433d-875d-d1e8f16fd774', + 'ed187898-178d-44e0-8dda-3953877fe7fc', + '1050404a-f321-4c8f-b6a3-496a4acb4408', + '629d7b0a-0c29-46f3-a1eb-063e16863586', + '0970504a-6836-4669-b8e4-f4b4139299d4', + ] + + return [ + { + address: zksyncGovExecutorAddress, + event: + 'event EthereumGovernanceExecutorUpdate(address oldEthereumGovernanceExecutor, address newEthereumGovernanceExecutor)', + alertId: 'GOV-BRIDGE-EXEC-UPDATED', + name: '🚨 ZkSync Gov Bridge: Ethereum Governance Executor Updated', + description: (args: Result) => + `Ethereum Governance Executor was updated from ` + + `${args.oldEthereumGovernanceExecutor} to ${args.newEthereumGovernanceExecutor}`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[0], + }, + { + address: zksyncGovExecutorAddress, + event: 'event GuardianUpdate(address oldGuardian, address newGuardian)', + alertId: 'GOV-BRIDGE-GUARDIAN-UPDATED', + name: '🚨 ZkSync Gov Bridge: Guardian Updated', + description: (args: Result) => `Guardian was updated from ` + `${args.oldGuardian} to ${args.newGuardian}`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[1], + }, + { + address: zksyncGovExecutorAddress, + event: 'event DelayUpdate(uint256 oldDelay, uint256 newDelay)', + alertId: 'GOV-BRIDGE-DELAY-UPDATED', + name: '⚠️ ZkSync Gov Bridge: Delay Updated', + description: (args: Result) => `Delay was updated from ` + `${args.oldDelay} to ${args.newDelay}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: uniqueKeys[2], + }, + { + address: zksyncGovExecutorAddress, + event: 'event GracePeriodUpdate(uint256 oldGracePeriod, uint256 newGracePeriod)', + alertId: 'GOV-BRIDGE-GRACE-PERIOD-UPDATED', + name: '⚠️ ZkSync Gov Bridge: Grace Period Updated', + description: (args: Result) => + `Grace Period was updated from ` + `${args.oldGracePeriod} to ${args.newGracePeriod}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: uniqueKeys[3], + }, + { + address: zksyncGovExecutorAddress, + event: 'event MinimumDelayUpdate(uint256 oldMinimumDelay, uint256 newMinimumDelay)', + alertId: 'GOV-BRIDGE-MIN-DELAY-UPDATED', + name: '⚠️ ZkSync Gov Bridge: Min Delay Updated', + description: (args: Result) => + `Min Delay was updated from ` + `${args.oldMinimumDelay} to ${args.newMinimumDelay}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: uniqueKeys[4], + }, + { + address: zksyncGovExecutorAddress, + event: 'event MaximumDelayUpdate(uint256 oldMaximumDelay, uint256 newMaximumDelay)', + alertId: 'GOV-BRIDGE-MAX-DELAY-UPDATED', + name: '⚠️ ZkSync Gov Bridge: Max Delay Updated', + description: (args: Result) => + `Max Delay was updated from ` + `${args.oldMaximumDelay} to ${args.newMaximumDelay}`, + severity: FindingSeverity.Medium, + type: FindingType.Info, + uniqueKey: uniqueKeys[5], + }, + { + address: zksyncGovExecutorAddress, + event: + 'event ActionsSetQueued(uint256 indexed id, address[] targets, uint256[] values, string[] signatures, bytes[] calldatas, bool[] withDelegatecalls, uint256 executionTime)', + alertId: 'GOV-BRIDGE-ACTION-SET-QUEUED', + name: 'ℹ️ ZkSync Gov Bridge: Action set queued', + description: (args: Result) => `Action set ${args.id} was queued`, + severity: FindingSeverity.Info, + type: FindingType.Info, + uniqueKey: uniqueKeys[6], + }, + { + address: zksyncGovExecutorAddress, + event: 'event ActionsSetExecuted(uint256 indexed id, address indexed initiatorExecution, bytes[] returnedData)', + alertId: 'GOV-BRIDGE-ACTION-SET-EXECUTED', + name: 'ℹ️ ZkSync Gov Bridge: Action set executed', + description: (args: Result) => `Action set ${args.id} was executed`, + severity: FindingSeverity.Info, + type: FindingType.Info, + uniqueKey: uniqueKeys[7], + }, + { + address: zksyncGovExecutorAddress, + event: 'event ActionsSetCanceled(uint256 indexed id)', + alertId: 'GOV-BRIDGE-ACTION-SET-CANCELED', + name: 'ℹ️ ZkSync Gov Bridge: Action set canceled', + description: (args: Result) => `Action set ${args.id} was canceled`, + severity: FindingSeverity.Info, + type: FindingType.Info, + uniqueKey: uniqueKeys[8], + }, + ] +} + +function getProxyAdminEvents( + zksyncWstethBridged: TransparentProxyInfo, + zksyncBridgeExecutor: TransparentProxyInfo, +): EventOfNotice[] { + const uniqueKeys = [ + 'f9e87d52-9ac5-4f26-8dbb-a2f56c5f06bb', + '44e4e424-f0ca-41dc-96db-26615f048126', + 'd02105a0-a7e7-4347-84dc-d3a67a632b33', + 'c3dff9f7-0b43-400d-a7aa-c081a9c6291d', + 'b950d684-7b89-4cde-af08-f2906d3b0ac9', + 'ca7d2108-fece-41fd-a262-e6959442fb48', + '200859ec-c205-44b3-ab55-2939b77a5c05', + '70377bd2-5047-42a2-9afa-e7222096808e', + ] + + return [ + { + address: zksyncWstethBridged.address, + event: 'event AdminChanged(address previousAdmin, address newAdmin)', + alertId: 'PROXY-ADMIN-CHANGED', + name: '🚨 ZkSync: Proxy admin changed', + description: (args: Result) => + `Proxy admin for ${zksyncWstethBridged.name}(${zksyncWstethBridged.proxyAdminAddress}) ` + + `was changed from ${args.previousAdmin} to ${args.newAdmin}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[0], + }, + { + address: zksyncWstethBridged.address, + event: 'event Upgraded(address indexed implementation)', + alertId: 'PROXY-UPGRADED', + name: '🚨 ZkSync: Proxy upgraded', + description: (args: Result) => + `Proxy for ${zksyncWstethBridged.name}(${zksyncWstethBridged.proxyAdminAddress}) ` + + `was updated to ${args.implementation}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[1], + }, + { + address: zksyncWstethBridged.address, + event: 'event BeaconUpgraded(address indexed beacon)', + alertId: 'PROXY-BEACON-UPGRADED', + name: '🚨 ZkSync: Proxy beacon upgraded', + description: (args: Result) => + `Proxy for ${zksyncWstethBridged.name}(${zksyncWstethBridged.proxyAdminAddress}) ` + + `beacon was updated to ${args.beacon}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[2], + }, + { + address: zksyncWstethBridged.address, + event: 'event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)', + alertId: 'PROXY-OWNER-TRANSFERRED', + name: '🚨 ZkSync: Proxy owner transferred', + description: (args: Result) => + `Proxy owner for ${zksyncWstethBridged.name}(${zksyncWstethBridged.proxyAdminAddress}) ` + + `was changed to ${args.newOwner}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[3], + }, + { + address: zksyncBridgeExecutor.address, + event: 'event AdminChanged(address previousAdmin, address newAdmin)', + alertId: 'PROXY-ADMIN-CHANGED', + name: '🚨 ZkSync: Proxy admin changed', + description: (args: Result) => + `Proxy admin for ${zksyncBridgeExecutor.name}(${zksyncBridgeExecutor.proxyAdminAddress}) ` + + `was changed from ${args.previousAdmin} to ${args.newAdmin}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[4], + }, + { + address: zksyncBridgeExecutor.address, + event: 'event Upgraded(address indexed implementation)', + alertId: 'PROXY-UPGRADED', + name: '🚨 ZkSync: Proxy upgraded', + description: (args: Result) => + `Proxy for ${zksyncBridgeExecutor.name}(${zksyncBridgeExecutor.proxyAdminAddress}) ` + + `was updated to ${args.implementation}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[5], + }, + { + address: zksyncBridgeExecutor.address, + event: 'event BeaconUpgraded(address indexed beacon)', + alertId: 'PROXY-BEACON-UPGRADED', + name: '🚨 ZkSync: Proxy beacon upgraded', + description: (args: Result) => + `Proxy for ${zksyncBridgeExecutor.name}(${zksyncBridgeExecutor.proxyAdminAddress}) ` + + `beacon was updated to ${args.beacon}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[6], + }, + { + address: zksyncBridgeExecutor.address, + event: 'event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)', + alertId: 'PROXY-OWNER-TRANSFERRED', + name: '🚨 ZkSync: Proxy owner transferred', + description: (args: Result) => + `Proxy owner for ${zksyncBridgeExecutor.name}(${zksyncBridgeExecutor.proxyAdminAddress}) ` + + `was changed to ${args.newOwner}` + + `\n(detected by event)`, + severity: FindingSeverity.High, + type: FindingType.Info, + uniqueKey: uniqueKeys[7], + }, + ] +} + +export default { + initialize: App.initialize(zksyncConstants), + handleBlock: App.handleBlock, +} diff --git a/l2-bridges/zksync/test/monitor_withdrawals.zksync.spec.ts b/l2-bridges/zksync/test/monitor_withdrawals.zksync.spec.ts new file mode 100644 index 00000000..32305a49 --- /dev/null +++ b/l2-bridges/zksync/test/monitor_withdrawals.zksync.spec.ts @@ -0,0 +1,36 @@ +import { MonitorWithdrawals } from '../../common/services/monitor_withdrawals' +import * as E from 'fp-ts/Either' +import { ethers } from 'forta-agent' +import { expect } from '@jest/globals' +import * as Winston from 'winston' +import { ERC20Short__factory } from '../../common/generated' +import { zksyncConstants } from '../src/agent' +import { L2Client } from '../../common/clients/l2_client' +import assert from 'assert' + +const SECOND = 1000 // ms + + +describe('MonitorWithdrawals', () => { + const logger = Winston.createLogger({ + format: Winston.format.simple(), + transports: [new Winston.transports.Console()], + }) + + const nodeClient = new ethers.providers.JsonRpcProvider(zksyncConstants.L2_NETWORK_RPC, zksyncConstants.L2_NETWORK_ID) + const bridgedWstethRunner = ERC20Short__factory.connect(zksyncConstants.L2_WSTETH_BRIDGED.address, nodeClient) + + const l2Client = new L2Client(nodeClient, logger, bridgedWstethRunner, zksyncConstants.MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST) + + const monitorWithdrawals = new MonitorWithdrawals( + l2Client, zksyncConstants.L2_ERC20_TOKEN_GATEWAY.address, logger, zksyncConstants.withdrawalInfo, + zksyncConstants.L2_APPROX_BLOCK_TIME_SECONDS + ) + + test(`getWithdrawalRecordsInBlockRange: 2 withdrawals (225_821 blocks)`, async () => { + const withdrawalRecords = await monitorWithdrawals._getWithdrawalRecordsInBlockRange(39424179, 39650000) + assert(E.isRight(withdrawalRecords)) + expect(withdrawalRecords.right).toHaveLength(2) + }, 20 * SECOND) + +}) diff --git a/l2-bridges/zksync/tsconfig.json b/l2-bridges/zksync/tsconfig.json new file mode 100644 index 00000000..da7b0c6f --- /dev/null +++ b/l2-bridges/zksync/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../dist/zksync/src" + } +} From 3df3ecf3acdd6cd0aac3c295f107157a73a7dcae Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Fri, 26 Jul 2024 21:47:18 +0300 Subject: [PATCH 12/18] test: fix build of monitor_withdrawal tests --- .../mantle/test/monitor_withdrawals.mantle.spec.ts | 5 +---- .../scroll/test/monitor_withdrawals.scroll.spec.ts | 12 +----------- .../zksync/test/monitor_withdrawals.zksync.spec.ts | 5 +---- 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts b/l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts index 0fef674a..a95a91f6 100644 --- a/l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts +++ b/l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts @@ -21,10 +21,7 @@ describe('MonitorWithdrawals', () => { const nodeClient = new ethers.providers.JsonRpcProvider(mantleConstants.L2_NETWORK_RPC, mantleConstants.L2_NETWORK_ID) const bridgedWstethRunner = ERC20Short__factory.connect(mantleConstants.L2_WSTETH_BRIDGED.address, nodeClient) const l2Client = new L2Client(nodeClient, logger, bridgedWstethRunner, mantleConstants.MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST) - const monitorWithdrawals = new MonitorWithdrawals( - l2Client, mantleConstants.L2_ERC20_TOKEN_GATEWAY.address, logger, mantleConstants.withdrawalInfo, - mantleConstants.L2_APPROX_BLOCK_TIME_SECONDS - ) + const monitorWithdrawals = new MonitorWithdrawals(l2Client, logger, mantleConstants) test(`getWithdrawalRecordsInBlockRange: 2 withdrawals, 1_023_599 blocks`, async () => { const withdrawalRecords = await monitorWithdrawals._getWithdrawalRecordsInBlockRange(64995881, 66019480) diff --git a/l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts b/l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts index c2c089ac..728e32aa 100644 --- a/l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts +++ b/l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts @@ -23,10 +23,7 @@ describe('MonitorWithdrawals', () => { const l2Client = new L2Client(nodeClient, logger, bridgedWstethRunner, scrollConstants.MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST) - const monitorWithdrawals = new MonitorWithdrawals( - l2Client, scrollConstants.L2_ERC20_TOKEN_GATEWAY.address, logger, scrollConstants.withdrawalInfo, - scrollConstants.L2_APPROX_BLOCK_TIME_SECONDS - ) + const monitorWithdrawals = new MonitorWithdrawals(l2Client, logger, scrollConstants) test(`getWithdrawalRecordsInBlockRange: 3 withdrawals, 3889 blocks`, async () => { const withdrawalRecords = await monitorWithdrawals._getWithdrawalRecordsInBlockRange(6608787, 6612676) @@ -52,11 +49,4 @@ describe('MonitorWithdrawals', () => { assert(E.isRight(withdrawalRecords)) expect(withdrawalRecords.right).toHaveLength(25) }, 40 * SECOND) - - xtest(`getWithdrawalRecordsInBlockRange for 1_000_000 blocks`, async () => { - const endBlock = 6_633_338 - const startBlock = endBlock - 1_000_000 - const withdrawalRecords = await monitorWithdrawals._getWithdrawalRecordsInBlockRange(startBlock, endBlock) - assert(E.isRight(withdrawalRecords)) - }, 20 * SECOND) }) diff --git a/l2-bridges/zksync/test/monitor_withdrawals.zksync.spec.ts b/l2-bridges/zksync/test/monitor_withdrawals.zksync.spec.ts index 32305a49..7ce65e55 100644 --- a/l2-bridges/zksync/test/monitor_withdrawals.zksync.spec.ts +++ b/l2-bridges/zksync/test/monitor_withdrawals.zksync.spec.ts @@ -22,10 +22,7 @@ describe('MonitorWithdrawals', () => { const l2Client = new L2Client(nodeClient, logger, bridgedWstethRunner, zksyncConstants.MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST) - const monitorWithdrawals = new MonitorWithdrawals( - l2Client, zksyncConstants.L2_ERC20_TOKEN_GATEWAY.address, logger, zksyncConstants.withdrawalInfo, - zksyncConstants.L2_APPROX_BLOCK_TIME_SECONDS - ) + const monitorWithdrawals = new MonitorWithdrawals(l2Client, logger, zksyncConstants) test(`getWithdrawalRecordsInBlockRange: 2 withdrawals (225_821 blocks)`, async () => { const withdrawalRecords = await monitorWithdrawals._getWithdrawalRecordsInBlockRange(39424179, 39650000) From fd71e27f83eaa902724a58ac9c56281c56ded3c7 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Sat, 27 Jul 2024 14:33:48 +0300 Subject: [PATCH 13/18] test: add setup for running unit tests on test fork node --- l2-bridges/common/utils/test.helpers.ts | 54 +++++++++++++++++++ l2-bridges/common/utils/time.ts | 2 + .../test/monitor_withdrawals.mantle.spec.ts | 31 +++++------ l2-bridges/package.json | 2 +- .../test/monitor_withdrawals.scroll.spec.ts | 40 +++++++------- .../test/monitor_withdrawals.zksync.spec.ts | 34 ++++++------ 6 files changed, 109 insertions(+), 54 deletions(-) create mode 100644 l2-bridges/common/utils/test.helpers.ts diff --git a/l2-bridges/common/utils/test.helpers.ts b/l2-bridges/common/utils/test.helpers.ts new file mode 100644 index 00000000..0031cdf1 --- /dev/null +++ b/l2-bridges/common/utils/test.helpers.ts @@ -0,0 +1,54 @@ +const { spawn } = require('node:child_process') +import assert from 'assert' +import { SECOND_MS } from './time' +import { Constants } from '../constants'; +import { ethers } from 'forta-agent' +import * as Winston from 'winston' +import { ERC20Short__factory } from '../generated' +import { L2Client } from '../clients/l2_client' +import { MonitorWithdrawals } from '../services/monitor_withdrawals' + +let TEST_NODE_PORT = 8577 + + +export async function spawnTestNode(networkId: number, l2RpcUrl: string) { + const nodeCmd = 'anvil' + const nodeArgs = [ + '-p', `${TEST_NODE_PORT}`, + '--chain', `${networkId}`, + '--auto-impersonate', + '--fork-url', l2RpcUrl, + ] + const nodeProcess = spawn(nodeCmd, nodeArgs); + + nodeProcess.stderr.on('data', (data: unknown) => { + console.error(`stderr: ${data}`); + }); + + await new Promise((r) => setTimeout(r, SECOND_MS)); + + assert(nodeProcess); + assert(nodeProcess.exitCode === null); + + console.log(`Spawned test node: ${nodeCmd} ${nodeArgs.join(' ')}`) + return { nodeProcess, rpcUrl: `http://localhost:${TEST_NODE_PORT}` } +} + +export async function stopTestNode(nodeProcess: any) { + assert(nodeProcess) + nodeProcess.kill('SIGINT') + await new Promise((r) => setTimeout(r, SECOND_MS)) + assert(nodeProcess.exitCode === 0) +} + +export function createMonitorWithdrawals(params: Constants) { + const logger = Winston.createLogger({ + format: Winston.format.simple(), + transports: [new Winston.transports.Console()], + }) + + const nodeClient = new ethers.providers.JsonRpcProvider(params.L2_NETWORK_RPC, params.L2_NETWORK_ID) + const bridgedWstethRunner = ERC20Short__factory.connect(params.L2_WSTETH_BRIDGED.address, nodeClient) + const l2Client = new L2Client(nodeClient, logger, bridgedWstethRunner, params.MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST) + return new MonitorWithdrawals(l2Client, logger, params) +} diff --git a/l2-bridges/common/utils/time.ts b/l2-bridges/common/utils/time.ts index acd3cb72..52a3491a 100644 --- a/l2-bridges/common/utils/time.ts +++ b/l2-bridges/common/utils/time.ts @@ -1,3 +1,5 @@ +export const SECOND_MS = 1000 + export function formatTime(timeInMillis: number): string { const seconds = (timeInMillis / 1000).toFixed(3) return `${seconds} seconds` diff --git a/l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts b/l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts index a95a91f6..b0f18237 100644 --- a/l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts +++ b/l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts @@ -1,27 +1,24 @@ import { MonitorWithdrawals } from '../../common/services/monitor_withdrawals' import * as E from 'fp-ts/Either' -import { ethers } from 'forta-agent' import { expect } from '@jest/globals' import BigNumber from 'bignumber.js' -import * as Winston from 'winston' -import { ERC20Short__factory } from '../../common/generated' import { mantleConstants } from '../src/agent' -import { L2Client } from '../../common/clients/l2_client' +import { SECOND_MS } from '../../common/utils/time' import assert from 'assert' +import { spawnTestNode, stopTestNode, createMonitorWithdrawals } from '../../common/utils/test.helpers' -const SECOND = 1000 // ms +describe('MonitorWithdrawals on Mantle', () => { + let testNodeProcess: any = null + let monitorWithdrawals: MonitorWithdrawals = null as any -describe('MonitorWithdrawals', () => { - const logger = Winston.createLogger({ - format: Winston.format.simple(), - transports: [new Winston.transports.Console()], - }) + beforeAll(async () => { + const { nodeProcess, rpcUrl } = await spawnTestNode(mantleConstants.L2_NETWORK_ID, mantleConstants.L2_NETWORK_RPC) + testNodeProcess = nodeProcess + mantleConstants.L2_NETWORK_RPC = rpcUrl - const nodeClient = new ethers.providers.JsonRpcProvider(mantleConstants.L2_NETWORK_RPC, mantleConstants.L2_NETWORK_ID) - const bridgedWstethRunner = ERC20Short__factory.connect(mantleConstants.L2_WSTETH_BRIDGED.address, nodeClient) - const l2Client = new L2Client(nodeClient, logger, bridgedWstethRunner, mantleConstants.MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST) - const monitorWithdrawals = new MonitorWithdrawals(l2Client, logger, mantleConstants) + monitorWithdrawals = createMonitorWithdrawals(mantleConstants) + }); test(`getWithdrawalRecordsInBlockRange: 2 withdrawals, 1_023_599 blocks`, async () => { const withdrawalRecords = await monitorWithdrawals._getWithdrawalRecordsInBlockRange(64995881, 66019480) @@ -36,5 +33,9 @@ describe('MonitorWithdrawals', () => { amount: new BigNumber('2107828861318592904'), }, ]) - }, 20 * SECOND) + }, 20 * SECOND_MS) + + afterAll(async () => { + await stopTestNode(testNodeProcess) + }); }) diff --git a/l2-bridges/package.json b/l2-bridges/package.json index c37541ff..94a357f2 100644 --- a/l2-bridges/package.json +++ b/l2-bridges/package.json @@ -12,7 +12,7 @@ "start:zksync": "yarn start zksync dev", "push:zksync": "yarn update-version && node push-agent.js zksync", - "test": "jest", + "test": "jest --runInBand", "postinstall": "yarn generate-types", "generate-types": "typechain --target=ethers-v5 --out-dir=./common/generated ./common/abi/*", diff --git a/l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts b/l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts index 728e32aa..6f75e964 100644 --- a/l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts +++ b/l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts @@ -1,29 +1,25 @@ -import { MonitorWithdrawals } from '../../common/services/monitor_withdrawals' import * as E from 'fp-ts/Either' -import { ethers } from 'forta-agent' +import { MonitorWithdrawals } from '../../common/services/monitor_withdrawals' +import assert from 'assert' import { expect } from '@jest/globals' import BigNumber from 'bignumber.js' -import * as Winston from 'winston' -import { ERC20Short__factory } from '../../common/generated' -import { scrollConstants } from '../src/agent' -import { L2Client } from '../../common/clients/l2_client' -import assert from 'assert' - -const SECOND = 1000 // ms +import { scrollConstants } from '../src/agent' +import { spawnTestNode, stopTestNode, createMonitorWithdrawals } from '../../common/utils/test.helpers' +import { SECOND_MS } from '../../common/utils/time' -describe('MonitorWithdrawals', () => { - const logger = Winston.createLogger({ - format: Winston.format.simple(), - transports: [new Winston.transports.Console()], - }) - const nodeClient = new ethers.providers.JsonRpcProvider(scrollConstants.L2_NETWORK_RPC, scrollConstants.L2_NETWORK_ID) - const bridgedWstethRunner = ERC20Short__factory.connect(scrollConstants.L2_WSTETH_BRIDGED.address, nodeClient) +describe('MonitorWithdrawals on Scroll', () => { + let testNodeProcess: any = null + let monitorWithdrawals: MonitorWithdrawals = null as any - const l2Client = new L2Client(nodeClient, logger, bridgedWstethRunner, scrollConstants.MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST) + beforeAll(async () => { + const { nodeProcess, rpcUrl } = await spawnTestNode(scrollConstants.L2_NETWORK_ID, scrollConstants.L2_NETWORK_RPC) + testNodeProcess = nodeProcess + scrollConstants.L2_NETWORK_RPC = rpcUrl - const monitorWithdrawals = new MonitorWithdrawals(l2Client, logger, scrollConstants) + monitorWithdrawals = createMonitorWithdrawals(scrollConstants) + }); test(`getWithdrawalRecordsInBlockRange: 3 withdrawals, 3889 blocks`, async () => { const withdrawalRecords = await monitorWithdrawals._getWithdrawalRecordsInBlockRange(6608787, 6612676) @@ -42,11 +38,15 @@ describe('MonitorWithdrawals', () => { amount: new BigNumber('16222703281150365'), }, ]) - }, 20 * SECOND) + }, 20 * SECOND_MS) test(`getWithdrawalRecordsInBlockRange: 25 withdrawals (51_984 blocks)`, async () => { const withdrawalRecords = await monitorWithdrawals._getWithdrawalRecordsInBlockRange(6581354, 6633338) assert(E.isRight(withdrawalRecords)) expect(withdrawalRecords.right).toHaveLength(25) - }, 40 * SECOND) + }, 40 * SECOND_MS) + + afterAll(async () => { + await stopTestNode(testNodeProcess) + }); }) diff --git a/l2-bridges/zksync/test/monitor_withdrawals.zksync.spec.ts b/l2-bridges/zksync/test/monitor_withdrawals.zksync.spec.ts index 7ce65e55..62818a29 100644 --- a/l2-bridges/zksync/test/monitor_withdrawals.zksync.spec.ts +++ b/l2-bridges/zksync/test/monitor_withdrawals.zksync.spec.ts @@ -1,33 +1,31 @@ -import { MonitorWithdrawals } from '../../common/services/monitor_withdrawals' import * as E from 'fp-ts/Either' -import { ethers } from 'forta-agent' import { expect } from '@jest/globals' -import * as Winston from 'winston' -import { ERC20Short__factory } from '../../common/generated' import { zksyncConstants } from '../src/agent' -import { L2Client } from '../../common/clients/l2_client' +import { SECOND_MS } from '../../common/utils/time' +import { MonitorWithdrawals } from '../../common/services/monitor_withdrawals' +import { spawnTestNode, stopTestNode, createMonitorWithdrawals } from '../../common/utils/test.helpers' import assert from 'assert' -const SECOND = 1000 // ms - - -describe('MonitorWithdrawals', () => { - const logger = Winston.createLogger({ - format: Winston.format.simple(), - transports: [new Winston.transports.Console()], - }) - const nodeClient = new ethers.providers.JsonRpcProvider(zksyncConstants.L2_NETWORK_RPC, zksyncConstants.L2_NETWORK_ID) - const bridgedWstethRunner = ERC20Short__factory.connect(zksyncConstants.L2_WSTETH_BRIDGED.address, nodeClient) +describe('MonitorWithdrawals on ZkSync', () => { + let testNodeProcess: any = null + let monitorWithdrawals: MonitorWithdrawals = null as any - const l2Client = new L2Client(nodeClient, logger, bridgedWstethRunner, zksyncConstants.MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST) + beforeAll(async () => { + const { nodeProcess, rpcUrl } = await spawnTestNode(zksyncConstants.L2_NETWORK_ID, zksyncConstants.L2_NETWORK_RPC) + testNodeProcess = nodeProcess + zksyncConstants.L2_NETWORK_RPC = rpcUrl - const monitorWithdrawals = new MonitorWithdrawals(l2Client, logger, zksyncConstants) + monitorWithdrawals = createMonitorWithdrawals(zksyncConstants) + }); test(`getWithdrawalRecordsInBlockRange: 2 withdrawals (225_821 blocks)`, async () => { const withdrawalRecords = await monitorWithdrawals._getWithdrawalRecordsInBlockRange(39424179, 39650000) assert(E.isRight(withdrawalRecords)) expect(withdrawalRecords.right).toHaveLength(2) - }, 20 * SECOND) + }, 20 * SECOND_MS) + afterAll(async () => { + await stopTestNode(testNodeProcess) + }); }) From 35df6286c1225248f0da7f85ee33188ec7c0c923 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 29 Jul 2024 22:49:47 +0300 Subject: [PATCH 14/18] test: add mvp example test which simulates event condition on the fork --- l2-bridges/common/agent.ts | 10 +- l2-bridges/common/constants.ts | 10 +- .../common/services/monitor_withdrawals.ts | 38 ++++---- l2-bridges/common/utils/finding.helpers.ts | 9 ++ l2-bridges/common/utils/test.helpers.ts | 13 ++- l2-bridges/mantle/test/mantle.test.helpers.ts | 95 +++++++++++++++++++ .../test/monitor_withdrawals.mantle.spec.ts | 59 +++++++++++- .../test/monitor_withdrawals.scroll.spec.ts | 4 +- .../test/monitor_withdrawals.zksync.spec.ts | 5 +- 9 files changed, 208 insertions(+), 35 deletions(-) create mode 100644 l2-bridges/mantle/test/mantle.test.helpers.ts diff --git a/l2-bridges/common/agent.ts b/l2-bridges/common/agent.ts index e2593429..dfceaff4 100644 --- a/l2-bridges/common/agent.ts +++ b/l2-bridges/common/agent.ts @@ -6,6 +6,7 @@ import * as E from 'fp-ts/Either' import VERSION from './utils/version' import { elapsedTime } from './utils/time' import BigNumber from 'bignumber.js' +import { JsonRpcProvider } from '@ethersproject/providers' import { L2Client } from './clients/l2_client' import { EventWatcher } from './services/event_watcher' @@ -31,9 +32,11 @@ export type Container = { proxyEventWatcher: EventWatcher findingsRW: DataRW logger: Logger + provider: JsonRpcProvider, // healthChecker: HealthChecker } + export class App { private static instance: Container private static isHandleBlockRunning = false @@ -47,10 +50,10 @@ export class App { transports: [new Winston.transports.Console()], }) - const nodeClient = new ethers.providers.JsonRpcProvider(params.L2_NETWORK_RPC, params.L2_NETWORK_ID) - const bridgedWstethRunner = ERC20Short__factory.connect(params.L2_WSTETH_BRIDGED.address, nodeClient) + const provider = new ethers.providers.JsonRpcProvider(params.L2_NETWORK_RPC, params.L2_NETWORK_ID) + const bridgedWstethRunner = ERC20Short__factory.connect(params.L2_WSTETH_BRIDGED.address, provider) - const l2Client = new L2Client(nodeClient, logger, bridgedWstethRunner, params.MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST) + const l2Client = new L2Client(provider, logger, bridgedWstethRunner, params.MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST) const bridgeEventWatcher = new EventWatcher('BridgeEventWatcher', params.bridgeEvents, logger) const govEventWatcher = new EventWatcher('GovEventWatcher', params.govEvents, logger) @@ -77,6 +80,7 @@ export class App { proxyEventWatcher, findingsRW: new DataRW([]), logger, + provider, // healthChecker: new HealthChecker(BorderTime, MaxNumberErrorsPerBorderTime), } diff --git a/l2-bridges/common/constants.ts b/l2-bridges/common/constants.ts index 22b274a9..233fbf6f 100644 --- a/l2-bridges/common/constants.ts +++ b/l2-bridges/common/constants.ts @@ -2,9 +2,11 @@ import { FindingSeverity, FindingType, Finding } from 'forta-agent' import { EventOfNotice } from './entity/events' import { getUniqueKey } from './utils/finding.helpers' import BigNumber from 'bignumber.js' - +import { ethers } from 'ethers' +import { formatEther } from './utils/finding.helpers' export const ETH_DECIMALS = new BigNumber(10).pow(18) +export const ETH_DECIMALS2 = 10n ** 18n export const MAINNET_CHAIN_ID = 1 export const DRPC_URL = 'https://eth.drpc.org/' export const L1_WSTETH_ADDRESS = '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0' @@ -15,14 +17,16 @@ export const DEFAULT_ROLES_MAP: RoleHashToName = new Map([ ['0x94a954c0bc99227eddbc0715a62a7e1056ed8784cd719c2303b685683908857c', 'WITHDRAWALS_DISABLER_ROLE'], ['0x0000000000000000000000000000000000000000000000000000000000000000', 'DEFAULT_ADMIN_ROLE'], ]) +export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' export const getHugeWithdrawalsFromL2AlertFactory = (l2Name: string, uniqueKey: string) => { return (params: HugeWithdrawalsFromL2AlertParams) => { + ethers.utils.formatEther(params.withdrawalsSum) return Finding.fromObject({ name: `⚠️ ${l2Name}: Huge withdrawals during the last ` + `${Math.floor(params.period / (60 * 60))} hour(s)`, description: `There were withdrawals requests from L2 to L1 for the ` + - `${params.withdrawalsSum.div(ETH_DECIMALS).toFixed(4)} wstETH in total`, + `${formatEther(params.withdrawalsSum, 4)} wstETH in total`, alertId: 'HUGE-WITHDRAWALS-FROM-L2', severity: FindingSeverity.Medium, type: FindingType.Suspicious, @@ -53,7 +57,7 @@ export type WithdrawalInfo = { export type HugeWithdrawalsFromL2AlertParams = { period: number, l2BlockNumber: number, - withdrawalsSum: BigNumber, + withdrawalsSum: bigint, } export type Constants = { diff --git a/l2-bridges/common/services/monitor_withdrawals.ts b/l2-bridges/common/services/monitor_withdrawals.ts index 02181886..d1d75048 100644 --- a/l2-bridges/common/services/monitor_withdrawals.ts +++ b/l2-bridges/common/services/monitor_withdrawals.ts @@ -7,22 +7,22 @@ import { BlockDto, WithdrawalRecord } from '../entity/blockDto' import { L2Client } from '../clients/l2_client' import { NetworkError } from '../utils/error' import { elapsedTime } from '../utils/time' -import { ETH_DECIMALS } from '../constants' +import { ETH_DECIMALS, ETH_DECIMALS2 } from '../constants' import { formatAddress } from 'forta-agent/dist/cli/utils' import { ethers } from 'ethers' import { WithdrawalInfo, ContractInfo, HugeWithdrawalsFromL2AlertParams } from '../constants' import assert from 'assert' -// 10k wstETH -const MAX_WITHDRAWALS_SUM = 10_000 -const HOURS_48 = 60 * 60 * 24 * 2 * 6 + + +export const MAX_WITHDRAWALS_SUM = 10_000 // 10k wstETH +const HOURS_48 = 60 * 60 * 24 * 2 export type MonitorWithdrawalsInitResp = { currentWithdrawals: string, } export type WithdrawalConstants = { - L2_NAME: string, withdrawalInfo: WithdrawalInfo, L2_APPROX_BLOCK_TIME_SECONDS: number, L2_ERC20_TOKEN_GATEWAY: ContractInfo, @@ -34,7 +34,6 @@ export class MonitorWithdrawals { private readonly logger: Logger private readonly l2Erc20TokenGatewayAddress: string - private readonly networkName: string private readonly l2Client: L2Client private readonly withdrawalInfo: WithdrawalInfo & { eventSignature: string, eventInterface: ethers.utils.Interface } private readonly l2BlockAverageTime: number @@ -55,7 +54,7 @@ export class MonitorWithdrawals { eventInterface: eventInterface, } this.l2BlockAverageTime = withdrawalConstants.L2_APPROX_BLOCK_TIME_SECONDS - this.networkName = withdrawalConstants.L2_NAME + } public getName(): string { @@ -109,13 +108,13 @@ export class MonitorWithdrawals { this.withdrawalsStore = withdrawalsCache - const withdrawalsSum = new BigNumber(0) + let withdrawalsSum = 0n for (const wc of this.withdrawalsStore) { - withdrawalsSum.plus(wc.amount) + withdrawalsSum += BigInt(wc.amount.toString()) } // block number condition is meant to "sync" agents alerts - if (withdrawalsSum.div(ETH_DECIMALS).isGreaterThanOrEqualTo(MAX_WITHDRAWALS_SUM) && l2Block.number % 10 === 0) { + if (withdrawalsSum >= BigInt(MAX_WITHDRAWALS_SUM) * ETH_DECIMALS2 && l2Block.number % 10 === 0) { const period = l2Block.timestamp - this.lastReportedTooManyWithdrawalsTimestamp < HOURS_48 ? l2Block.timestamp - this.lastReportedTooManyWithdrawalsTimestamp @@ -181,7 +180,7 @@ export class MonitorWithdrawals { for (const l2Log of l2Logs) { logIndexToLogs.set(l2Log.logIndex, l2Log) - addresses.add(l2Log.address.toLowerCase()) + addresses.add(formatAddress(l2Log.address)) } for (const l2BlockDto of l2BlocksDto) { @@ -193,16 +192,17 @@ export class MonitorWithdrawals { const events = filterLog(l2Logs, this.withdrawalInfo.eventDefinition, formatAddress(this.l2Erc20TokenGatewayAddress)) for (const event of events) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - const log: Log = logIndexToLogs.get(event.logIndex) - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - const blockDto: BlockDto = blockNumberToBlock.get(log.blockNumber) - + const log = logIndexToLogs.get(event.logIndex) + assert(log !== undefined) + + assert(typeof log.blockNumber === 'string') + const blockDto = blockNumberToBlock.get(parseInt(log.blockNumber as unknown as string, 16)) + assert(blockDto) // TODO + const amount = event.args[this.withdrawalInfo.amountFieldName] + assert(amount !== null && amount !== undefined) out.push({ time: blockDto.timestamp, - amount: new BigNumber(String(event.args.amount)), + amount, }) } } diff --git a/l2-bridges/common/utils/finding.helpers.ts b/l2-bridges/common/utils/finding.helpers.ts index fe456796..6ef36559 100644 --- a/l2-bridges/common/utils/finding.helpers.ts +++ b/l2-bridges/common/utils/finding.helpers.ts @@ -1,4 +1,5 @@ import { Finding, FindingSeverity, FindingType } from 'forta-agent' +import assert from 'node:assert' export const NetworkErrorFinding = 'NETWORK-ERROR' @@ -21,3 +22,11 @@ export function networkAlert(err: Error, name: string, desc: string, blockNumber export function getUniqueKey(uniqueKey: string, blockNumber: number): string { return uniqueKey + '-' + blockNumber.toString() } + +export function formatEther(value: bigint, decimals: number) { + // TODO: refactor + assert(decimals >= 0 && decimals <= 17) + const intWithDecimalsPlus1Zero = value / (10n ** (18n - BigInt(decimals) - 1n)) + const float = parseFloat(intWithDecimalsPlus1Zero.toString()) / parseFloat((10n ** BigInt(decimals + 1)).toString()) + return float.toFixed(decimals) +} \ No newline at end of file diff --git a/l2-bridges/common/utils/test.helpers.ts b/l2-bridges/common/utils/test.helpers.ts index 0031cdf1..b865466b 100644 --- a/l2-bridges/common/utils/test.helpers.ts +++ b/l2-bridges/common/utils/test.helpers.ts @@ -25,7 +25,7 @@ export async function spawnTestNode(networkId: number, l2RpcUrl: string) { console.error(`stderr: ${data}`); }); - await new Promise((r) => setTimeout(r, SECOND_MS)); + await new Promise((r) => setTimeout(r, 2 * SECOND_MS)) assert(nodeProcess); assert(nodeProcess.exitCode === null); @@ -37,8 +37,8 @@ export async function spawnTestNode(networkId: number, l2RpcUrl: string) { export async function stopTestNode(nodeProcess: any) { assert(nodeProcess) nodeProcess.kill('SIGINT') - await new Promise((r) => setTimeout(r, SECOND_MS)) - assert(nodeProcess.exitCode === 0) + await new Promise((r) => setTimeout(r, 1 * SECOND_MS)) + assert(nodeProcess.exitCode === 0, `ERROR: non-zero test node process exit code: ${nodeProcess.exitCode}`) } export function createMonitorWithdrawals(params: Constants) { @@ -50,5 +50,10 @@ export function createMonitorWithdrawals(params: Constants) { const nodeClient = new ethers.providers.JsonRpcProvider(params.L2_NETWORK_RPC, params.L2_NETWORK_ID) const bridgedWstethRunner = ERC20Short__factory.connect(params.L2_WSTETH_BRIDGED.address, nodeClient) const l2Client = new L2Client(nodeClient, logger, bridgedWstethRunner, params.MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST) - return new MonitorWithdrawals(l2Client, logger, params) + + return { + monitorWithdrawals: new MonitorWithdrawals(l2Client, logger, params), + provider: nodeClient, + l2Client, + } } diff --git a/l2-bridges/mantle/test/mantle.test.helpers.ts b/l2-bridges/mantle/test/mantle.test.helpers.ts new file mode 100644 index 00000000..64bbd9c0 --- /dev/null +++ b/l2-bridges/mantle/test/mantle.test.helpers.ts @@ -0,0 +1,95 @@ +import { Constants } from '../../common/constants' +import { JsonRpcProvider } from '@ethersproject/providers' +import { Contract, ethers } from 'ethers' +import { BigNumberish } from 'ethers' + + +export async function withdrawWsteth(amount: BigNumberish, params: Constants, provider: JsonRpcProvider, sender: string) { + const l2BridgeAddress = params.L2_ERC20_TOKEN_GATEWAY.address + + provider.send('hardhat_setBalance', [l2BridgeAddress, ethers.utils.parseEther('10').toHexString()]) + provider.send('hardhat_setBalance', [sender, ethers.utils.parseEther('10').toHexString()]) + + const l2Wsteth = new Contract(params.L2_WSTETH_BRIDGED.address, MANTLE_WSTETH_MINT_ABI, provider) + const bridgeSigner = await provider.getSigner(l2BridgeAddress) + const mintTx = await l2Wsteth.connect(bridgeSigner).bridgeMint(sender, amount) + await mintTx.wait() + + const bridge = new Contract(l2BridgeAddress, MANTLE_BRIDGE_WITHDRAW_ABI, provider) + const senderSigner = await provider.getSigner(sender) + const withdrawTx = await bridge.connect(senderSigner).withdraw(params.L2_WSTETH_BRIDGED.address, amount, 1000000, "0x") + await withdrawTx.wait() +} + + +const MANTLE_BRIDGE_WITHDRAW_ABI = [ + { + "inputs": [ + { + "internalType": "address", + "name": "l2Token_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + }, + { + "internalType": "uint32", + "name": "l1Gas_", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "data_", + "type": "bytes" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] + + +const MANTLE_WSTETH_MINT_ABI = [ + { + "inputs": [ + { + "internalType": "address", + "name": "_account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "bridgeMint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts b/l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts index b0f18237..2e8d13d4 100644 --- a/l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts +++ b/l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts @@ -1,4 +1,5 @@ import { MonitorWithdrawals } from '../../common/services/monitor_withdrawals' +import { ethers } from 'ethers' import * as E from 'fp-ts/Either' import { expect } from '@jest/globals' import BigNumber from 'bignumber.js' @@ -6,20 +7,74 @@ import { mantleConstants } from '../src/agent' import { SECOND_MS } from '../../common/utils/time' import assert from 'assert' import { spawnTestNode, stopTestNode, createMonitorWithdrawals } from '../../common/utils/test.helpers' +import { JsonRpcProvider } from '@ethersproject/providers' +import { withdrawWsteth } from './mantle.test.helpers' +import { L2Client } from '../../common/clients/l2_client' +import { MAX_WITHDRAWALS_SUM } from '../../common/services/monitor_withdrawals' +import { App } from "../../common/agent" +import { BlockEvent, EventType, Network, Block } from "forta-agent" describe('MonitorWithdrawals on Mantle', () => { let testNodeProcess: any = null let monitorWithdrawals: MonitorWithdrawals = null as any + let provider: JsonRpcProvider = null as any + let l2Client: L2Client = null as any + beforeAll(async () => { const { nodeProcess, rpcUrl } = await spawnTestNode(mantleConstants.L2_NETWORK_ID, mantleConstants.L2_NETWORK_RPC) testNodeProcess = nodeProcess - mantleConstants.L2_NETWORK_RPC = rpcUrl + mantleConstants.L2_NETWORK_RPC = rpcUrl; - monitorWithdrawals = createMonitorWithdrawals(mantleConstants) + ({ monitorWithdrawals, provider, l2Client } = createMonitorWithdrawals(mantleConstants)) }); + xtest(`TODO`, async () => { + let initialize = App.initialize(mantleConstants) + await initialize() + let app = await App.getInstance() + + const currentL2Block = await app.provider.getBlock('latest') + console.debug({ currentL2Block }) + + const blockEvent = new BlockEvent(EventType.BLOCK, Network.MAINNET, { + blockNumber: currentL2Block.number, + block: { + number: currentL2Block.number, + } + } as unknown as Block) + console.debug({ blockEvent }) + console.debug({ blockEventNUmber: blockEvent.blockNumber }) + // await App.handleBlock(blockEvent) + }) + + test(`mvp of a test with emulation: max withdrawals`, async () => { + const currentL2Block = await provider.getBlock('latest') + await monitorWithdrawals.initialize(currentL2Block.number) // TODO: use return value of initialize + + await l2Client.getNotYetProcessedL2Blocks() + + const arbitrarySender = '0x61ce1F6514C651C39964961DB1948C6f70a4747a' + await withdrawWsteth(ethers.utils.parseEther(`${MAX_WITHDRAWALS_SUM}`), mantleConstants, provider, arbitrarySender) + await provider.send("hardhat_mine", ["0x09"]) // mine a few so that there are at least 10 block to trigger (% 10 == 0) condition in MonitorWithdrawals + + const l2BlocksDtoOrErr = await l2Client.getNotYetProcessedL2Blocks() + assert(E.isRight(l2BlocksDtoOrErr)) + const l2BlocksDto = l2BlocksDtoOrErr.right + + const l2LogsOrErr = await l2Client.getL2LogsOrNetworkAlert(l2BlocksDto) + assert(E.isRight(l2LogsOrErr)) + const l2Logs = l2LogsOrErr.right + + const findings = await monitorWithdrawals.handleBlocks(l2Logs, l2BlocksDto) + assert(findings.length >= 1) + + await new Promise((r) => setTimeout(r, SECOND_MS)) // TODO: get rid of this + + assert(true) + }, 40 * SECOND_MS) + test(`getWithdrawalRecordsInBlockRange: 2 withdrawals, 1_023_599 blocks`, async () => { const withdrawalRecords = await monitorWithdrawals._getWithdrawalRecordsInBlockRange(64995881, 66019480) assert(E.isRight(withdrawalRecords)) diff --git a/l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts b/l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts index 6f75e964..f0d39734 100644 --- a/l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts +++ b/l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts @@ -16,9 +16,9 @@ describe('MonitorWithdrawals on Scroll', () => { beforeAll(async () => { const { nodeProcess, rpcUrl } = await spawnTestNode(scrollConstants.L2_NETWORK_ID, scrollConstants.L2_NETWORK_RPC) testNodeProcess = nodeProcess - scrollConstants.L2_NETWORK_RPC = rpcUrl + scrollConstants.L2_NETWORK_RPC = rpcUrl; - monitorWithdrawals = createMonitorWithdrawals(scrollConstants) + ({ monitorWithdrawals } = createMonitorWithdrawals(scrollConstants)) }); test(`getWithdrawalRecordsInBlockRange: 3 withdrawals, 3889 blocks`, async () => { diff --git a/l2-bridges/zksync/test/monitor_withdrawals.zksync.spec.ts b/l2-bridges/zksync/test/monitor_withdrawals.zksync.spec.ts index 62818a29..78d394c2 100644 --- a/l2-bridges/zksync/test/monitor_withdrawals.zksync.spec.ts +++ b/l2-bridges/zksync/test/monitor_withdrawals.zksync.spec.ts @@ -4,6 +4,7 @@ import { zksyncConstants } from '../src/agent' import { SECOND_MS } from '../../common/utils/time' import { MonitorWithdrawals } from '../../common/services/monitor_withdrawals' import { spawnTestNode, stopTestNode, createMonitorWithdrawals } from '../../common/utils/test.helpers' + import assert from 'assert' @@ -14,9 +15,9 @@ describe('MonitorWithdrawals on ZkSync', () => { beforeAll(async () => { const { nodeProcess, rpcUrl } = await spawnTestNode(zksyncConstants.L2_NETWORK_ID, zksyncConstants.L2_NETWORK_RPC) testNodeProcess = nodeProcess - zksyncConstants.L2_NETWORK_RPC = rpcUrl + zksyncConstants.L2_NETWORK_RPC = rpcUrl; - monitorWithdrawals = createMonitorWithdrawals(zksyncConstants) + ({ monitorWithdrawals } = createMonitorWithdrawals(zksyncConstants)) }); test(`getWithdrawalRecordsInBlockRange: 2 withdrawals (225_821 blocks)`, async () => { From ff2459e0b077865487c3f14094d2c244ccb2c709 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 29 Jul 2024 22:50:21 +0300 Subject: [PATCH 15/18] fix(MonitorWithdrawals): fix error in checking events addresses --- l2-bridges/common/services/monitor_withdrawals.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l2-bridges/common/services/monitor_withdrawals.ts b/l2-bridges/common/services/monitor_withdrawals.ts index d1d75048..383fa22c 100644 --- a/l2-bridges/common/services/monitor_withdrawals.ts +++ b/l2-bridges/common/services/monitor_withdrawals.ts @@ -188,7 +188,7 @@ export class MonitorWithdrawals { } const out: WithdrawalRecord[] = [] - if (formatAddress(this.l2Erc20TokenGatewayAddress) in addresses) { + if (addresses.has(formatAddress(this.l2Erc20TokenGatewayAddress))) { const events = filterLog(l2Logs, this.withdrawalInfo.eventDefinition, formatAddress(this.l2Erc20TokenGatewayAddress)) for (const event of events) { From 57c2a6d9a22ce2bd761d2603dfc75240d9555624 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Tue, 20 Aug 2024 12:55:28 +0300 Subject: [PATCH 16/18] feat: prototype for event-based alert bundles for Mantle --- l2-bridges/common/agent.ts | 187 ++++++++-------- l2-bridges/common/alert-bundles.ts | 209 ++++++++++++++++++ l2-bridges/common/entity/events.ts | 4 + .../services/event_watcher_universal.ts | 48 ++++ .../common/services/monitor_withdrawals.ts | 4 +- .../common/test/event-based-alerts.spec.ts | 46 ++++ l2-bridges/common/utils/test.helpers.ts | 62 ++++-- l2-bridges/jest.config.js | 2 + l2-bridges/jest.setup.ts | 27 +++ l2-bridges/jest.teardown.ts | 14 ++ l2-bridges/mantle/src/agent.ts | 10 +- l2-bridges/mantle/test/mantle.test.helpers.ts | 4 +- .../test/monitor_withdrawals.mantle.spec.ts | 39 +--- l2-bridges/package.json | 2 +- l2-bridges/scroll/src/agent.ts | 10 +- .../test/monitor_withdrawals.scroll.spec.ts | 21 +- l2-bridges/yarn.lock | 2 +- l2-bridges/zksync/src/agent.ts | 12 +- .../test/monitor_withdrawals.zksync.spec.ts | 15 +- 19 files changed, 530 insertions(+), 188 deletions(-) create mode 100644 l2-bridges/common/alert-bundles.ts create mode 100644 l2-bridges/common/services/event_watcher_universal.ts create mode 100644 l2-bridges/common/test/event-based-alerts.spec.ts create mode 100644 l2-bridges/jest.setup.ts create mode 100644 l2-bridges/jest.teardown.ts diff --git a/l2-bridges/common/agent.ts b/l2-bridges/common/agent.ts index dfceaff4..6822a89f 100644 --- a/l2-bridges/common/agent.ts +++ b/l2-bridges/common/agent.ts @@ -1,4 +1,5 @@ import { ethers, BlockEvent, Finding, FindingSeverity, FindingType } from 'forta-agent' + import * as process from 'process' import { InitializeResponse } from 'forta-agent/dist/sdk/initialize.response' import { Initialize } from 'forta-agent/dist/sdk/handlers' @@ -9,7 +10,7 @@ import BigNumber from 'bignumber.js' import { JsonRpcProvider } from '@ethersproject/providers' import { L2Client } from './clients/l2_client' -import { EventWatcher } from './services/event_watcher' +import { EventWatcher } from './services/event_watcher_universal' import { ERC20Short__factory } from './generated' import { MonitorWithdrawals } from './services/monitor_withdrawals' import { DataRW } from './utils/mutex' @@ -19,17 +20,22 @@ import { ETHProvider } from './clients/eth_provider_client' import { BridgeBalanceSrv } from './services/bridge_balance' import { getJsonRpcUrl } from 'forta-agent/dist/sdk/utils' import { Constants, MAINNET_CHAIN_ID, DRPC_URL, L1_WSTETH_ADDRESS } from './constants' +import { getEventBasedAlerts } from './alert-bundles' + // import { BorderTime, HealthChecker, MaxNumberErrorsPerBorderTime } from './services/health-checker/health-checker.srv' +const METADATA: { [key: string]: string } = { + 'version.commitHash': VERSION.commitHash, + 'version.commitMsg': VERSION.commitMsg, +} + export type Container = { params: Constants, l2Client: L2Client monitorWithdrawals: MonitorWithdrawals - bridgeWatcher: EventWatcher + eventWatcher: EventWatcher bridgeBalanceSrv: BridgeBalanceSrv - govWatcher: EventWatcher - proxyEventWatcher: EventWatcher findingsRW: DataRW logger: Logger provider: JsonRpcProvider, @@ -38,28 +44,35 @@ export type Container = { export class App { - private static instance: Container - private static isHandleBlockRunning = false - - private constructor() {} - - public static async createInstance(params: Constants): Promise { - - const logger: Winston.Logger = Winston.createLogger({ + public static instance: App + + public params: Constants + public l2Client: L2Client + public monitorWithdrawals: MonitorWithdrawals + public eventWatcher: EventWatcher + public bridgeBalanceSrv: BridgeBalanceSrv + public findingsRW: DataRW + public logger: Logger + public provider: JsonRpcProvider + public isHandleBlockRunning: boolean = false + + public constructor(params: Constants) { + this.params = params + + this.logger = Winston.createLogger({ format: Winston.format.simple(), transports: [new Winston.transports.Console()], }) - const provider = new ethers.providers.JsonRpcProvider(params.L2_NETWORK_RPC, params.L2_NETWORK_ID) - const bridgedWstethRunner = ERC20Short__factory.connect(params.L2_WSTETH_BRIDGED.address, provider) + this.provider = new ethers.providers.JsonRpcProvider(params.L2_NETWORK_RPC, params.L2_NETWORK_ID) + const bridgedWstethRunner = ERC20Short__factory.connect(params.L2_WSTETH_BRIDGED.address, this.provider) - const l2Client = new L2Client(provider, logger, bridgedWstethRunner, params.MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST) + this.l2Client = new L2Client(this.provider, this.logger, bridgedWstethRunner, params.MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST) - const bridgeEventWatcher = new EventWatcher('BridgeEventWatcher', params.bridgeEvents, logger) - const govEventWatcher = new EventWatcher('GovEventWatcher', params.govEvents, logger) - const proxyEventWatcher = new EventWatcher('ProxyEventWatcher', params.proxyAdminEvents, logger) + const eventAlertsToWatch = getEventBasedAlerts(params.L2_NAME) + this.eventWatcher = new EventWatcher(eventAlertsToWatch, this.logger) - const monitorWithdrawals = new MonitorWithdrawals(l2Client, logger, params) + this.monitorWithdrawals = new MonitorWithdrawals(this.l2Client, this.logger, params) const ethProvider = new ethers.providers.FallbackProvider([ new ethers.providers.JsonRpcProvider(getJsonRpcUrl(), MAINNET_CHAIN_ID), @@ -67,70 +80,57 @@ export class App { ]) const wstethRunner = ERC20Short__factory.connect(L1_WSTETH_ADDRESS, ethProvider) - const ethClient = new ETHProvider(logger, wstethRunner) - const bridgeBalanceSrv = new BridgeBalanceSrv(params.L2_NAME, logger, ethClient, l2Client, params.L1_ERC20_TOKEN_GATEWAY_ADDRESS) - - App.instance = { - params, - l2Client, - monitorWithdrawals, - bridgeWatcher: bridgeEventWatcher, - bridgeBalanceSrv, - govWatcher: govEventWatcher, - proxyEventWatcher, - findingsRW: new DataRW([]), - logger, - provider, - // healthChecker: new HealthChecker(BorderTime, MaxNumberErrorsPerBorderTime), - } + const ethClient = new ETHProvider(this.logger, wstethRunner) + this.bridgeBalanceSrv = new BridgeBalanceSrv(params.L2_NAME, this.logger, ethClient, this.l2Client, params.L1_ERC20_TOKEN_GATEWAY_ADDRESS) - return App.instance + this.findingsRW = new DataRW([]) } - public static async getInstance(): Promise { - if (!App.instance) { - throw new Error(`App instance is not created`) + public static createStaticInstance(params: Constants): Container { + if (App.instance) { + throw new Error(`App instance is already created`) } + App.instance = new App(params) return App.instance } - public static async handleBlock(blockEvent: BlockEvent): Promise { - const app = await App.getInstance() + public static async handleBlockStatic(blockEvent: BlockEvent): Promise { + return await App.instance.handleBlock(blockEvent) + } - const startTime = new Date().getTime() - if (App.isHandleBlockRunning) { + public async handleBlock(blockEvent: BlockEvent): Promise { + const startTime = new Date().getTime() + if (this.isHandleBlockRunning) { return [] } - App.isHandleBlockRunning = true + this.isHandleBlockRunning = true const findings: Finding[] = [] - const findingsAsync = await app.findingsRW.read() + const findingsAsync = await this.findingsRW.read() if (findingsAsync.length > 0) { findings.push(...findingsAsync) } - const l2blocksDto = await app.l2Client.getNotYetProcessedL2Blocks() + const l2blocksDto = await this.l2Client.getNotYetProcessedL2Blocks() if (E.isLeft(l2blocksDto)) { - App.isHandleBlockRunning = false + this.isHandleBlockRunning = false return [l2blocksDto.left] } - app.logger.info( - `ETH block ${blockEvent.blockNumber.toString()}. Fetched ${app.params.L2_NAME} blocks from ${l2blocksDto.right[0].number} to ${ + this.logger.info( + `ETH block ${blockEvent.blockNumber.toString()}. Fetched ${this.params.L2_NAME} blocks from ${l2blocksDto.right[0].number} to ${ l2blocksDto.right[l2blocksDto.right.length - 1].number }. Total: ${l2blocksDto.right.length}`, ) - const logs = await app.l2Client.getL2LogsOrNetworkAlert(l2blocksDto.right) + const logs = await this.l2Client.getL2LogsOrNetworkAlert(l2blocksDto.right) if (E.isLeft(logs)) { - App.isHandleBlockRunning = false + this.isHandleBlockRunning = false return [logs.left] } - const bridgeEventFindings = app.bridgeWatcher.handleLogs(logs.right) - const govEventFindings = app.govWatcher.handleLogs(logs.right) - const proxyAdminEventFindings = app.proxyEventWatcher.handleLogs(logs.right) - const monitorWithdrawalsFindings = app.monitorWithdrawals.handleBlocks(logs.right, l2blocksDto.right) + const eventBasedFindings = this.eventWatcher.handleLogs(logs.right) + const monitorWithdrawalsFindings = this.monitorWithdrawals.handleBlocks(logs.right, l2blocksDto.right) const l2blockNumbersSet: Set = new Set() for (const log of logs.right) { @@ -140,63 +140,62 @@ export class App { const l2blockNumbers = Array.from(l2blockNumbersSet) const [bridgeBalanceFindings] = await Promise.all([ - app.bridgeBalanceSrv.handleBlock(blockEvent.block.number, l2blockNumbers), + this.bridgeBalanceSrv.handleBlock(blockEvent.block.number, l2blockNumbers), ]) findings.push( - ...bridgeEventFindings, + ...eventBasedFindings, ...bridgeBalanceFindings, - ...govEventFindings, - ...proxyAdminEventFindings, ...monitorWithdrawalsFindings, ) - app.logger.info(elapsedTime('handleBlock', startTime) + '\n') - App.isHandleBlockRunning = false + this.logger.info(elapsedTime('handleBlock', startTime) + '\n') + this.isHandleBlockRunning = false return findings } - public static initialize(params: Constants): Initialize { - const metadata: { [key: string]: string } = { - 'version.commitHash': VERSION.commitHash, - 'version.commitMsg': VERSION.commitMsg, + public static initializeStatic(params: Constants): Initialize { + if (!App.instance) { + App.createStaticInstance(params) } return async function (): Promise { - const app = await App.createInstance(params) - - const latestL2Block = await app.l2Client.getLatestL2Block() - if (E.isLeft(latestL2Block)) { - app.logger.error(latestL2Block.left) - - process.exit(1) - } + await App.instance.initialize() + } + } - const monitorWithdrawalsInitResp = await app.monitorWithdrawals.initialize(latestL2Block.right.number) - if (E.isLeft(monitorWithdrawalsInitResp)) { - app.logger.error(monitorWithdrawalsInitResp.left) + public async initialize() { + const latestL2Block = await this.l2Client.getLatestL2Block() + if (E.isLeft(latestL2Block)) { + this.logger.error(latestL2Block.left) - process.exit(1) - } + process.exit(1) + } - metadata[`${app.monitorWithdrawals.getName()}.currentWithdrawals`] = - monitorWithdrawalsInitResp.right.currentWithdrawals + const monitorWithdrawalsInitResp = await this.monitorWithdrawals.initialize(latestL2Block.right.number) + if (E.isLeft(monitorWithdrawalsInitResp)) { + this.logger.error(monitorWithdrawalsInitResp.left) - const agents: string[] = [app.monitorWithdrawals.getName()] - metadata.agents = '[' + agents.toString() + ']' + process.exit(1) + } - await app.findingsRW.write([ - Finding.fromObject({ - name: 'Agent launched', - description: `Version: ${VERSION.desc}`, - alertId: 'LIDO-AGENT-LAUNCHED', - severity: FindingSeverity.Info, - type: FindingType.Info, - metadata, - }), - ]) + METADATA[`${this.monitorWithdrawals.getName()}.currentWithdrawals`] = + monitorWithdrawalsInitResp.right.currentWithdrawals + + const agents: string[] = [this.monitorWithdrawals.getName()] + METADATA.agents = '[' + agents.toString() + ']' + + await this.findingsRW.write([ + Finding.fromObject({ + name: 'Agent launched', + description: `Version: ${VERSION.desc}`, + alertId: 'LIDO-AGENT-LAUNCHED', + severity: FindingSeverity.Info, + type: FindingType.Info, + metadata: METADATA, + }), + ]) - app.logger.info('Bot initialization is done!') - } + this.logger.info('Bot initialization is done!') } } diff --git a/l2-bridges/common/alert-bundles.ts b/l2-bridges/common/alert-bundles.ts new file mode 100644 index 00000000..20f16214 --- /dev/null +++ b/l2-bridges/common/alert-bundles.ts @@ -0,0 +1,209 @@ +import { strict as assert } from 'node:assert' +import { Result as EventArgs } from '@ethersproject/abi/lib' +import { FindingSeverity, FindingType } from 'forta-agent' +import { formatAddress } from 'forta-agent/dist/cli/utils' +import { EventOfNotice, SimulateFunc } from './entity/events' +import { Contract, ethers } from 'ethers' +import { JsonRpcProvider } from '@ethersproject/providers' + + +const MIN_PROXY_ABI = [ + "function proxy__getAdmin() external view returns (address)", + "function proxy__getImplementation() view returns (address)", + "function proxy__changeAdmin(address owner)", + "function proxy__upgradeTo(address _newImplementation)", + "function proxy__ossify()", + "function proxy__getIsOssified() view returns (bool)", +] + + +export function skipNetwork(network: L2Network) { + if (network === L2Network.Default + || network === L2Network.ZkSync + || network === L2Network.Scroll + // || network === L2Network.Mantle + ) { + return true + } else { + return false + } +} + + +export enum L2Network { + Default = 'Default', + Mantle = 'Mantle', + ZkSync = 'ZkSync', + Scroll = 'Scroll', +} + + +enum ContractType { + L2TokensBridge = 'L2TokensBridge', + L2GovExecutor = 'L2GovExecutor', + L2Wsteth = 'L2Wsteth', + L2ProxyAdmin = 'ProxyAdmin', +} + + +export type ContractsSheet = { + [key: string]: { + [key: string]: string, + } +} + +export const contractsSheet: ContractsSheet = { + [L2Network.Mantle]: { + [ContractType.L2TokensBridge]: '0x9c46560D6209743968cC24150893631A39AfDe4d', + [ContractType.L2GovExecutor]: '0x3a7b055bf88cdc59d20d0245809c6e6b3c5819dd', + [ContractType.L2Wsteth]: '0x458ed78EB972a369799fb278c0243b25e5242A83', + [ContractType.L2ProxyAdmin]: '0x8e34d07eb348716a1f0a48a507a9de8a3a6dce45', + }, + [L2Network.ZkSync]: { + [ContractType.L2TokensBridge]: '0xe1d6a50e7101c8f8db77352897ee3f1ac53f782b', + [ContractType.L2GovExecutor]: '0x139ee25dcad405d2a038e7a67f9ffdbf0f573f3c', + [ContractType.L2Wsteth]: '0x703b52F2b28fEbcB60E1372858AF5b18849FE867', + [ContractType.L2ProxyAdmin]: '0xbd80e505ecc49bae2cc86094a78fa0e2db28b52a', + }, + [L2Network.Scroll]: { + [ContractType.L2TokensBridge]: '0x8aE8f22226B9d789A36AC81474e633f8bE2856c9', + [ContractType.L2GovExecutor]: '0x0c67D8D067E349669dfEAB132A7c03A90594eE09', + [ContractType.L2Wsteth]: '0xf610A9dfB7C89644979b4A0f27063E9e7d7Cda32', + [ContractType.L2ProxyAdmin]: '0x8e34D07Eb348716a1f0a48A507A9de8a3A6DcE45', + }, +} + + +export type NameArgs = { network: string } + +export type EventBundlesSheet = + { + [key: string]: { + name: (_: NameArgs) => string, + severity: FindingSeverity, + event: string, + description: (contractAlias: string, address: string) => (_: EventArgs) => string, + contracts: { + [key: string]: ContractType[], + }, + simulate?: SimulateFunc, + } +} + + +export const eventBasedAlertBundleSheet: EventBundlesSheet = { + 'PROXY-UPGRADED': { + name: (_: NameArgs) => `🚨 ${_.network}: Proxy upgraded`, + severity: FindingSeverity.High, + description: (contractAlias: string, address: string) => { + return (_: EventArgs) => { + return `Proxy for ${contractAlias}(${address}) ` + + `was updated to ${_.implementation}` + + `\n(detected by event)` + } + }, + event: 'event Upgraded(address indexed implementation)', + contracts: { + [L2Network.Default]: [ContractType.L2TokensBridge, ContractType.L2Wsteth], + // [L2Network.ZkSync]: [ContractType.L2TokensBridge, ContractType.L2Wsteth, ContractType.L2GovExecutor], + [L2Network.ZkSync]: [ContractType.L2TokensBridge], + }, + simulate: async (provider: JsonRpcProvider, address: string) => { + const newImplementation = address // just reusing an address at hand for simplicity + const contract = new Contract(address, MIN_PROXY_ABI, provider) + const adminAddress = await contract.proxy__getAdmin() + // console.debug({ adminAddress }) + await provider.send('hardhat_setBalance', [adminAddress, ethers.utils.parseEther('10').toHexString()]) + const signer = await provider.getSigner(adminAddress) + + const tx = await contract.connect(signer)["proxy__upgradeTo"](newImplementation) + const receipt = await tx.wait() + assert(receipt.logs.length > 0) + // TODO: fix assert + // assert(await contract['proxy__getImplementation']() === newImplementation) + }, + }, + 'PROXY-ADMIN-CHANGED': { + name: (_: NameArgs) => `🚨 ${_.network}: Proxy admin changed`, + severity: FindingSeverity.High, + description: (contractAlias: string, address: string) => { + return (_: EventArgs) => { + return `Proxy admin for ${contractAlias}(${address}) ` + + `was changed from ${_.previousAdmin} to ${_.newAdmin}` + + `\n(detected by event)` + } + }, + event: 'event AdminChanged(address previousAdmin, address newAdmin)', + contracts: { + [L2Network.Default]: [ContractType.L2TokensBridge, ContractType.L2Wsteth], + [L2Network.ZkSync]: [ContractType.L2TokensBridge, ContractType.L2Wsteth, ContractType.L2GovExecutor], + }, + simulate: async (provider: JsonRpcProvider, address: string) => { + const arbitraryAddress = '0xb4ef9590f724565caf344cc6AFB86Df266529CeE' + const contract = new Contract(address, MIN_PROXY_ABI, provider) + const adminAddress = await contract['proxy__getAdmin']() + await provider.send('hardhat_setBalance', [adminAddress, ethers.utils.parseEther('10').toHexString()]) + const signer = await provider.getSigner(adminAddress) + const tx = await contract.connect(signer)["proxy__changeAdmin"](arbitraryAddress) + const receipt = await tx.wait() + assert(receipt.logs.length > 0) + assert(await contract['proxy__getAdmin']() === arbitraryAddress) + }, + }, + 'PROXY-OSSIFIED': { + name: (_: NameArgs) => `🚨 ${_.network}: Proxy ossified`, + severity: FindingSeverity.High, + description: (contractAlias: string, address: string) => { + return (_: EventArgs) => { + return `Proxy for ${contractAlias}(${address}) was ossified` + + `\n(detected by event)` + } + }, + event: 'event ProxyOssified()', + contracts: { + [L2Network.Default]: [ContractType.L2TokensBridge, ContractType.L2Wsteth], + // [L2Network.ZkSync]: [ContractType.L2TokensBridge, ContractType.L2Wsteth, ContractType.L2GovExecutor], + [L2Network.ZkSync]: [ContractType.L2TokensBridge], + }, + simulate: async (provider: JsonRpcProvider, address: string) => { + const contract = new Contract(address, MIN_PROXY_ABI, provider) + const adminAddress = await contract['proxy__getAdmin']() + await provider.send('hardhat_setBalance', [adminAddress, ethers.utils.parseEther('10').toHexString()]) + const signer = await provider.getSigner(adminAddress) + const tx = await contract.connect(signer)["proxy__ossify"]() + const receipt = await tx.wait() + assert(receipt.logs.length > 0) + assert(await contract['proxy__getIsOssified']()) + }, + }, +} + + +export function getEventBasedAlerts(networkName: string) { + assert((Object.values(L2Network) as string[]).includes(networkName)) + const result: EventOfNotice[] = [] + for (const [alertId, params] of Object.entries(eventBasedAlertBundleSheet)) { + + const contractsAliases = params.contracts[networkName] || params.contracts[L2Network.Default] + assert(contractsAliases !== undefined) + + for (const contractAlias of contractsAliases) { + assert(contractsSheet[networkName]) + assert(contractsSheet[networkName][contractAlias]) + const contractAddress = formatAddress(contractsSheet[networkName][contractAlias]) + assert(contractAddress === contractAddress.toLowerCase()) + result.push({ + name: params.name({network: networkName}), + address: contractAddress, + event: params.event, + alertId: alertId, + description: params.description(contractAlias, contractAddress), + severity: params.severity, + type: FindingType.Info, + uniqueKey: "", // TODO + simulate: params.simulate, + }) + } + } + return result +} \ No newline at end of file diff --git a/l2-bridges/common/entity/events.ts b/l2-bridges/common/entity/events.ts index c806a8c6..3beffe90 100644 --- a/l2-bridges/common/entity/events.ts +++ b/l2-bridges/common/entity/events.ts @@ -1,4 +1,7 @@ import { FindingSeverity, FindingType } from 'forta-agent' +import { JsonRpcProvider } from '@ethersproject/providers' + +export type SimulateFunc = (provider: JsonRpcProvider, address: string) => Promise export type EventOfNotice = { name: string @@ -9,4 +12,5 @@ export type EventOfNotice = { severity: FindingSeverity type: FindingType uniqueKey: string + simulate?: SimulateFunc, } diff --git a/l2-bridges/common/services/event_watcher_universal.ts b/l2-bridges/common/services/event_watcher_universal.ts new file mode 100644 index 00000000..0946f74d --- /dev/null +++ b/l2-bridges/common/services/event_watcher_universal.ts @@ -0,0 +1,48 @@ +import { Logger } from 'winston' +import { Log } from '@ethersproject/abstract-provider' +import { filterLog, Finding } from 'forta-agent' +import { EventOfNotice } from '../entity/events' +import { getUniqueKey } from '../utils/finding.helpers' +import { elapsedTime } from '../utils/time' + + +export class EventWatcher { + private readonly eventsToFinding: EventOfNotice[] + private readonly logger: Logger + + constructor(events: EventOfNotice[], logger: Logger) { + this.eventsToFinding = events + this.logger = logger + } + + public handleLogs(logs: Log[]): Finding[] { + const start = new Date().getTime() + const addresses: string[] = [] + + for (const log of logs) { + addresses.push(log.address) + } + + const findings: Finding[] = [] + for (const eventToFinding of this.eventsToFinding) { + const ind = addresses.indexOf(eventToFinding.address) + if (ind >= 0) { + const filteredEvents = filterLog(logs, eventToFinding.event, eventToFinding.address) + for (const event of filteredEvents) { + findings.push(Finding.fromObject({ + name: eventToFinding.name, + description: eventToFinding.description(event.args), + alertId: eventToFinding.alertId, + severity: eventToFinding.severity, + type: eventToFinding.type, + metadata: { args: String(event.args) }, + uniqueKey: getUniqueKey(eventToFinding.uniqueKey, logs[ind].blockNumber), + })) + } + } + } + + this.logger.info(elapsedTime(EventWatcher.name + '.' + this.handleLogs.name, start)) + return findings + } +} diff --git a/l2-bridges/common/services/monitor_withdrawals.ts b/l2-bridges/common/services/monitor_withdrawals.ts index 383fa22c..0b09b96c 100644 --- a/l2-bridges/common/services/monitor_withdrawals.ts +++ b/l2-bridges/common/services/monitor_withdrawals.ts @@ -11,8 +11,7 @@ import { ETH_DECIMALS, ETH_DECIMALS2 } from '../constants' import { formatAddress } from 'forta-agent/dist/cli/utils' import { ethers } from 'ethers' import { WithdrawalInfo, ContractInfo, HugeWithdrawalsFromL2AlertParams } from '../constants' -import assert from 'assert' - +import { strict as assert } from 'node:assert' export const MAX_WITHDRAWALS_SUM = 10_000 // 10k wstETH @@ -81,6 +80,7 @@ export class MonitorWithdrawals { + ` Fetched past withdrawal events in blocks range [${startBlock}, ${endBlock}]`) this.logger.info(elapsedTime(MonitorWithdrawals.name + '.' + this._getWithdrawalRecordsInBlockRange.name, start)) return E.right({ + // TODO: use formatEther currentWithdrawals: (new BigNumber(withdrawalsSum.toString())).div(ETH_DECIMALS).toFixed(4), }) } diff --git a/l2-bridges/common/test/event-based-alerts.spec.ts b/l2-bridges/common/test/event-based-alerts.spec.ts new file mode 100644 index 00000000..40feed5f --- /dev/null +++ b/l2-bridges/common/test/event-based-alerts.spec.ts @@ -0,0 +1,46 @@ +import { strict as assert } from 'node:assert' +import { expect } from '@jest/globals' +import { L2Network, getEventBasedAlerts, skipNetwork } from '../alert-bundles' + +import { getLatestBlockEvent as getBlockEvent } from '../../common/utils/test.helpers' +import { SECOND_MS } from '../../common/utils/time' +import { globalExtended, paramsByNetwork } from '../utils/test.helpers' +import { App } from '../agent' + + +describe('Bundle tests', () => { + + test(`All event-based tests`, async () => { + + for (const network of Object.values(L2Network)) { + if (skipNetwork(network)) { continue } + assert(network !== L2Network.Default) + + const app = new App({ ...paramsByNetwork[network], L2_NETWORK_RPC: globalExtended.testNodes[network].rpcUrl }) + + await app.initialize() + const dummyBlockEvent = await getBlockEvent(app.provider, 'latest') + const findings = await app.handleBlock(dummyBlockEvent) + + const networkAlerts = getEventBasedAlerts(network) + for (const alert of networkAlerts) { + if (!alert.simulate) { + app.logger.info(`SKIP: ${alert.alertId} for ${network} at ${alert.address} (no simulate function)`) + continue + } + const snapshotId = await app.provider.send('evm_snapshot', []) + + // TODO: if use snapshots need to reinitialize app every time + + await alert.simulate(app.provider, alert.address) + await app.provider.send("hardhat_mine", ["0x01"]) + + const findings = await app.handleBlock(dummyBlockEvent) + expect(findings.length).toBeGreaterThan(0) + app.logger.info(`DONE: ${alert.alertId} for ${network} at ${alert.address}`) + + await app.provider.send('evm_revert', [snapshotId]) + } + } + }, 30 * 60 * SECOND_MS) // TODO: same timeout +}) diff --git a/l2-bridges/common/utils/test.helpers.ts b/l2-bridges/common/utils/test.helpers.ts index b865466b..2652df78 100644 --- a/l2-bridges/common/utils/test.helpers.ts +++ b/l2-bridges/common/utils/test.helpers.ts @@ -1,44 +1,76 @@ -const { spawn } = require('node:child_process') -import assert from 'assert' -import { SECOND_MS } from './time' -import { Constants } from '../constants'; -import { ethers } from 'forta-agent' +import * as child_process from 'node:child_process' +import { JsonRpcProvider } from '@ethersproject/providers' +import { strict as assert } from 'node:assert' +import { BlockEvent, EventType, Network, Block, ethers } from "forta-agent" import * as Winston from 'winston' +import { SECOND_MS } from './time' +import { Constants } from '../constants' import { ERC20Short__factory } from '../generated' import { L2Client } from '../clients/l2_client' import { MonitorWithdrawals } from '../services/monitor_withdrawals' +import { L2Network } from '../../common/alert-bundles' +import { scrollConstants } from '../../scroll/src/agent' +import { mantleConstants } from '../../mantle/src/agent' +import { zksyncConstants } from '../../zksync/src/agent' + +export type ChildProcess = child_process.ChildProcessWithoutNullStreams + +export async function getLatestBlockEvent(provider: JsonRpcProvider, blockNumber: number | 'latest') { + const currentL2Block = await provider.getBlock(blockNumber) + const blockEvent = new BlockEvent(EventType.BLOCK, Network.MAINNET, currentL2Block as unknown as Block) + return blockEvent +} + -let TEST_NODE_PORT = 8577 +export type GlobalThisExtended = (typeof globalThis) & { + testNodes: { [key: string]: { process: ChildProcess, rpcUrl: string } } +} +export const portsByNetwork = { + [L2Network.Mantle]: 8651, + [L2Network.ZkSync]: 8652, + [L2Network.Scroll]: 8653, +} -export async function spawnTestNode(networkId: number, l2RpcUrl: string) { +export const paramsByNetwork = { + [L2Network.Mantle]: mantleConstants, + [L2Network.ZkSync]: zksyncConstants, + [L2Network.Scroll]: scrollConstants, +} + + +export const globalExtended: GlobalThisExtended = global as unknown as GlobalThisExtended + + +export async function spawnTestNode(networkId: number, l2RpcUrl: string, port: number) { const nodeCmd = 'anvil' const nodeArgs = [ - '-p', `${TEST_NODE_PORT}`, + '-p', `${port}`, '--chain', `${networkId}`, '--auto-impersonate', '--fork-url', l2RpcUrl, ] - const nodeProcess = spawn(nodeCmd, nodeArgs); + const nodeProcess = child_process.spawn(nodeCmd, nodeArgs) nodeProcess.stderr.on('data', (data: unknown) => { - console.error(`stderr: ${data}`); - }); + console.error(`${nodeCmd}'s stderr: ${data}`) + }) await new Promise((r) => setTimeout(r, 2 * SECOND_MS)) - assert(nodeProcess); - assert(nodeProcess.exitCode === null); + assert(nodeProcess) + assert(nodeProcess.exitCode === null) console.log(`Spawned test node: ${nodeCmd} ${nodeArgs.join(' ')}`) - return { nodeProcess, rpcUrl: `http://localhost:${TEST_NODE_PORT}` } + return { nodeProcess, rpcUrl: `http://localhost:${port}` } } -export async function stopTestNode(nodeProcess: any) { +export async function stopTestNode(nodeProcess: ChildProcess) { assert(nodeProcess) nodeProcess.kill('SIGINT') await new Promise((r) => setTimeout(r, 1 * SECOND_MS)) assert(nodeProcess.exitCode === 0, `ERROR: non-zero test node process exit code: ${nodeProcess.exitCode}`) + console.debug(`Stopped anvil process with pid ${nodeProcess.pid}`) } export function createMonitorWithdrawals(params: Constants) { diff --git a/l2-bridges/jest.config.js b/l2-bridges/jest.config.js index 9dfeb5d2..a9efbb9a 100644 --- a/l2-bridges/jest.config.js +++ b/l2-bridges/jest.config.js @@ -3,4 +3,6 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', testPathIgnorePatterns: ['dist'], + globalSetup: "./jest.setup.ts", + globalTeardown: "./jest.teardown.ts", } diff --git a/l2-bridges/jest.setup.ts b/l2-bridges/jest.setup.ts new file mode 100644 index 00000000..dbc5e21e --- /dev/null +++ b/l2-bridges/jest.setup.ts @@ -0,0 +1,27 @@ +import type { Config } from '@jest/types' +import { L2Network, skipNetwork } from './common/alert-bundles' +import { spawnTestNode } from './common/utils/test.helpers' +import { globalExtended, portsByNetwork, paramsByNetwork } from './common/utils/test.helpers' + + +module.exports = async function (globalConfig: Config.GlobalConfig, projectConfig: Config.ProjectConfig) { + // TODO: run nodes in parallel + globalExtended.testNodes = {} + for (const network of Object.values(L2Network)) { + if (network === L2Network.Default) { continue } + if (skipNetwork(network)) { continue } + // if (network === L2Network.Mantle) { continue } + // if (network === L2Network.ZkSync) { continue } + + + const params = paramsByNetwork[network] + console.log(`\n`) + const { nodeProcess, rpcUrl } = await spawnTestNode( + params.L2_NETWORK_ID, params.L2_NETWORK_RPC, portsByNetwork[network] + ) + globalExtended.testNodes[network] = { + rpcUrl, + process: nodeProcess, + } + } +} diff --git a/l2-bridges/jest.teardown.ts b/l2-bridges/jest.teardown.ts new file mode 100644 index 00000000..39b303aa --- /dev/null +++ b/l2-bridges/jest.teardown.ts @@ -0,0 +1,14 @@ +import type { Config } from '@jest/types' +import { L2Network } from './common/alert-bundles' +import { globalExtended } from './common/utils/test.helpers' +import { stopTestNode } from './common/utils/test.helpers' + + +module.exports = async function (globalConfig: Config.GlobalConfig, projectConfig: Config.ProjectConfig) { + // TODO: stop in parallel + for (const network of Object.values(L2Network)) { + if (globalExtended.testNodes[network]) { + await stopTestNode(globalExtended.testNodes[network].process) + } + } +} diff --git a/l2-bridges/mantle/src/agent.ts b/l2-bridges/mantle/src/agent.ts index 413a64e6..02f459a6 100644 --- a/l2-bridges/mantle/src/agent.ts +++ b/l2-bridges/mantle/src/agent.ts @@ -42,12 +42,12 @@ export const mantleConstants: Constants = { L2_NAME, `51F04709-7E86-4FB3-B53C-24C53C99DA24` ), } -mantleConstants.bridgeEvents = getBridgeEvents(mantleConstants.L2_ERC20_TOKEN_GATEWAY.address, mantleConstants.rolesMap); -mantleConstants.govEvents = getGovEvents(mantleConstants.govExecutor as string); +mantleConstants.bridgeEvents = getBridgeEvents(mantleConstants.L2_ERC20_TOKEN_GATEWAY.address, mantleConstants.rolesMap) +mantleConstants.govEvents = getGovEvents(mantleConstants.govExecutor as string) mantleConstants.proxyAdminEvents = getProxyAdminEvents( mantleConstants.L2_WSTETH_BRIDGED as ContractInfo, mantleConstants.L2_ERC20_TOKEN_GATEWAY -); +) function getBridgeEvents(l2TokenBridgeAddress: string, rolesMap: RoleHashToName): EventOfNotice[] { @@ -394,6 +394,6 @@ function getProxyAdminEvents(l2Wsteth: ContractInfo, l2TokenBridge: ContractInfo export default { - initialize: App.initialize(mantleConstants), - handleBlock: App.handleBlock, + initialize: App.initializeStatic(mantleConstants), + handleBlock: App.handleBlockStatic, } diff --git a/l2-bridges/mantle/test/mantle.test.helpers.ts b/l2-bridges/mantle/test/mantle.test.helpers.ts index 64bbd9c0..e1e6d9a7 100644 --- a/l2-bridges/mantle/test/mantle.test.helpers.ts +++ b/l2-bridges/mantle/test/mantle.test.helpers.ts @@ -7,8 +7,8 @@ import { BigNumberish } from 'ethers' export async function withdrawWsteth(amount: BigNumberish, params: Constants, provider: JsonRpcProvider, sender: string) { const l2BridgeAddress = params.L2_ERC20_TOKEN_GATEWAY.address - provider.send('hardhat_setBalance', [l2BridgeAddress, ethers.utils.parseEther('10').toHexString()]) - provider.send('hardhat_setBalance', [sender, ethers.utils.parseEther('10').toHexString()]) + await provider.send('hardhat_setBalance', [l2BridgeAddress, ethers.utils.parseEther('10').toHexString()]) + await provider.send('hardhat_setBalance', [sender, ethers.utils.parseEther('10').toHexString()]) const l2Wsteth = new Contract(params.L2_WSTETH_BRIDGED.address, MANTLE_WSTETH_MINT_ABI, provider) const bridgeSigner = await provider.getSigner(l2BridgeAddress) diff --git a/l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts b/l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts index 2e8d13d4..d4c338a8 100644 --- a/l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts +++ b/l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts @@ -6,47 +6,26 @@ import BigNumber from 'bignumber.js' import { mantleConstants } from '../src/agent' import { SECOND_MS } from '../../common/utils/time' import assert from 'assert' -import { spawnTestNode, stopTestNode, createMonitorWithdrawals } from '../../common/utils/test.helpers' +import { globalExtended, createMonitorWithdrawals } from '../../common/utils/test.helpers' import { JsonRpcProvider } from '@ethersproject/providers' import { withdrawWsteth } from './mantle.test.helpers' import { L2Client } from '../../common/clients/l2_client' import { MAX_WITHDRAWALS_SUM } from '../../common/services/monitor_withdrawals' -import { App } from "../../common/agent" -import { BlockEvent, EventType, Network, Block } from "forta-agent" + +import { L2Network } from "../../common/alert-bundles" describe('MonitorWithdrawals on Mantle', () => { - let testNodeProcess: any = null let monitorWithdrawals: MonitorWithdrawals = null as any let provider: JsonRpcProvider = null as any let l2Client: L2Client = null as any beforeAll(async () => { - const { nodeProcess, rpcUrl } = await spawnTestNode(mantleConstants.L2_NETWORK_ID, mantleConstants.L2_NETWORK_RPC) - testNodeProcess = nodeProcess - mantleConstants.L2_NETWORK_RPC = rpcUrl; - - ({ monitorWithdrawals, provider, l2Client } = createMonitorWithdrawals(mantleConstants)) - }); - - xtest(`TODO`, async () => { - let initialize = App.initialize(mantleConstants) - await initialize() - let app = await App.getInstance() - - const currentL2Block = await app.provider.getBlock('latest') - console.debug({ currentL2Block }) - - const blockEvent = new BlockEvent(EventType.BLOCK, Network.MAINNET, { - blockNumber: currentL2Block.number, - block: { - number: currentL2Block.number, - } - } as unknown as Block) - console.debug({ blockEvent }) - console.debug({ blockEventNUmber: blockEvent.blockNumber }) - // await App.handleBlock(blockEvent) + ({ monitorWithdrawals, provider, l2Client } = createMonitorWithdrawals({ + ...mantleConstants, + L2_NETWORK_RPC: globalExtended.testNodes[L2Network.Mantle].rpcUrl, + })) }) test(`mvp of a test with emulation: max withdrawals`, async () => { @@ -89,8 +68,4 @@ describe('MonitorWithdrawals on Mantle', () => { }, ]) }, 20 * SECOND_MS) - - afterAll(async () => { - await stopTestNode(testNodeProcess) - }); }) diff --git a/l2-bridges/package.json b/l2-bridges/package.json index 94a357f2..01ec09a9 100644 --- a/l2-bridges/package.json +++ b/l2-bridges/package.json @@ -34,7 +34,7 @@ "@ethersproject/abi": "^5.0.0", "@ethersproject/providers": "^5.0.0", "@jest/globals": "^29.7.0", - "@tsconfig/node20": "^20.1.2", + "@tsconfig/node20": "^20.1.4", "@typechain/ethers-v5": "^11.1.2", "@types/jest": "^29.5.10", "@types/nodemon": "^1.19.0", diff --git a/l2-bridges/scroll/src/agent.ts b/l2-bridges/scroll/src/agent.ts index 9a505a72..7ba678b5 100644 --- a/l2-bridges/scroll/src/agent.ts +++ b/l2-bridges/scroll/src/agent.ts @@ -43,12 +43,12 @@ export const scrollConstants: Constants = { L2_NAME, `E1F5563E-C44D-4ACA-825E-F5A771B9D8C0` ) } -scrollConstants.bridgeEvents = getBridgeEvents(scrollConstants.L2_ERC20_TOKEN_GATEWAY.address, scrollConstants.rolesMap); -scrollConstants.govEvents = getGovEvents(scrollConstants.govExecutor as string); +scrollConstants.bridgeEvents = getBridgeEvents(scrollConstants.L2_ERC20_TOKEN_GATEWAY.address, scrollConstants.rolesMap) +scrollConstants.govEvents = getGovEvents(scrollConstants.govExecutor as string) scrollConstants.proxyAdminEvents = getProxyAdminEvents( scrollConstants.L2_WSTETH_BRIDGED as ContractInfo, scrollConstants.L2_ERC20_TOKEN_GATEWAY -); +) function getBridgeEvents(l2GatewayAddress: string, RolesAddrToNameMap: RoleHashToName): EventOfNotice[] { @@ -332,6 +332,6 @@ function getProxyAdminEvents(l2WstethContract: ContractInfo, l2GatewayContract: } export default { - initialize: App.initialize(scrollConstants), - handleBlock: App.handleBlock, + initialize: App.initializeStatic(scrollConstants), + handleBlock: App.handleBlockStatic, } diff --git a/l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts b/l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts index f0d39734..bacacacf 100644 --- a/l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts +++ b/l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts @@ -1,25 +1,24 @@ import * as E from 'fp-ts/Either' -import { MonitorWithdrawals } from '../../common/services/monitor_withdrawals' import assert from 'assert' import { expect } from '@jest/globals' import BigNumber from 'bignumber.js' +import { MonitorWithdrawals } from '../../common/services/monitor_withdrawals' import { scrollConstants } from '../src/agent' -import { spawnTestNode, stopTestNode, createMonitorWithdrawals } from '../../common/utils/test.helpers' +import { createMonitorWithdrawals } from '../../common/utils/test.helpers' import { SECOND_MS } from '../../common/utils/time' describe('MonitorWithdrawals on Scroll', () => { - let testNodeProcess: any = null let monitorWithdrawals: MonitorWithdrawals = null as any beforeAll(async () => { - const { nodeProcess, rpcUrl } = await spawnTestNode(scrollConstants.L2_NETWORK_ID, scrollConstants.L2_NETWORK_RPC) - testNodeProcess = nodeProcess - scrollConstants.L2_NETWORK_RPC = rpcUrl; - - ({ monitorWithdrawals } = createMonitorWithdrawals(scrollConstants)) - }); + ({ monitorWithdrawals } = createMonitorWithdrawals({ + ...scrollConstants, + // Commented because fork is not needed for these tests + // L2_NETWORK_RPC: globalExtended.testNodes[L2Network.Scroll].rpcUrl, + })) + }) test(`getWithdrawalRecordsInBlockRange: 3 withdrawals, 3889 blocks`, async () => { const withdrawalRecords = await monitorWithdrawals._getWithdrawalRecordsInBlockRange(6608787, 6612676) @@ -45,8 +44,4 @@ describe('MonitorWithdrawals on Scroll', () => { assert(E.isRight(withdrawalRecords)) expect(withdrawalRecords.right).toHaveLength(25) }, 40 * SECOND_MS) - - afterAll(async () => { - await stopTestNode(testNodeProcess) - }); }) diff --git a/l2-bridges/yarn.lock b/l2-bridges/yarn.lock index eafd9ed8..1e65f10a 100644 --- a/l2-bridges/yarn.lock +++ b/l2-bridges/yarn.lock @@ -1085,7 +1085,7 @@ dependencies: "@sinonjs/commons" "^3.0.0" -"@tsconfig/node20@^20.1.2": +"@tsconfig/node20@^20.1.4": version "20.1.4" resolved "https://registry.yarnpkg.com/@tsconfig/node20/-/node20-20.1.4.tgz#3457d42eddf12d3bde3976186ab0cd22b85df928" integrity sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg== diff --git a/l2-bridges/zksync/src/agent.ts b/l2-bridges/zksync/src/agent.ts index 3564e6f2..d284a37b 100644 --- a/l2-bridges/zksync/src/agent.ts +++ b/l2-bridges/zksync/src/agent.ts @@ -8,7 +8,7 @@ const L2_NAME = 'ZkSync' const L2_PROXY_ADMIN_CONTRACT_ADDRESS = '0xbd80e505ecc49bae2cc86094a78fa0e2db28b52a'; export const zksyncConstants: Constants = { L2_NAME: L2_NAME, - L2_NETWORK_RPC: 'https://mainnet.era.zksync.io', + L2_NETWORK_RPC: 'https://mainnet.era.zksync.io', // 'https://zksync.drpc.org' MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST: 50_000, L2_NETWORK_ID: 324, L2_APPROX_BLOCK_TIME_SECONDS: 1, // see info at https://zksync.blockscout.com/ @@ -46,12 +46,12 @@ export const zksyncConstants: Constants = { L2_NAME, `C167F276-D519-4906-90CB-C4455E9ABBD4` ) } -zksyncConstants.bridgeEvents = getBridgeEvents(zksyncConstants.L2_ERC20_TOKEN_GATEWAY.address, zksyncConstants.rolesMap); -zksyncConstants.govEvents = getGovEvents((zksyncConstants.govExecutor as TransparentProxyInfo).address); +zksyncConstants.bridgeEvents = getBridgeEvents(zksyncConstants.L2_ERC20_TOKEN_GATEWAY.address, zksyncConstants.rolesMap) +zksyncConstants.govEvents = getGovEvents((zksyncConstants.govExecutor as TransparentProxyInfo).address) zksyncConstants.proxyAdminEvents = getProxyAdminEvents( zksyncConstants.L2_WSTETH_BRIDGED as TransparentProxyInfo, zksyncConstants.govExecutor as TransparentProxyInfo, -); +) function getBridgeEvents( @@ -402,6 +402,6 @@ function getProxyAdminEvents( } export default { - initialize: App.initialize(zksyncConstants), - handleBlock: App.handleBlock, + initialize: App.initializeStatic(zksyncConstants), + handleBlock: App.handleBlockStatic, } diff --git a/l2-bridges/zksync/test/monitor_withdrawals.zksync.spec.ts b/l2-bridges/zksync/test/monitor_withdrawals.zksync.spec.ts index 78d394c2..39df351b 100644 --- a/l2-bridges/zksync/test/monitor_withdrawals.zksync.spec.ts +++ b/l2-bridges/zksync/test/monitor_withdrawals.zksync.spec.ts @@ -1,32 +1,23 @@ import * as E from 'fp-ts/Either' import { expect } from '@jest/globals' +import assert from 'assert' import { zksyncConstants } from '../src/agent' import { SECOND_MS } from '../../common/utils/time' import { MonitorWithdrawals } from '../../common/services/monitor_withdrawals' -import { spawnTestNode, stopTestNode, createMonitorWithdrawals } from '../../common/utils/test.helpers' +import { createMonitorWithdrawals } from '../../common/utils/test.helpers' -import assert from 'assert' describe('MonitorWithdrawals on ZkSync', () => { - let testNodeProcess: any = null let monitorWithdrawals: MonitorWithdrawals = null as any beforeAll(async () => { - const { nodeProcess, rpcUrl } = await spawnTestNode(zksyncConstants.L2_NETWORK_ID, zksyncConstants.L2_NETWORK_RPC) - testNodeProcess = nodeProcess - zksyncConstants.L2_NETWORK_RPC = rpcUrl; - ({ monitorWithdrawals } = createMonitorWithdrawals(zksyncConstants)) - }); + }) test(`getWithdrawalRecordsInBlockRange: 2 withdrawals (225_821 blocks)`, async () => { const withdrawalRecords = await monitorWithdrawals._getWithdrawalRecordsInBlockRange(39424179, 39650000) assert(E.isRight(withdrawalRecords)) expect(withdrawalRecords.right).toHaveLength(2) }, 20 * SECOND_MS) - - afterAll(async () => { - await stopTestNode(testNodeProcess) - }); }) From 78f858eb5691b50f8df81dbe8f5d777bcad3d9ad Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Tue, 20 Aug 2024 12:56:08 +0300 Subject: [PATCH 17/18] refactor: use bigint instead of BigNumber for MonitorWithdrawals --- l2-bridges/common/entity/blockDto.ts | 2 +- l2-bridges/common/services/monitor_withdrawals.ts | 4 ++-- l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts | 4 ++-- l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts | 7 +++---- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/l2-bridges/common/entity/blockDto.ts b/l2-bridges/common/entity/blockDto.ts index b7aec27c..67509277 100644 --- a/l2-bridges/common/entity/blockDto.ts +++ b/l2-bridges/common/entity/blockDto.ts @@ -7,5 +7,5 @@ export type BlockDto = { export type WithdrawalRecord = { time: number - amount: BigNumber + amount: bigint } diff --git a/l2-bridges/common/services/monitor_withdrawals.ts b/l2-bridges/common/services/monitor_withdrawals.ts index 0b09b96c..383faff8 100644 --- a/l2-bridges/common/services/monitor_withdrawals.ts +++ b/l2-bridges/common/services/monitor_withdrawals.ts @@ -148,14 +148,14 @@ export class MonitorWithdrawals { } const blocksToRequest = new Set() const blockNumberToTime = new Map() - const withdrawalRecordsAux: { amount: BigNumber, blockNumber: number }[] = [] + const withdrawalRecordsAux: { amount: bigint, blockNumber: number }[] = [] for (const log of withdrawalLogsE.right) { const event = this.withdrawalInfo.eventInterface.parseLog(log) // NB: log.blockNumber is actually a string, although its typescript type is number const blockNumber = (typeof log.blockNumber === 'number') ? log.blockNumber : Number(log.blockNumber) withdrawalRecordsAux.push({ blockNumber: blockNumber, - amount: new BigNumber(String(event.args[this.withdrawalInfo.amountFieldName])) + amount: BigInt(String(event.args[this.withdrawalInfo.amountFieldName])) }) blocksToRequest.add(blockNumber) } diff --git a/l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts b/l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts index d4c338a8..c0ff0496 100644 --- a/l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts +++ b/l2-bridges/mantle/test/monitor_withdrawals.mantle.spec.ts @@ -60,11 +60,11 @@ describe('MonitorWithdrawals on Mantle', () => { expect(withdrawalRecords.right).toEqual([ { time: 1718122074, - amount: new BigNumber('337496585884301715'), + amount: 337496585884301715n, }, { time: 1720169272, - amount: new BigNumber('2107828861318592904'), + amount: 2107828861318592904n, }, ]) }, 20 * SECOND_MS) diff --git a/l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts b/l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts index bacacacf..afaa15fa 100644 --- a/l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts +++ b/l2-bridges/scroll/test/monitor_withdrawals.scroll.spec.ts @@ -1,7 +1,6 @@ import * as E from 'fp-ts/Either' import assert from 'assert' import { expect } from '@jest/globals' -import BigNumber from 'bignumber.js' import { MonitorWithdrawals } from '../../common/services/monitor_withdrawals' import { scrollConstants } from '../src/agent' @@ -26,15 +25,15 @@ describe('MonitorWithdrawals on Scroll', () => { expect(withdrawalRecords.right).toEqual([ { time: 1718529531, - amount: new BigNumber('2557056545136494'), + amount: 2557056545136494n, }, { time: 1718531871, - amount: new BigNumber('55285277863366'), + amount: 55285277863366n, }, { time: 1718541199, - amount: new BigNumber('16222703281150365'), + amount: 16222703281150365n, }, ]) }, 20 * SECOND_MS) From 9ff36e6918294b28a87b745d731123d98c169085 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Tue, 20 Aug 2024 14:26:06 +0300 Subject: [PATCH 18/18] feat: add optimism network --- l2-bridges/common/alert-bundles.ts | 137 ++++++++++++------ l2-bridges/common/constants.ts | 6 +- .../common/test/event-based-alerts.spec.ts | 2 + l2-bridges/common/utils/test.helpers.ts | 3 + l2-bridges/mantle/src/agent.ts | 18 +-- l2-bridges/optimism/package.json | 27 ++++ l2-bridges/optimism/src/agent.ts | 44 ++++++ l2-bridges/optimism/tsconfig.json | 6 + l2-bridges/scroll/src/agent.ts | 18 +-- l2-bridges/zksync/src/agent.ts | 18 +-- 10 files changed, 202 insertions(+), 77 deletions(-) create mode 100644 l2-bridges/optimism/package.json create mode 100644 l2-bridges/optimism/src/agent.ts create mode 100644 l2-bridges/optimism/tsconfig.json diff --git a/l2-bridges/common/alert-bundles.ts b/l2-bridges/common/alert-bundles.ts index 20f16214..bbccc2e5 100644 --- a/l2-bridges/common/alert-bundles.ts +++ b/l2-bridges/common/alert-bundles.ts @@ -8,19 +8,25 @@ import { JsonRpcProvider } from '@ethersproject/providers' const MIN_PROXY_ABI = [ - "function proxy__getAdmin() external view returns (address)", - "function proxy__getImplementation() view returns (address)", - "function proxy__changeAdmin(address owner)", - "function proxy__upgradeTo(address _newImplementation)", - "function proxy__ossify()", - "function proxy__getIsOssified() view returns (bool)", + 'function proxy__getAdmin() external view returns (address)', + 'function proxy__getImplementation() view returns (address)', + 'function proxy__changeAdmin(address owner)', + 'function proxy__upgradeTo(address _newImplementation)', + 'function proxy__ossify()', + 'function proxy__getIsOssified() view returns (bool)', +] + +const MIN_GOV_EXECUTOR_ABI = [ + 'function updateEthereumGovernanceExecutor(address ethereumGovernanceExecutor)', + 'function getEthereumGovernanceExecutor() view returns (address)', ] export function skipNetwork(network: L2Network) { if (network === L2Network.Default || network === L2Network.ZkSync - || network === L2Network.Scroll + // || network === L2Network.Scroll + // || network === L2Network.Optimism // || network === L2Network.Mantle ) { return true @@ -32,6 +38,7 @@ export function skipNetwork(network: L2Network) { export enum L2Network { Default = 'Default', + Optimism = 'Optimism', Mantle = 'Mantle', ZkSync = 'ZkSync', Scroll = 'Scroll', @@ -47,12 +54,17 @@ enum ContractType { export type ContractsSheet = { - [key: string]: { + [key: string]: { // one of enum L2Network [key: string]: string, } } export const contractsSheet: ContractsSheet = { + [L2Network.Optimism]: { + [ContractType.L2TokensBridge]: '0x8e01013243a96601a86eb3153f0d9fa4fbfb6957', + [ContractType.L2GovExecutor]: '0xefa0db536d2c8089685630fafe88cf7805966fc3', + [ContractType.L2Wsteth]: '0x1f32b1c2345538c0c6f582fcb022739c4a194ebb', + }, [L2Network.Mantle]: { [ContractType.L2TokensBridge]: '0x9c46560D6209743968cC24150893631A39AfDe4d', [ContractType.L2GovExecutor]: '0x3a7b055bf88cdc59d20d0245809c6e6b3c5819dd', @@ -105,24 +117,10 @@ export const eventBasedAlertBundleSheet: EventBundlesSheet = { event: 'event Upgraded(address indexed implementation)', contracts: { [L2Network.Default]: [ContractType.L2TokensBridge, ContractType.L2Wsteth], - // [L2Network.ZkSync]: [ContractType.L2TokensBridge, ContractType.L2Wsteth, ContractType.L2GovExecutor], - [L2Network.ZkSync]: [ContractType.L2TokensBridge], - }, - simulate: async (provider: JsonRpcProvider, address: string) => { - const newImplementation = address // just reusing an address at hand for simplicity - const contract = new Contract(address, MIN_PROXY_ABI, provider) - const adminAddress = await contract.proxy__getAdmin() - // console.debug({ adminAddress }) - await provider.send('hardhat_setBalance', [adminAddress, ethers.utils.parseEther('10').toHexString()]) - const signer = await provider.getSigner(adminAddress) - - const tx = await contract.connect(signer)["proxy__upgradeTo"](newImplementation) - const receipt = await tx.wait() - assert(receipt.logs.length > 0) - // TODO: fix assert - // assert(await contract['proxy__getImplementation']() === newImplementation) + [L2Network.Scroll]: [], }, }, + 'PROXY-ADMIN-CHANGED': { name: (_: NameArgs) => `🚨 ${_.network}: Proxy admin changed`, severity: FindingSeverity.High, @@ -136,20 +134,10 @@ export const eventBasedAlertBundleSheet: EventBundlesSheet = { event: 'event AdminChanged(address previousAdmin, address newAdmin)', contracts: { [L2Network.Default]: [ContractType.L2TokensBridge, ContractType.L2Wsteth], - [L2Network.ZkSync]: [ContractType.L2TokensBridge, ContractType.L2Wsteth, ContractType.L2GovExecutor], - }, - simulate: async (provider: JsonRpcProvider, address: string) => { - const arbitraryAddress = '0xb4ef9590f724565caf344cc6AFB86Df266529CeE' - const contract = new Contract(address, MIN_PROXY_ABI, provider) - const adminAddress = await contract['proxy__getAdmin']() - await provider.send('hardhat_setBalance', [adminAddress, ethers.utils.parseEther('10').toHexString()]) - const signer = await provider.getSigner(adminAddress) - const tx = await contract.connect(signer)["proxy__changeAdmin"](arbitraryAddress) - const receipt = await tx.wait() - assert(receipt.logs.length > 0) - assert(await contract['proxy__getAdmin']() === arbitraryAddress) + [L2Network.Scroll]: [], }, }, + 'PROXY-OSSIFIED': { name: (_: NameArgs) => `🚨 ${_.network}: Proxy ossified`, severity: FindingSeverity.High, @@ -162,22 +150,75 @@ export const eventBasedAlertBundleSheet: EventBundlesSheet = { event: 'event ProxyOssified()', contracts: { [L2Network.Default]: [ContractType.L2TokensBridge, ContractType.L2Wsteth], - // [L2Network.ZkSync]: [ContractType.L2TokensBridge, ContractType.L2Wsteth, ContractType.L2GovExecutor], - [L2Network.ZkSync]: [ContractType.L2TokensBridge], + [L2Network.Scroll]: [], + }, + }, + + 'GOV-BRIDGE-EXEC-UPDATED': { + name: (_: NameArgs) => `🚨 ${_.network} Gov Bridge: Ethereum Governance Executor Updated`, + severity: FindingSeverity.High, + description: (contractAlias: string, address: string) => { + return (_: EventArgs) => { + return `Ethereum Governance Executor was updated from ` + + `${_.oldEthereumGovernanceExecutor} to ${_.newEthereumGovernanceExecutor}` + } }, - simulate: async (provider: JsonRpcProvider, address: string) => { - const contract = new Contract(address, MIN_PROXY_ABI, provider) - const adminAddress = await contract['proxy__getAdmin']() - await provider.send('hardhat_setBalance', [adminAddress, ethers.utils.parseEther('10').toHexString()]) - const signer = await provider.getSigner(adminAddress) - const tx = await contract.connect(signer)["proxy__ossify"]() - const receipt = await tx.wait() - assert(receipt.logs.length > 0) - assert(await contract['proxy__getIsOssified']()) + event: 'event EthereumGovernanceExecutorUpdate(address oldEthereumGovernanceExecutor, address newEthereumGovernanceExecutor)', + contracts: { + [L2Network.Default]: [ContractType.L2GovExecutor], }, }, } +eventBasedAlertBundleSheet['PROXY-UPGRADED'].simulate = async (provider: JsonRpcProvider, address: string) => { + const newImplementation = address // just reusing an address at hand for simplicity + const contract = new Contract(address, MIN_PROXY_ABI, provider) + const adminAddress = await contract.proxy__getAdmin() + // console.debug({ adminAddress }) + await provider.send('hardhat_setBalance', [adminAddress, ethers.utils.parseEther('10').toHexString()]) + const signer = await provider.getSigner(adminAddress) + + const tx = await contract.connect(signer)["proxy__upgradeTo"](newImplementation) + const receipt = await tx.wait() + assert(receipt.logs.length > 0) + // TODO: fix assert + // assert(await contract['proxy__getImplementation']() === newImplementation) +} + +eventBasedAlertBundleSheet['PROXY-ADMIN-CHANGED'].simulate = async (provider: JsonRpcProvider, address: string) => { + const arbitraryAddress = '0xb4ef9590f724565caf344cc6AFB86Df266529CeE' + const contract = new Contract(address, MIN_PROXY_ABI, provider) + const adminAddress = await contract['proxy__getAdmin']() + await provider.send('hardhat_setBalance', [adminAddress, ethers.utils.parseEther('10').toHexString()]) + const signer = await provider.getSigner(adminAddress) + const tx = await contract.connect(signer)["proxy__changeAdmin"](arbitraryAddress) + const receipt = await tx.wait() + assert(receipt.logs.length > 0) + assert(await contract['proxy__getAdmin']() === arbitraryAddress) +} + +eventBasedAlertBundleSheet['PROXY-OSSIFIED'].simulate = async (provider: JsonRpcProvider, address: string) => { + const contract = new Contract(address, MIN_PROXY_ABI, provider) + const adminAddress = await contract['proxy__getAdmin']() + await provider.send('hardhat_setBalance', [adminAddress, ethers.utils.parseEther('10').toHexString()]) + const signer = await provider.getSigner(adminAddress) + const tx = await contract.connect(signer)["proxy__ossify"]() + const receipt = await tx.wait() + assert(receipt.logs.length > 0) + assert(await contract['proxy__getIsOssified']()) +} + +eventBasedAlertBundleSheet['GOV-BRIDGE-EXEC-UPDATED'].simulate = async (provider: JsonRpcProvider, address: string) => { + const arbitraryAddress = '0xb4ef9590f724565caf344cc6AFB86Df266529CeE' + const contract = new Contract(address, MIN_GOV_EXECUTOR_ABI, provider) + await provider.send('hardhat_setBalance', [address, ethers.utils.parseEther('10').toHexString()]) + const signer = await provider.getSigner(address) + const tx = await contract.connect(signer)["updateEthereumGovernanceExecutor"](arbitraryAddress) + const receipt = await tx.wait() + assert(receipt.logs.length > 0) + assert(await contract['getEthereumGovernanceExecutor']() === arbitraryAddress) +} + export function getEventBasedAlerts(networkName: string) { assert((Object.values(L2Network) as string[]).includes(networkName)) @@ -190,8 +231,10 @@ export function getEventBasedAlerts(networkName: string) { for (const contractAlias of contractsAliases) { assert(contractsSheet[networkName]) assert(contractsSheet[networkName][contractAlias]) + const contractAddress = formatAddress(contractsSheet[networkName][contractAlias]) assert(contractAddress === contractAddress.toLowerCase()) + result.push({ name: params.name({network: networkName}), address: contractAddress, diff --git a/l2-bridges/common/constants.ts b/l2-bridges/common/constants.ts index 233fbf6f..224c9d11 100644 --- a/l2-bridges/common/constants.ts +++ b/l2-bridges/common/constants.ts @@ -73,8 +73,8 @@ export type Constants = { L2_WSTETH_BRIDGED: ContractInfo | TransparentProxyInfo, rolesMap: RoleHashToName, withdrawalInfo: WithdrawalInfo, - bridgeEvents: EventOfNotice[], - govEvents: EventOfNotice[], - proxyAdminEvents: EventOfNotice[], + // bridgeEvents: EventOfNotice[], + // govEvents: EventOfNotice[], + // proxyAdminEvents: EventOfNotice[], getHugeWithdrawalsFromL2Alert: (params: HugeWithdrawalsFromL2AlertParams) => Finding, } diff --git a/l2-bridges/common/test/event-based-alerts.spec.ts b/l2-bridges/common/test/event-based-alerts.spec.ts index 40feed5f..bf6b7bf3 100644 --- a/l2-bridges/common/test/event-based-alerts.spec.ts +++ b/l2-bridges/common/test/event-based-alerts.spec.ts @@ -24,6 +24,8 @@ describe('Bundle tests', () => { const networkAlerts = getEventBasedAlerts(network) for (const alert of networkAlerts) { + // if (alert.alertId !== 'GOV-BRIDGE-EXEC-UPDATED') { continue } + if (!alert.simulate) { app.logger.info(`SKIP: ${alert.alertId} for ${network} at ${alert.address} (no simulate function)`) continue diff --git a/l2-bridges/common/utils/test.helpers.ts b/l2-bridges/common/utils/test.helpers.ts index 2652df78..524e73ad 100644 --- a/l2-bridges/common/utils/test.helpers.ts +++ b/l2-bridges/common/utils/test.helpers.ts @@ -9,6 +9,7 @@ import { ERC20Short__factory } from '../generated' import { L2Client } from '../clients/l2_client' import { MonitorWithdrawals } from '../services/monitor_withdrawals' import { L2Network } from '../../common/alert-bundles' +import { optimismConstants } from '../../optimism/src/agent' import { scrollConstants } from '../../scroll/src/agent' import { mantleConstants } from '../../mantle/src/agent' import { zksyncConstants } from '../../zksync/src/agent' @@ -30,9 +31,11 @@ export const portsByNetwork = { [L2Network.Mantle]: 8651, [L2Network.ZkSync]: 8652, [L2Network.Scroll]: 8653, + [L2Network.Optimism]: 8654, } export const paramsByNetwork = { + [L2Network.Optimism]: optimismConstants, [L2Network.Mantle]: mantleConstants, [L2Network.ZkSync]: zksyncConstants, [L2Network.Scroll]: scrollConstants, diff --git a/l2-bridges/mantle/src/agent.ts b/l2-bridges/mantle/src/agent.ts index 02f459a6..9ff5ce45 100644 --- a/l2-bridges/mantle/src/agent.ts +++ b/l2-bridges/mantle/src/agent.ts @@ -35,19 +35,19 @@ export const mantleConstants: Constants = { )`, amountFieldName: "_amount", }, - bridgeEvents: [], - govEvents: [], - proxyAdminEvents: [], + // bridgeEvents: [], + // govEvents: [], + // proxyAdminEvents: [], getHugeWithdrawalsFromL2Alert: getHugeWithdrawalsFromL2AlertFactory( L2_NAME, `51F04709-7E86-4FB3-B53C-24C53C99DA24` ), } -mantleConstants.bridgeEvents = getBridgeEvents(mantleConstants.L2_ERC20_TOKEN_GATEWAY.address, mantleConstants.rolesMap) -mantleConstants.govEvents = getGovEvents(mantleConstants.govExecutor as string) -mantleConstants.proxyAdminEvents = getProxyAdminEvents( - mantleConstants.L2_WSTETH_BRIDGED as ContractInfo, - mantleConstants.L2_ERC20_TOKEN_GATEWAY -) +// mantleConstants.bridgeEvents = getBridgeEvents(mantleConstants.L2_ERC20_TOKEN_GATEWAY.address, mantleConstants.rolesMap) +// mantleConstants.govEvents = getGovEvents(mantleConstants.govExecutor as string) +// mantleConstants.proxyAdminEvents = getProxyAdminEvents( + // mantleConstants.L2_WSTETH_BRIDGED as ContractInfo, + // mantleConstants.L2_ERC20_TOKEN_GATEWAY +// ) function getBridgeEvents(l2TokenBridgeAddress: string, rolesMap: RoleHashToName): EventOfNotice[] { diff --git a/l2-bridges/optimism/package.json b/l2-bridges/optimism/package.json new file mode 100644 index 00000000..2c2ded68 --- /dev/null +++ b/l2-bridges/optimism/package.json @@ -0,0 +1,27 @@ +{ + "name": "lido-l2-bridge-optimism-bot", + "displayName": "Forta Agent Starter", + "version": "0.0.1", + "description": "Lido Detection Bot for Optimism part of L2 bridge", + "repository": { + "type": "git", + "directory": "https://github.com/lidofinance/alerting-forta/tree/main/l2-bridges" + }, + "license": "MIT", + "chainIds": [ + 1 + ], + "chainSettings": { + "default": { + "shards": 1, + "target": 5 + } + }, + "scripts": { + }, + "dependencies": { + }, + "devDependencies": { + }, + "packageManager": "yarn@1.22.21" +} diff --git a/l2-bridges/optimism/src/agent.ts b/l2-bridges/optimism/src/agent.ts new file mode 100644 index 00000000..5b4667f6 --- /dev/null +++ b/l2-bridges/optimism/src/agent.ts @@ -0,0 +1,44 @@ +import { App } from '../../common/agent' +import { Constants, DEFAULT_ROLES_MAP, getHugeWithdrawalsFromL2AlertFactory } from '../../common/constants' + +const L2_NAME = 'Optimism' +export const optimismConstants: Constants = { + L2_NAME: L2_NAME, + L2_NETWORK_RPC: 'https://mainnet.optimism.io', + MAX_BLOCKS_PER_RPC_GET_LOGS_REQUEST: 10_000, + L2_NETWORK_ID: 10, + L2_APPROX_BLOCK_TIME_SECONDS: 2, // exactly two secs for Optimism + L2_PROXY_ADMIN_CONTRACT_ADDRESS: '', // TODO + govExecutor: '0xefa0db536d2c8089685630fafe88cf7805966fc3', + L1_ERC20_TOKEN_GATEWAY_ADDRESS: '0x76943c0d61395d8f2edf9060e1533529cae05de6', + L2_ERC20_TOKEN_GATEWAY: { + name: 'L2_ERC20_TOKEN_GATEWAY', + address: '0x8e01013243a96601a86eb3153f0d9fa4fbfb6957', + }, + L2_WSTETH_BRIDGED: { + name: 'OPTIMISM_WSTETH_BRIDGED', + address: '0x1f32b1c2345538c0c6f582fcb022739c4a194ebb', + }, + rolesMap: DEFAULT_ROLES_MAP, + withdrawalInfo: { + eventName: 'WithdrawalInitiated', + eventDefinition: `event WithdrawalInitiated( + address indexed _l1Token, + address indexed _l2Token, + address indexed _from, + address _to, + uint256 _amount, + bytes _data +)`, + amountFieldName: "_amount", + }, + getHugeWithdrawalsFromL2Alert: getHugeWithdrawalsFromL2AlertFactory( + L2_NAME, `B833CDC4-7661-46E2-AD6C-F5A71277EBD5` + ), +} + + +export default { + initialize: App.initializeStatic(optimismConstants), + handleBlock: App.handleBlockStatic, +} diff --git a/l2-bridges/optimism/tsconfig.json b/l2-bridges/optimism/tsconfig.json new file mode 100644 index 00000000..642e5f34 --- /dev/null +++ b/l2-bridges/optimism/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../dist/optimism/src" + } +} diff --git a/l2-bridges/scroll/src/agent.ts b/l2-bridges/scroll/src/agent.ts index 7ba678b5..2c4ea046 100644 --- a/l2-bridges/scroll/src/agent.ts +++ b/l2-bridges/scroll/src/agent.ts @@ -36,19 +36,19 @@ export const scrollConstants: Constants = { )`, amountFieldName: "amount", }, - bridgeEvents: [], - govEvents: [], - proxyAdminEvents: [], + // bridgeEvents: [], + // govEvents: [], + // proxyAdminEvents: [], getHugeWithdrawalsFromL2Alert: getHugeWithdrawalsFromL2AlertFactory( L2_NAME, `E1F5563E-C44D-4ACA-825E-F5A771B9D8C0` ) } -scrollConstants.bridgeEvents = getBridgeEvents(scrollConstants.L2_ERC20_TOKEN_GATEWAY.address, scrollConstants.rolesMap) -scrollConstants.govEvents = getGovEvents(scrollConstants.govExecutor as string) -scrollConstants.proxyAdminEvents = getProxyAdminEvents( - scrollConstants.L2_WSTETH_BRIDGED as ContractInfo, - scrollConstants.L2_ERC20_TOKEN_GATEWAY -) +// scrollConstants.bridgeEvents = getBridgeEvents(scrollConstants.L2_ERC20_TOKEN_GATEWAY.address, scrollConstants.rolesMap) +// scrollConstants.govEvents = getGovEvents(scrollConstants.govExecutor as string) +// scrollConstants.proxyAdminEvents = getProxyAdminEvents( + // scrollConstants.L2_WSTETH_BRIDGED as ContractInfo, + // scrollConstants.L2_ERC20_TOKEN_GATEWAY +// ) function getBridgeEvents(l2GatewayAddress: string, RolesAddrToNameMap: RoleHashToName): EventOfNotice[] { diff --git a/l2-bridges/zksync/src/agent.ts b/l2-bridges/zksync/src/agent.ts index d284a37b..935d7ef9 100644 --- a/l2-bridges/zksync/src/agent.ts +++ b/l2-bridges/zksync/src/agent.ts @@ -39,19 +39,19 @@ export const zksyncConstants: Constants = { );`, amountFieldName: "amount", }, - bridgeEvents: [], - govEvents: [], - proxyAdminEvents: [], + // bridgeEvents: [], + // govEvents: [], + // proxyAdminEvents: [], getHugeWithdrawalsFromL2Alert: getHugeWithdrawalsFromL2AlertFactory( L2_NAME, `C167F276-D519-4906-90CB-C4455E9ABBD4` ) } -zksyncConstants.bridgeEvents = getBridgeEvents(zksyncConstants.L2_ERC20_TOKEN_GATEWAY.address, zksyncConstants.rolesMap) -zksyncConstants.govEvents = getGovEvents((zksyncConstants.govExecutor as TransparentProxyInfo).address) -zksyncConstants.proxyAdminEvents = getProxyAdminEvents( - zksyncConstants.L2_WSTETH_BRIDGED as TransparentProxyInfo, - zksyncConstants.govExecutor as TransparentProxyInfo, -) +// zksyncConstants.bridgeEvents = getBridgeEvents(zksyncConstants.L2_ERC20_TOKEN_GATEWAY.address, zksyncConstants.rolesMap) +// zksyncConstants.govEvents = getGovEvents((zksyncConstants.govExecutor as TransparentProxyInfo).address) +// zksyncConstants.proxyAdminEvents = getProxyAdminEvents( + // zksyncConstants.L2_WSTETH_BRIDGED as TransparentProxyInfo, + // zksyncConstants.govExecutor as TransparentProxyInfo, +// ) function getBridgeEvents(