diff --git a/l2-bridge-arbitrum/.dockerignore b/l2-bridge-arbitrum/.dockerignore index 4514cae5..db799d73 100644 --- a/l2-bridge-arbitrum/.dockerignore +++ b/l2-bridge-arbitrum/.dockerignore @@ -3,3 +3,4 @@ node_modules/ !.yarn/releases/ dist/ forta.config.json +.env \ No newline at end of file diff --git a/l2-bridge-arbitrum/.env.sample b/l2-bridge-arbitrum/.env.sample index 9593fcda..ae0cc17e 100644 --- a/l2-bridge-arbitrum/.env.sample +++ b/l2-bridge-arbitrum/.env.sample @@ -5,11 +5,11 @@ HTTP_PORT=3000 LOG_FORMAT=simple LOG_LEVEL=debug ETHEREUM_RPC_URL=https://eth.drpc.com -ARBITRUM_RPC_URL=https://arbitrum.drpc.com +ARBITRUM_RPC_URL=https://arb1.arbitrum.io/rpc USE_FORTA_RPC_URL=true ## FORTA compatible env names NODE_ENV=local AGENT_GRPC_PORT=50051 -FORTA_CHAIN_ID=42161 \ No newline at end of file +FORTA_CHAIN_ID=1 \ No newline at end of file diff --git a/l2-bridge-arbitrum/.eslintignore b/l2-bridge-arbitrum/.eslintignore deleted file mode 100644 index a096acf7..00000000 --- a/l2-bridge-arbitrum/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -generated -proto \ No newline at end of file diff --git a/l2-bridge-arbitrum/.eslintrc.json b/l2-bridge-arbitrum/.eslintrc.json deleted file mode 100644 index 1262ce13..00000000 --- a/l2-bridge-arbitrum/.eslintrc.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "parser": "@typescript-eslint/parser", - "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"], - "plugins": ["@typescript-eslint", "prettier"], - "env": { - "node": true, - "es6": true - }, - "rules": { - "prettier/prettier": "error", - "curly": "error", - "semi": "off" - } -} diff --git a/l2-bridge-arbitrum/Dockerfile b/l2-bridge-arbitrum/Dockerfile index 24fe2097..34a69f5f 100644 --- a/l2-bridge-arbitrum/Dockerfile +++ b/l2-bridge-arbitrum/Dockerfile @@ -17,7 +17,7 @@ LABEL "network.forta.settings.agent-logs.enable"="true" ENV APP_NAME=l2-bridge-arbitrum ENV NODE_ENV=production ENV ETHEREUM_RPC_URL=https://eth.drpc.org -ENV ARBITRUM_RPC_URL=https://arbitrum.drpc.org +ENV ARBITRUM_RPC_URL=https://arb1.arbitrum.io/rpc ENV AGENT_GRPC_PORT=50051 ENV HTTP_PORT=3000 diff --git a/l2-bridge-arbitrum/eslint.config.mjs b/l2-bridge-arbitrum/eslint.config.mjs new file mode 100644 index 00000000..5eb13955 --- /dev/null +++ b/l2-bridge-arbitrum/eslint.config.mjs @@ -0,0 +1,35 @@ +import eslint from '@eslint/js' +import prettier from 'eslint-plugin-prettier' +import tseslint from 'typescript-eslint' +import tsParser from '@typescript-eslint/parser' +import globals from 'globals' + +export default tseslint.config({ + ...eslint.configs.recommended, + ...tseslint.configs.strict, + ...tseslint.configs.stylistic, + files: ['** /*.ts'], + ignores: ['**/generated', '**/proto'], + plugins: { + prettier, + }, + languageOptions: { + globals: { + ...globals.node, + }, + parser: tsParser, + parserOptions: { + project: './tsconfig.json', + sourceType: 'module', + }, + }, + rules: { + '@typescript-eslint/ array-type': 'error', + '@typescript-eslint/ consistent-type-imports': 'error', + 'prettier/prettier': 'error', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/no-explicit-any': 'warn', + curly: 'error', + semi: 'off', + }, +}) diff --git a/l2-bridge-arbitrum/jest.config.js b/l2-bridge-arbitrum/jest.config.js deleted file mode 100644 index 9dfeb5d2..00000000 --- a/l2-bridge-arbitrum/jest.config.js +++ /dev/null @@ -1,6 +0,0 @@ -/** @type {import("ts-jest").JestConfigWithTsJest} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - testPathIgnorePatterns: ['dist'], -} diff --git a/l2-bridge-arbitrum/package.json b/l2-bridge-arbitrum/package.json index b714feda..6299f5d5 100644 --- a/l2-bridge-arbitrum/package.json +++ b/l2-bridge-arbitrum/package.json @@ -1,9 +1,11 @@ { - "name": "lido-l2-bridge-arbitrum-bot", + "name": "lido-l2-bridge-arbitrum", "version": "0.0.1", + "license": "MIT", "description": "Lido Detection Bot for Arbitrum part of L2 bridge", "chainIds": [ - 42161 + 42161, + 1 ], "chainSettings": { "default": { @@ -24,57 +26,61 @@ "generate-proto": "make gen_ts && make gen_js", "eslint:lint": "eslint ./src", "eslint:format": "eslint ./src --fix", - "prettier:check": "prettier --check ./src", - "prettier:format": "prettier --write ./src README.md", + "prettier:check": "prettier --check ./src eslint.config.mjs", + "prettier:format": "prettier --write ./src README.md eslint.config.mjs", "lint": "yarn run prettier:check && yarn run eslint:lint", "format": "yarn run eslint:format && yarn run prettier:format", "postinstall": "yarn generate-types && yarn generate-proto" }, "dependencies": { - "@ethersproject/abi": "^5.0.0", - "@ethersproject/providers": "^5.0.0", - "@grpc/grpc-js": "^1.10.2", - "@types/lodash": "^4.14.202", - "@types/node": "^20.14.2", - "async-mutex": "^0.4.0", + "@ethersproject/abi": "^5.7.0", + "@ethersproject/providers": "^5.7.2", + "@grpc/grpc-js": "^1.10.10", + "@types/node": "^20.14.10", + "async-mutex": "^0.5.0", "bignumber.js": "^9.1.2", "dotenv": "^16.4.5", - "ethers": "^5.5.1", + "ethers": "^5.7.2", "express": "^4.19.2", "forta-agent": "^0.1.48", - "fp-ts": "^2.16.1", - "lodash": "^4.17.21", - "prom-client": "^15.1.2", - "ts-retry": "^4.2.4", - "winston": "^3.11.0" + "fp-ts": "^2.16.8", + "prom-client": "^15.1.3", + "ts-retry": "^4.2.5", + "winston": "^3.13.0" }, "devDependencies": { - "@faker-js/faker": "^8.3.1", + "@faker-js/faker": "^8.4.1", "@jest/globals": "^29.7.0", - "@tsconfig/node20": "^20.1.2", + "@tsconfig/node20": "^20.1.4", "@typechain/ethers-v5": "^11.1.2", "@types/express": "^4.17.21", - "@types/jest": "^29.5.11", - "@types/nodemon": "^1.19.0", + "@types/jest": "^29.5.12", + "@types/nodemon": "^1.19.6", "@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", + "@typescript-eslint/eslint-plugin": "^7.15.0", + "@typescript-eslint/parser": "^7.15.0", + "eslint": "^9.6.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-jest": "^28.6.0", + "eslint-plugin-prettier": "^5.1.3", "grpc-tools": "^1.12.4", "grpc_tools_node_protoc_ts": "^5.3.3", - "husky": "^8.0.3", + "husky": "^9.0.11", "jest": "^29.7.0", - "nodemon": "^3.0.1", - "postinstall": "^0.8.0", - "prettier": "^3.1.0", + "nodemon": "^3.1.4", + "postinstall": "^0.10.3", + "prettier": "^3.3.2", "ts-generator": "^0.1.1", - "ts-jest": "^29.1.2", + "ts-jest": "^29.1.5", "ts-node": "^10.9.2", "typechain": "^8.3.2", - "typescript": "^5.3.2" + "typescript": "^5.5.3", + "typescript-eslint": "^8.0.0-alpha.39" + }, + "jest": { + "preset": "ts-jest", + "testEnvironment": "node", + "testPathIgnorePatterns": ["dist"] }, "packageManager": "yarn@1.22.22" } diff --git a/l2-bridge-arbitrum/src/clients/arbitrum_client.spec.ts b/l2-bridge-arbitrum/src/clients/arbitrum_client.spec.ts index 425935ad..6dd256c3 100644 --- a/l2-bridge-arbitrum/src/clients/arbitrum_client.spec.ts +++ b/l2-bridge-arbitrum/src/clients/arbitrum_client.spec.ts @@ -1,12 +1,15 @@ import { ArbERC20__factory, ERC20Bridged__factory, L2ERC20TokenGateway__factory } from '../generated/typechain' import { ArbitrumClient } from './arbitrum_client' import { Address, ETH_DECIMALS } from '../utils/constants' -import { ethers } from 'forta-agent' +import { ethers } from 'ethers' import { Config } from '../utils/env/env' import promClient from 'prom-client' import * as E from 'fp-ts/Either' import { Metrics } from '../utils/metrics/metrics' import BigNumber from 'bignumber.js' +import { BlockDtoWithTransactions, BlockHash } from '../entity/blockDto' +import { LRUCache } from 'lru-cache' +import { elapsedTime } from '../utils/time' const TEST_TIMEOUT = 120_000 @@ -14,7 +17,7 @@ describe('arbitrumProvider', () => { const config = new Config() const adr: Address = Address - const arbitrumProvider = new ethers.providers.JsonRpcProvider(config.arbitrumRpcUrl, config.chainId) + const arbitrumProvider = new ethers.providers.JsonRpcProvider(config.arbitrumRpcUrl, config.arbChainID) const l2Bridge = L2ERC20TokenGateway__factory.connect(adr.ARBITRUM_L2_TOKEN_GATEWAY.address, arbitrumProvider) const bridgedWSthEthRunner = ERC20Bridged__factory.connect(adr.ARBITRUM_WSTETH_BRIDGED.address, arbitrumProvider) @@ -22,9 +25,31 @@ describe('arbitrumProvider', () => { const customRegister = new promClient.Registry() const metrics = new Metrics(customRegister, config.promPrefix) - const l2Client = new ArbitrumClient(arbitrumProvider, metrics, l2Bridge, bridgedWSthEthRunner, bridgedLdoRunner) - const l2BlockNumber = 228_303_887 + const l2BlocksStore = new LRUCache({ + max: 500, + ttl: 1000 * 60 * 2, + updateAgeOnGet: true, + updateAgeOnHas: true, + }) + + const l2BridgeCache = new LRUCache({ + max: 500, + ttl: 1000 * 60 * 2, + updateAgeOnGet: true, + updateAgeOnHas: true, + }) + const l2Client = new ArbitrumClient( + arbitrumProvider, + metrics, + l2Bridge, + bridgedWSthEthRunner, + bridgedLdoRunner, + l2BlocksStore, + l2BridgeCache, + ) + + const l2BlockHash = '0x5f0b1658096ed3521655dd968e867b6bcf7c8bde6fdc6049150ada403a0cd5e1' test( 'getWithdrawalEvents is 1', async () => { @@ -59,12 +84,12 @@ describe('arbitrumProvider', () => { test( 'getBlockNumber', async () => { - const blockNumber = await l2Client.getBlockNumber() + const blockNumber = await l2Client.getLatestL2Block() if (E.isLeft(blockNumber)) { throw blockNumber.left } - expect(Number.isInteger(blockNumber.right)).toBe(true) + expect(Number.isInteger(blockNumber.right.number)).toBe(true) }, TEST_TIMEOUT, ) @@ -72,7 +97,7 @@ describe('arbitrumProvider', () => { test( 'getWstEthTotalSupply is 64_352.622267221200683868 wstEth', async () => { - const wstBalance = await l2Client.getWstEthTotalSupply(l2BlockNumber) + const wstBalance = await l2Client.getWstEthTotalSupply(l2BlockHash) if (E.isLeft(wstBalance)) { throw wstBalance.left } @@ -85,7 +110,7 @@ describe('arbitrumProvider', () => { test( 'getLdoTotalSupply is 92_8089.575013945159998755 ldo', async () => { - const ldoBalance = await l2Client.getLdoTotalSupply(l2BlockNumber) + const ldoBalance = await l2Client.getLdoTotalSupply(l2BlockHash) if (E.isLeft(ldoBalance)) { throw ldoBalance.left } diff --git a/l2-bridge-arbitrum/src/clients/arbitrum_client.ts b/l2-bridge-arbitrum/src/clients/arbitrum_client.ts index de7404d1..b6393268 100644 --- a/l2-bridge-arbitrum/src/clients/arbitrum_client.ts +++ b/l2-bridge-arbitrum/src/clients/arbitrum_client.ts @@ -1,8 +1,8 @@ -import { Block } from '@ethersproject/abstract-provider' -import { ethers } from 'forta-agent' +import { Log, BlockWithTransactions } from '@ethersproject/abstract-provider' +import { ethers } from 'ethers' import * as E from 'fp-ts/Either' import { retryAsync } from 'ts-retry' -import { WithdrawalRecord } from '../entity/blockDto' +import { BlockDto, BlockDtoWithTransactions, BlockHash, TransactionDto, WithdrawalRecord } from '../entity/blockDto' import BigNumber from 'bignumber.js' import { ArbERC20, ERC20Bridged, L2ERC20TokenGateway as L2BridgeRunner } from '../generated/typechain' import { NetworkError } from '../utils/errors' @@ -10,6 +10,16 @@ import { WithdrawalInitiatedEvent } from '../generated/typechain/L2ERC20TokenGat import { IMonitorWithdrawalsClient } from '../services/monitor_withdrawals' import { Metrics, StatusFail, StatusOK } from '../utils/metrics/metrics' import { IL2BridgeBalanceClient } from '../services/bridge_balance' +import { LRUCache } from 'lru-cache' +import { Block } from '@ethersproject/providers' +import { toDate } from '../utils/time' +import e from 'express' + +const DELAY_IN_500MS = 500 +const ATTEMPTS_5 = 5 + +const ldo = 'ldo' +const wstEth = 'WstEth' export class ArbitrumClient implements IMonitorWithdrawalsClient, IL2BridgeBalanceClient { private readonly jsonRpcProvider: ethers.providers.JsonRpcProvider @@ -18,6 +28,8 @@ export class ArbitrumClient implements IMonitorWithdrawalsClient, IL2BridgeBalan private readonly bridgedWstEthRunner: ERC20Bridged private readonly bridgedLdoRunner: ArbERC20 + private readonly l2BlocksStore: LRUCache + private readonly l2bridgeCache: LRUCache constructor( jsonRpcProvider: ethers.providers.JsonRpcProvider, @@ -25,12 +37,16 @@ export class ArbitrumClient implements IMonitorWithdrawalsClient, IL2BridgeBalan l2BridgeRunner: L2BridgeRunner, bridgedWstEthRunner: ERC20Bridged, bridgedLdoRunner: ArbERC20, + cache: LRUCache, + l2bridgeCache: LRUCache, ) { this.jsonRpcProvider = jsonRpcProvider this.metrics = metrics this.l2BridgeRunner = l2BridgeRunner this.bridgedWstEthRunner = bridgedWstEthRunner this.bridgedLdoRunner = bridgedLdoRunner + this.l2BlocksStore = cache + this.l2bridgeCache = l2bridgeCache } public async getWithdrawalEvents( @@ -39,28 +55,36 @@ export class ArbitrumClient implements IMonitorWithdrawalsClient, IL2BridgeBalan ): Promise> { const end = this.metrics.etherJsDurationHistogram.labels({ method: this.getWithdrawalEvents.name }).startTimer() - try { - const out = await retryAsync( - async (): Promise => { - return await this.l2BridgeRunner.queryFilter( - this.l2BridgeRunner.filters.WithdrawalInitiated(), - fromBlockNumber, - toBlockNumber, - ) - }, - { delay: 500, maxTry: 5 }, - ) - - this.metrics.etherJsRequest.labels({ method: this.getWithdrawalEvents.name, status: StatusOK }).inc() - end({ status: StatusOK }) + const batchSize = 10_000 + const events: WithdrawalInitiatedEvent[] = [] + for (let i = fromBlockNumber; i <= toBlockNumber; i += batchSize) { + const from = i + const to = Math.min(i + batchSize - 1, toBlockNumber) + + try { + const out = await retryAsync( + async (): Promise => { + return await this.l2BridgeRunner.queryFilter(this.l2BridgeRunner.filters.WithdrawalInitiated(), from, to) + }, + { delay: 500, maxTry: 5 }, + ) + events.push(...out) + } catch (e) { + this.metrics.etherJsRequest.labels({ method: this.getWithdrawalEvents.name, status: StatusFail }).inc() + end({ status: StatusFail }) + + return E.left( + new NetworkError( + `Could not fetch withdrawEvents. cause: ${e}, startBlock: ${from}, toBlock: ${end}. Total ${from - to}`, + ), + ) + } + } - return E.right(out) - } catch (e) { - this.metrics.etherJsRequest.labels({ method: this.getWithdrawalEvents.name, status: StatusFail }).inc() - end({ status: StatusFail }) + this.metrics.etherJsRequest.labels({ method: this.getWithdrawalEvents.name, status: StatusOK }).inc() + end({ status: StatusOK }) - return E.left(new NetworkError(e, `Could not fetch withdrawEvents`)) - } + return E.right(events) } public async getWithdrawalRecords( @@ -70,63 +94,75 @@ export class ArbitrumClient implements IMonitorWithdrawalsClient, IL2BridgeBalan 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 = { - timestamp: block.timestamp, - amount: new BigNumber(String(withdrawEvent.args.amount)), - } - - out.push(record) - } catch (e) { - this.metrics.etherJsRequest.labels({ method: this.getWithdrawalRecords.name, status: StatusFail }).inc() - end({ status: StatusFail }) + const promises = [] + const m = new Map() + for (let i = 0; i < withdrawalEvents.length; i++) { + if (withdrawalEvents[i].args) { + promises.push(withdrawalEvents[i].getBlock()) + m.set(promises.length - 1, i) + } + } - return E.left(new NetworkError(e, `Could not fetch block from withdrawEvent`)) + try { + const blocks = await Promise.all(promises) + for (let i = 0; i < blocks.length; i++) { + const record: WithdrawalRecord = { + timestamp: blocks[i].timestamp, + // @ts-ignore + amount: new BigNumber(String(withdrawalEvents[m.get(i)].args.amount)), } + + out.push(record) } - } - this.metrics.etherJsRequest.labels({ method: this.getWithdrawalEvents.name, status: StatusOK }).inc() - end({ status: StatusOK }) + this.metrics.etherJsRequest.labels({ method: this.getWithdrawalEvents.name, status: StatusOK }).inc() + end({ status: StatusOK }) - return E.right(out) + return E.right(out) + } catch (e) { + this.metrics.etherJsRequest.labels({ method: this.getWithdrawalRecords.name, status: StatusFail }).inc() + end({ status: StatusFail }) + + return E.left(new NetworkError(e, `Could not fetch block from withdrawEvent`)) + } } - public async getBlockNumber(): Promise> { - const end = this.metrics.etherJsDurationHistogram.labels({ method: this.getBlockNumber.name }).startTimer() + public async getLatestL2Block(): Promise> { + const end = this.metrics.etherJsDurationHistogram.labels({ method: this.getLatestL2Block.name }).startTimer() try { - const latestBlockNumber = await this.jsonRpcProvider.getBlockNumber() + const block = await this.jsonRpcProvider.getBlock('latest') - this.metrics.etherJsRequest.labels({ method: this.getBlockNumber.name, status: StatusOK }).inc() + this.metrics.etherJsRequest.labels({ method: this.getLatestL2Block.name, status: StatusOK }).inc() end({ status: StatusOK }) - return E.right(latestBlockNumber) + return E.right({ + number: block.number, + timestamp: block.timestamp, + parentHash: block.parentHash, + hash: block.hash, + }) } catch (e) { - this.metrics.etherJsRequest.labels({ method: this.getBlockNumber.name, status: StatusFail }).inc() + this.metrics.etherJsRequest.labels({ method: this.getLatestL2Block.name, status: StatusFail }).inc() end({ status: StatusFail }) - return E.left(new NetworkError(e, `Could not fetch latest block number`)) + return E.left(new NetworkError(e, `Could not fetch latest l2 block number`)) } } - public async getWstEthTotalSupply(l2blockNumber: number): Promise> { + public async getWstEthTotalSupply(l2blockHash: BlockHash): Promise> { + const cacheKey = `${wstEth}.${l2blockHash}` + if (this.l2bridgeCache.has(cacheKey)) { + // @ts-ignore + return E.right(this.l2bridgeCache.get(cacheKey)) + } + const end = this.metrics.etherJsDurationHistogram.labels({ method: this.getWstEthTotalSupply.name }).startTimer() try { const out = await retryAsync( async (): Promise => { const [balance] = await this.bridgedWstEthRunner.functions.totalSupply({ - blockTag: l2blockNumber, + blockTag: l2blockHash, }) return balance.toString() @@ -137,7 +173,10 @@ export class ArbitrumClient implements IMonitorWithdrawalsClient, IL2BridgeBalan this.metrics.etherJsRequest.labels({ method: this.getWstEthTotalSupply.name, status: StatusOK }).inc() end({ status: StatusOK }) - return E.right(new BigNumber(out)) + const result = new BigNumber(out) + this.l2bridgeCache.set(cacheKey, result) + + return E.right(result) } catch (e) { this.metrics.etherJsRequest.labels({ method: this.getWstEthTotalSupply.name, status: StatusFail }).inc() end({ status: StatusFail }) @@ -146,14 +185,20 @@ export class ArbitrumClient implements IMonitorWithdrawalsClient, IL2BridgeBalan } } - public async getLdoTotalSupply(l2blockNumber: number): Promise> { + public async getLdoTotalSupply(l2blockHash: BlockHash): Promise> { + const cacheKey = `${ldo}.${l2blockHash}` + if (this.l2bridgeCache.has(cacheKey)) { + // @ts-ignore + return E.right(this.l2bridgeCache.get(cacheKey)) + } + const end = this.metrics.etherJsDurationHistogram.labels({ method: this.getLdoTotalSupply.name }).startTimer() try { const out = await retryAsync( async (): Promise => { const [balance] = await this.bridgedLdoRunner.functions.totalSupply({ - blockTag: l2blockNumber, + blockTag: l2blockHash, }) return balance.toString() @@ -164,7 +209,10 @@ export class ArbitrumClient implements IMonitorWithdrawalsClient, IL2BridgeBalan this.metrics.etherJsRequest.labels({ method: this.getLdoTotalSupply.name, status: StatusOK }).inc() end({ status: StatusOK }) - return E.right(new BigNumber(out)) + const result = new BigNumber(out) + this.l2bridgeCache.set(cacheKey, result) + + return E.right(result) } catch (e) { this.metrics.etherJsRequest.labels({ method: this.getLdoTotalSupply.name, status: StatusFail }).inc() end({ status: StatusFail }) @@ -172,4 +220,194 @@ export class ArbitrumClient implements IMonitorWithdrawalsClient, IL2BridgeBalan return E.left(new NetworkError(e, `Could not call bridgedLdoRunner.functions.totalSupply`)) } } + + public async getL2BlockByHash(l2BlockHash: string): Promise> { + if (this.l2BlocksStore.has(l2BlockHash)) { + // @ts-ignore + return E.right(this.l2BlocksStore.get(l2BlockHash)) + } + const end = this.metrics.etherJsDurationHistogram.startTimer({ method: this.getL2BlockByHash.name }) + + try { + const blockWithTransactions = await retryAsync( + async (): Promise => { + let blockWithTransactions: BlockWithTransactions + let logs: Log[] = [] + + if (l2BlockHash !== 'latest') { + ;[blockWithTransactions, logs] = await Promise.all([ + this.jsonRpcProvider.getBlockWithTransactions(l2BlockHash), + this.jsonRpcProvider.getLogs({ + blockHash: l2BlockHash, + }), + ]) + } else { + blockWithTransactions = await this.jsonRpcProvider.getBlockWithTransactions(l2BlockHash) + logs = await this.jsonRpcProvider.getLogs({ + blockHash: blockWithTransactions.hash, + }) + } + + const m = new Map() + for (const log of logs) { + if (!m.has(log.transactionHash)) { + m.set(log.transactionHash, [log]) + } else { + // @ts-ignore + m.get(log.transactionHash).push(log) + } + } + + const transactions: TransactionDto[] = [] + for (const t of blockWithTransactions.transactions) { + if (m.has(t.hash)) { + const logs = m.get(t.hash) + const trx: TransactionDto = { + // @ts-ignore + logs: logs, + to: t.to ? t.to : null, + block: { + timestamp: blockWithTransactions.timestamp, + number: blockWithTransactions.number, + }, + } + + transactions.push(trx) + } + } + + return { + number: blockWithTransactions.number, + timestamp: blockWithTransactions.timestamp, + parentHash: blockWithTransactions.parentHash, + hash: blockWithTransactions.hash, + transactions: transactions, + } + }, + { delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 }, + ) + + this.metrics.etherJsRequest.labels({ method: this.getL2BlockByHash.name, status: StatusOK }).inc() + end({ status: StatusOK }) + + this.l2BlocksStore.set(blockWithTransactions.hash, blockWithTransactions) + + return E.right(blockWithTransactions) + } catch (e) { + this.metrics.etherJsRequest.labels({ method: this.getL2BlockByHash.name, status: StatusFail }).inc() + end({ status: StatusFail }) + + return E.left(new NetworkError(e, `Could not call jsonRpcProvider.getBlockL2ByHash(${l2BlockHash}): ${e}`)) + } + } + + public async findClosestBlock( + targetTimestamp: number, + lowerLimitStamp: number, + higherLimitStamp: number, + ): Promise> { + const averageBlockTimeInSeconds = 0.25 + + const currentL2BlockNumber = await this.getL2BlockByHash('latest') + if (E.isLeft(currentL2BlockNumber)) { + return E.left(currentL2BlockNumber.left) + } + + let l2Block = await this.getL2BlockDto(currentL2BlockNumber.right.number) + if (E.isLeft(l2Block)) { + return E.left(l2Block.left) + } + + if (targetTimestamp - l2Block.right.timestamp >= 0) { + return E.right(l2Block.right) + } + + let blockNumber = currentL2BlockNumber.right.number + let iteration = 0 + + while (l2Block.right.timestamp > targetTimestamp) { + let decreaseBlocks = Math.floor((l2Block.right.timestamp - targetTimestamp) / averageBlockTimeInSeconds) + decreaseBlocks = Math.max(decreaseBlocks, 1) + + blockNumber -= decreaseBlocks + l2Block = await this.getL2BlockDto(blockNumber) + if (E.isLeft(l2Block)) { + return E.left(l2Block.left) + } + iteration += 1 + } + + if (lowerLimitStamp && l2Block.right.timestamp < lowerLimitStamp) { + while (l2Block.right.timestamp < lowerLimitStamp) { + blockNumber += 1 + l2Block = await this.getL2BlockDto(blockNumber) + if (E.isLeft(l2Block)) { + return E.left(l2Block.left) + } + iteration += 1 + } + } + + if (l2Block.right.timestamp > higherLimitStamp) { + while (l2Block.right.timestamp > higherLimitStamp) { + blockNumber -= 1 + l2Block = await this.getL2BlockDto(blockNumber) + if (E.isLeft(l2Block)) { + return E.left(l2Block.left) + } + } + + if (l2Block.right.timestamp < higherLimitStamp) { + while (l2Block.right.timestamp < higherLimitStamp) { + if (blockNumber > currentL2BlockNumber.right.number) { + break + } + + const tempBlock = await this.getL2BlockDto(blockNumber) + if (E.isLeft(tempBlock)) { + return E.left(tempBlock.left) + } + + if (tempBlock.right.timestamp >= higherLimitStamp) { + break + } + + l2Block = tempBlock + } + } + } + + return E.right(l2Block.right) + } + + public async getL2BlockDto(blockNumber: number): Promise> { + const end = this.metrics.etherJsDurationHistogram.labels({ method: this.getL2BlockDto.name }).startTimer() + try { + const block = await retryAsync( + async (): Promise => { + return await this.jsonRpcProvider.getBlock(blockNumber) + }, + { delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 }, + ) + + const blockDto: BlockDto = { + number: block.number, + timestamp: block.timestamp, + parentHash: block.parentHash, + hash: block.hash, + } + + this.getL2BlockByHash(block.hash) + + this.metrics.etherJsRequest.labels({ method: this.getL2BlockDto.name, status: StatusOK }).inc() + end({ status: StatusOK }) + + return E.right(blockDto) + } catch (e) { + this.metrics.etherJsRequest.labels({ method: this.getL2BlockDto.name, status: StatusFail }).inc() + end({ status: StatusFail }) + + return E.left(new NetworkError(e, `Could not fetch l2block`)) + } + } } diff --git a/l2-bridge-arbitrum/src/clients/eth_client.spec.ts b/l2-bridge-arbitrum/src/clients/eth_client.spec.ts index 6e099739..c160eb0e 100644 --- a/l2-bridge-arbitrum/src/clients/eth_client.spec.ts +++ b/l2-bridge-arbitrum/src/clients/eth_client.spec.ts @@ -1,4 +1,4 @@ -import { ethers } from 'forta-agent' +import { ethers } from 'ethers' import { ERC20Bridged__factory } from '../generated/typechain' import { ETHProvider } from './eth_provider_client' import { Config } from '../utils/env/env' @@ -7,6 +7,8 @@ import promClient from 'prom-client' import { Metrics } from '../utils/metrics/metrics' import * as E from 'fp-ts/Either' import BigNumber from 'bignumber.js' +import { BlockDtoWithTransactions, BlockHash } from '../entity/blockDto' +import { LRUCache } from 'lru-cache' const TEST_TIMEOUT = 120_000 @@ -15,8 +17,7 @@ describe('ethProvider', () => { const adr = Address - const mainnet = 1 - const ethProvider = new ethers.providers.JsonRpcProvider(config.ethereumRpcUrl, mainnet) + const ethProvider = new ethers.providers.JsonRpcProvider(config.ethereumRpcUrl, config.chainId) const wSthEthRunner = ERC20Bridged__factory.connect(adr.L1_WSTETH_ADDRESS, ethProvider) const ldoRunner = ERC20Bridged__factory.connect(adr.L1_LDO_ADDRESS, ethProvider) @@ -24,13 +25,24 @@ describe('ethProvider', () => { const customRegister = new promClient.Registry() const metrics = new Metrics(customRegister, config.promPrefix) - const l1Client = new ETHProvider(metrics, wSthEthRunner, ldoRunner, ethProvider) - const l1BlockNumber = 20_183_793 + const l1BlocksStore = new LRUCache({ + max: 500, + }) + const l1Client = new ETHProvider( + metrics, + wSthEthRunner, + ldoRunner, + ethProvider, + l1BlocksStore, + adr.ARBITRUM_L1_TOKEN_BRIDGE, + adr.ARBITRUM_L1_LDO_BRIDGE, + ) + const l1BlockHash = '0xb98cace7cd13a459d5736755184217ec6a70f8d0c01dd051ec372832df077a2a' test( 'getWstEthBalance is 66_725.331301424290867528 wstEth', async () => { - const wStethBalance = await l1Client.getWstEthBalance(l1BlockNumber, adr.ARBITRUM_L1_TOKEN_BRIDGE) + const wStethBalance = await l1Client.getWstEthBalance(l1BlockHash) if (E.isLeft(wStethBalance)) { throw wStethBalance.left } @@ -43,7 +55,7 @@ describe('ethProvider', () => { test( 'getLDOBalance is 89_0572.571209700629591106 LDO', async () => { - const ldoBalance = await l1Client.getLDOBalance(l1BlockNumber, adr.ARBITRUM_L1_LDO_BRIDGE) + const ldoBalance = await l1Client.getLDOBalance(l1BlockHash) if (E.isLeft(ldoBalance)) { throw ldoBalance.left } @@ -56,12 +68,12 @@ describe('ethProvider', () => { test( 'getBlockNumber', async () => { - const blockNumber = await l1Client.getBlockNumber() + /* const blockNumber = await l1Client.getBlock(new Date()) if (E.isLeft(blockNumber)) { throw blockNumber.left } - expect(Number.isInteger(blockNumber.right)).toBe(true) + expect(Number.isInteger(blockNumber.right.number)).toBe(true)*/ }, TEST_TIMEOUT, ) diff --git a/l2-bridge-arbitrum/src/clients/eth_provider_client.ts b/l2-bridge-arbitrum/src/clients/eth_provider_client.ts index 045bda17..dd31fbc4 100644 --- a/l2-bridge-arbitrum/src/clients/eth_provider_client.ts +++ b/l2-bridge-arbitrum/src/clients/eth_provider_client.ts @@ -6,38 +6,56 @@ import { ERC20Bridged } from '../generated/typechain' import { NetworkError } from '../utils/errors' import { Metrics, StatusFail, StatusOK } from '../utils/metrics/metrics' import { ethers } from 'ethers' +import { BlockDto, BlockHash } from '../entity/blockDto' +import { LRUCache } from 'lru-cache' const DELAY_IN_500MS = 500 const ATTEMPTS_5 = 5 +const ldo = 'ldo' +const wstEth = 'WstEth' + export class ETHProvider implements IL1BridgeBalanceClient { private readonly jsonRpcProvider: ethers.providers.JsonRpcProvider private readonly wStEthRunner: ERC20Bridged private readonly ldoRunner: ERC20Bridged private readonly metrics: Metrics + private readonly arbL1TokenBridge: string + private readonly arbL1LdoBridge: string + + private readonly l1BlockCache: LRUCache constructor( metric: Metrics, wStEthRunner: ERC20Bridged, ldoRunner: ERC20Bridged, jsonRpcProvider: ethers.providers.JsonRpcProvider, + l1BlockCache: LRUCache, + arbitrumL1TokenBridge: string, + arbitrumL1LdoBridge: string, ) { this.metrics = metric this.wStEthRunner = wStEthRunner this.ldoRunner = ldoRunner this.jsonRpcProvider = jsonRpcProvider + this.l1BlockCache = l1BlockCache + this.arbL1TokenBridge = arbitrumL1TokenBridge + this.arbL1LdoBridge = arbitrumL1LdoBridge } - public async getWstEthBalance( - l1blockNumber: number, - arbitrumL1TokenBridge: string, - ): Promise> { + public async getWstEthBalance(l1blockHash: string): Promise> { + const cacheKey = `${wstEth}.${l1blockHash}` + if (this.l1BlockCache.has(cacheKey)) { + // @ts-ignore + return E.right(this.l1BlockCache.get(cacheKey)) + } + const end = this.metrics.etherJsDurationHistogram.labels({ method: this.getWstEthBalance.name }).startTimer() try { const out = await retryAsync( async (): Promise => { - const [balance] = await this.wStEthRunner.functions.balanceOf(arbitrumL1TokenBridge, { - blockTag: l1blockNumber, + const [balance] = await this.wStEthRunner.functions.balanceOf(this.arbL1TokenBridge, { + blockTag: l1blockHash, }) this.metrics.etherJsRequest.labels({ method: this.getWstEthBalance.name, status: StatusOK }).inc() @@ -48,7 +66,9 @@ export class ETHProvider implements IL1BridgeBalanceClient { { delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 }, ) - return E.right(new BigNumber(out)) + const result = new BigNumber(out) + this.l1BlockCache.set(cacheKey, result) + return E.right(result) } catch (e) { this.metrics.etherJsRequest.labels({ method: this.getWstEthBalance.name, status: StatusFail }).inc() end({ status: StatusFail }) @@ -57,13 +77,19 @@ export class ETHProvider implements IL1BridgeBalanceClient { } } - public async getLDOBalance(l1blockNumber: number, arbitrumL1LdoBridge: string): Promise> { + public async getLDOBalance(l1blockHash: string): Promise> { + const cacheKey = `${ldo}.${l1blockHash}` + if (this.l1BlockCache.has(cacheKey)) { + // @ts-ignore + return E.right(this.l1BlockCache.get(cacheKey)) + } + const end = this.metrics.etherJsDurationHistogram.labels({ method: this.getLDOBalance.name }).startTimer() try { const out = await retryAsync( async (): Promise => { - const [balance] = await this.ldoRunner.functions.balanceOf(arbitrumL1LdoBridge, { - blockTag: l1blockNumber, + const [balance] = await this.ldoRunner.functions.balanceOf(this.arbL1LdoBridge, { + blockTag: l1blockHash, }) this.metrics.etherJsRequest.labels({ method: this.getLDOBalance.name, status: StatusOK }).inc() @@ -74,7 +100,9 @@ export class ETHProvider implements IL1BridgeBalanceClient { { delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 }, ) - return E.right(new BigNumber(out)) + const result = new BigNumber(out) + this.l1BlockCache.set(cacheKey, result) + return E.right(result) } catch (e) { this.metrics.etherJsRequest.labels({ method: this.getLDOBalance.name, status: StatusFail }).inc() end({ status: StatusFail }) @@ -83,22 +111,24 @@ export class ETHProvider implements IL1BridgeBalanceClient { } } - public async getBlockNumber(): Promise> { - const end = this.metrics.etherJsDurationHistogram.labels({ method: this.getBlockNumber.name }).startTimer() + public async getBlockDto(): Promise> { + const end = this.metrics.etherJsDurationHistogram.labels({ method: this.getBlockDto.name }).startTimer() try { - const latestBlockNumber = await retryAsync( - async (): Promise => { - return await this.jsonRpcProvider.getBlockNumber() - }, - { delay: DELAY_IN_500MS, maxTry: 10 }, - ) + const block = await this.jsonRpcProvider.getBlock('latest') - this.metrics.etherJsRequest.labels({ method: this.getBlockNumber.name, status: StatusOK }).inc() + this.metrics.etherJsRequest.labels({ method: this.getBlockDto.name, status: StatusOK }).inc() end({ status: StatusOK }) - return E.right(latestBlockNumber) + const l1Block: BlockDto = { + number: new BigNumber(block.number, 10).toNumber(), + timestamp: new BigNumber(block.timestamp, 10).toNumber(), + parentHash: block.parentHash, + hash: block.hash, + } + + return E.right(l1Block) } catch (e) { - this.metrics.etherJsRequest.labels({ method: this.getBlockNumber.name, status: StatusFail }).inc() + this.metrics.etherJsRequest.labels({ method: this.getBlockDto.name, status: StatusFail }).inc() end({ status: StatusFail }) return E.left(new NetworkError(e, `Could not fetch latest block number`)) diff --git a/l2-bridge-arbitrum/src/clients/proxy_contract_client.spec.ts b/l2-bridge-arbitrum/src/clients/proxy_contract_client.spec.ts index 94fa4965..abd983b7 100644 --- a/l2-bridge-arbitrum/src/clients/proxy_contract_client.spec.ts +++ b/l2-bridge-arbitrum/src/clients/proxy_contract_client.spec.ts @@ -1,6 +1,6 @@ import { OssifiableProxy__factory } from '../generated/typechain' import { Address } from '../utils/constants' -import { ethers } from 'forta-agent' +import { ethers } from 'ethers' import { Config } from '../utils/env/env' import promClient from 'prom-client' import * as E from 'fp-ts/Either' @@ -13,7 +13,7 @@ describe('ProxyContractClient', () => { const config = new Config() const adr: Address = Address - const arbitrumProvider = new ethers.providers.JsonRpcProvider(config.arbitrumRpcUrl, config.chainId) + const arbitrumProvider = new ethers.providers.JsonRpcProvider(config.arbitrumRpcUrl, config.arbChainID) const l2BlockNumber = 121_951_308 diff --git a/l2-bridge-arbitrum/src/entity/blockDto.ts b/l2-bridge-arbitrum/src/entity/blockDto.ts index f1d02692..21c63b02 100644 --- a/l2-bridge-arbitrum/src/entity/blockDto.ts +++ b/l2-bridge-arbitrum/src/entity/blockDto.ts @@ -1,4 +1,5 @@ import BigNumber from 'bignumber.js' +import { Log } from '@ethersproject/abstract-provider' export type BlockDto = { number: number @@ -11,3 +12,22 @@ export type WithdrawalRecord = { timestamp: number amount: BigNumber } + +export type BlockDtoWithTransactions = { + number: number + timestamp: number + parentHash: string + hash: string + transactions: TransactionDto[] +} + +export type TransactionDto = { + logs: Log[] + to: string | null + block: { + timestamp: number + number: number + } +} + +export type BlockHash = string diff --git a/l2-bridge-arbitrum/src/entity/events.ts b/l2-bridge-arbitrum/src/entity/events.ts index c8d3780e..64aa2ccc 100644 --- a/l2-bridge-arbitrum/src/entity/events.ts +++ b/l2-bridge-arbitrum/src/entity/events.ts @@ -1,7 +1,3 @@ -import { Log } from '@ethersproject/abstract-provider' -import BigNumber from 'bignumber.js' -import * as agent_pb from '../generated/proto/agent_pb' -import { TransactionEvent } from '../generated/proto/agent_pb' import { Finding } from '../generated/proto/alert_pb' export type EventOfNotice = { @@ -15,43 +11,3 @@ export type EventOfNotice = { } export type Metadata = { [key: string]: string } - -export type TransactionDto = { - logs: Log[] - to: string | null - block: { - timestamp: number - number: number - } -} - -export function newTransactionDto(request: agent_pb.EvaluateTxRequest): TransactionDto { - const txEvent = request.getEvent() - const transaction = txEvent.getTransaction() - const logList = >txEvent.getLogsList() - const block = txEvent.getBlock() - - const logs: Log[] = [] - for (const l of logList) { - logs.push({ - blockNumber: new BigNumber(l.getBlocknumber(), 10).toNumber(), - blockHash: l.getTransactionhash(), - transactionIndex: new BigNumber(l.getTransactionindex(), 10).toNumber(), - removed: l.getRemoved(), - address: l.getAddress(), - data: l.getData(), - topics: l.getTopicsList(), - transactionHash: l.getTransactionhash(), - logIndex: new BigNumber(l.getLogindex(), 10).toNumber(), - }) - } - - return { - logs: logs, - to: transaction.getTo() ? transaction.getTo().toLowerCase() : null, - block: { - number: new BigNumber(block.getBlocknumber(), 10).toNumber(), - timestamp: new BigNumber(block.getBlocktimestamp(), 10).toNumber(), - }, - } -} diff --git a/l2-bridge-arbitrum/src/handlers/alert.handler.ts b/l2-bridge-arbitrum/src/handlers/alert.handler.ts deleted file mode 100644 index 2ee864d1..00000000 --- a/l2-bridge-arbitrum/src/handlers/alert.handler.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { sendUnaryData, ServerUnaryCall } from '@grpc/grpc-js' -import { EvaluateAlertRequest, EvaluateAlertResponse, ResponseStatus } from '../generated/proto/agent_pb' - -export class AlertHandler { - public handleAlert() { - return async ( - call: ServerUnaryCall, - callback: sendUnaryData, - ) => { - const resp = new EvaluateAlertResponse() - resp.setStatus(ResponseStatus.SUCCESS) - resp.setFindingsList([]) - resp.setTimestamp(new Date().toISOString()) - - callback(null, resp) - } - } -} diff --git a/l2-bridge-arbitrum/src/handlers/block.handler.spec.ts b/l2-bridge-arbitrum/src/handlers/block.handler.spec.ts new file mode 100644 index 00000000..8d2f471f --- /dev/null +++ b/l2-bridge-arbitrum/src/handlers/block.handler.spec.ts @@ -0,0 +1,215 @@ +import { either as E } from 'fp-ts' +import { BlockHandler } from './block.handler' +import { Finding } from '../generated/proto/alert_pb' +import { Config } from '../utils/env/env' +import * as Winston from 'winston' +import { Address } from '../utils/constants' +import promClient from 'prom-client' +import { Metrics } from '../utils/metrics/metrics' +import { getJsonRpcUrl } from 'forta-agent/dist/sdk/utils' +import { MonitorWithdrawals } from '../services/monitor_withdrawals' +import { ArbitrumClient } from '../clients/arbitrum_client' +import { BorderTime, HealthChecker, MaxNumberErrorsPerBorderTime } from '../services/health-checker/health-checker.srv' +import { ProxyWatcher } from '../services/proxy_watcher' +import { ProxyContractClient } from '../clients/proxy_contract_client' +import { EventWatcher } from '../services/event_watcher' +import { getL2BridgeEvents } from '../utils/events/bridge_events' +import { getGovEvents } from '../utils/events/gov_events' +import { getProxyAdminEvents } from '../utils/events/proxy_admin_events' +import { BridgeBalanceSrv } from '../services/bridge_balance' +import { ETHProvider } from '../clients/eth_provider_client' +import { ethers } from 'ethers' +import { + ArbERC20__factory, + ERC20Bridged__factory, + L2ERC20TokenGateway__factory, + OssifiableProxy__factory, +} from '../generated/typechain' +import { LRUCache } from 'lru-cache' +import { BlockDtoWithTransactions, BlockHash } from '../entity/blockDto' +import BigNumber from 'bignumber.js' +import { elapsedTime } from '../utils/time' + +const MINUTES_60 = 1000 * 60 * 60 +describe('arbitrumProvider', () => { + const config = new Config() + + const logger: Winston.Logger = Winston.createLogger({ + format: config.logFormat === 'simple' ? Winston.format.simple() : Winston.format.json(), + transports: [new Winston.transports.Console()], + }) + + const defaultRegistry = promClient + defaultRegistry.collectDefaultMetrics({ + prefix: config.promPrefix, + }) + + const customRegister = new promClient.Registry() + const mergedRegistry = promClient.Registry.merge([defaultRegistry.register, customRegister]) + mergedRegistry.setDefaultLabels({ instance: config.instance, dataProvider: config.dataProvider }) + + const metrics = new Metrics(mergedRegistry, config.promPrefix) + + const ethProvider = config.useFortaProvider + ? new ethers.providers.JsonRpcProvider(getJsonRpcUrl(), config.chainId) + : new ethers.providers.JsonRpcProvider(config.ethereumRpcUrl, config.chainId) + + const nodeClient = new ethers.providers.JsonRpcProvider(config.arbitrumRpcUrl, config.arbChainID) + + const adr: Address = Address + + const onAppFindings: Finding[] = [] + const healthChecker = new HealthChecker(logger, metrics, BorderTime, MaxNumberErrorsPerBorderTime) + + const l2Bridge = L2ERC20TokenGateway__factory.connect(adr.ARBITRUM_L2_TOKEN_GATEWAY.address, nodeClient) + const bridgedWSthEthRunner = ERC20Bridged__factory.connect(adr.ARBITRUM_WSTETH_BRIDGED.address, nodeClient) + const bridgedLdoRunner = ArbERC20__factory.connect(adr.ARBITRUM_LDO_BRIDGED_ADDRESS, nodeClient) + + const l2BlocksStore = new LRUCache({ + max: 10_000, + ttl: MINUTES_60, + updateAgeOnGet: true, + updateAgeOnHas: true, + }) + + const l2BridgeCache = new LRUCache({ + max: 20_000, + ttl: MINUTES_60, + updateAgeOnGet: true, + updateAgeOnHas: true, + }) + const arbitrumClient = new ArbitrumClient( + nodeClient, + metrics, + l2Bridge, + bridgedWSthEthRunner, + bridgedLdoRunner, + l2BlocksStore, + l2BridgeCache, + ) + const processedWithdrawalBlock = new LRUCache({ + max: 20_000, + ttl: MINUTES_60, + updateAgeOnGet: true, + updateAgeOnHas: true, + }) + const withdrawalsSrv = new MonitorWithdrawals( + arbitrumClient, + adr.ARBITRUM_L2_TOKEN_GATEWAY.address, + logger, + processedWithdrawalBlock, + ) + + const proxyWatchers: ProxyWatcher[] = [ + new ProxyWatcher( + new ProxyContractClient( + adr.ARBITRUM_WSTETH_BRIDGED.name, + OssifiableProxy__factory.connect(adr.ARBITRUM_WSTETH_BRIDGED.address, nodeClient), + metrics, + ), + logger, + ), + new ProxyWatcher( + new ProxyContractClient( + adr.ARBITRUM_L2_TOKEN_GATEWAY.name, + OssifiableProxy__factory.connect(adr.ARBITRUM_L2_TOKEN_GATEWAY.address, nodeClient), + metrics, + ), + logger, + ), + ] + + const bridgeEventWatcher = new EventWatcher( + 'BridgeEventWatcher', + getL2BridgeEvents(adr.ARBITRUM_L2_TOKEN_GATEWAY.address, adr.RolesMap), + ) + const govEventWatcher = new EventWatcher('GovEventWatcher', getGovEvents(adr.GOV_BRIDGE_ADDRESS)) + const proxyEventWatcher = new EventWatcher( + 'ProxyEventWatcher', + getProxyAdminEvents(adr.ARBITRUM_WSTETH_BRIDGED, adr.ARBITRUM_L2_TOKEN_GATEWAY), + ) + + const wSthEthRunner = ERC20Bridged__factory.connect(adr.L1_WSTETH_ADDRESS, ethProvider) + const ldoRunner = ERC20Bridged__factory.connect(adr.L1_LDO_ADDRESS, ethProvider) + + const l1BlockCache = new LRUCache({ + max: 200, + ttl: MINUTES_60, + }) + const l1Client = new ETHProvider( + metrics, + wSthEthRunner, + ldoRunner, + ethProvider, + l1BlockCache, + adr.ARBITRUM_L1_TOKEN_BRIDGE, + adr.ARBITRUM_L1_LDO_BRIDGE, + ) + const processedKeyPairsCache = new LRUCache({ + max: 20_000, + ttl: MINUTES_60, + }) + const bridgeBalanceSrv = new BridgeBalanceSrv(logger, l1Client, arbitrumClient, processedKeyPairsCache) + + const blockH = new BlockHandler( + arbitrumClient, + l1Client, + logger, + metrics, + + proxyWatchers, + withdrawalsSrv, + bridgeBalanceSrv, + + bridgeEventWatcher, + govEventWatcher, + proxyEventWatcher, + + healthChecker, + onAppFindings, + ) + + /* test( + 'benchmark asyncProcess', + async () => { + const startTime = new Date() + const l1Block = await ethProvider.getBlock('latest') + + const startedL2Block = await blockH.findStartingL2BlockByBinarySearch(l1Block) + if (E.isLeft(startedL2Block)) { + throw startedL2Block + } + + await blockH.asyncProcess(l1Block, startedL2Block.right) + + console.log(elapsedTime('benchmark asyncProcess', startTime.getTime())) + }, + 60_000 * 5, + )*/ + + test( + 'findClosestBlock', + async () => { + const l1Block = await ethProvider.getBlock('latest') + + const startTime = new Date() + const startedL2Block = await blockH.findStartingL2BlockByBinarySearch(l1Block) + if (E.isLeft(startedL2Block)) { + throw startedL2Block + } + + console.log( + `l2timestamp: ${startedL2Block.right.timestamp} \n` + + `l1timestamp: ${l1Block.timestamp} \n` + + `l2number: ${startedL2Block.right.number} \n` + + `l1date: ${new Date(l1Block.timestamp * 1000).toUTCString()} \n` + + `l2date: ${new Date(startedL2Block.right.timestamp * 1000).toUTCString()} \n`, + ) + + const expected_60_seconds = 60 + expect(startedL2Block.right.timestamp - l1Block.timestamp).toEqual(expected_60_seconds) + console.log(elapsedTime('findClosestBlock', startTime.getTime())) + }, + 60_000 * 2, + ) +}) diff --git a/l2-bridge-arbitrum/src/handlers/block.handler.ts b/l2-bridge-arbitrum/src/handlers/block.handler.ts index e8e4b51f..cb880bfd 100644 --- a/l2-bridge-arbitrum/src/handlers/block.handler.ts +++ b/l2-bridge-arbitrum/src/handlers/block.handler.ts @@ -2,47 +2,75 @@ import { sendUnaryData, ServerUnaryCall } from '@grpc/grpc-js' import { BlockEvent, EvaluateBlockRequest, EvaluateBlockResponse, ResponseStatus } from '../generated/proto/agent_pb' import { HealthChecker } from '../services/health-checker/health-checker.srv' import { Logger } from 'winston' -import { elapsedTime } from '../utils/time' +import { elapsedTime, isWorkInterval, SECONDS_60, SECONDS_768 } from '../utils/time' import BigNumber from 'bignumber.js' import { Finding } from '../generated/proto/alert_pb' -import { HandleBlockLabel, Metrics, StatusFail, StatusOK } from '../utils/metrics/metrics' +import { HandleL1BlockLabel, HandleL2BlockLabel, Metrics, StatusFail, StatusOK } from '../utils/metrics/metrics' import { MonitorWithdrawals } from '../services/monitor_withdrawals' -import { BlockDto } from '../entity/blockDto' +import { BlockDto, BlockDtoWithTransactions } from '../entity/blockDto' import { ProxyWatcher } from '../services/proxy_watcher' import { BridgeBalanceSrv } from '../services/bridge_balance' -import { ETHProvider } from '../clients/eth_provider_client' import * as E from 'fp-ts/Either' -import { networkAlert } from '../utils/errors' +import { networkAlert, NetworkError } from '../utils/errors' +import { ArbitrumClient } from '../clients/arbitrum_client' +import { ArrRW } from '../utils/mutex' +import { retryAsync } from 'ts-retry' +import { EventWatcher } from '../services/event_watcher' +import { ETHProvider } from '../clients/eth_provider_client' + +const DELAY_IN_500MS = 500 +const ATTEMPTS_5 = 5 export class BlockHandler { + private arbProvider: ArbitrumClient + private ethProvider: ETHProvider private logger: Logger private metrics: Metrics + private readonly proxyWatchers: ProxyWatcher[] private WithdrawalsSrv: MonitorWithdrawals private bridgeBalanceSrv: BridgeBalanceSrv + + private bridgeWatcher: EventWatcher + private govWatcher: EventWatcher + private proxyWatcher: EventWatcher + private healthChecker: HealthChecker - private ethProvider: ETHProvider + private findings: ArrRW - private onAppStartFindings: Finding[] = [] + private launch: Date constructor( + arbProvider: ArbitrumClient, + ethProvider: ETHProvider, logger: Logger, metrics: Metrics, proxyWatchers: ProxyWatcher[], WithdrawalsSrv: MonitorWithdrawals, bridgeBalanceSrv: BridgeBalanceSrv, + bridgeWatcher: EventWatcher, + govWatcher: EventWatcher, + proxyWatcher: EventWatcher, healthChecker: HealthChecker, - ethProvider: ETHProvider, - onAppStartFindings: Finding[], + findings: Finding[], ) { this.logger = logger this.metrics = metrics + this.proxyWatchers = proxyWatchers this.WithdrawalsSrv = WithdrawalsSrv this.bridgeBalanceSrv = bridgeBalanceSrv + + this.bridgeWatcher = bridgeWatcher + this.govWatcher = govWatcher + this.proxyWatcher = proxyWatcher + this.healthChecker = healthChecker + this.arbProvider = arbProvider this.ethProvider = ethProvider - this.onAppStartFindings = onAppStartFindings + this.findings = new ArrRW(findings) + + this.launch = new Date() } public handleBlock() { @@ -50,75 +78,249 @@ export class BlockHandler { call: ServerUnaryCall, callback: sendUnaryData, ) => { - this.metrics.lastAgentTouch.labels({ method: HandleBlockLabel }).set(new Date().getTime()) - const end = this.metrics.summaryHandlers.labels({ method: HandleBlockLabel }).startTimer() + this.metrics.lastAgentTouch.labels({ method: HandleL1BlockLabel }).set(new Date().getTime()) + const end = this.metrics.summaryHandlers.labels({ method: HandleL1BlockLabel }).startTimer() const event = call.request.getEvent() const block = event.getBlock() - const l2blockDtoEvent: BlockDto = { + const findings: Finding[] = [] + const fnds = await this.findings.read() + await this.findings.clear() + + const out = new EvaluateBlockResponse() + if (fnds.length > 0) { + findings.push(...fnds) + + const errCount = this.healthChecker.check(findings) + errCount === 0 + ? this.metrics.processedIterations.labels({ method: HandleL1BlockLabel, status: StatusOK }).inc() + : this.metrics.processedIterations.labels({ method: HandleL1BlockLabel, status: StatusFail }).inc() + + out.setStatus(ResponseStatus.SUCCESS) + out.setPrivate(false) + out.setFindingsList(findings) + const m = out.getMetadataMap() + m.set('timestamp', new Date().toISOString()) + } + + const l1BlockFromInfra: BlockDto = { number: new BigNumber(block.getNumber(), 10).toNumber(), timestamp: new BigNumber(block.getTimestamp(), 10).toNumber(), parentHash: block.getParenthash(), hash: block.getHash(), } - const findings: Finding[] = [] + const l1Block = await this.ethProvider.getBlockDto() + if (E.isLeft(l1Block)) { + const blockResponse = this.errorResponse(l1Block.left) - this.logger.info(`#arbitrum block: ${l2blockDtoEvent.number}`) - const startTime = new Date().getTime() + this.logger.error(`Could not handleBlock: ${l1Block.left.name}: ${l1Block.left.message}`) + this.metrics.processedIterations.labels({ method: HandleL1BlockLabel, status: StatusFail }).inc() + end() - if (this.onAppStartFindings.length > 0) { - findings.push(...this.onAppStartFindings) - this.onAppStartFindings = [] + callback(null, blockResponse) + return } - const l1BlockNumber = await this.ethProvider.getBlockNumber() - if (E.isLeft(l1BlockNumber)) { - this.logger.warn(`#ETH block skipped`) - findings.push( - networkAlert( - l1BlockNumber.left, - `Error in ${BlockHandler.name}.${this.ethProvider.getBlockNumber.name}:80`, - `Could not call clientL1.getBlockNumber`, - ), - ) + const latestL2Block = await this.findStartingL2BlockByBinarySearch(l1Block.right) + // Need to max l2 block number from l1.timestamp + 60 + // const latestL2Block = await this.arbProvider.getL2BlockByHash('latest') + if (E.isLeft(latestL2Block)) { + const blockResponse = this.errorResponse(latestL2Block.left) + + this.logger.error(`Could not handleBlock: ${latestL2Block.left.name}: ${latestL2Block.left.message}`) + this.metrics.processedIterations.labels({ method: HandleL1BlockLabel, status: StatusFail }).inc() + end() + + callback(null, blockResponse) + return } - if (E.isRight(l1BlockNumber)) { - this.logger.info(`#ETH block: ${l1BlockNumber.right}`) - const bridgeBalanceFindings = await this.bridgeBalanceSrv.handleBlock( - l1BlockNumber.right, - l2blockDtoEvent.number, - ) - findings.push(...bridgeBalanceFindings) + this.logger.info(`\n\n`) + this.logger.info(`\tStarting handleBlock(${l1Block.right.number}). From infra${l1BlockFromInfra.number}`) + + let startedL2Block = latestL2Block.right + if (latestL2Block.right.timestamp - l1Block.right.timestamp > 60) { + const foundL2Block = await this.findStartingL2BlockByBinarySearch(l1Block.right) + if (E.isLeft(foundL2Block)) { + const blockResponse = this.errorResponse( + new NetworkError(`Could not get starting l2block: ${foundL2Block.left}`), + ) + + this.logger.error(`Could not get starting l2block: ${foundL2Block.left}`) + this.metrics.processedIterations.labels({ method: HandleL1BlockLabel, status: StatusFail }).inc() + end() + + callback(null, blockResponse) + return + } + + startedL2Block = latestL2Block.right } - for (const proxyWatcher of this.proxyWatchers) { - const fnds = await proxyWatcher.handleL2Block(l2blockDtoEvent.number) - findings.push(...fnds) + this.asyncProcess(l1Block.right, startedL2Block).then(() => end()) + + callback(null, out) + } + } + + private errorResponse(error: NetworkError): EvaluateBlockResponse { + const blockResponse = new EvaluateBlockResponse() + blockResponse.setStatus(ResponseStatus.ERROR) + blockResponse.setPrivate(false) + + const finding = networkAlert(new Error('Could not handleBlock'), error.name, error.message) + blockResponse.setFindingsList([finding]) + const m = blockResponse.getMetadataMap() + m.set('timestamp', new Date().toISOString()) + return blockResponse + } + + public async asyncProcess(l1Block: BlockDto, latestL2Block: BlockDtoWithTransactions): Promise { + const startTime = new Date().getTime() + + const chain: BlockDtoWithTransactions[] = [] + if (isWorkInterval(l1Block.timestamp, latestL2Block.timestamp)) { + chain.push(latestL2Block) + } + + // explanation: + // l1: . . . . ⬇️ . . . . . + // l2: . . . . . . . . . ⬇️ + // l2: . . . . . . . . ⬇️ . + // l2: . . . . . . . ⬇️ . . + // ... + // l2: . . . . ⬇️ . . . . . <- Go dipper and dipper on each iteration + let blockTag = latestL2Block.parentHash + let l2BlockTimestamp = latestL2Block.timestamp + + while (isWorkInterval(l1Block.timestamp, l2BlockTimestamp)) { + try { + const l2block = await retryAsync( + async (): Promise => { + const l2block = await this.arbProvider.getL2BlockByHash(blockTag) + this.arbProvider.getL2BlockByHash('latest') + + if (E.isLeft(l2block)) { + throw l2block.left + } + + return l2block.right + }, + { delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 }, + ) + + // If, after starting the application, + // we reach an interval where L2 is in the past relative to L1, (L2 < L1) + // then finish the work and collect the cache! + if (Math.round(+new Date() / 1000) - Math.round(+this.launch / 1000) < SECONDS_768) { + if (l2block.timestamp - l1Block.timestamp <= -1) { + break + } + } + + // If the L2 block is older than the L1 block by 12.8, then finish the work + if (l1Block.timestamp - l2block.timestamp < -SECONDS_768) { + break + } + + if (isWorkInterval(l1Block.timestamp, l2block.timestamp)) { + chain.unshift(l2block) + this.logger.info('\n') + this.logger.info( + `#ETH block: ${l1Block.number} at ${new Date(l1Block.timestamp * 1000).toUTCString()}. ${l1Block.timestamp}`, + ) + this.logger.info( + `#ARB block src: ${l2block.number} at ${new Date(l2block.timestamp * 1000).toUTCString()}. ${l2block.timestamp} `, + ) + + this.bridgeBalanceSrv.handleBlock(l1Block, l2block).then((findings) => { + if (findings.length > 0) { + this.findings.write(findings) + } + }) + + for (const proxyWatcher of this.proxyWatchers) { + proxyWatcher.handleL2Block(l2block).then((findings) => { + if (findings.length > 0) { + this.findings.write(findings) + } + }) + } + } + + l2BlockTimestamp = l2block.timestamp + blockTag = l2block.parentHash + } catch (e) { + this.logger.warn(`Could not call arbProvider.getL2BlockByHash ${e}`) } + } - const withdrawalsFindings = this.WithdrawalsSrv.handleL2Block(l2blockDtoEvent) - findings.push(...withdrawalsFindings) + const transactions = [] + const startWithdrawalsSrv = new Date().getTime() + for (const l2Block of chain) { + for (const trx of l2Block.transactions) { + transactions.push(trx) + this.WithdrawalsSrv.handleTransaction(trx) - const errCount = this.healthChecker.check(findings) - errCount === 0 - ? this.metrics.processedIterations.labels({ method: HandleBlockLabel, status: StatusOK }).inc() - : this.metrics.processedIterations.labels({ method: HandleBlockLabel, status: StatusFail }).inc() + const bridgeFindings = this.bridgeWatcher.handleL2Logs(trx.logs) + const govFindings = this.govWatcher.handleL2Logs(trx.logs) + const proxyFindings = this.proxyWatcher.handleL2Logs(trx.logs) - const blockResponse = new EvaluateBlockResponse() - blockResponse.setStatus(ResponseStatus.SUCCESS) - blockResponse.setPrivate(false) - blockResponse.setFindingsList(findings) - const m = blockResponse.getMetadataMap() - m.set('timestamp', new Date().toISOString()) + const findings = [...bridgeFindings, ...govFindings, ...proxyFindings] + if (findings.length > 0) { + await this.findings.write(findings) + } + } + const withdrawalFindings = this.WithdrawalsSrv.handleL2Block(l2Block) + if (withdrawalFindings.length > 0) { + await this.findings.write(withdrawalFindings) + } + } - this.logger.info(elapsedTime('handleBlock', startTime) + '\n') - this.metrics.lastBlockNumber.set(l2blockDtoEvent.number) + this.logger.info( + elapsedTime(this.WithdrawalsSrv.getName() + '.' + this.WithdrawalsSrv.handleL2Block.name, startWithdrawalsSrv), + ) - end() - callback(null, blockResponse) + if (chain.length > 0) { + const firstL2 = chain[0] + const lastL2 = chain[chain.length - 1] + this.logger.info( + `#ETH block: ${l1Block.number} at ${new Date(l1Block.timestamp * 1000).toUTCString()}. ${l1Block.timestamp} \n` + + `#ARB block src: ${firstL2.number} at ${new Date(firstL2.timestamp * 1000).toUTCString()}. ${firstL2.timestamp} \n` + + `#ARB block dst: ${lastL2.number} at ${new Date(lastL2.timestamp * 1000).toUTCString()}. ${lastL2.timestamp} Total: ${chain.length}`, + ) + } else { + this.logger.info( + `#ETH block: ${l1Block.number} #ARB block dst: ${latestL2Block.number} \n` + + `#ETH block: ${new Date(l1Block.timestamp * 1000).toUTCString()}. ${l1Block.timestamp} \n` + + `#ARB block src: ${new Date((l1Block.timestamp - SECONDS_768) * 1000).toUTCString()}. ${latestL2Block.timestamp - SECONDS_768} \n` + + `#ARB real: ${new Date(latestL2Block.timestamp * 1000).toUTCString()}. ${latestL2Block.timestamp} \n` + + `#ARB block dst: ${new Date((l1Block.timestamp + SECONDS_60) * 1000).toUTCString()}. ${latestL2Block.timestamp + SECONDS_60}`, + ) } + + this.logger.info(elapsedTime(`\tFinish: handleBlock(${l1Block.number})`, startTime)) + this.metrics.lastBlockNumber.set(l1Block.number) + this.metrics.processedIterations.labels({ method: HandleL1BlockLabel, status: StatusOK }).inc() + this.metrics.processedIterations.labels({ method: HandleL2BlockLabel, status: StatusOK }).inc(chain.length) + } + + public async findStartingL2BlockByBinarySearch( + l1Block: BlockDto, + ): Promise> { + const L2targetHighTimestamp = l1Block.timestamp + SECONDS_60 + await new Promise((f) => setTimeout(f, 61 * 1000)) + const highestL2Block = await this.arbProvider.findClosestBlock( + L2targetHighTimestamp, + l1Block.number, + L2targetHighTimestamp, + ) + if (E.isLeft(highestL2Block)) { + return E.left(highestL2Block.left) + } + + return await this.arbProvider.getL2BlockByHash(highestL2Block.right.hash) } } diff --git a/l2-bridge-arbitrum/src/handlers/tx.handler.ts b/l2-bridge-arbitrum/src/handlers/tx.handler.ts deleted file mode 100644 index b104d063..00000000 --- a/l2-bridge-arbitrum/src/handlers/tx.handler.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { sendUnaryData, ServerUnaryCall } from '@grpc/grpc-js' -import { HealthChecker } from '../services/health-checker/health-checker.srv' -import { EvaluateTxRequest, EvaluateTxResponse, ResponseStatus } from '../generated/proto/agent_pb' -import { newTransactionDto } from '../entity/events' -import { Finding } from '../generated/proto/alert_pb' -import { HandleTxLabel, Metrics, StatusFail, StatusOK } from '../utils/metrics/metrics' -import { MonitorWithdrawals } from '../services/monitor_withdrawals' -import { EventWatcher } from '../services/event_watcher' - -export class TxHandler { - private metrics: Metrics - private bridgeWatcher: EventWatcher - private govWatcher: EventWatcher - private proxyWatcher: EventWatcher - private WithdrawalsSrv: MonitorWithdrawals - private healthChecker: HealthChecker - - constructor( - metrics: Metrics, - bridgeWatcher: EventWatcher, - govWatcher: EventWatcher, - proxyWatcher: EventWatcher, - WithdrawalsSrv: MonitorWithdrawals, - healthChecker: HealthChecker, - ) { - this.metrics = metrics - this.bridgeWatcher = bridgeWatcher - this.WithdrawalsSrv = WithdrawalsSrv - this.govWatcher = govWatcher - this.proxyWatcher = proxyWatcher - this.healthChecker = healthChecker - } - - public handleTx() { - return async ( - call: ServerUnaryCall, - callback: sendUnaryData, - ) => { - const end = this.metrics.summaryHandlers.labels({ method: HandleTxLabel }).startTimer() - this.metrics.lastAgentTouch.labels({ method: HandleTxLabel }).set(new Date().getTime()) - - const txEvent = newTransactionDto(call.request) - - const findings: Finding[] = [] - - const bridgeFindings = this.bridgeWatcher.handleL2Logs(txEvent.logs) - const govFindings = this.govWatcher.handleL2Logs(txEvent.logs) - const proxyFindings = this.proxyWatcher.handleL2Logs(txEvent.logs) - this.WithdrawalsSrv.handleTransaction(txEvent) - - findings.push(...bridgeFindings, ...govFindings, ...proxyFindings) - - const errCount = this.healthChecker.check(findings) - errCount === 0 - ? this.metrics.processedIterations.labels({ method: HandleTxLabel, status: StatusOK }).inc() - : this.metrics.processedIterations.labels({ method: HandleTxLabel, status: StatusFail }).inc() - - const txResponse = new EvaluateTxResponse() - txResponse.setStatus(ResponseStatus.SUCCESS) - txResponse.setPrivate(false) - txResponse.setFindingsList(findings) - const m = txResponse.getMetadataMap() - m.set('timestamp', new Date().toISOString()) - - end() - callback(null, txResponse) - } - } -} diff --git a/l2-bridge-arbitrum/src/main.ts b/l2-bridge-arbitrum/src/main.ts index fdb4a348..2c8108ed 100644 --- a/l2-bridge-arbitrum/src/main.ts +++ b/l2-bridge-arbitrum/src/main.ts @@ -12,11 +12,10 @@ import * as Winston from 'winston' import { Address } from './utils/constants' import promClient from 'prom-client' import { Metrics } from './utils/metrics/metrics' -import { getEthersProvider } from 'forta-agent/dist/sdk/utils' +import { getJsonRpcUrl } from 'forta-agent/dist/sdk/utils' import { MonitorWithdrawals } from './services/monitor_withdrawals' import { ArbitrumClient } from './clients/arbitrum_client' import { BorderTime, HealthChecker, MaxNumberErrorsPerBorderTime } from './services/health-checker/health-checker.srv' -import { TxHandler } from './handlers/tx.handler' import { ProxyWatcher } from './services/proxy_watcher' import { ProxyContractClient } from './clients/proxy_contract_client' import process from 'process' @@ -24,20 +23,23 @@ import { EventWatcher } from './services/event_watcher' import { getL2BridgeEvents } from './utils/events/bridge_events' import { getGovEvents } from './utils/events/gov_events' import { getProxyAdminEvents } from './utils/events/proxy_admin_events' -import { AlertHandler } from './handlers/alert.handler' import { BridgeBalanceSrv } from './services/bridge_balance' import { ETHProvider } from './clients/eth_provider_client' -import { ethers } from 'forta-agent' +import { ethers } from 'ethers' import { ArbERC20__factory, ERC20Bridged__factory, L2ERC20TokenGateway__factory, OssifiableProxy__factory, } from './generated/typechain' +import { LRUCache } from 'lru-cache' +import { BlockDtoWithTransactions, BlockHash } from './entity/blockDto' +import BigNumber from 'bignumber.js' + +const MINUTES_60 = 1000 * 60 * 60 const main = async () => { const config = new Config() - console.log(config) const logger: Winston.Logger = Winston.createLogger({ format: config.logFormat === 'simple' ? Winston.format.simple() : Winston.format.json(), @@ -55,11 +57,11 @@ const main = async () => { const metrics = new Metrics(mergedRegistry, config.promPrefix) - const arbitrumProvider = new ethers.providers.JsonRpcProvider(config.arbitrumRpcUrl, config.chainId) - let nodeClient = getEthersProvider() - if (!config.useFortaProvider) { - nodeClient = arbitrumProvider - } + const ethProvider = config.useFortaProvider + ? new ethers.providers.JsonRpcProvider(getJsonRpcUrl(), config.chainId) + : new ethers.providers.JsonRpcProvider(config.ethereumRpcUrl, config.chainId) + + const nodeClient = new ethers.providers.JsonRpcProvider(config.arbitrumRpcUrl, config.arbChainID) const adr: Address = Address @@ -70,8 +72,40 @@ const main = async () => { const bridgedWSthEthRunner = ERC20Bridged__factory.connect(adr.ARBITRUM_WSTETH_BRIDGED.address, nodeClient) const bridgedLdoRunner = ArbERC20__factory.connect(adr.ARBITRUM_LDO_BRIDGED_ADDRESS, nodeClient) - const arbitrumClient = new ArbitrumClient(nodeClient, metrics, l2Bridge, bridgedWSthEthRunner, bridgedLdoRunner) - const withdrawalsSrv = new MonitorWithdrawals(arbitrumClient, adr.ARBITRUM_L2_TOKEN_GATEWAY.address, logger) + const l2BlocksStore = new LRUCache({ + max: 10_000, + ttl: MINUTES_60, + updateAgeOnGet: true, + updateAgeOnHas: true, + }) + + const l2BridgeCache = new LRUCache({ + max: 20_000, + ttl: MINUTES_60, + updateAgeOnGet: true, + updateAgeOnHas: true, + }) + const arbitrumClient = new ArbitrumClient( + nodeClient, + metrics, + l2Bridge, + bridgedWSthEthRunner, + bridgedLdoRunner, + l2BlocksStore, + l2BridgeCache, + ) + const processedWithdrawalBlock = new LRUCache({ + max: 20_000, + ttl: MINUTES_60, + updateAgeOnGet: true, + updateAgeOnHas: true, + }) + const withdrawalsSrv = new MonitorWithdrawals( + arbitrumClient, + adr.ARBITRUM_L2_TOKEN_GATEWAY.address, + logger, + processedWithdrawalBlock, + ) const proxyWatchers: ProxyWatcher[] = [ new ProxyWatcher( @@ -102,62 +136,68 @@ const main = async () => { getProxyAdminEvents(adr.ARBITRUM_WSTETH_BRIDGED, adr.ARBITRUM_L2_TOKEN_GATEWAY), ) - const mainnet = 1 - const ethProvider = new ethers.providers.JsonRpcProvider(config.ethereumRpcUrl, mainnet) - const wSthEthRunner = ERC20Bridged__factory.connect(adr.L1_WSTETH_ADDRESS, ethProvider) const ldoRunner = ERC20Bridged__factory.connect(adr.L1_LDO_ADDRESS, ethProvider) - const l1Client = new ETHProvider(metrics, wSthEthRunner, ldoRunner, ethProvider) - const bridgeBalanceSrv = new BridgeBalanceSrv( - logger, - l1Client, + const l1BlockCache = new LRUCache({ + max: 200, + ttl: MINUTES_60, + }) + const l1Client = new ETHProvider( + metrics, + wSthEthRunner, + ldoRunner, + ethProvider, + l1BlockCache, adr.ARBITRUM_L1_TOKEN_BRIDGE, adr.ARBITRUM_L1_LDO_BRIDGE, - arbitrumClient, ) + const processedKeyPairsCache = new LRUCache({ + max: 20_000, + ttl: MINUTES_60, + }) + const bridgeBalanceSrv = new BridgeBalanceSrv(logger, l1Client, arbitrumClient, processedKeyPairsCache) const gRPCserver = new grpc.Server() + + const latestL2BlockNumber = await arbitrumClient.getLatestL2Block() + if (E.isLeft(latestL2BlockNumber)) { + logger.error(latestL2BlockNumber.left) + + process.exit(1) + } + const blockH = new BlockHandler( + arbitrumClient, + l1Client, logger, metrics, + proxyWatchers, withdrawalsSrv, bridgeBalanceSrv, - healthChecker, - l1Client, - onAppFindings, - ) - const txH = new TxHandler( - metrics, bridgeEventWatcher, govEventWatcher, proxyEventWatcher, - withdrawalsSrv, + healthChecker, + onAppFindings, ) + const healthH = new HealthHandler(healthChecker, metrics, logger, config.ethereumRpcUrl, config.chainId) const initH = new InitHandler(config.appName, logger, onAppFindings) - const alertH = new AlertHandler() gRPCserver.addService(AgentService, { initialize: initH.handleInit(), evaluateBlock: blockH.handleBlock(), - evaluateTx: txH.handleTx(), - healthCheck: healthH.healthGrpc(), + // evaluateTx: txH.handleTx(), + // healthCheck: healthH.healthGrpc(), // not used, but required for grpc contract - evaluateAlert: alertH.handleAlert(), + // evaluateAlert: alertH.handleAlert(), }) - const latestL2BlockNumber = await arbitrumClient.getBlockNumber() - if (E.isLeft(latestL2BlockNumber)) { - logger.error(latestL2BlockNumber.left) - - process.exit(1) - } - - const withdrawalsSrvErr = await withdrawalsSrv.initialize(latestL2BlockNumber.right) + const withdrawalsSrvErr = await withdrawalsSrv.initialize(latestL2BlockNumber.right.number) if (E.isLeft(withdrawalsSrvErr)) { logger.error('Could not init withdrawalsSrvErr', withdrawalsSrvErr.left) @@ -165,7 +205,7 @@ const main = async () => { } for (const proxyWatcher of proxyWatchers) { - const proxyWatcherErr = await proxyWatcher.initialize(latestL2BlockNumber.right) + const proxyWatcherErr = await proxyWatcher.initialize(latestL2BlockNumber.right.number) if (proxyWatcherErr !== null) { logger.error('Could not init proxyWatcherSrv', proxyWatcherErr.message) diff --git a/l2-bridge-arbitrum/src/services/bridge_balance.ts b/l2-bridge-arbitrum/src/services/bridge_balance.ts index a7d61727..5f1db8b2 100644 --- a/l2-bridge-arbitrum/src/services/bridge_balance.ts +++ b/l2-bridge-arbitrum/src/services/bridge_balance.ts @@ -5,15 +5,17 @@ import * as E from 'fp-ts/Either' import { ETH_DECIMALS } from '../utils/constants' import { networkAlert } from '../utils/errors' import { Finding } from '../generated/proto/alert_pb' +import { BlockDto, BlockHash } from '../entity/blockDto' +import { LRUCache } from 'lru-cache' export abstract class IL1BridgeBalanceClient { - abstract getWstEthBalance(l1blockNumber: number, address: string): Promise> - abstract getLDOBalance(l1blockNumber: number, arbitrumL1LdoBridge: string): Promise> + abstract getWstEthBalance(l1blockNumber: BlockHash): Promise> + abstract getLDOBalance(l1blockNumber: BlockHash): Promise> } export abstract class IL2BridgeBalanceClient { - abstract getWstEthTotalSupply(l1blockNumber: number): Promise> - abstract getLdoTotalSupply(l2blockNumber: number): Promise> + abstract getWstEthTotalSupply(l2BlockHash: BlockHash): Promise> + abstract getLdoTotalSupply(l2BlockHash: BlockHash): Promise> } export class BridgeBalanceSrv { @@ -21,79 +23,89 @@ export class BridgeBalanceSrv { private readonly logger: Logger private readonly clientL1: IL1BridgeBalanceClient private readonly clientL2: IL2BridgeBalanceClient - - private readonly arbitrumL1TokenBridge: string - private readonly arbitrumL1LdoBridge: string + private readonly processedKeyPairsCache: LRUCache constructor( logger: Logger, clientL1: IL1BridgeBalanceClient, - arbitrumL1TokenBridge: string, - arbitrumL1LdoBridge: string, clientL2: IL2BridgeBalanceClient, + processedKeyPairsCache: LRUCache, ) { this.logger = logger this.clientL1 = clientL1 this.clientL2 = clientL2 - this.arbitrumL1TokenBridge = arbitrumL1TokenBridge - this.arbitrumL1LdoBridge = arbitrumL1LdoBridge + this.processedKeyPairsCache = processedKeyPairsCache } - async handleBlock(l1BlockNumber: number, l2BlockNumber: number): Promise { + async handleBlock(l1Block: BlockDto, l2Block: BlockDto): Promise { + const cacheKey = `${l1Block.number}.${l2Block.number}` + if (this.processedKeyPairsCache.has(cacheKey)) { + return [] + } + const start = new Date().getTime() const out: Finding[] = [] const [wStethFindings, ldoFindings] = await Promise.all([ - this.handleBridgeBalanceWstETH(l1BlockNumber, l2BlockNumber), - this.handleBridgeBalanceLDO(l1BlockNumber, l2BlockNumber), + this.handleBridgeBalanceWstETH(l1Block, l2Block), + this.handleBridgeBalanceLDO(l1Block, l2Block), ]) out.push(...wStethFindings, ...ldoFindings) - this.logger.info(elapsedTime(this.name + '.' + this.handleBlock.name, start)) + this.processedKeyPairsCache.set(cacheKey, true) + this.logger.info(elapsedTime(this.name + '.' + this.handleBlock.name + `(${l2Block.number})`, start)) return out } - private async handleBridgeBalanceWstETH(l1BlockNumber: number, l2BlockNumber: number): Promise { - const [wstETHBalance_onL1arbitrumBridge, wstETHTotalSupply_onarbitrum] = await Promise.all([ - this.clientL1.getWstEthBalance(l1BlockNumber, this.arbitrumL1TokenBridge), - this.clientL2.getWstEthTotalSupply(l2BlockNumber), + private async handleBridgeBalanceWstETH(l1Block: BlockDto, l2BlockDto: BlockDto): Promise { + const [wstETHBalance_onL1ArbitrumBridge, wstETHTotalSupply_onArbitrum] = await Promise.all([ + this.clientL1.getWstEthBalance(l1Block.hash), + this.clientL2.getWstEthTotalSupply(l2BlockDto.hash), ]) const out: Finding[] = [] - if (E.isRight(wstETHTotalSupply_onarbitrum) && E.isRight(wstETHBalance_onL1arbitrumBridge)) { - if (wstETHTotalSupply_onarbitrum.right.isGreaterThan(wstETHBalance_onL1arbitrumBridge.right)) { + if (E.isRight(wstETHTotalSupply_onArbitrum) && E.isRight(wstETHBalance_onL1ArbitrumBridge)) { + const l1 = wstETHBalance_onL1ArbitrumBridge.right.dividedBy(ETH_DECIMALS).toFixed(2) + const l2 = wstETHTotalSupply_onArbitrum.right.dividedBy(ETH_DECIMALS).toFixed(2) + + // TODO to think about prom metrics + this.logger.info(`WstETH balance: l1(${l1Block.number}) = ${l1}`) + this.logger.info(`WstETH balance: l2(${l2BlockDto.number}) = ${l2}`) + + if (wstETHTotalSupply_onArbitrum.right.isGreaterThan(wstETHBalance_onL1ArbitrumBridge.right)) { const f = new Finding() f.setName(`🚨🚨🚨 Arbitrum bridge wstETH:stEth balance mismatch`) f.setDescription( `Total supply of bridged wstETH is greater than balanceOf L1 bridge side!\n` + - `L2 total supply: ${wstETHTotalSupply_onarbitrum.right.dividedBy(ETH_DECIMALS).toFixed(2)}\n` + - `L1 balanceOf: ${wstETHBalance_onL1arbitrumBridge.right.dividedBy(ETH_DECIMALS).toFixed(2)}\n\n` + - `ETH: ${l1BlockNumber}\n` + - `arbitrum: ${l2BlockNumber}\n`, + `L2 total supply: ${l2}\n` + + `L1 balanceOf: ${l1}\n\n` + + `ETH: ${l1Block.number}\n` + + `Arbitrum: ${l2BlockDto.number}\n`, ) f.setSeverity(Finding.Severity.CRITICAL) f.setAlertid('OP-STETH-BRIDGE-BALANCE-MISMATCH') f.setType(Finding.FindingType.SUSPICIOUS) - f.setProtocol('arbitrum') + f.setProtocol('ethereum') + f.setUniquekey((l1Block.number + l2BlockDto.number).toString()) out.push(f) } } else { - if (E.isLeft(wstETHBalance_onL1arbitrumBridge)) { + if (E.isLeft(wstETHBalance_onL1ArbitrumBridge)) { out.push( networkAlert( - wstETHBalance_onL1arbitrumBridge.left, + wstETHBalance_onL1ArbitrumBridge.left, `Error in ${BridgeBalanceSrv.name}.${this.handleBridgeBalanceWstETH.name}:46`, `Could not call clientL1.getWstEthBalance`, ), ) - if (E.isLeft(wstETHTotalSupply_onarbitrum)) { + if (E.isLeft(wstETHTotalSupply_onArbitrum)) { out.push( networkAlert( - wstETHTotalSupply_onarbitrum.left, + wstETHTotalSupply_onArbitrum.left, `Error in ${BridgeBalanceSrv.name}.${this.handleBridgeBalanceWstETH.name}:46`, `Could not call clientL2.getWstEthTotalSupply`, ), @@ -105,49 +117,57 @@ export class BridgeBalanceSrv { return out } - private async handleBridgeBalanceLDO(l1BlockNumber: number, l2BlockNumber: number): Promise { - const [ldoBalance_onL1arbitrumBridge, ldoTotalSupply_onarbitrum] = await Promise.all([ - this.clientL1.getLDOBalance(l1BlockNumber, this.arbitrumL1LdoBridge), - this.clientL2.getLdoTotalSupply(l2BlockNumber), + private async handleBridgeBalanceLDO(l1Block: BlockDto, l2BlockDto: BlockDto): Promise { + const [ldoBalance_onL1ArbitrumBridge, ldoTotalSupply_onArbitrum] = await Promise.all([ + this.clientL1.getLDOBalance(l1Block.hash), + this.clientL2.getLdoTotalSupply(l2BlockDto.hash), ]) const out: Finding[] = [] - if (E.isRight(ldoBalance_onL1arbitrumBridge) && E.isRight(ldoTotalSupply_onarbitrum)) { - if (ldoTotalSupply_onarbitrum.right.isGreaterThan(ldoBalance_onL1arbitrumBridge.right)) { + if (E.isRight(ldoBalance_onL1ArbitrumBridge) && E.isRight(ldoTotalSupply_onArbitrum)) { + const l1 = ldoBalance_onL1ArbitrumBridge.right.dividedBy(ETH_DECIMALS).toFixed(2) + const l2 = ldoTotalSupply_onArbitrum.right.dividedBy(ETH_DECIMALS).toFixed(2) + + // TODO to think about prom metrics + this.logger.info(`Ldo balance: l1(${l1Block.number}) = ${l1}`) + this.logger.info(`Ldo balance: l2(${l2BlockDto.number}) = ${l2}`) + + if (ldoTotalSupply_onArbitrum.right.isGreaterThan(ldoBalance_onL1ArbitrumBridge.right)) { const f = new Finding() f.setName(`🚨🚨🚨 Arbitrum bridge LDO balance mismatch`) f.setDescription( `Total supply of bridged LDO is greater than balanceOf L1 bridge side!\n` + - `L2 total supply: ${ldoTotalSupply_onarbitrum.right.dividedBy(ETH_DECIMALS).toFixed(2)}\n` + - `L1 balanceOf: ${ldoBalance_onL1arbitrumBridge.right.dividedBy(ETH_DECIMALS).toFixed(2)}\n\n` + - `ETH: ${l1BlockNumber}\n` + - `arbitrum: ${l2BlockNumber}\n`, + `L2 total supply: ${ldoTotalSupply_onArbitrum.right.dividedBy(ETH_DECIMALS).toFixed(2)}\n` + + `L1 balanceOf: ${ldoBalance_onL1ArbitrumBridge.right.dividedBy(ETH_DECIMALS).toFixed(2)}\n\n` + + `ETH: ${l1Block.number}\n` + + `Arbitrum: ${l2BlockDto.number}\n`, ) f.setSeverity(Finding.Severity.CRITICAL) f.setType(Finding.FindingType.SUSPICIOUS) f.setSeverity(Finding.Severity.CRITICAL) f.setAlertid('OP-LDO-BRIDGE-BALANCE-MISMATCH') f.setType(Finding.FindingType.SUSPICIOUS) - f.setProtocol('arbitrum') + f.setProtocol('ethereum') + f.setUniquekey((l1Block.number + l2BlockDto.number).toString()) out.push(f) } } else { - if (E.isLeft(ldoBalance_onL1arbitrumBridge)) { + if (E.isLeft(ldoBalance_onL1ArbitrumBridge)) { out.push( networkAlert( - ldoBalance_onL1arbitrumBridge.left, + ldoBalance_onL1ArbitrumBridge.left, `Error in ${BridgeBalanceSrv.name}.${this.handleBridgeBalanceLDO.name}:103`, `Could not call clientL1.getLDOBalance`, ), ) - if (E.isLeft(ldoTotalSupply_onarbitrum)) { + if (E.isLeft(ldoTotalSupply_onArbitrum)) { out.push( networkAlert( - ldoTotalSupply_onarbitrum.left, + ldoTotalSupply_onArbitrum.left, `Error in ${BridgeBalanceSrv.name}.${this.handleBridgeBalanceLDO.name}:102`, `Could not call clientL2.getLdoTotalSupply`, ), diff --git a/l2-bridge-arbitrum/src/services/event_watcher.ts b/l2-bridge-arbitrum/src/services/event_watcher.ts index 5036e4e2..c737ba8e 100644 --- a/l2-bridge-arbitrum/src/services/event_watcher.ts +++ b/l2-bridge-arbitrum/src/services/event_watcher.ts @@ -42,7 +42,8 @@ export class EventWatcher { f.setAlertid(eventToFinding.alertId) f.setSeverity(eventToFinding.severity) f.setType(eventToFinding.type) - f.setProtocol('arbitrum') + f.setProtocol('ethereum') + f.setUniquekey(log.blockNumber.toString()) const m = f.getMetadataMap() m.set('args', String(logDesc.args)) diff --git a/l2-bridge-arbitrum/src/services/health-checker/health-checker.srv.ts b/l2-bridge-arbitrum/src/services/health-checker/health-checker.srv.ts index 5f07ca97..9dcd1f37 100644 --- a/l2-bridge-arbitrum/src/services/health-checker/health-checker.srv.ts +++ b/l2-bridge-arbitrum/src/services/health-checker/health-checker.srv.ts @@ -35,11 +35,9 @@ export class HealthChecker { if (f.getAlertid() === NetworkErrorFinding) { this.logger.warn(f.getName(), { desc: f.getDescription(), - err: { - stack: f.getMetadataMap()['stack'], - msg: f.getMetadataMap()['message'], - err: f.getMetadataMap()['name'], - }, + stack: f.getMetadataMap()['stack'], + msg: f.getMetadataMap()['message'], + err: f.getMetadataMap()['name'], }) errCount += 1 diff --git a/l2-bridge-arbitrum/src/services/monitor_withdrawals.spec.ts b/l2-bridge-arbitrum/src/services/monitor_withdrawals.spec.ts index d03d5986..45004a86 100644 --- a/l2-bridge-arbitrum/src/services/monitor_withdrawals.spec.ts +++ b/l2-bridge-arbitrum/src/services/monitor_withdrawals.spec.ts @@ -5,11 +5,11 @@ import { Config } from '../utils/env/env' import promClient from 'prom-client' import { Metrics } from '../utils/metrics/metrics' import { MonitorWithdrawals } from './monitor_withdrawals' -import { TransactionDto } from '../entity/events' import { ArbitrumClient } from '../clients/arbitrum_client' import * as Winston from 'winston' import BigNumber from 'bignumber.js' -import { WithdrawalRecord } from '../entity/blockDto' +import { BlockDtoWithTransactions, BlockHash, TransactionDto, WithdrawalRecord } from '../entity/blockDto' +import { LRUCache } from 'lru-cache' const TEST_TIMEOUT = 120_000 @@ -22,7 +22,7 @@ describe('monitor_withdrawals', () => { transports: [new Winston.transports.Console()], }) - const arbitrumProvider = new ethers.providers.JsonRpcProvider(config.arbitrumRpcUrl, config.chainId) + const arbitrumProvider = new ethers.providers.JsonRpcProvider(config.arbitrumRpcUrl, config.arbChainID) const l2Bridge = L2ERC20TokenGateway__factory.connect(adr.ARBITRUM_L2_TOKEN_GATEWAY.address, arbitrumProvider) const bridgedWSthEthRunner = ERC20Bridged__factory.connect(adr.ARBITRUM_WSTETH_BRIDGED.address, arbitrumProvider) @@ -30,9 +30,41 @@ describe('monitor_withdrawals', () => { const customRegister = new promClient.Registry() const metrics = new Metrics(customRegister, config.promPrefix) - const l2Client = new ArbitrumClient(arbitrumProvider, metrics, l2Bridge, bridgedWSthEthRunner, bridgedLdoRunner) - const withdrawalsSrv = new MonitorWithdrawals(l2Client, adr.ARBITRUM_L2_TOKEN_GATEWAY.address, logger) + const l2BlocksStore = new LRUCache({ + max: 500, + ttl: 1000 * 60 * 2, + }) + + const l2BridgeCache = new LRUCache({ + max: 500, + ttl: 1000 * 60 * 2, + updateAgeOnGet: true, + updateAgeOnHas: true, + }) + const l2Client = new ArbitrumClient( + arbitrumProvider, + metrics, + l2Bridge, + bridgedWSthEthRunner, + bridgedLdoRunner, + l2BlocksStore, + l2BridgeCache, + ) + + const processedWithdrawalBlock = new LRUCache({ + max: 20_000, + ttl: 1000 * 60 * 60, + updateAgeOnGet: true, + updateAgeOnHas: true, + }) + + const withdrawalsSrv = new MonitorWithdrawals( + l2Client, + adr.ARBITRUM_L2_TOKEN_GATEWAY.address, + logger, + processedWithdrawalBlock, + ) test( 'handleTransaction is 1.732302579999414411', diff --git a/l2-bridge-arbitrum/src/services/monitor_withdrawals.ts b/l2-bridge-arbitrum/src/services/monitor_withdrawals.ts index d3c3fa0c..28e73781 100644 --- a/l2-bridge-arbitrum/src/services/monitor_withdrawals.ts +++ b/l2-bridge-arbitrum/src/services/monitor_withdrawals.ts @@ -1,14 +1,13 @@ import BigNumber from 'bignumber.js' -import { BlockDto, WithdrawalRecord } from '../entity/blockDto' +import { BlockDto, TransactionDto, WithdrawalRecord } from '../entity/blockDto' import * as E from 'fp-ts/Either' -import { elapsedTime } from '../utils/time' import { Logger } from 'winston' import { NetworkError } from '../utils/errors' import { WithdrawalInitiatedEvent } from '../generated/typechain/L2ERC20TokenGateway' import { Finding } from '../generated/proto/alert_pb' -import { TransactionDto } from '../entity/events' -import { ethers } from 'forta-agent' +import { ethers } from 'ethers' import { ETH_DECIMALS } from '../utils/constants' +import { LRUCache } from 'lru-cache' // 10k wstETH const MAX_WITHDRAWALS_10K_WstEth = 10_000 @@ -43,10 +42,18 @@ export class MonitorWithdrawals { private withdrawalStore: WithdrawalRecord[] = [] private toManyWithdrawalsTimestamp = 0 - constructor(withdrawalsClient: IMonitorWithdrawalsClient, arbitrumL2TokenGateway: string, logger: Logger) { + private readonly processedWithdrawalBlock: LRUCache + + constructor( + withdrawalsClient: IMonitorWithdrawalsClient, + arbitrumL2TokenGateway: string, + logger: Logger, + processedWithdrawalBlock: LRUCache, + ) { this.withdrawalsClient = withdrawalsClient this.arbitrumL2TokenGateway = arbitrumL2TokenGateway this.logger = logger + this.processedWithdrawalBlock = processedWithdrawalBlock } public getName(): string { @@ -80,7 +87,9 @@ export class MonitorWithdrawals { } public handleL2Block(l2BlockDto: BlockDto): Finding[] { - const start = new Date().getTime() + if (this.processedWithdrawalBlock.has(l2BlockDto.number)) { + return [] + } const out: Finding[] = [] @@ -110,7 +119,7 @@ export class MonitorWithdrawals { : HOURS_48 const f: Finding = new Finding() - f.setName(`⚠️ arbitrum: Huge withdrawals during the last ` + `${Math.floor(period / (60 * 60))} hour(s)`) + f.setName(`⚠️ Arbitrum: Huge withdrawals during the last ` + `${Math.floor(period / (60 * 60))} hour(s)`) f.setDescription( `There were withdrawals requests from L2 to L1 for the ` + `${withdrawalsSum.div(ETH_DECIMALS).toFixed(4)} wstETH in total`, @@ -118,7 +127,8 @@ export class MonitorWithdrawals { f.setAlertid('HUGE-WITHDRAWALS-FROM-L2') f.setSeverity(Finding.Severity.MEDIUM) f.setType(Finding.FindingType.SUSPICIOUS) - f.setProtocol('arbitrum') + f.setProtocol('ethereum') + f.setUniquekey(l2BlockDto.number.toString()) out.push(f) @@ -134,7 +144,7 @@ export class MonitorWithdrawals { this.withdrawalStore = tmp } - this.logger.info(elapsedTime(MonitorWithdrawals.name + '.' + this.handleL2Block.name, start)) + this.processedWithdrawalBlock.set(l2BlockDto.number, true) return out } diff --git a/l2-bridge-arbitrum/src/services/proxy_watcher.ts b/l2-bridge-arbitrum/src/services/proxy_watcher.ts index c774889b..ebc44e72 100644 --- a/l2-bridge-arbitrum/src/services/proxy_watcher.ts +++ b/l2-bridge-arbitrum/src/services/proxy_watcher.ts @@ -4,6 +4,7 @@ import { elapsedTime } from '../utils/time' import { Logger } from 'winston' import { networkAlert } from '../utils/errors' import { Finding } from '../generated/proto/alert_pb' +import { BlockDto } from '../entity/blockDto' export class ProxyWatcher { private readonly name: string = 'ProxyWatcher' @@ -61,16 +62,16 @@ export class ProxyWatcher { return null } - async handleL2Block(l2blockNumber: number): Promise { + async handleL2Block(l2Block: BlockDto): Promise { const start = new Date().getTime() const BLOCK_INTERVAL = 25 const out: Finding[] = [] - if (l2blockNumber % BLOCK_INTERVAL === 0) { + if (l2Block.number % BLOCK_INTERVAL === 0) { const [impl, admin] = await Promise.all([ - this.handleProxyImplementationChanges(l2blockNumber), - this.handleProxyAdminChanges(l2blockNumber), + this.handleProxyImplementationChanges(l2Block.number), + this.handleProxyAdminChanges(l2Block.number), ]) out.push(...impl, ...admin) @@ -78,7 +79,9 @@ export class ProxyWatcher { this.logger.info(this.getName() + '.impl = ' + this.getImpl()) this.logger.info(this.getName() + '.adm = ' + this.getAdmin()) - this.logger.info(elapsedTime(this.proxyContract.getName() + `.` + this.handleL2Block.name, start)) + this.logger.info( + elapsedTime(this.proxyContract.getName() + `.` + this.handleL2Block.name + `(${l2Block.number})`, start), + ) return out } @@ -99,7 +102,7 @@ export class ProxyWatcher { if (newImpl.right.toLowerCase() != this.getImpl().toLowerCase()) { const f: Finding = new Finding() - f.setName('🚨 arbitrum: Proxy implementation changed') + f.setName('🚨 Arbitrum: Proxy implementation changed') f.setDescription( `Proxy implementation for ${this.proxyContract.getName()}(${this.proxyContract.getAddress()}) ` + `was changed form ${this.getImpl()} to ${newImpl}` + @@ -107,7 +110,8 @@ export class ProxyWatcher { ) f.setSeverity(Finding.Severity.HIGH) f.setType(Finding.FindingType.INFORMATION) - f.setProtocol('arbitrum') + f.setProtocol('ethereum') + f.setUniquekey(l2BlockNumber.toString()) const m = f.getMetadataMap() m.set('newImpl', newImpl.right) @@ -145,7 +149,8 @@ export class ProxyWatcher { ) f.setSeverity(Finding.Severity.HIGH) f.setType(Finding.FindingType.INFORMATION) - f.setProtocol('arbitrum') + f.setProtocol('ethereum') + f.setUniquekey(l2blockNumber.toString()) const m = f.getMetadataMap() m.set('newAdmin', newAdmin.right) diff --git a/l2-bridge-arbitrum/src/utils/constants.ts b/l2-bridge-arbitrum/src/utils/constants.ts index 04798565..4c715b4b 100644 --- a/l2-bridge-arbitrum/src/utils/constants.ts +++ b/l2-bridge-arbitrum/src/utils/constants.ts @@ -19,6 +19,7 @@ export type Address = { export type RoleHashToNameMap = Map export const ETH_DECIMALS = new BigNumber(10).pow(18) +export const ETH_BLOCK_TIME_12Sec = 12 const L1_LDO_ADDRESS = '0x5a98fcbea516cf06857215779fd812ca3bef1b32' const L1_WSTETH_ADDRESS = '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0' diff --git a/l2-bridge-arbitrum/src/utils/env/env.ts b/l2-bridge-arbitrum/src/utils/env/env.ts index badd095d..16745f4e 100644 --- a/l2-bridge-arbitrum/src/utils/env/env.ts +++ b/l2-bridge-arbitrum/src/utils/env/env.ts @@ -14,6 +14,7 @@ export class Config { public readonly logLevel: string public readonly chainId: number + public readonly arbChainID: number public readonly promPrefix: string public readonly useFortaProvider: boolean @@ -27,9 +28,10 @@ export class Config { this.logFormat = process.env.LOG_FORMAT || 'simple' this.logLevel = process.env.LOG_LEVEL || 'info' - this.chainId = parseInt(process.env.FORTA_CHAIN_ID!, 10) || 42161 + this.chainId = parseInt(process.env.FORTA_CHAIN_ID!, 10) || 1 + this.arbChainID = 42161 this.ethereumRpcUrl = process.env.ETHEREUM_RPC_URL || 'https://eth.drpc.org' - this.arbitrumRpcUrl = process.env.ARBITRUM_RPC_URL || 'https://arbitrum.drpc.org' + this.arbitrumRpcUrl = process.env.ARBITRUM_RPC_URL || 'https://arb1.arbitrum.io/rpc' this.promPrefix = this.appName.replaceAll('-', '_') diff --git a/l2-bridge-arbitrum/src/utils/metrics/metrics.ts b/l2-bridge-arbitrum/src/utils/metrics/metrics.ts index 743e4e79..a0d3f678 100644 --- a/l2-bridge-arbitrum/src/utils/metrics/metrics.ts +++ b/l2-bridge-arbitrum/src/utils/metrics/metrics.ts @@ -2,7 +2,8 @@ import { Counter, Gauge, Histogram, Registry, Summary } from 'prom-client' export const StatusOK = 'ok' export const StatusFail = 'fail' -export const HandleBlockLabel = 'handleBlock' +export const HandleL1BlockLabel = 'handleL1Block' +export const HandleL2BlockLabel = 'handleL2Block' export const HandleTxLabel = 'handleTx' export class Metrics { diff --git a/l2-bridge-arbitrum/src/utils/mutex.ts b/l2-bridge-arbitrum/src/utils/mutex.ts index 377f148f..85b59797 100644 --- a/l2-bridge-arbitrum/src/utils/mutex.ts +++ b/l2-bridge-arbitrum/src/utils/mutex.ts @@ -1,6 +1,6 @@ import { Mutex, MutexInterface, withTimeout } from 'async-mutex' -export class DataRW { +export class ArrRW { private mutex: MutexInterface private value: T[] @@ -10,18 +10,20 @@ export class DataRW { } async read(): Promise { + await this.mutex.waitForUnlock() await this.mutex.acquire() + let out: T[] try { - const out = this.value - this.value = [] - - return out + out = this.value } finally { this.mutex.release() } + + return out } async write(newValue: T[]): Promise { + await this.mutex.waitForUnlock() await this.mutex.acquire() try { this.value.push(...newValue) @@ -29,4 +31,14 @@ export class DataRW { this.mutex.release() } } + + async clear(): Promise { + await this.mutex.waitForUnlock() + await this.mutex.acquire() + try { + this.value = [] + } finally { + this.mutex.release() + } + } } diff --git a/l2-bridge-arbitrum/src/utils/time.spec.ts b/l2-bridge-arbitrum/src/utils/time.spec.ts new file mode 100644 index 00000000..ad201fec --- /dev/null +++ b/l2-bridge-arbitrum/src/utils/time.spec.ts @@ -0,0 +1,45 @@ +import { isWorkInterval, SECONDS_60, SECONDS_768 } from './time' + +describe('test time', () => { + const l1Timestamp = Math.round(+new Date() / 1000) + + test('future l2Block is true', () => { + const l2Timestamp = l1Timestamp + SECONDS_60 + + console.log(new Date(l1Timestamp * 1000).toUTCString()) + console.log(new Date(l2Timestamp * 1000).toUTCString()) + expect(isWorkInterval(l1Timestamp, l2Timestamp)).toEqual(true) + }) + + test('future l2Block is false', () => { + const l2Timestamp = l1Timestamp + 61 + + console.log(new Date(l1Timestamp * 1000).toUTCString()) + console.log(new Date(l2Timestamp * 1000).toUTCString()) + expect(isWorkInterval(l1Timestamp, l2Timestamp)).toEqual(false) + }) + + test('future l2Block is true', () => { + const l2Timestamp = l1Timestamp + SECONDS_60 + + console.log(new Date(l1Timestamp * 1000).toUTCString()) + console.log(new Date(l2Timestamp * 1000).toUTCString()) + expect(isWorkInterval(l1Timestamp, l2Timestamp)).toEqual(true) + }) + + test('past l2Block is true', () => { + const l2Timestamp = l1Timestamp - SECONDS_768 + + console.log(new Date(l1Timestamp * 1000).toUTCString()) + console.log(new Date(l2Timestamp * 1000).toUTCString()) + expect(isWorkInterval(l1Timestamp, l2Timestamp)).toEqual(true) + }) + + test('past l2Block is false', () => { + const l2Timestamp = l1Timestamp - 769 + + console.log(new Date(l1Timestamp * 1000).toUTCString()) + console.log(new Date(l2Timestamp * 1000).toUTCString()) + expect(isWorkInterval(l1Timestamp, l2Timestamp)).toEqual(false) + }) +}) diff --git a/l2-bridge-arbitrum/src/utils/time.ts b/l2-bridge-arbitrum/src/utils/time.ts index 2b2433be..827f0825 100644 --- a/l2-bridge-arbitrum/src/utils/time.ts +++ b/l2-bridge-arbitrum/src/utils/time.ts @@ -1,3 +1,6 @@ +export const SECONDS_60 = 60 +export const SECONDS_768 = 768 + export function formatTime(timeInMillis: number): string { const seconds = (timeInMillis / 1000).toFixed(3) return `${seconds} seconds` @@ -17,11 +20,11 @@ function formatTimeToHumanReadable(date: Date): string { export function formatDelay(fullDelaySec: number): string { const sign = fullDelaySec >= 0 ? 1 : -1 let delayHours = 0 - let delayMin = Math.floor((sign * fullDelaySec) / 60) - const delaySec = sign * fullDelaySec - delayMin * 60 - if (delayMin >= 60) { - delayHours = Math.floor(delayMin / 60) - delayMin -= delayHours * 60 + let delayMin = Math.floor((sign * fullDelaySec) / SECONDS_60) + const delaySec = sign * fullDelaySec - delayMin * SECONDS_60 + if (delayMin >= SECONDS_60) { + delayHours = Math.floor(delayMin / SECONDS_60) + delayMin -= delayHours * SECONDS_60 } return ( (sign == 1 ? '' : '-') + @@ -30,3 +33,11 @@ export function formatDelay(fullDelaySec: number): string { `${delaySec} sec` ) } + +export function isWorkInterval(l1BlockTimesTamp: number, l2BlockTimesTamp: number): boolean { + return -SECONDS_768 <= l2BlockTimesTamp - l1BlockTimesTamp && l2BlockTimesTamp - l1BlockTimesTamp <= SECONDS_60 +} + +export function toDate(unixTimestamp: number): string { + return new Date(unixTimestamp * 1000).toISOString() +} diff --git a/l2-bridge-arbitrum/yarn.lock b/l2-bridge-arbitrum/yarn.lock index 847ed3cb..2a1feae1 100644 --- a/l2-bridge-arbitrum/yarn.lock +++ b/l2-bridge-arbitrum/yarn.lock @@ -334,32 +334,46 @@ 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-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": + version "4.11.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae" + integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A== -"@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== +"@eslint/config-array@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.17.0.tgz#ff305e1ee618a00e6e5d0485454c8d92d94a860d" + integrity sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA== + dependencies: + "@eslint/object-schema" "^2.1.4" + debug "^4.3.1" + minimatch "^3.1.2" + +"@eslint/eslintrc@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.1.0.tgz#dbd3482bfd91efa663cbe7aa1f506839868207b6" + integrity sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.6.0" - globals "^13.19.0" + espree "^10.0.1" + globals "^14.0.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== +"@eslint/js@9.6.0": + version "9.6.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.6.0.tgz#5b0cb058cc13d9c92d4e561d3538807fa5127c95" + integrity sha512-D9B0/3vNg44ZeWbYMpBoXqNP4j6eQD5vNwIlGAuFRRzK/WtT/jvDQW3Bi9kkf3PMDMlM7Yi+73VLUsn5bJcl8A== + +"@eslint/object-schema@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.4.tgz#9e69f8bb4031e11df79e03db09f9dbbae1740843" + integrity sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ== -"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.0", "@ethersproject/abi@^5.7.0": +"@ethersproject/abi@5.7.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== @@ -550,7 +564,7 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.0.0": +"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.7.2": version "5.7.2" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== @@ -701,12 +715,12 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@faker-js/faker@^8.3.1": +"@faker-js/faker@^8.4.1": version "8.4.1" resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-8.4.1.tgz#5d5e8aee8fce48f5e189bf730ebd1f758f491451" integrity sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg== -"@grpc/grpc-js@^1.10.2", "@grpc/grpc-js@^1.3.6": +"@grpc/grpc-js@^1.10.10", "@grpc/grpc-js@^1.3.6": version "1.10.10" resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.10.10.tgz#476d315feeb9dbb0f2d6560008c92688c30f13e0" integrity sha512-HPa/K5NX6ahMoeBv15njAc/sfF4/jmiXLar9UlC2UfHFKZzsCVLc3wbe7+7qua7w9VPh2/L6EBxyAV7/E8Wftg== @@ -735,24 +749,27 @@ 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== +"@humanwhocodes/retry@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.0.tgz#6d86b8cb322660f03d3f0aa94b99bdd8e172d570" + integrity sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew== + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" @@ -1048,6 +1065,11 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + "@pkgr/core@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" @@ -1145,7 +1167,7 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== -"@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== @@ -1257,7 +1279,7 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^29.5.11": +"@types/jest@^29.5.12": version "29.5.12" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.12.tgz#7f7dc6eb4cf246d2474ed78744b05d06ce025544" integrity sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw== @@ -1265,16 +1287,6 @@ 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.5" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.5.tgz#e6c29b58e66995d57cd170ce3e2a61926d55ee04" - integrity sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw== - "@types/long@^4.0.1": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" @@ -1292,14 +1304,14 @@ dependencies: "@types/node" "*" -"@types/node@*", "@types/node@>=13.7.0", "@types/node@^20.14.2": - version "20.14.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.8.tgz#45c26a2a5de26c3534a9504530ddb3b27ce031ac" - integrity sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA== +"@types/node@*", "@types/node@>=13.7.0", "@types/node@^20.14.10": + version "20.14.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.10.tgz#a1a218290f1b6428682e3af044785e5874db469a" + integrity sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ== dependencies: undici-types "~5.26.4" -"@types/nodemon@^1.19.0": +"@types/nodemon@^1.19.6": version "1.19.6" resolved "https://registry.yarnpkg.com/@types/nodemon/-/nodemon-1.19.6.tgz#1c14bac51dfd3d354e2b5046949f925a742412c4" integrity sha512-vjKuaQOLUA5EY2zkUmWG1ipXbKt9Wd+H/0SiIuHVeH4cHtt6509iRUGH9ZR0iqgUrtj3BrP9KqoTuV3ZCbQcYA== @@ -1328,11 +1340,6 @@ 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/send@*": version "0.17.4" resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" @@ -1384,144 +1391,167 @@ 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== +"@typescript-eslint/eslint-plugin@8.0.0-alpha.41": + version "8.0.0-alpha.41" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.0.0-alpha.41.tgz#8dfb0416c802cf53d9b8ba2688bb5d87c3b5a5ba" + integrity sha512-WePtbzWMaQO4qtGAXp3zzEN8yYZCEuAHVCERCUXgoSUTQ80F5UB7T5lYyA9ySpFDB7rqJ2ev98DtnbS4U3Ms+w== 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" + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.0.0-alpha.41" + "@typescript-eslint/type-utils" "8.0.0-alpha.41" + "@typescript-eslint/utils" "8.0.0-alpha.41" + "@typescript-eslint/visitor-keys" "8.0.0-alpha.41" graphemer "^1.4.0" - ignore "^5.2.4" + ignore "^5.3.1" natural-compare "^1.4.0" - semver "^7.5.4" - ts-api-utils "^1.0.1" + ts-api-utils "^1.3.0" + +"@typescript-eslint/eslint-plugin@^7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.15.0.tgz#8eaf396ac2992d2b8f874b68eb3fcd6b179cb7f3" + integrity sha512-uiNHpyjZtFrLwLDpHnzaDlP3Tt6sGMqTCiqmxaN4n4RP0EfYZDODJyddiFDF44Hjwxr5xAcaYxVKm9QKQFJFLA== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "7.15.0" + "@typescript-eslint/type-utils" "7.15.0" + "@typescript-eslint/utils" "7.15.0" + "@typescript-eslint/visitor-keys" "7.15.0" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/parser@8.0.0-alpha.41": + version "8.0.0-alpha.41" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.0.0-alpha.41.tgz#d6b5f3a869a78d490c94628bc119a9a38c5842a8" + integrity sha512-7HMXwy/q/59ZASBXz2FtdIsR7LgABrR8j2dTKq9GMR8OkjjdO4klxWSY/uOBozVt4UxlMRYsBdBDhEq4/tHRiw== + dependencies: + "@typescript-eslint/scope-manager" "8.0.0-alpha.41" + "@typescript-eslint/types" "8.0.0-alpha.41" + "@typescript-eslint/typescript-estree" "8.0.0-alpha.41" + "@typescript-eslint/visitor-keys" "8.0.0-alpha.41" + debug "^4.3.4" -"@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== +"@typescript-eslint/parser@^7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.15.0.tgz#f4a536e5fc6a1c05c82c4d263a2bfad2da235c80" + integrity sha512-k9fYuQNnypLFcqORNClRykkGOMOj+pV6V91R4GO/l1FDGwpqmSwoOQrOHo3cGaH63e+D3ZiCAOsuS/D2c99j/A== 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" + "@typescript-eslint/scope-manager" "7.15.0" + "@typescript-eslint/types" "7.15.0" + "@typescript-eslint/typescript-estree" "7.15.0" + "@typescript-eslint/visitor-keys" "7.15.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== +"@typescript-eslint/scope-manager@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.15.0.tgz#201b34b0720be8b1447df17b963941bf044999b2" + integrity sha512-Q/1yrF/XbxOTvttNVPihxh1b9fxamjEoz2Os/Pe38OHwxC24CyCqXxGTOdpb4lt6HYtqw9HetA/Rf6gDGaMPlw== dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" + "@typescript-eslint/types" "7.15.0" + "@typescript-eslint/visitor-keys" "7.15.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== +"@typescript-eslint/scope-manager@8.0.0-alpha.41": + version "8.0.0-alpha.41" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.0.0-alpha.41.tgz#7729d129d966cc34a3b37c12cf08ba1b0467d516" + integrity sha512-iNxuQ0TMVfFiMJ2al4bGd/mY9+aLtBxnHfo7B2xoVzR6cRFgUdBLlMa//MSIjSmVRpCEqNLQnkxpJb96tFG+xw== dependencies: - "@typescript-eslint/types" "6.21.0" - "@typescript-eslint/visitor-keys" "6.21.0" + "@typescript-eslint/types" "8.0.0-alpha.41" + "@typescript-eslint/visitor-keys" "8.0.0-alpha.41" -"@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== +"@typescript-eslint/type-utils@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.15.0.tgz#5b83c904c6de91802fb399305a50a56d10472c39" + integrity sha512-SkgriaeV6PDvpA6253PDVep0qCqgbO1IOBiycjnXsszNTVQe5flN5wR5jiczoEoDEnAqYFSFFc9al9BSGVltkg== dependencies: - "@typescript-eslint/typescript-estree" "6.21.0" - "@typescript-eslint/utils" "6.21.0" + "@typescript-eslint/typescript-estree" "7.15.0" + "@typescript-eslint/utils" "7.15.0" debug "^4.3.4" - ts-api-utils "^1.0.1" + ts-api-utils "^1.3.0" -"@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/type-utils@8.0.0-alpha.41": + version "8.0.0-alpha.41" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.0.0-alpha.41.tgz#6da1e44d9ce1e060e66feaa45f73a792965a0e69" + integrity sha512-+QIA1z/jrox6bbvqlyqBQjotpevieLTycfiuoKuqGcKoskFZV5Rma51BV8LCJacnOafwJtSi+7b8zDo8OsXUvA== + dependencies: + "@typescript-eslint/typescript-estree" "8.0.0-alpha.41" + "@typescript-eslint/utils" "8.0.0-alpha.41" + debug "^4.3.4" + ts-api-utils "^1.3.0" + +"@typescript-eslint/types@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.15.0.tgz#fb894373a6e3882cbb37671ffddce44f934f62fc" + integrity sha512-aV1+B1+ySXbQH0pLK0rx66I3IkiZNidYobyfn0WFsdGhSXw+P3YOqeTq5GED458SfB24tg+ux3S+9g118hjlTw== -"@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/types@8.0.0-alpha.41": + version "8.0.0-alpha.41" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.0.0-alpha.41.tgz#c1f8dacfb118e4d9febdff2f065802c4db69beae" + integrity sha512-n0P2FP3YC3pD3yoiCf4lHqbUP45xlnOk8HkjB+LtKSUZZWLLJ8k1ZXZtQj7MEX22tytCMj//Bmq403xFuCwfIg== -"@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== +"@typescript-eslint/typescript-estree@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.15.0.tgz#e323bfa3966e1485b638ce751f219fc1f31eba37" + integrity sha512-gjyB/rHAopL/XxfmYThQbXbzRMGhZzGw6KpcMbfe8Q3nNQKStpxnUKeXb0KiN/fFDR42Z43szs6rY7eHk0zdGQ== dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" + "@typescript-eslint/types" "7.15.0" + "@typescript-eslint/visitor-keys" "7.15.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.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== +"@typescript-eslint/typescript-estree@8.0.0-alpha.41": + version "8.0.0-alpha.41" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.0-alpha.41.tgz#c78c96d6b3f39355aac2bdf2f999abfd9333121f" + integrity sha512-adCr+vbLYTFhwhIwjIjjMxTdUYiPA2Jlyuhnbj092IzgLHtT79bvuwcgPWeTyLbFb/13SMKmOEka00xHiqLpig== dependencies: - "@typescript-eslint/types" "6.21.0" - "@typescript-eslint/visitor-keys" "6.21.0" + "@typescript-eslint/types" "8.0.0-alpha.41" + "@typescript-eslint/visitor-keys" "8.0.0-alpha.41" 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" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" -"@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== +"@typescript-eslint/utils@7.15.0", "@typescript-eslint/utils@^6.0.0 || ^7.0.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.15.0.tgz#9e6253c4599b6e7da2fb64ba3f549c73eb8c1960" + integrity sha512-hfDMDqaqOqsUVGiEPSMLR/AjTSCsmJwjpKkYQRo1FNbmW4tBwBspYDwO9eh7sKSTwMQgBw9/T4DHudPaqshRWA== 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/scope-manager" "7.15.0" + "@typescript-eslint/types" "7.15.0" + "@typescript-eslint/typescript-estree" "7.15.0" -"@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== +"@typescript-eslint/utils@8.0.0-alpha.41": + version "8.0.0-alpha.41" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.0.0-alpha.41.tgz#de7d1fb1856773c7343028352cf326a668aa43b9" + integrity sha512-DTxc9VdERS6iloiw1P5tgRDqRArmp/sIuvgdHBvGh2SiltEFc3VjLGnHHGSTr6GfH7tjFWvcCnCtxx+pjWfp5Q== 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" + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "8.0.0-alpha.41" + "@typescript-eslint/types" "8.0.0-alpha.41" + "@typescript-eslint/typescript-estree" "8.0.0-alpha.41" -"@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== +"@typescript-eslint/visitor-keys@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.15.0.tgz#1da0726201a859343fe6a05742a7c1792fff5b66" + integrity sha512-Hqgy/ETgpt2L5xueA/zHHIl4fJI2O4XUE9l4+OIfbJIRSnTJb/QscncdqqZzofQegIJugRIF57OJea1khw2SDw== dependencies: - "@typescript-eslint/types" "6.21.0" - eslint-visitor-keys "^3.4.1" + "@typescript-eslint/types" "7.15.0" + eslint-visitor-keys "^3.4.3" -"@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== +"@typescript-eslint/visitor-keys@8.0.0-alpha.41": + version "8.0.0-alpha.41" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.0-alpha.41.tgz#6ddefe0a08a36683e2e8db4b161d6b67374b7757" + integrity sha512-uetCAUBVC+YarBdZnWzDDgX11PpAEGV8Cw31I3d1xNrhx6/bJGThKX+holEmd3amMdnr4w/XUKH/4YuQOgtjDA== + dependencies: + "@typescript-eslint/types" "8.0.0-alpha.41" + eslint-visitor-keys "^3.4.3" abbrev@1: version "1.1.1" @@ -1548,10 +1578,10 @@ acorn-walk@^8.1.1: dependencies: acorn "^8.11.0" -acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: - version "8.12.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.0.tgz#1627bfa2e058148036133b8d9b51a700663c294c" - integrity sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw== +acorn@^8.11.0, acorn@^8.12.0, acorn@^8.4.1: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== aes-js@3.0.0: version "3.0.0" @@ -1587,6 +1617,11 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -1606,6 +1641,11 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -1673,10 +1713,10 @@ asn1.js@^4.10.1: 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== +async-mutex@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.5.0.tgz#353c69a0b9e75250971a64ac203b0ebfddd75482" + integrity sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA== dependencies: tslib "^2.4.0" @@ -2001,9 +2041,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001629: - version "1.0.30001636" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz#b15f52d2bdb95fad32c2f53c0b68032b85188a78" - integrity sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg== + version "1.0.30001640" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001640.tgz#32c467d4bf1f1a0faa63fc793c2ba81169e7652f" + integrity sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA== chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" @@ -2264,7 +2304,7 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, 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== @@ -2397,27 +2437,25 @@ dir-glob@^3.0.1: 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" - dotenv@^16.4.5: version "16.4.5" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== electron-to-chromium@^1.4.796: - version "1.4.811" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.811.tgz#031c8b101e7d0a7cde1dfdb0623dbdb5e19655cd" - integrity sha512-CDyzcJ5XW78SHzsIOdn27z8J4ist8eaFLhdto2hSMSJQgsiwvbv2fbizcKUICryw1Wii1TI/FEkvzvJsR3awrA== + version "1.4.818" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.818.tgz#7762c8bfd15a07c3833b7f5deed990e9e5a4c24f" + integrity sha512-eGvIk2V0dGImV9gWLq8fDfTTsCAeMDwZqEPMr+jMInxZdnp9Us8UpovYpRCf9NQ7VOFgrN2doNSgvISbsbNpxA== elliptic@6.5.4: version "6.5.4" @@ -2455,6 +2493,11 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + enabled@2.0.x: version "2.0.0" resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" @@ -2509,19 +2552,19 @@ escape-string-regexp@^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: +eslint-config-prettier@^9.1.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== +eslint-plugin-jest@^28.6.0: + version "28.6.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-28.6.0.tgz#8410588d60bcafa68a91b6ec272e4a415502302a" + integrity sha512-YG28E1/MIKwnz+e2H7VwYPzHUYU4aMa19w0yGcwXnnmJH6EfgHahTJ2un3IyraUxNfnz/KUhJAFXNNwWPo12tg== dependencies: - "@typescript-eslint/utils" "^5.10.0" + "@typescript-eslint/utils" "^6.0.0 || ^7.0.0" -eslint-plugin-prettier@^5.0.1: +eslint-plugin-prettier@^5.1.3: 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== @@ -2529,62 +2572,55 @@ eslint-plugin-prettier@^5.0.1: 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== +eslint-scope@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.0.1.tgz#a9601e4b81a0b9171657c343fb13111688963cfc" + integrity sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og== 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: +eslint-visitor-keys@^3.3.0, 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== +eslint-visitor-keys@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb" + integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw== + +eslint@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.6.0.tgz#9f54373afa15e1ba356656a8d96233182027fb49" + integrity sha512-ElQkdLMEEqQNM9Njff+2Y4q2afHk7JpkPvrd7Xh7xefwgQynqPxwf55J7di9+MEibWUGdNjFF9ITG9Pck5M84w== 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" + "@eslint/config-array" "^0.17.0" + "@eslint/eslintrc" "^3.1.0" + "@eslint/js" "9.6.0" "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.3.0" "@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" + eslint-scope "^8.0.1" + eslint-visitor-keys "^4.0.0" + espree "^10.1.0" + esquery "^1.5.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" + file-entry-cache "^8.0.0" 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" @@ -2594,21 +2630,21 @@ eslint@^8.54.0: 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== +espree@^10.0.1, espree@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.1.0.tgz#8788dae611574c0f070691f522e4116c5a11fc56" + integrity sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA== dependencies: - acorn "^8.9.0" + acorn "^8.12.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.1" + eslint-visitor-keys "^4.0.0" 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: +esquery@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== @@ -2622,11 +2658,6 @@ esrecurse@^4.3.0: 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" @@ -2642,7 +2673,7 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== -ethers@^5.5.1: +ethers@^5.5.1, ethers@^5.7.2: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== @@ -2809,12 +2840,12 @@ fecha@^4.2.0: 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== +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== dependencies: - flat-cache "^3.0.4" + flat-cache "^4.0.0" fill-range@^7.1.1: version "7.1.1" @@ -2868,6 +2899,14 @@ flat-cache@^3.0.4: keyv "^4.5.3" rimraf "^3.0.2" +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.4" + flatted@^3.2.9: version "3.3.1" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" @@ -2883,6 +2922,14 @@ follow-redirects@^1.15.6: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== +foreground-child@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.2.1.tgz#767004ccf3a5b30df39bed90718bab43fe0a59f7" + integrity sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + form-data@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" @@ -2924,10 +2971,10 @@ forwarded@0.2.0: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== -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== +fp-ts@^2.16.8: + version "2.16.8" + resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.16.8.tgz#dfa1ea1c967ac6794c43ce877aeb8ed76f5e0df7" + integrity sha512-nmDtNqmMZkOxu0M5hkrS9YA15/KPkYkILb6Axg9XBAoUoYEtzg+LFmVWqZrl9FNttsW0qIUpx9RCA9INbv+Bxw== fresh@0.5.2: version "0.5.2" @@ -3037,6 +3084,18 @@ glob@7.1.7: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^10.3.10: + version "10.4.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.3.tgz#e0ba2253dd21b3d0acdfb5d507c59a29f513fc7a" + integrity sha512-Q38SGlYRpVtDBPSWEylRyctn7uDeTp4NQERTLiCT1FqA9JXPYWqAVmQU6qh4r/zMM5ehxTcbaO8EjhWnvEhmyg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + 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" @@ -3049,28 +3108,15 @@ glob@^7.0.0, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: 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" +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== globby@^11.1.0: version "11.1.0" @@ -3235,10 +3281,10 @@ human-signals@^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== +husky@^9.0.11: + version "9.0.11" + resolved "https://registry.yarnpkg.com/husky/-/husky-9.0.11.tgz#fc91df4c756050de41b3e478b2158b87c1e79af9" + integrity sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw== iconv-lite@0.4.24: version "0.4.24" @@ -3257,7 +3303,7 @@ ignore-by-default@^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: +ignore@^5.2.0, ignore@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== @@ -3394,9 +3440,9 @@ istanbul-lib-instrument@^5.0.4: 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== + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== dependencies: "@babel/core" "^7.23.9" "@babel/parser" "^7.23.9" @@ -3430,6 +3476,15 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +jackspeak@^3.1.2: + version "3.4.1" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.1.tgz#145422416740568e9fc357bf60c844b3c1585f09" + integrity sha512-U23pQPDnmYybVkYjObcuYMk43VRlMLLqLI+RdZy8s8WV8WsxO9SnqSroKaluuvcNOdCAlauKszDwd+umbot5Mg== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.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" @@ -3887,7 +3942,7 @@ keythereum@^1.2.0: sjcl "1.0.6" uuid "3.0.0" -keyv@^4.5.3: +keyv@^4.5.3, keyv@^4.5.4: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== @@ -3985,6 +4040,11 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" +lru-cache@^10.2.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.0.tgz#cb29b4b2dd55b22e4a729cdb096093d7f85df02d" + integrity sha512-bfJaPTuEiTYBu+ulDaeQ0F+uLmlfFkMgXj4cbwfuMSjgObGMzb55FMMbDvbRU0fAHZ4sLGkz2mKwcMg8Dvm8Ww== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -4100,28 +4160,21 @@ minimalistic-crypto-utils@^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: +minimatch@^3.0.4, 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== +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== dependencies: brace-expansion "^2.0.1" -minimist@^1.2.5, minimist@^1.2.6: +minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -4138,6 +4191,11 @@ minipass@^5.0.0: resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + minizlib@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" @@ -4233,7 +4291,7 @@ node-releases@^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: +nodemon@^3.1.4: version "3.1.4" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.1.4.tgz#c34dcd8eb46a05723ccde60cbdd25addcc8725e4" integrity sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ== @@ -4361,6 +4419,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +package-json-from-dist@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00" + integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -4431,6 +4494,14 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -4474,16 +4545,15 @@ pkg-dir@^4.2.0: 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== +postinstall@^0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/postinstall/-/postinstall-0.10.3.tgz#dcfbf9cabbd4fcc2b4e0cd46b29ce919dc18fc7a" + integrity sha512-ENJzsDixmwtLx+/+6brMy0yjHNAkazW6+xRfO8rKVlh+z3s5/4qyfZBudEvXscdXDtHHQ/mKZBsrgVOZL6lB8Q== dependencies: "@danieldietrich/copy" "^0.4.2" - glob "^8.0.3" - minimist "^1.2.6" - resolve-from "^5.0.0" - resolve-pkg "^2.0.0" + glob "^10.3.10" + minimist "^1.2.8" + resolve "^1.22.8" prelude-ls@^1.2.1: version "1.2.1" @@ -4502,7 +4572,7 @@ prettier@^2.1.2, prettier@^2.3.1: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== -prettier@^3.1.0: +prettier@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.2.tgz#03ff86dc7c835f2d2559ee76876a3914cec4a90a" integrity sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA== @@ -4521,10 +4591,10 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -prom-client@^15.1.2: - version "15.1.2" - resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-15.1.2.tgz#78d79f12c35d395ca97edf7111c18210cf07f815" - integrity sha512-on3h1iXb04QFLLThrmVYg1SChBQ9N1c+nKAjebBjokBqipddH3uxmOUcEkTnzmJ8Jh/5TSUnUqS40i2QB2dJHQ== +prom-client@^15.1.3: + version "15.1.3" + resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-15.1.3.tgz#69fa8de93a88bc9783173db5f758dc1c69fa8fc2" + integrity sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g== dependencies: "@opentelemetry/api" "^1.4.0" tdigest "^0.1.1" @@ -4729,19 +4799,12 @@ resolve-from@^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: +resolve@^1.1.6, resolve@^1.20.0, resolve@^1.22.8, 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== @@ -4821,7 +4884,7 @@ semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.5, semver@^7.3.7, semver@^7.5.3, semver@^7.5.4: +semver@^7.3.5, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0: version "7.6.2" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== @@ -4928,6 +4991,11 @@ signal-exit@^3.0.0, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -5005,6 +5073,15 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" +"string-width-cjs@npm:string-width@^4.2.0": + 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-width@^1.0.2 || 2 || 3 || 4", 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" @@ -5014,6 +5091,15 @@ string-length@^4.0.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.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" @@ -5028,6 +5114,13 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +"strip-ansi-cjs@npm: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-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" @@ -5035,6 +5128,13 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.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" @@ -5169,7 +5269,7 @@ triple-beam@^1.3.0: 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: +ts-api-utils@^1.3.0: 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== @@ -5209,7 +5309,7 @@ ts-generator@^0.1.1: resolve "^1.8.1" ts-essentials "^1.0.0" -ts-jest@^29.1.2: +ts-jest@^29.1.5: version "29.1.5" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.5.tgz#d6c0471cc78bffa2cb4664a0a6741ef36cfe8f69" integrity sha512-UuClSYxM7byvvYfyWdFI+/2UxMmwNyJb0NPkZPQE2hew3RurV7l7zURgOHAd/1I1ZdPpe3GUsXNXAcN8TFKSIg== @@ -5242,28 +5342,16 @@ ts-node@^10.9.2: v8-compile-cache-lib "^3.0.1" yn "3.1.1" -ts-retry@^4.2.4: +ts-retry@^4.2.5: 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" @@ -5276,11 +5364,6 @@ type-detect@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" @@ -5310,10 +5393,19 @@ typechain@^8.3.2: ts-command-line-args "^2.2.0" ts-essentials "^7.0.1" -typescript@^5.3.2: - version "5.5.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.2.tgz#c26f023cb0054e657ce04f72583ea2d85f8d0507" - integrity sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew== +typescript-eslint@^8.0.0-alpha.39: + version "8.0.0-alpha.41" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.0.0-alpha.41.tgz#b88af15dfbfa08051f4697698193fcae04ee147f" + integrity sha512-+e7D2XDZeHLe9D3bP7S0Va8YdLHzn3YcesoxMS9SjMWhtaSb5ylxk2txqT84sUS0WIDQetZlvDg2/UmY5B/ycg== + dependencies: + "@typescript-eslint/eslint-plugin" "8.0.0-alpha.41" + "@typescript-eslint/parser" "8.0.0-alpha.41" + "@typescript-eslint/utils" "8.0.0-alpha.41" + +typescript@^5.5.3: + version "5.5.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.3.tgz#e1b0a3c394190838a0b168e771b0ad56a0af0faa" + integrity sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ== typical@^4.0.0: version "4.0.0" @@ -5351,9 +5443,9 @@ unpipe@1.0.0, unpipe@~1.0.0: integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== update-browserslist-db@^1.0.16: - version "1.0.16" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz#f6d489ed90fb2f07d67784eb3f53d7891f736356" - integrity sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ== + version "1.1.0" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" + integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ== dependencies: escalade "^3.1.2" picocolors "^1.0.1" @@ -5447,7 +5539,7 @@ winston-transport@^4.7.0: readable-stream "^3.6.0" triple-beam "^1.3.0" -winston@^3.11.0: +winston@^3.13.0: version "3.13.0" resolved "https://registry.yarnpkg.com/winston/-/winston-3.13.0.tgz#e76c0d722f78e04838158c61adc1287201de7ce3" integrity sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ== @@ -5482,6 +5574,15 @@ wordwrapjs@^4.0.0: reduce-flatten "^2.0.0" typical "^5.2.0" +"wrap-ansi-cjs@npm: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" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -5491,6 +5592,15 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"