From 36e80be933c1733fe9c4cbb36aebe3acefbe71f3 Mon Sep 17 00:00:00 2001 From: Sergey White Date: Wed, 10 Jan 2024 18:39:09 +0300 Subject: [PATCH 01/22] feat: eth-steth-v2 --- ethereum-governance/src/common/utils.ts | 8 +- ethereum-huge-tx/src/common/utils.ts | 22 +- ethereum-steth-v2/.dockerignore | 3 + ethereum-steth-v2/.eslintrc.json | 13 + ethereum-steth-v2/.gitignore | 8 + ethereum-steth-v2/.prettierrc.json | 7 + ethereum-steth-v2/Dockerfile | 24 + ethereum-steth-v2/README.md | 43 + ethereum-steth-v2/package.json | 84 + ethereum-steth-v2/src/abi/Lido.json | 1 + .../src/abi/WithdrawalQueueERC721.json | 1 + ethereum-steth-v2/src/agent.ts | 80 + ethereum-steth-v2/src/app.ts | 54 + .../src/clients/eth_formatter.ts | 15 + ethereum-steth-v2/src/clients/eth_provider.ts | 139 + ethereum-steth-v2/src/entity/events.ts | 11 + ethereum-steth-v2/src/entity/metadata.ts | 1 + .../steth_operation/StethOperation.cache.ts | 42 + .../steth_operation/StethOperation.srv.ts | 271 + ethereum-steth-v2/src/utils/constants.ts | 11 + ethereum-steth-v2/src/utils/error.ts | 14 + ethereum-steth-v2/src/utils/mutex.ts | 33 + ethereum-steth-v2/src/utils/tier.ts | 56 + ethereum-steth-v2/src/utils/time.ts | 15 + ethereum-steth-v2/src/utils/version.ts | 27 + ethereum-steth-v2/tsconfig.json | 12 + ethereum-steth-v2/yarn.lock | 4665 +++++++++++++++++ ethereum-validators-set/src/common/utils.ts | 8 +- lido-on-polygon/src/helpers.ts | 6 +- polygon/src/helpers.ts | 6 +- storage-watcher/src/common/utils.ts | 8 +- voting-watcher/src/helpers.ts | 6 +- 32 files changed, 5669 insertions(+), 25 deletions(-) create mode 100644 ethereum-steth-v2/.dockerignore create mode 100644 ethereum-steth-v2/.eslintrc.json create mode 100644 ethereum-steth-v2/.gitignore create mode 100644 ethereum-steth-v2/.prettierrc.json create mode 100644 ethereum-steth-v2/Dockerfile create mode 100644 ethereum-steth-v2/README.md create mode 100644 ethereum-steth-v2/package.json create mode 100644 ethereum-steth-v2/src/abi/Lido.json create mode 100644 ethereum-steth-v2/src/abi/WithdrawalQueueERC721.json create mode 100644 ethereum-steth-v2/src/agent.ts create mode 100644 ethereum-steth-v2/src/app.ts create mode 100644 ethereum-steth-v2/src/clients/eth_formatter.ts create mode 100644 ethereum-steth-v2/src/clients/eth_provider.ts create mode 100644 ethereum-steth-v2/src/entity/events.ts create mode 100644 ethereum-steth-v2/src/entity/metadata.ts create mode 100644 ethereum-steth-v2/src/services/steth_operation/StethOperation.cache.ts create mode 100644 ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts create mode 100644 ethereum-steth-v2/src/utils/constants.ts create mode 100644 ethereum-steth-v2/src/utils/error.ts create mode 100644 ethereum-steth-v2/src/utils/mutex.ts create mode 100644 ethereum-steth-v2/src/utils/tier.ts create mode 100644 ethereum-steth-v2/src/utils/time.ts create mode 100644 ethereum-steth-v2/src/utils/version.ts create mode 100644 ethereum-steth-v2/tsconfig.json create mode 100644 ethereum-steth-v2/yarn.lock diff --git a/ethereum-governance/src/common/utils.ts b/ethereum-governance/src/common/utils.ts index 0812a4de..3dadfd54 100644 --- a/ethereum-governance/src/common/utils.ts +++ b/ethereum-governance/src/common/utils.ts @@ -44,8 +44,8 @@ export function etherscanNft(address: string, id: number | string): string { /** * Special wrapper under `require` function that allows to - * redefine variables from a file with the same name and `.` suffix. - * `` is a string that is passed by `FORTA_AGENT_RUN_TIER` environment variable. + * redefine variables from a file with the same name and `.` suffix. + * `` is a string that is passed by `FORTA_AGENT_RUN_TIER` environment variable. * @param module module object to get the path from. * @param path relative to module path to the main file to import. * @param mode `strict` or `merge`. Default: `strict`. @@ -56,7 +56,9 @@ export function requireWithTier( mode: RedefineMode = RedefineMode.Strict, ): T { const defaultContent = require(`${module.path}/${path}`); - if (!RUN_TIER) return defaultContent; + if (!RUN_TIER) { + return defaultContent; + } let tieredContent: any; try { tieredContent = require(`${module.path}/${path}.${RUN_TIER}`); diff --git a/ethereum-huge-tx/src/common/utils.ts b/ethereum-huge-tx/src/common/utils.ts index a47c9705..11e6c23c 100644 --- a/ethereum-huge-tx/src/common/utils.ts +++ b/ethereum-huge-tx/src/common/utils.ts @@ -2,21 +2,21 @@ import { TransactionEvent } from "forta-agent"; import BigNumber from "bignumber.js"; import { - TransferEventInfo, ComplexTransferPattern, - TransferPattern, - TransferText, - TransferEventMetadata, - ETH_DECIMALS, CURVE_EXCHANGE_EVENT, + CURVE_POOL_ADDRESS, + ETH_DECIMALS, EXCHANGE_ETH_TO_STETH_CURVE_PATTERN, EXCHANGE_STETH_TO_ETH_CURVE_PATTERN, - TX_AMOUNT_THRESHOLD, + LDO_TOKEN_ADDRESS, PARTIALLY_MONITORED_TOKENS, SIMPLE_TRANSFERS, + TransferEventInfo, + TransferEventMetadata, + TransferPattern, + TransferText, + TX_AMOUNT_THRESHOLD, TX_AMOUNT_THRESHOLD_LDO, - LDO_TOKEN_ADDRESS, - CURVE_POOL_ADDRESS, } from "../subagents/huge-tx/constants"; const SI_SYMBOL = ["", "k", "M", "G", "T", "P", "E"]; @@ -235,11 +235,13 @@ export function applicableAmount(transferInfo: TransferEventInfo) { } export function abbreviateNumber(number: number): string { - // what tier? (determines SI symbol) + // what tier.ts? (determines SI symbol) const tier = (Math.log10(Math.abs(number)) / 3) | 0; // if zero, we don't need a suffix - if (tier == 0) return Math.round(number).toString(); + if (tier == 0) { + return Math.round(number).toString(); + } // get suffix and determine scale const suffix = SI_SYMBOL[tier]; diff --git a/ethereum-steth-v2/.dockerignore b/ethereum-steth-v2/.dockerignore new file mode 100644 index 00000000..78972be8 --- /dev/null +++ b/ethereum-steth-v2/.dockerignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +forta.config.json diff --git a/ethereum-steth-v2/.eslintrc.json b/ethereum-steth-v2/.eslintrc.json new file mode 100644 index 00000000..867579eb --- /dev/null +++ b/ethereum-steth-v2/.eslintrc.json @@ -0,0 +1,13 @@ +{ + "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" + } +} diff --git a/ethereum-steth-v2/.gitignore b/ethereum-steth-v2/.gitignore new file mode 100644 index 00000000..f73222b7 --- /dev/null +++ b/ethereum-steth-v2/.gitignore @@ -0,0 +1,8 @@ +node_modules +dist +forta.config.json +.yarn/* +!.yarn/releases/ +*.log +version.json +.DS_Store diff --git a/ethereum-steth-v2/.prettierrc.json b/ethereum-steth-v2/.prettierrc.json new file mode 100644 index 00000000..852349dd --- /dev/null +++ b/ethereum-steth-v2/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "semi": false, + "trailingComma": "all", + "singleQuote": true, + "printWidth": 120, + "tabWidth": 2 +} diff --git a/ethereum-steth-v2/Dockerfile b/ethereum-steth-v2/Dockerfile new file mode 100644 index 00000000..f3cd7ac6 --- /dev/null +++ b/ethereum-steth-v2/Dockerfile @@ -0,0 +1,24 @@ +# Build stage: compile Typescript to Javascript +FROM node:20.10.0-alpine3.18 AS base + +FROM base as builder + +WORKDIR /app + +COPY . . +RUN yarn install --immutable && yarn run build +# Build app +RUN yarn run build + +# Final stage: copy compiled Javascript from previous stage and install production dependencies +FROM base as production +LABEL "network.forta.settings.agent-logs.enable"="true" +ENV NODE_ENV=production +WORKDIR /app + +COPY package*.json yarn.lock ./ +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/dist ./src +COPY version.json ./ + +CMD ["yarn", "run", "start:prod"] \ No newline at end of file diff --git a/ethereum-steth-v2/README.md b/ethereum-steth-v2/README.md new file mode 100644 index 00000000..e858f90f --- /dev/null +++ b/ethereum-steth-v2/README.md @@ -0,0 +1,43 @@ +# Lido Detection Mantle Bot + +How does it work. + +The bot works on two networks: ETH mainnet and Mantle. +Here's how it operates: The bot monitors new blocks on the ETH mainnet. +Since Forta doesn't currently support Mantle, the bot reads blocks on the L2 network (Mantle) and stores the latest one +in an in-memory cache. +When the bot reads the next block on ETH, it also retrieves a segment of Mantle blocks (cachedBlock, LatestBlock) from +the cache. + +## Supported chains + +- Ethereum mainnet, Mantle network + +## Alerts + +1. StETH operations + 1. 🚨🚨🚨 Buffered ETH drain + 2. 🚨 Huge depositable ETH amount + 3. ⚠️ High depositable ETH amount + +## Development (Forta specific) + +Edit `alerting-forta//forta.config.json` and set `jsonRpcUrl` to your JSON-RPC provider. Install deps: + +``` +yarn install +``` + +Running in a live mode: + +``` +yarn start:dev +``` + +Testing on a specific block/range/transaction: + +``` +yarn block 13626668 +yarn range '13626667..13626668' +yarn tx 0x2d2774c04e3faf9f17cd26e0978bb812081b9d0b5cc6fd8bf04cc441f92c0a8c +``` diff --git a/ethereum-steth-v2/package.json b/ethereum-steth-v2/package.json new file mode 100644 index 00000000..a422db6e --- /dev/null +++ b/ethereum-steth-v2/package.json @@ -0,0 +1,84 @@ +{ + "name": "lido-steth-forta-bot", + "version": "0.0.1", + "description": "Forta Bot for Lido stETH", + "repository": { + "type": "git", + "directory": "https://github.com/lidofinance/alerting-forta/tree/main/ethereum-steth" + }, + "license": "MIT", + "chainIds": [ + 1 + ], + "husky": { + "hooks": { + "pre-commit": "yarn run lint" + } + }, + "scripts": { + "update-version": "node ../utils/write-version.js", + "build": "tsc && yarn run copy-version", + "copy-version": "cp version.json dist", + "start": "yarn run update-version && yarn run start:dev", + "start:dev": "nodemon --watch src --watch forta.config.json -e js,ts,json --exec \"yarn run build && yarn run copy-version && forta-agent run\"", + "start:prod": "forta-agent run --prod", + "tx": "yarn run build && forta-agent run --tx", + "block": "yarn run build && forta-agent run --block", + "range": "yarn run build && forta-agent run --range", + "alert": "yarn run build && forta-agent run --alert", + "sequence": "yarn run build && forta-agent run --sequence", + "file": "yarn run build && forta-agent run --file", + "publish": "yarn run update-version && forta-agent publish", + "info": "forta-agent info", + "logs": "forta-agent logs", + "push": "yarn run update-version && forta-agent push", + "disable": "forta-agent disable", + "enable": "forta-agent enable", + "keyfile": "forta-agent keyfile", + "stake": "forta-agent stake", + "test": "jest", + "generate-types": "typechain --target=ethers-v5 --out-dir=./src/generated ./src/abi/*", + "eslint:lint": "eslint ./src", + "eslint:format": "eslint ./src --fix", + "prettier:check": "prettier --check .", + "prettier:format": "prettier --write .", + "lint": "yarn run prettier:check && yarn run eslint:lint", + "format": "yarn run eslint:format && yarn run prettier:format", + "postinstall": "yarn generate-types" + }, + "dependencies": { + "@types/lodash": "^4.14.202", + "async-mutex": "^0.4.0", + "bignumber.js": "^9.1.2", + "ethers": "^5.5.1", + "forta-agent": "^0.1.48", + "fp-ts": "^2.16.1", + "lodash": "^4.17.21", + "ts-retry": "^4.2.4" + }, + "devDependencies": { + "@ethersproject/abi": "^5.0.0", + "@ethersproject/providers": "^5.0.0", + "@tsconfig/node20": "^20.1.2", + "@typechain/ethers-v5": "^11.1.2", + "@types/jest": "^29.5.10", + "@types/nodemon": "^1.19.0", + "@types/ws": "^8.5.10", + "@typescript-eslint/eslint-plugin": "^6.12.0", + "@typescript-eslint/parser": "^6.12.0", + "eslint": "^8.54.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-jest": "^27.6.0", + "eslint-plugin-prettier": "^5.0.1", + "husky": "^8.0.3", + "jest": "^29.7.0", + "nodemon": "^3.0.1", + "postinstall": "^0.8.0", + "prettier": "^3.1.0", + "ts-generator": "^0.1.1", + "ts-jest": "^29.1.1", + "typechain": "^8.3.2", + "typescript": "^5.3.2" + }, + "packageManager": "yarn@1.22.21" +} diff --git a/ethereum-steth-v2/src/abi/Lido.json b/ethereum-steth-v2/src/abi/Lido.json new file mode 100644 index 00000000..41247452 --- /dev/null +++ b/ethereum-steth-v2/src/abi/Lido.json @@ -0,0 +1 @@ +[{"constant":false,"inputs":[],"name":"resume","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[],"name":"stop","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"STAKING_CONTROL_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ethAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStakingPaused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_maxStakeLimit","type":"uint256"},{"name":"_stakeLimitIncreasePerBlock","type":"uint256"}],"name":"setStakingLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"RESUME_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_lidoLocator","type":"address"},{"name":"_eip712StETH","type":"address"}],"name":"finalizeUpgrade_v2","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_newDepositedValidators","type":"uint256"}],"name":"unsafeChangeDepositedValidators","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTreasury","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBufferedEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_lidoLocator","type":"address"},{"name":"_eip712StETH","type":"address"}],"name":"initialize","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"receiveELRewards","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentStakeLimit","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getStakeLimitFullInfo","outputs":[{"name":"isStakingPaused","type":"bool"},{"name":"isStakingLimitSet","type":"bool"},{"name":"currentStakeLimit","type":"uint256"},{"name":"maxStakeLimit","type":"uint256"},{"name":"maxStakeLimitGrowthBlocks","type":"uint256"},{"name":"prevStakeLimit","type":"uint256"},{"name":"prevStakeBlockNumber","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferSharesFrom","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"resumeStaking","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFeeDistribution","outputs":[{"name":"treasuryFeeBasisPoints","type":"uint16"},{"name":"insuranceFeeBasisPoints","type":"uint16"},{"name":"operatorsFeeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"receiveWithdrawals","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"nonces","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOracle","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"eip712Domain","outputs":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getContractVersion","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getEIP712StETH","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_referral","type":"address"}],"name":"submit","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_maxDepositsCount","type":"uint256"},{"name":"_stakingModuleId","type":"uint256"},{"name":"_depositCalldata","type":"bytes"}],"name":"deposit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"UNSAFE_CHANGE_DEPOSITED_VALIDATORS_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBeaconStat","outputs":[{"name":"depositedValidators","type":"uint256"},{"name":"beaconValidators","type":"uint256"},{"name":"beaconBalance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"removeStakingLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_reportTimestamp","type":"uint256"},{"name":"_timeElapsed","type":"uint256"},{"name":"_clValidators","type":"uint256"},{"name":"_clBalance","type":"uint256"},{"name":"_withdrawalVaultBalance","type":"uint256"},{"name":"_elRewardsVaultBalance","type":"uint256"},{"name":"_sharesRequestedToBurn","type":"uint256"},{"name":"_withdrawalFinalizationBatches","type":"uint256[]"},{"name":"_simulatedShareRate","type":"uint256"}],"name":"handleOracleReport","outputs":[{"name":"postRebaseAmounts","type":"uint256[4]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFee","outputs":[{"name":"totalFee","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_deadline","type":"uint256"},{"name":"_v","type":"uint8"},{"name":"_r","type":"bytes32"},{"name":"_s","type":"bytes32"}],"name":"permit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getLidoLocator","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"canDeposit","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"STAKING_PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getDepositableEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"pauseStaking","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTotalELRewardsCollected","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[],"name":"StakingPaused","type":"event"},{"anonymous":false,"inputs":[],"name":"StakingResumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"maxStakeLimit","type":"uint256"},{"indexed":false,"name":"stakeLimitIncreasePerBlock","type":"uint256"}],"name":"StakingLimitSet","type":"event"},{"anonymous":false,"inputs":[],"name":"StakingLimitRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"reportTimestamp","type":"uint256"},{"indexed":false,"name":"preCLValidators","type":"uint256"},{"indexed":false,"name":"postCLValidators","type":"uint256"}],"name":"CLValidatorsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"depositedValidators","type":"uint256"}],"name":"DepositedValidatorsChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"reportTimestamp","type":"uint256"},{"indexed":false,"name":"preCLBalance","type":"uint256"},{"indexed":false,"name":"postCLBalance","type":"uint256"},{"indexed":false,"name":"withdrawalsWithdrawn","type":"uint256"},{"indexed":false,"name":"executionLayerRewardsWithdrawn","type":"uint256"},{"indexed":false,"name":"postBufferedEther","type":"uint256"}],"name":"ETHDistributed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"reportTimestamp","type":"uint256"},{"indexed":false,"name":"timeElapsed","type":"uint256"},{"indexed":false,"name":"preTotalShares","type":"uint256"},{"indexed":false,"name":"preTotalEther","type":"uint256"},{"indexed":false,"name":"postTotalShares","type":"uint256"},{"indexed":false,"name":"postTotalEther","type":"uint256"},{"indexed":false,"name":"sharesMintedAsFees","type":"uint256"}],"name":"TokenRebased","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"lidoLocator","type":"address"}],"name":"LidoLocatorSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"ELRewardsReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"WithdrawalsReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"referral","type":"address"}],"name":"Submitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Unbuffered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"eip712StETH","type":"address"}],"name":"EIP712StETHInitialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"sharesValue","type":"uint256"}],"name":"TransferShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":false,"name":"preRebaseTokenAmount","type":"uint256"},{"indexed":false,"name":"postRebaseTokenAmount","type":"uint256"},{"indexed":false,"name":"sharesAmount","type":"uint256"}],"name":"SharesBurnt","type":"event"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"}] diff --git a/ethereum-steth-v2/src/abi/WithdrawalQueueERC721.json b/ethereum-steth-v2/src/abi/WithdrawalQueueERC721.json new file mode 100644 index 00000000..2c1dd832 --- /dev/null +++ b/ethereum-steth-v2/src/abi/WithdrawalQueueERC721.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"_wstETH","type":"address"},{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AdminZeroAddress","type":"error"},{"inputs":[],"name":"ApprovalToOwner","type":"error"},{"inputs":[],"name":"ApproveToCaller","type":"error"},{"inputs":[{"internalType":"uint256","name":"_firstArrayLength","type":"uint256"},{"internalType":"uint256","name":"_secondArrayLength","type":"uint256"}],"name":"ArraysLengthMismatch","type":"error"},{"inputs":[],"name":"BatchesAreNotSorted","type":"error"},{"inputs":[],"name":"CantSendValueRecipientMayHaveReverted","type":"error"},{"inputs":[],"name":"EmptyBatches","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[{"internalType":"uint256","name":"_hint","type":"uint256"}],"name":"InvalidHint","type":"error"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"InvalidOwnerAddress","type":"error"},{"inputs":[],"name":"InvalidReportTimestamp","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"InvalidRequestId","type":"error"},{"inputs":[{"internalType":"uint256","name":"startId","type":"uint256"},{"internalType":"uint256","name":"endId","type":"uint256"}],"name":"InvalidRequestIdRange","type":"error"},{"inputs":[],"name":"InvalidState","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"NotEnoughEther","type":"error"},{"inputs":[{"internalType":"address","name":"_sender","type":"address"},{"internalType":"address","name":"_owner","type":"address"}],"name":"NotOwner","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"NotOwnerOrApproved","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"NotOwnerOrApprovedForAll","type":"error"},{"inputs":[],"name":"PauseUntilMustBeInFuture","type":"error"},{"inputs":[],"name":"PausedExpected","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"RequestAlreadyClaimed","type":"error"},{"inputs":[{"internalType":"uint256","name":"_amountOfStETH","type":"uint256"}],"name":"RequestAmountTooLarge","type":"error"},{"inputs":[{"internalType":"uint256","name":"_amountOfStETH","type":"uint256"}],"name":"RequestAmountTooSmall","type":"error"},{"inputs":[],"name":"RequestIdsNotSorted","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"RequestNotFoundOrNotFinalized","type":"error"},{"inputs":[],"name":"ResumedExpected","type":"error"},{"inputs":[{"internalType":"string","name":"str","type":"string"}],"name":"StringTooLong","type":"error"},{"inputs":[{"internalType":"uint256","name":"sent","type":"uint256"},{"internalType":"uint256","name":"maxExpected","type":"uint256"}],"name":"TooMuchEtherToFinalize","type":"error"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"realOwner","type":"address"}],"name":"TransferFromIncorrectOwner","type":"error"},{"inputs":[],"name":"TransferFromZeroAddress","type":"error"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"TransferToNonIERC721Receiver","type":"error"},{"inputs":[],"name":"TransferToThemselves","type":"error"},{"inputs":[],"name":"TransferToZeroAddress","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[],"name":"ZeroAmountOfETH","type":"error"},{"inputs":[],"name":"ZeroMetadata","type":"error"},{"inputs":[],"name":"ZeroPauseDuration","type":"error"},{"inputs":[],"name":"ZeroRecipient","type":"error"},{"inputs":[],"name":"ZeroShareRate","type":"error"},{"inputs":[],"name":"ZeroTimestamp","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"baseURI","type":"string"}],"name":"BaseURISet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_fromTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_toTokenId","type":"uint256"}],"name":"BatchMetadataUpdate","type":"event"},{"anonymous":false,"inputs":[],"name":"BunkerModeDisabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_sinceTimestamp","type":"uint256"}],"name":"BunkerModeEnabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_admin","type":"address"}],"name":"InitializedV1","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"MetadataUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"nftDescriptorAddress","type":"address"}],"name":"NftDescriptorAddressSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"duration","type":"uint256"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfETH","type":"uint256"}],"name":"WithdrawalClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"address","name":"requestor","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfShares","type":"uint256"}],"name":"WithdrawalRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"from","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"to","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfETHLocked","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sharesToBurn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"WithdrawalsFinalized","type":"event"},{"inputs":[],"name":"BUNKER_MODE_DISABLED_TIMESTAMP","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FINALIZE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_TOKEN_URI_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_BATCHES_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STETH_WITHDRAWAL_AMOUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_STETH_WITHDRAWAL_AMOUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ORACLE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSE_INFINITELY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RESUME_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STETH","outputs":[{"internalType":"contract IStETH","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WSTETH","outputs":[{"internalType":"contract IWstETH","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bunkerModeSinceTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxShareRate","type":"uint256"},{"internalType":"uint256","name":"_maxTimestamp","type":"uint256"},{"internalType":"uint256","name":"_maxRequestsPerCall","type":"uint256"},{"components":[{"internalType":"uint256","name":"remainingEthBudget","type":"uint256"},{"internalType":"bool","name":"finished","type":"bool"},{"internalType":"uint256[36]","name":"batches","type":"uint256[36]"},{"internalType":"uint256","name":"batchesLength","type":"uint256"}],"internalType":"struct WithdrawalQueueBase.BatchesCalculationState","name":"_state","type":"tuple"}],"name":"calculateFinalizationBatches","outputs":[{"components":[{"internalType":"uint256","name":"remainingEthBudget","type":"uint256"},{"internalType":"bool","name":"finished","type":"bool"},{"internalType":"uint256[36]","name":"batches","type":"uint256[36]"},{"internalType":"uint256","name":"batchesLength","type":"uint256"}],"internalType":"struct WithdrawalQueueBase.BatchesCalculationState","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"claimWithdrawal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_hints","type":"uint256[]"}],"name":"claimWithdrawals","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_hints","type":"uint256[]"},{"internalType":"address","name":"_recipient","type":"address"}],"name":"claimWithdrawalsTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_lastRequestIdToBeFinalized","type":"uint256"},{"internalType":"uint256","name":"_maxShareRate","type":"uint256"}],"name":"finalize","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"},{"internalType":"uint256","name":"_firstIndex","type":"uint256"},{"internalType":"uint256","name":"_lastIndex","type":"uint256"}],"name":"findCheckpointHints","outputs":[{"internalType":"uint256[]","name":"hintIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBaseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_hints","type":"uint256[]"}],"name":"getClaimableEther","outputs":[{"internalType":"uint256[]","name":"claimableEthValues","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastCheckpointIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastFinalizedRequestId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastRequestId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLockedEtherAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getNFTDescriptorAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getResumeSinceTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"getWithdrawalRequests","outputs":[{"internalType":"uint256[]","name":"requestsIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"}],"name":"getWithdrawalStatus","outputs":[{"components":[{"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"internalType":"uint256","name":"amountOfShares","type":"uint256"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bool","name":"isFinalized","type":"bool"},{"internalType":"bool","name":"isClaimed","type":"bool"}],"internalType":"struct WithdrawalQueueBase.WithdrawalRequestStatus[]","name":"statuses","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isBunkerModeActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"_isBunkerModeNow","type":"bool"},{"internalType":"uint256","name":"_bunkerStartTimestamp","type":"uint256"},{"internalType":"uint256","name":"_currentReportTimestamp","type":"uint256"}],"name":"onOracleReport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_duration","type":"uint256"}],"name":"pauseFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pauseUntilInclusive","type":"uint256"}],"name":"pauseUntil","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_batches","type":"uint256[]"},{"internalType":"uint256","name":"_maxShareRate","type":"uint256"}],"name":"prefinalize","outputs":[{"internalType":"uint256","name":"ethToLock","type":"uint256"},{"internalType":"uint256","name":"sharesToBurn","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"}],"name":"requestWithdrawals","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"},{"components":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct WithdrawalQueue.PermitInput","name":"_permit","type":"tuple"}],"name":"requestWithdrawalsWithPermit","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"}],"name":"requestWithdrawalsWstETH","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"},{"components":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct WithdrawalQueue.PermitInput","name":"_permit","type":"tuple"}],"name":"requestWithdrawalsWstETHWithPermit","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"resume","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_requestId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_operator","type":"address"},{"internalType":"bool","name":"_approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_baseURI","type":"string"}],"name":"setBaseURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_nftDescriptorAddress","type":"address"}],"name":"setNFTDescriptorAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unfinalizedRequestNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unfinalizedStETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] diff --git a/ethereum-steth-v2/src/agent.ts b/ethereum-steth-v2/src/agent.ts new file mode 100644 index 00000000..b707c65f --- /dev/null +++ b/ethereum-steth-v2/src/agent.ts @@ -0,0 +1,80 @@ +import { BlockEvent, Finding, FindingSeverity, FindingType, HandleBlock } from 'forta-agent' +import * as process from 'process' +import { argv } from 'process' +import { InitializeResponse } from 'forta-agent/dist/sdk/initialize.response' +import { Initialize } from 'forta-agent/dist/sdk/handlers' +import * as E from 'fp-ts/Either' +import { App } from './app' +import { elapsedTime } from './utils/time' +import { StethOperationSrv } from './services/steth_operation/StethOperation.srv' + +export function initialize(): Initialize { + /* const metadata: Metadata = { + 'version.commitHash': VERSION.commitHash, + 'version.commitMsg': VERSION.commitMsg, + }*/ + + return async function (): Promise { + const startTime = new Date().getTime() + const app = await App.getInstance() + + const latestBlockNumber = await app.ethClient.getStartedBlockForApp(argv) + if (E.isLeft(latestBlockNumber)) { + console.log(`Error: ${latestBlockNumber.left.message}`) + console.log(`Stack: ${latestBlockNumber.left.stack}`) + + process.exit(1) + } + + const startStethOperationSrv = new Date().getTime() + const err = await app.StethOperationSrv.initialize(latestBlockNumber.right) + if (err !== null) { + console.log(`Error: ${err.message}`) + console.log(`Stack: ${err.stack}`) + + process.exit(1) + } + + console.log(elapsedTime('App.StethOperationSrv.initialize', startStethOperationSrv)) + console.log(elapsedTime('Agent.initialize', startTime) + '\n') + } +} + +let isHandleBLockRunning: boolean = false +export const handleBlock = (): HandleBlock => { + return async function (blockEvent: BlockEvent): Promise { + const startTime = new Date().getTime() + if (isHandleBLockRunning) { + return [] + } + + isHandleBLockRunning = true + const app = await App.getInstance() + + const out: Finding[] = [] + const startHandleBufferedEth = new Date().getTime() + const bufferedEth = await app.StethOperationSrv.handleBufferedEth(blockEvent) + if (E.isLeft(bufferedEth)) { + const f: Finding = Finding.fromObject({ + name: `Error in ${StethOperationSrv.name}.${app.StethOperationSrv.handleBufferedEth.name}:55`, + description: `Handle handleBufferedEth is failed. Cause: ${bufferedEth.left.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: `${bufferedEth.left.stack}` }, + }) + + out.push(f) + } + console.log(elapsedTime('app.StethOperationSrv.handleBufferedEth', startHandleBufferedEth)) + + console.log(elapsedTime('handleBlock', startTime) + '\n') + isHandleBLockRunning = false + return out + } +} + +export default { + initialize: initialize(), + handleBlock: handleBlock(), +} diff --git a/ethereum-steth-v2/src/app.ts b/ethereum-steth-v2/src/app.ts new file mode 100644 index 00000000..3999bc60 --- /dev/null +++ b/ethereum-steth-v2/src/app.ts @@ -0,0 +1,54 @@ +import { StethOperationSrv } from './services/steth_operation/StethOperation.srv' +import { ethers, getEthersProvider } from 'forta-agent' +import { DEPOSIT_SECURITY_ADDRESS, LIDO_STETH_ADDRESS, WITHDRAWAL_QUEUE_ADDRESS } from './utils/constants' +import { StethOperationCache } from './services/steth_operation/StethOperation.cache' +import { ETHProvider, IETHProvider } from './clients/eth_provider' +import { FormatterWithEIP1898 } from './clients/eth_formatter' +import { Lido__factory, WithdrawalQueueERC721__factory } from './generated' + +export type Container = { + ethClient: IETHProvider + StethOperationSrv: StethOperationSrv +} + +export class App { + private static instance: Container + + private constructor() {} + + public static async getInstance(): Promise { + if (!App.instance) { + const etherscanKey = Buffer.from('SVZCSjZUSVBXWUpZSllXSVM0SVJBSlcyNjRITkFUUjZHVQ==', 'base64').toString('utf-8') + + const etherscanProvider = new ethers.providers.EtherscanProvider( + process.env.FORTA_AGENT_RUN_TIER == 'testnet' ? 'goerli' : undefined, + etherscanKey, + ) + + const ethersProvider = getEthersProvider() + ethersProvider.formatter = new FormatterWithEIP1898() + + const ethClient = new ETHProvider(ethersProvider, etherscanProvider) + + const lidoContact = Lido__factory.connect(LIDO_STETH_ADDRESS, ethersProvider) + const wdQueueContact = WithdrawalQueueERC721__factory.connect(WITHDRAWAL_QUEUE_ADDRESS, ethersProvider) + + const stethOperationCache = new StethOperationCache() + const stethOperationSrv = new StethOperationSrv( + stethOperationCache, + ethClient, + DEPOSIT_SECURITY_ADDRESS, + LIDO_STETH_ADDRESS, + lidoContact, + wdQueueContact, + ) + + App.instance = { + ethClient: ethClient, + StethOperationSrv: stethOperationSrv, + } + } + + return App.instance + } +} diff --git a/ethereum-steth-v2/src/clients/eth_formatter.ts b/ethereum-steth-v2/src/clients/eth_formatter.ts new file mode 100644 index 00000000..16be5518 --- /dev/null +++ b/ethereum-steth-v2/src/clients/eth_formatter.ts @@ -0,0 +1,15 @@ +import { Formatter } from '@ethersproject/providers' + +export class FormatterWithEIP1898 extends Formatter { + /** + * blockTag formatter with EIP-1898 support + * https://eips.ethereum.org/EIPS/eip-1898 + */ + blockTag(blockTag: any): any { + if (typeof blockTag === 'object' && blockTag != null && (blockTag.blockNumber || blockTag.blockHash)) { + return blockTag + } + + return super.blockTag(blockTag) + } +} diff --git a/ethereum-steth-v2/src/clients/eth_provider.ts b/ethereum-steth-v2/src/clients/eth_provider.ts new file mode 100644 index 00000000..55d6f4c1 --- /dev/null +++ b/ethereum-steth-v2/src/clients/eth_provider.ts @@ -0,0 +1,139 @@ +import { TransactionResponse } from '@ethersproject/abstract-provider' +import { ethers } from 'forta-agent' +import * as E from 'fp-ts/Either' +import { retryAsync } from 'ts-retry' +import { BlockTag } from '@ethersproject/providers' +import { BigNumber as EtherBigNumber } from '@ethersproject/bignumber/lib/bignumber' + +export abstract class IETHProvider { + public abstract getTransaction(txHash: string): Promise> + + public abstract getStartedBlockForApp(argv: string[]): Promise> + + public abstract getHistory( + depositSecurityAddress: string, + startBlock: number, + endBlock: number, + ): Promise> + + public abstract getBalance(lidoStethAddress: string, block: number): Promise> +} + +interface IEtherscanProvider { + getHistory( + addressOrName: string | Promise, + startBlock?: BlockTag, + endBlock?: BlockTag, + ): Promise> + + getBalance(addressOrName: string | Promise, blockTag?: BlockTag | Promise): Promise +} + +export class ETHProvider implements IETHProvider { + private jsonRpcProvider: ethers.providers.JsonRpcProvider + private etherscanProvider: IEtherscanProvider + + constructor(jsonRpcProvider: ethers.providers.JsonRpcProvider, etherscanProvider: IEtherscanProvider) { + this.jsonRpcProvider = jsonRpcProvider + this.etherscanProvider = etherscanProvider + } + + public async getStartedBlockForApp(argv: string[]): Promise> { + let latestBlockNumber: number = -1 + + if (argv.includes('--block')) { + latestBlockNumber = parseInt(argv[4]) + } else if (argv.includes('--range')) { + latestBlockNumber = parseInt(argv[4].slice(0, argv[4].indexOf('.'))) + } else if (argv.includes('--tx')) { + const txHash = argv[4] + const tx = await this.getTransaction(txHash) + if (E.isLeft(tx)) { + return E.left(tx.left) + } + + if (tx.right.blockNumber !== undefined) { + latestBlockNumber = tx.right.blockNumber + } + } + if (latestBlockNumber == -1) { + try { + latestBlockNumber = await this.jsonRpcProvider.getBlockNumber() + } catch (e) { + return E.left(new Error(`Could not fetch latest block number. cause: ${e}`)) + } + } + + return E.right(latestBlockNumber) + } + + public async getTransaction(txHash: string): Promise> { + try { + const out = await retryAsync( + async (): Promise => { + const tx = await this.jsonRpcProvider.getTransaction(txHash) + + if (!tx) { + throw new Error(`Can't find transaction ${txHash}`) + } + + if (tx.blockNumber === undefined) { + throw new Error(`Transaction ${txHash} was not yet included into block`) + } + + return tx + }, + { delay: 500, maxTry: 5 }, + ) + + return E.right(out) + } catch (e) { + return E.left(new Error(`Could not fetch transaction. cause: ${e}`)) + } + } + + public async getHistory( + depositSecurityAddress: string, + startBlock: number, + endBlock: number, + ): Promise> { + const out: TransactionResponse[] = [] + const batchSize = 50 + + for (let i = startBlock; i <= endBlock; i += batchSize) { + const start = i + const end = Math.min(i + batchSize - 1, endBlock) + + let chunkTrxResp: TransactionResponse[] = [] + try { + chunkTrxResp = await retryAsync( + async (): Promise => { + return await this.etherscanProvider.getHistory(depositSecurityAddress, start, end) + }, + { delay: 500, maxTry: 5 }, + ) + } catch (e) { + return E.left(new Error(`Could not fetch transaction history for last 3 days. Cause: ${e}`)) + } + + out.push(...chunkTrxResp) + } + + return E.right(out) + } + + public async getBalance(lidoStethAddress: string, block: number): Promise> { + try { + const out = await retryAsync( + async (): Promise => { + return await this.etherscanProvider.getBalance(lidoStethAddress, block) + }, + { delay: 500, maxTry: 5 }, + ) + + return E.right(out) + } catch (e) { + return E.left(new Error(`Could not fetch Steth balance. cause: ${e}`)) + } + } +} diff --git a/ethereum-steth-v2/src/entity/events.ts b/ethereum-steth-v2/src/entity/events.ts new file mode 100644 index 00000000..d7816035 --- /dev/null +++ b/ethereum-steth-v2/src/entity/events.ts @@ -0,0 +1,11 @@ +import { FindingSeverity, FindingType } from 'forta-agent' + +export type EventOfNotice = { + name: string + address: string + event: string + alertId: string + description: CallableFunction + severity: FindingSeverity + type: FindingType +} diff --git a/ethereum-steth-v2/src/entity/metadata.ts b/ethereum-steth-v2/src/entity/metadata.ts new file mode 100644 index 00000000..32745c4d --- /dev/null +++ b/ethereum-steth-v2/src/entity/metadata.ts @@ -0,0 +1 @@ +export type Metadata = { [key: string]: string } diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.cache.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.cache.ts new file mode 100644 index 00000000..d0aeba94 --- /dev/null +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.cache.ts @@ -0,0 +1,42 @@ +import BigNumber from 'bignumber.js' + +export class StethOperationCache { + private _lastDepositorTxTime = 0 + private _lastBufferedEth = new BigNumber(0) + private _criticalDepositableAmountTime = 0 + private _lastReportedDepositableEth = 0 + + constructor() {} + + public getLastDepositorTxTime(): number { + return this._lastDepositorTxTime + } + + public setLastDepositorTxTime(value: number) { + this._lastDepositorTxTime = value + } + + public getLastBufferedEth(): BigNumber { + return this._lastBufferedEth + } + + public setLastBufferedEth(value: BigNumber) { + this._lastBufferedEth = value + } + + public getCriticalDepositableAmountTime(): number { + return this._criticalDepositableAmountTime + } + + public setCriticalDepositableAmountTime(blockTimestamp: number) { + this._criticalDepositableAmountTime = blockTimestamp + } + + public getLastReportedDepositableEth(): number { + return this._lastReportedDepositableEth + } + + public setLastReportedDepositableEth(blockTimestamp: number) { + this._lastReportedDepositableEth = blockTimestamp + } +} diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts new file mode 100644 index 00000000..650b7278 --- /dev/null +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts @@ -0,0 +1,271 @@ +import BigNumber from 'bignumber.js' +import { StethOperationCache } from './StethOperation.cache' +import { ETH_DECIMALS } from '../../utils/constants' +import * as E from 'fp-ts/Either' +import { IETHProvider } from '../../clients/eth_provider' +import { BlockEvent, Finding, FindingSeverity, FindingType } from 'forta-agent' +import { Lido as LidoContract, WithdrawalQueueERC721 as WithdrawalQueue } from '../../generated' +import { retryAsync } from 'ts-retry' +import { BigNumber as EtherBigNumber } from '@ethersproject/bignumber/lib/bignumber' +import { Event as EthersEvent } from 'ethers' + +// Formula: (60 * 60 * 72) / 13 = 19_938 +const HISTORY_BLOCK_OFFSET: number = Math.floor((60 * 60 * 72) / 13) +const BLOCK_CHECK_INTERVAL: number = 100 +const MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL: number = 20_000 // 20000 ETH +const REPORT_WINDOW = 60 * 60 * 24 // 24 hours +const MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL_TIME = 60 * 60 // 1 hour +const MAX_DEPOSITABLE_ETH_AMOUNT_MEDIUM = 10_000 // 10000 ETH +const MAX_DEPOSITOR_TX_DELAY = 60 * 60 * 72 // 72 Hours + +export class StethOperationSrv { + private readonly name = 'StethOperation' + private readonly cache: StethOperationCache + private readonly ethProvider: IETHProvider + private readonly depositSecurityAddress: string + private readonly lidoStethAddress: string + private readonly lidoContract: LidoContract + private readonly wdQueueContract: WithdrawalQueue + + constructor( + cache: StethOperationCache, + ethProvider: IETHProvider, + depositSecurityAddress: string, + lidoStethAddress: string, + lidoContract: LidoContract, + wdQueueContract: WithdrawalQueue, + ) { + this.cache = cache + this.ethProvider = ethProvider + this.depositSecurityAddress = depositSecurityAddress + this.lidoStethAddress = lidoStethAddress + this.lidoContract = lidoContract + this.wdQueueContract = wdQueueContract + } + + public async initialize(currentBlock: number): Promise { + console.log(`[${this.name}] started on block ${currentBlock}`) + + const history = await this.ethProvider.getHistory( + this.depositSecurityAddress, + currentBlock - HISTORY_BLOCK_OFFSET, + currentBlock - 1, + ) + if (E.isLeft(history)) { + return history.left + } + + const depositorTxTimestamps: number[] = [] + for (const record of history.right) { + depositorTxTimestamps.push(record.timestamp ? record.timestamp : 0) + } + + if (depositorTxTimestamps.length > 0) { + depositorTxTimestamps.sort((a, b) => b - a) + this.cache.setLastDepositorTxTime(depositorTxTimestamps[0]) + } + + const bufferedEthRaw = await this.ethProvider.getBalance(this.lidoStethAddress, currentBlock) + if (E.isLeft(bufferedEthRaw)) { + return bufferedEthRaw.left + } + + this.cache.setLastBufferedEth(new BigNumber(String(bufferedEthRaw.right))) + + console.log(`[${this.name}] Last Depositor TxTime: ${this.cache.getLastDepositorTxTime()}`) + console.log( + `[${this.name}] Buffered Eth: ${this.cache + .getLastBufferedEth() + .div(ETH_DECIMALS) + .toFixed(2)} on ${currentBlock} block`, + ) + + return null + } + + public async handleBlock(blockEvent: BlockEvent) { + const findings: Finding[] = [] + + await Promise.all([ + // handleBufferedEth(blockEvent, findings), + // handleDepositExecutorBalance(blockEvent, findings), + // handleStakingLimit(blockEvent, findings), + ]) + + return findings + } + + public async handleBufferedEth(blockEvent: BlockEvent): Promise> { + const blockNumber = blockEvent.block.number + const blockTimestamp = blockEvent.block.timestamp + + let bufferedEthRaw: BigNumber + try { + const resp = await retryAsync( + async (): Promise => { + return await this.lidoContract.getBufferedEther({ + blockTag: blockNumber, + }) + }, + { delay: 500, maxTry: 5 }, + ) + + bufferedEthRaw = new BigNumber(resp.toString()) + } catch (e) { + return E.left(new Error(`Could not call "lidoContract.getBufferedEther". Cause: ${e}`)) + } + + const bufferedEth = bufferedEthRaw.div(ETH_DECIMALS).toNumber() + let depositableEther: number + + try { + const resp = await retryAsync( + async (): Promise => { + return await this.lidoContract.getDepositableEther({ + blockTag: blockNumber, + }) + }, + { delay: 500, maxTry: 5 }, + ) + + const depositableEtherRaw = new BigNumber(resp.toString()) + depositableEther = depositableEtherRaw.div(ETH_DECIMALS).toNumber() + } catch (e) { + return E.left(new Error(`Could not call "lidoContract.getDepositableEther". Cause: ${e}`)) + } + + // We use shifted block number to ensure that nodes return correct values + const shiftedBlockNumber = blockNumber - 3 + const shifte3dBufferedEthRaw = await this.getBufferedEther(shiftedBlockNumber) + if (E.isLeft(shifte3dBufferedEthRaw)) { + return E.left(new Error(`Could not call "getBufferedEther". Cause: ${shifte3dBufferedEthRaw.left}`)) + } + const shifte4dBufferedEthRaw = await this.getBufferedEther(shiftedBlockNumber - 1) + if (E.isLeft(shifte4dBufferedEthRaw)) { + return E.left(new Error(`Could not call "getBufferedEther". Cause: ${shifte4dBufferedEthRaw.left}`)) + } + + const out: Finding[] = [] + if (shifte3dBufferedEthRaw.right.lt(shifte4dBufferedEthRaw.right)) { + let unbufferedEvents: EthersEvent[] + + try { + unbufferedEvents = await retryAsync( + async (): Promise => { + return await this.lidoContract.queryFilter( + this.lidoContract.filters.Unbuffered(), + shiftedBlockNumber, + shiftedBlockNumber, + ) + }, + { delay: 500, maxTry: 5 }, + ) + } catch (e) { + return E.left(new Error(`Could not fetch unbufferedEvents. Cause: ${e}`)) + } + + let wdReqFinalizedEvents: EthersEvent[] + try { + wdReqFinalizedEvents = await retryAsync( + async (): Promise => { + return await this.wdQueueContract.queryFilter( + this.wdQueueContract.filters.WithdrawalsFinalized(), + shiftedBlockNumber, + shiftedBlockNumber, + ) + }, + { delay: 500, maxTry: 5 }, + ) + } catch (e) { + return E.left(new Error(`Could not fetch wdReqFinalizedEvents. Cause: ${e}`)) + } + + if (unbufferedEvents.length === 0 && wdReqFinalizedEvents.length === 0) { + out.push( + Finding.fromObject({ + name: '🚨🚨🚨 Buffered ETH drain', + description: + `Buffered ETH amount decreased from ` + + `${shifte4dBufferedEthRaw.right.div(ETH_DECIMALS).toFixed(2)} ` + + `to ${shifte3dBufferedEthRaw.right.div(ETH_DECIMALS).toFixed(2)} ` + + `without Unbuffered or WithdrawalsFinalized events\n\nNote: actual handled block number is ${shiftedBlockNumber}`, + alertId: 'BUFFERED-ETH-DRAIN', + severity: FindingSeverity.Critical, + type: FindingType.Suspicious, + }), + ) + } + } + + if (blockNumber % BLOCK_CHECK_INTERVAL === 0) { + // Keep track of buffer size above MAX_BUFFERED_ETH_AMOUNT_CRITICAL + if (depositableEther > MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL) { + if (this.cache.getCriticalDepositableAmountTime() === 0) { + this.cache.setCriticalDepositableAmountTime(blockTimestamp) + } + } else { + // reset counter if buffered amount goes below MAX_BUFFERED_ETH_AMOUNT_CRITICAL + this.cache.setCriticalDepositableAmountTime(0) + } + + if (this.cache.getLastReportedDepositableEth() + REPORT_WINDOW < blockTimestamp) { + if ( + depositableEther > MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL && + this.cache.getCriticalDepositableAmountTime() < blockTimestamp - MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL_TIME + ) { + out.push( + Finding.fromObject({ + name: '🚨 Huge depositable ETH amount', + description: + `There are ${depositableEther.toFixed(2)} ` + + `depositable ETH in DAO for more than ` + + `${Math.floor(MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL_TIME / (60 * 60))} hour(s)`, + alertId: 'HUGE-DEPOSITABLE-ETH', + severity: FindingSeverity.High, + type: FindingType.Degraded, + }), + ) + this.cache.setLastReportedDepositableEth(blockTimestamp) + } else if ( + depositableEther > MAX_DEPOSITABLE_ETH_AMOUNT_MEDIUM && + this.cache.getLastDepositorTxTime() < blockTimestamp - MAX_DEPOSITOR_TX_DELAY && + this.cache.getLastDepositorTxTime() !== 0 + ) { + out.push( + Finding.fromObject({ + name: '⚠️ High depositable ETH amount', + description: + `There are ${bufferedEth.toFixed(2)} ` + + `depositable ETH in DAO and there are more than ` + + `${Math.floor(MAX_DEPOSITOR_TX_DELAY / (60 * 60))} ` + + `hours since last Depositor TX`, + alertId: 'HIGH-DEPOSITABLE-ETH', + severity: FindingSeverity.Medium, + type: FindingType.Suspicious, + }), + ) + this.cache.setLastReportedDepositableEth(blockTimestamp) + } + } + } + + return E.right(out) + } + + private async getBufferedEther(blockNumber: number): Promise> { + try { + const resp = await retryAsync( + async (): Promise => { + return await this.lidoContract.getBufferedEther({ + blockTag: blockNumber, + }) + }, + { delay: 500, maxTry: 5 }, + ) + + return E.right(new BigNumber(resp.toString())) + } catch (e) { + return E.left(new Error(`Could not call "lidoContract.getBufferedEther". Cause: ${e}`)) + } + } +} diff --git a/ethereum-steth-v2/src/utils/constants.ts b/ethereum-steth-v2/src/utils/constants.ts new file mode 100644 index 00000000..26ad9fd2 --- /dev/null +++ b/ethereum-steth-v2/src/utils/constants.ts @@ -0,0 +1,11 @@ +import BigNumber from 'bignumber.js' + +export const RUN_TIER = process.env.FORTA_AGENT_RUN_TIER + +export const ETH_DECIMALS = new BigNumber(10).pow(18) + +export const DEPOSIT_SECURITY_ADDRESS = '0xc77f8768774e1c9244beed705c4354f2113cfc09' + +export const LIDO_STETH_ADDRESS = '0xae7ab96520de3a18e5e111b5eaab095312d7fe84' + +export const WITHDRAWAL_QUEUE_ADDRESS = '0x889edc2edab5f40e902b864ad4d7ade8e412f9b1' diff --git a/ethereum-steth-v2/src/utils/error.ts b/ethereum-steth-v2/src/utils/error.ts new file mode 100644 index 00000000..865f75b2 --- /dev/null +++ b/ethereum-steth-v2/src/utils/error.ts @@ -0,0 +1,14 @@ +import { Finding, FindingSeverity, FindingType } from 'forta-agent' + +export function errorToFinding(e: unknown, className: string, fnName: string): Finding { + const err: Error = e instanceof Error ? e : new Error(`non-Error thrown: ${e}`) + + return Finding.fromObject({ + name: `Error in ${className}.${fnName}`, + description: `${err.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.High, + type: FindingType.Degraded, + metadata: { stack: `${err.stack}` }, + }) +} diff --git a/ethereum-steth-v2/src/utils/mutex.ts b/ethereum-steth-v2/src/utils/mutex.ts new file mode 100644 index 00000000..45f80341 --- /dev/null +++ b/ethereum-steth-v2/src/utils/mutex.ts @@ -0,0 +1,33 @@ +import { Mutex } from 'async-mutex' +import { Finding } from 'forta-agent' + +export class FindingsRW { + private mutex: Mutex + private value: Finding[] + + constructor(initialValue: Finding[]) { + this.mutex = new Mutex() + this.value = initialValue + } + + async read(): Promise { + await this.mutex.acquire() + try { + const out = this.value + this.value = [] + + return out + } finally { + this.mutex.release() + } + } + + async write(newValue: Finding[]): Promise { + await this.mutex.acquire() + try { + this.value.push(...newValue) + } finally { + this.mutex.release() + } + } +} diff --git a/ethereum-steth-v2/src/utils/tier.ts b/ethereum-steth-v2/src/utils/tier.ts new file mode 100644 index 00000000..6e0f64f8 --- /dev/null +++ b/ethereum-steth-v2/src/utils/tier.ts @@ -0,0 +1,56 @@ +import { RUN_TIER } from './constants' + +export enum RedefineMode { + Strict = 'strict', + Merge = 'merge', +} + +/** + * Special wrapper under `require` function that allows to + * redefine variables from a file with the same name and `.` suffix. + * `` is a string that is passed by `FORTA_AGENT_RUN_TIER` environment variable. + * @param module module object to get the path from. + * @param path relative to module path to the main file to import. + * @param mode `strict` or `merge`. Default: `strict`. + */ +export function requireWithTier(module: NodeModule, path: string, mode: RedefineMode = RedefineMode.Strict): T { + const defaultContent = require(`${module.path}/${path}`) + if (!RUN_TIER) { + return defaultContent + } + let tieredContent: any + try { + tieredContent = require(`${module.path}/${path}.${RUN_TIER}`) + module.exports.__tier__ = RUN_TIER + } catch (e) { + return defaultContent + } + if (mode == RedefineMode.Strict) { + const valid = (key: string) => { + return key in tieredContent && typeof defaultContent[key] == typeof tieredContent[key] + } + if (Object.keys(defaultContent).every((key) => valid(key))) { + return tieredContent + } else { + throw new Error( + `Failed to import module: '${module.path}/${path}.${RUN_TIER}' doesn't contain all keys or unmatched types + with '${module.path}/${path}'`, + ) + } + } + if (mode == RedefineMode.Merge) { + const valid = (key: string) => { + if (key in defaultContent) { + return typeof defaultContent[key] == typeof tieredContent[key] + } else { + return true + } + } + if (Object.keys(tieredContent).every((key) => valid(key))) { + return { ...defaultContent, ...tieredContent } + } else { + throw new Error(`Failed to import module: '${path}.${RUN_TIER}' unmatched types with '${path}'`) + } + } + throw new Error(`Unknown require mode: ${mode}`) +} diff --git a/ethereum-steth-v2/src/utils/time.ts b/ethereum-steth-v2/src/utils/time.ts new file mode 100644 index 00000000..acd3cb72 --- /dev/null +++ b/ethereum-steth-v2/src/utils/time.ts @@ -0,0 +1,15 @@ +export function formatTime(timeInMillis: number): string { + const seconds = (timeInMillis / 1000).toFixed(3) + return `${seconds} seconds` +} + +export function elapsedTime(methodName: string, startTime: number): string { + const elapsedTime = new Date().getTime() - startTime + return `${methodName} started at ${formatTimeToHumanReadable(new Date(startTime))}. Elapsed: ${formatTime( + elapsedTime, + )}` +} + +function formatTimeToHumanReadable(date: Date): string { + return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}` +} diff --git a/ethereum-steth-v2/src/utils/version.ts b/ethereum-steth-v2/src/utils/version.ts new file mode 100644 index 00000000..34c3048c --- /dev/null +++ b/ethereum-steth-v2/src/utils/version.ts @@ -0,0 +1,27 @@ +import path from 'path' + +export interface Version { + desc: string + commitHash: string + commitHashShort: string + commitMsg: string + commitMsgShort: string + isWdClean: boolean +} + +export default readVersion(path.join(__dirname, '..', './version.json')) + +function readVersion(versionFilePath: string): Version { + try { + return require(versionFilePath) + } catch (e) { + return { + desc: 'unknown', + commitHash: 'unknown', + commitHashShort: 'unknown', + commitMsg: 'unknown', + commitMsgShort: 'unknown', + isWdClean: false, + } + } +} diff --git a/ethereum-steth-v2/tsconfig.json b/ethereum-steth-v2/tsconfig.json new file mode 100644 index 00000000..631cc43b --- /dev/null +++ b/ethereum-steth-v2/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@tsconfig/node20/tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "baseUrl": ".", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "noImplicitAny": true, + "resolveJsonModule": true + }, + "exclude": ["e2e", "node_modules"] +} diff --git a/ethereum-steth-v2/yarn.lock b/ethereum-steth-v2/yarn.lock new file mode 100644 index 00000000..dc40ad02 --- /dev/null +++ b/ethereum-steth-v2/yarn.lock @@ -0,0 +1,4665 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== + +"@ampproject/remapping@^2.2.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" + integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" + integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== + dependencies: + "@babel/highlight" "^7.23.4" + chalk "^2.4.2" + +"@babel/compat-data@^7.22.9": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" + integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.5.tgz#6e23f2acbcb77ad283c5ed141f824fd9f70101c7" + integrity sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.5" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.23.5" + "@babel/parser" "^7.23.5" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.5" + "@babel/types" "^7.23.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.23.5", "@babel/generator@^7.7.2": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.5.tgz#17d0a1ea6b62f351d281350a5f80b87a810c4755" + integrity sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA== + dependencies: + "@babel/types" "^7.23.5" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + +"@babel/helper-compilation-targets@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" + integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== + dependencies: + "@babel/compat-data" "^7.22.9" + "@babel/helper-validator-option" "^7.22.15" + browserslist "^4.21.9" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-module-imports@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== + dependencies: + "@babel/types" "^7.22.15" + +"@babel/helper-module-transforms@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" + integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" + integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== + +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-string-parser@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" + integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/helper-validator-option@^7.22.15": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" + integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== + +"@babel/helpers@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.5.tgz#52f522840df8f1a848d06ea6a79b79eefa72401e" + integrity sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg== + dependencies: + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.5" + "@babel/types" "^7.23.5" + +"@babel/highlight@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" + integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.5.tgz#37dee97c4752af148e1d38c34b856b2507660563" + integrity sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ== + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz#8f2e4f8a9b5f9aa16067e142c1ac9cd9f810f473" + integrity sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz#24f460c85dbbc983cd2b9c4994178bcc01df958f" + integrity sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/template@^7.22.15", "@babel/template@^7.3.3": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" + integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" + +"@babel/traverse@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.5.tgz#f546bf9aba9ef2b042c0e00d245990c15508e7ec" + integrity sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.23.5" + "@babel/types" "^7.23.5" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.5", "@babel/types@^7.3.3": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.5.tgz#48d730a00c95109fa4393352705954d74fb5b602" + integrity sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@danieldietrich/copy@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@danieldietrich/copy/-/copy-0.4.2.tgz#c1cabfa499d8b473ba95413c446c1c1efae64d24" + integrity sha512-ZVNZIrgb2KeomfNahP77rL445ho6aQj0HHqU6hNlQ61o4rhvca+NS+ePj0d82zQDq2UPk1mjVZBTXgP+ErsDgw== + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": + version "4.10.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" + integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.55.0": + version "8.55.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.55.0.tgz#b721d52060f369aa259cf97392403cb9ce892ec6" + integrity sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA== + +"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.0", "@ethersproject/abi@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" + integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/abstract-provider@5.7.0", "@ethersproject/abstract-provider@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz#b0a8550f88b6bf9d51f90e4795d48294630cb9ef" + integrity sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + +"@ethersproject/abstract-signer@5.7.0", "@ethersproject/abstract-signer@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz#13f4f32117868452191a4649723cb086d2b596b2" + integrity sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + +"@ethersproject/address@5.7.0", "@ethersproject/address@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.7.0.tgz#19b56c4d74a3b0a46bfdbb6cfcc0a153fc697f37" + integrity sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + +"@ethersproject/base64@5.7.0", "@ethersproject/base64@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.7.0.tgz#ac4ee92aa36c1628173e221d0d01f53692059e1c" + integrity sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ== + dependencies: + "@ethersproject/bytes" "^5.7.0" + +"@ethersproject/basex@5.7.0", "@ethersproject/basex@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.7.0.tgz#97034dc7e8938a8ca943ab20f8a5e492ece4020b" + integrity sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + +"@ethersproject/bignumber@5.7.0", "@ethersproject/bignumber@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.7.0.tgz#e2f03837f268ba655ffba03a57853e18a18dc9c2" + integrity sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + bn.js "^5.2.1" + +"@ethersproject/bytes@5.7.0", "@ethersproject/bytes@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.7.0.tgz#a00f6ea8d7e7534d6d87f47188af1148d71f155d" + integrity sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/constants@5.7.0", "@ethersproject/constants@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.7.0.tgz#df80a9705a7e08984161f09014ea012d1c75295e" + integrity sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + +"@ethersproject/contracts@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.7.0.tgz#c305e775abd07e48aa590e1a877ed5c316f8bd1e" + integrity sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg== + dependencies: + "@ethersproject/abi" "^5.7.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + +"@ethersproject/hash@5.7.0", "@ethersproject/hash@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.7.0.tgz#eb7aca84a588508369562e16e514b539ba5240a7" + integrity sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/hdnode@5.7.0", "@ethersproject/hdnode@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.7.0.tgz#e627ddc6b466bc77aebf1a6b9e47405ca5aef9cf" + integrity sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wordlists" "^5.7.0" + +"@ethersproject/json-wallets@5.7.0", "@ethersproject/json-wallets@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz#5e3355287b548c32b368d91014919ebebddd5360" + integrity sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hdnode" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + aes-js "3.0.0" + scrypt-js "3.0.1" + +"@ethersproject/keccak256@5.7.0", "@ethersproject/keccak256@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.7.0.tgz#3186350c6e1cd6aba7940384ec7d6d9db01f335a" + integrity sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + js-sha3 "0.8.0" + +"@ethersproject/logger@5.7.0", "@ethersproject/logger@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" + integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== + +"@ethersproject/networks@5.7.1", "@ethersproject/networks@^5.7.0": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.1.tgz#118e1a981d757d45ccea6bb58d9fd3d9db14ead6" + integrity sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/pbkdf2@5.7.0", "@ethersproject/pbkdf2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz#d2267d0a1f6e123f3771007338c47cccd83d3102" + integrity sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + +"@ethersproject/properties@5.7.0", "@ethersproject/properties@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.7.0.tgz#a6e12cb0439b878aaf470f1902a176033067ed30" + integrity sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.0.0": + version "5.7.2" + resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" + integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + bech32 "1.1.4" + ws "7.4.6" + +"@ethersproject/random@5.7.0", "@ethersproject/random@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.7.0.tgz#af19dcbc2484aae078bb03656ec05df66253280c" + integrity sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/rlp@5.7.0", "@ethersproject/rlp@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.7.0.tgz#de39e4d5918b9d74d46de93af80b7685a9c21304" + integrity sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/sha2@5.7.0", "@ethersproject/sha2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.7.0.tgz#9a5f7a7824ef784f7f7680984e593a800480c9fb" + integrity sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + hash.js "1.1.7" + +"@ethersproject/signing-key@5.7.0", "@ethersproject/signing-key@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.7.0.tgz#06b2df39411b00bc57c7c09b01d1e41cf1b16ab3" + integrity sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + bn.js "^5.2.1" + elliptic "6.5.4" + hash.js "1.1.7" + +"@ethersproject/solidity@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.7.0.tgz#5e9c911d8a2acce2a5ebb48a5e2e0af20b631cb8" + integrity sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/strings@5.7.0", "@ethersproject/strings@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.7.0.tgz#54c9d2a7c57ae8f1205c88a9d3a56471e14d5ed2" + integrity sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.7.0.tgz#91318fc24063e057885a6af13fdb703e1f993d3b" + integrity sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + +"@ethersproject/units@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.7.0.tgz#637b563d7e14f42deeee39245275d477aae1d8b1" + integrity sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/wallet@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.7.0.tgz#4e5d0790d96fe21d61d38fb40324e6c7ef350b2d" + integrity sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/hdnode" "^5.7.0" + "@ethersproject/json-wallets" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wordlists" "^5.7.0" + +"@ethersproject/web@5.7.1", "@ethersproject/web@^5.7.0": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.1.tgz#de1f285b373149bee5928f4eb7bcb87ee5fbb4ae" + integrity sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w== + dependencies: + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/wordlists@5.7.0", "@ethersproject/wordlists@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.7.0.tgz#8fb2c07185d68c3e09eb3bfd6e779ba2774627f5" + integrity sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@grpc/grpc-js@^1.3.6": + version "1.9.12" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.9.12.tgz#a45b23a7d9ee1eadc9fa8fe480e27edbc6544cdd" + integrity sha512-Um5MBuge32TS3lAKX02PGCnFM4xPT996yLgZNb5H03pn6NyJ4Iwn5YcPq6Jj9yxGRk7WOgaZFtVRH5iTdYBeUg== + dependencies: + "@grpc/proto-loader" "^0.7.8" + "@types/node" ">=12.12.47" + +"@grpc/proto-loader@^0.6.4": + version "0.6.13" + resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.6.13.tgz#008f989b72a40c60c96cd4088522f09b05ac66bc" + integrity sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g== + dependencies: + "@types/long" "^4.0.1" + lodash.camelcase "^4.3.0" + long "^4.0.0" + protobufjs "^6.11.3" + yargs "^16.2.0" + +"@grpc/proto-loader@^0.7.8": + version "0.7.10" + resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.10.tgz#6bf26742b1b54d0a473067743da5d3189d06d720" + integrity sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ== + dependencies: + lodash.camelcase "^4.3.0" + long "^5.0.0" + protobufjs "^7.2.4" + yargs "^17.7.2" + +"@humanwhocodes/config-array@^0.11.13": + version "0.11.13" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.13.tgz#075dc9684f40a531d9b26b0822153c1e832ee297" + integrity sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ== + dependencies: + "@humanwhocodes/object-schema" "^2.0.1" + debug "^4.1.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.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz#e5211452df060fa8522b55c7b3c0c4d1981cb044" + integrity sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.20" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" + integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@pkgr/utils@^2.4.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.4.2.tgz#9e638bbe9a6a6f165580dc943f138fd3309a2cbc" + integrity sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw== + dependencies: + cross-spawn "^7.0.3" + fast-glob "^3.3.0" + is-glob "^4.0.3" + open "^9.1.0" + picocolors "^1.0.0" + tslib "^2.6.0" + +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sinonjs/commons@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" + integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@tsconfig/node20@^20.1.2": + version "20.1.2" + resolved "https://registry.yarnpkg.com/@tsconfig/node20/-/node20-20.1.2.tgz#b93128c411d38e9507035255195bc8a6718541e3" + integrity sha512-madaWq2k+LYMEhmcp0fs+OGaLFk0OenpHa4gmI4VEmCKX4PJntQ6fnnGADVFrVkBj0wIdAlQnK/MrlYTHsa1gQ== + +"@typechain/ethers-v5@^11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-11.1.2.tgz#82510c1744f37a2f906b9e0532ac18c0b74ffe69" + integrity sha512-ID6pqWkao54EuUQa0P5RgjvfA3MYqxUQKpbGKERbsjBW5Ra7EIXvbMlPp2pcP5IAdUkyMCFYsP2SN5q7mPdLDQ== + dependencies: + lodash "^4.17.15" + ts-essentials "^7.0.1" + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.7" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.7.tgz#a7aebf15c7bc0eb9abd638bdb5c0b8700399c9d0" + integrity sha512-6Sfsq+EaaLrw4RmdFWE9Onp63TOUue71AWb4Gpa6JxzgTYtimbM086WnYTy2U67AofR++QKCo08ZP6pwx8YFHQ== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.20.4" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.4.tgz#ec2c06fed6549df8bc0eb4615b683749a4a92e1b" + integrity sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA== + dependencies: + "@babel/types" "^7.20.7" + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^29.5.10": + version "29.5.10" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.10.tgz#a10fc5bab9e426081c12b2ef73d24d4f0c9b7f50" + integrity sha512-tE4yxKEphEyxj9s4inideLHktW/x6DwesIwWZ9NN1FKf9zbJYsnhBoA9vrHA/IuIOKwPa5PcFBNV4lpMIOEzyQ== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/json-schema@^7.0.12", "@types/json-schema@^7.0.9": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/lodash@^4.14.202": + version "4.14.202" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.202.tgz#f09dbd2fb082d507178b2f2a5c7e74bd72ff98f8" + integrity sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ== + +"@types/long@^4.0.1": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" + integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== + +"@types/mkdirp@^0.5.2": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-0.5.2.tgz#503aacfe5cc2703d5484326b1b27efa67a339c1f" + integrity sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg== + dependencies: + "@types/node" "*" + +"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0": + version "20.10.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.3.tgz#4900adcc7fc189d5af5bb41da8f543cea6962030" + integrity sha512-XJavIpZqiXID5Yxnxv3RUDKTN5b81ddNC3ecsA0SoFXz/QU8OGBwZGMomiq0zw+uuqbL/krztv/DINAQ/EV4gg== + dependencies: + undici-types "~5.26.4" + +"@types/nodemon@^1.19.0": + version "1.19.6" + resolved "https://registry.yarnpkg.com/@types/nodemon/-/nodemon-1.19.6.tgz#1c14bac51dfd3d354e2b5046949f925a742412c4" + integrity sha512-vjKuaQOLUA5EY2zkUmWG1ipXbKt9Wd+H/0SiIuHVeH4cHtt6509iRUGH9ZR0iqgUrtj3BrP9KqoTuV3ZCbQcYA== + dependencies: + "@types/node" "*" + +"@types/prettier@^2.1.1": + version "2.7.3" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f" + integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== + +"@types/resolve@^0.0.8": + version "0.0.8" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" + integrity sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ== + dependencies: + "@types/node" "*" + +"@types/semver@^7.3.12", "@types/semver@^7.5.0": + version "7.5.6" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.6.tgz#c65b2bfce1bec346582c07724e3f8c1017a20339" + integrity sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A== + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/uuid@^8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== + +"@types/ws@^8.5.10": + version "8.5.10" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" + integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== + dependencies: + "@types/node" "*" + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.32" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.32.tgz#030774723a2f7faafebf645f4e5a48371dca6229" + integrity sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^6.12.0": + version "6.13.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.1.tgz#f98bd887bf95551203c917e734d113bf8d527a0c" + integrity sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA== + dependencies: + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "6.13.1" + "@typescript-eslint/type-utils" "6.13.1" + "@typescript-eslint/utils" "6.13.1" + "@typescript-eslint/visitor-keys" "6.13.1" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/parser@^6.12.0": + version "6.13.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.13.1.tgz#29d6d4e5fab4669e58bc15f6904b67da65567487" + integrity sha512-fs2XOhWCzRhqMmQf0eicLa/CWSaYss2feXsy7xBD/pLyWke/jCIVc2s1ikEAtSW7ina1HNhv7kONoEfVNEcdDQ== + dependencies: + "@typescript-eslint/scope-manager" "6.13.1" + "@typescript-eslint/types" "6.13.1" + "@typescript-eslint/typescript-estree" "6.13.1" + "@typescript-eslint/visitor-keys" "6.13.1" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" + integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== + dependencies: + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" + +"@typescript-eslint/scope-manager@6.13.1": + version "6.13.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz#58c7c37c6a957d3d9f59bc4f64c2888e0cac1d70" + integrity sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ== + dependencies: + "@typescript-eslint/types" "6.13.1" + "@typescript-eslint/visitor-keys" "6.13.1" + +"@typescript-eslint/type-utils@6.13.1": + version "6.13.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.13.1.tgz#e6e5885e387841cae9c38fc0638fd8b7561973d6" + integrity sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ== + dependencies: + "@typescript-eslint/typescript-estree" "6.13.1" + "@typescript-eslint/utils" "6.13.1" + debug "^4.3.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/types@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" + integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== + +"@typescript-eslint/types@6.13.1": + version "6.13.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.13.1.tgz#b56f26130e7eb8fa1e429c75fb969cae6ad7bb5c" + integrity sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg== + +"@typescript-eslint/typescript-estree@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" + integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== + dependencies: + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/typescript-estree@6.13.1": + version "6.13.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz#d01dda78d2487434d1c503853fa00291c566efa4" + integrity sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ== + dependencies: + "@typescript-eslint/types" "6.13.1" + "@typescript-eslint/visitor-keys" "6.13.1" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/utils@6.13.1": + version "6.13.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.13.1.tgz#925b3a2453a71ada914ae329b7bb7e7d96634b2f" + integrity sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.13.1" + "@typescript-eslint/types" "6.13.1" + "@typescript-eslint/typescript-estree" "6.13.1" + semver "^7.5.4" + +"@typescript-eslint/utils@^5.10.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" + integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" + eslint-scope "^5.1.1" + semver "^7.3.7" + +"@typescript-eslint/visitor-keys@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" + integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== + dependencies: + "@typescript-eslint/types" "5.62.0" + eslint-visitor-keys "^3.3.0" + +"@typescript-eslint/visitor-keys@6.13.1": + version "6.13.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz#c4b692dcc23a4fc60685b718f10fde789d65a540" + integrity sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ== + dependencies: + "@typescript-eslint/types" "6.13.1" + eslint-visitor-keys "^3.4.1" + +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.9.0: + version "8.11.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" + integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== + +aes-js@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" + integrity sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +anymatch@^3.0.3, anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-back@^3.0.1, array-back@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0" + integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q== + +array-back@^4.0.1, array-back@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e" + integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +asn1.js@^5.2.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" + integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + safer-buffer "^2.1.0" + +async-mutex@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.4.0.tgz#ae8048cd4d04ace94347507504b3cf15e631c25f" + integrity sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA== + dependencies: + tslib "^2.4.0" + +async-retry@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.3.tgz#0e7f36c04d8478e7a58bdbed80cedf977785f280" + integrity sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw== + dependencies: + retry "0.13.1" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +awilix@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/awilix/-/awilix-4.3.4.tgz#aeecc662efa96256981af3bc6243eb201c8b4a4f" + integrity sha512-NgRwUPxUnoK+OTRa2fXcRQVFPOPQXlwCN1FJPkhO3IHKQJHokhdVpDfgz9L3VZTcA1iSaOFE3N/Q/5P7lIDqig== + dependencies: + camel-case "^4.1.2" + glob "^7.1.6" + +axios@^1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2" + integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A== + dependencies: + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-arraybuffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc" + integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bech32@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" + integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== + +big-integer@^1.6.44: + version "1.6.52" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85" + integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg== + +bignumber.js@^9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" + integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + +bn.js@^5.0.0, bn.js@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + +bplist-parser@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.2.0.tgz#43a9d183e5bf9d545200ceac3e712f79ebbe8d0e" + integrity sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw== + dependencies: + big-integer "^1.6.44" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.2, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +brorand@^1.0.1, brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== + +browserify-aes@^1.0.0, browserify-aes@^1.0.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + +browserify-cipher@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" + integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== + dependencies: + browserify-aes "^1.0.4" + browserify-des "^1.0.0" + evp_bytestokey "^1.0.0" + +browserify-des@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" + integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== + dependencies: + cipher-base "^1.0.1" + des.js "^1.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +browserify-rsa@^4.0.0, browserify-rsa@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" + integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== + dependencies: + bn.js "^5.0.0" + randombytes "^2.0.1" + +browserify-sign@^4.0.0: + version "4.2.2" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.2.tgz#e78d4b69816d6e3dd1c747e64e9947f9ad79bc7e" + integrity sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg== + dependencies: + bn.js "^5.2.1" + browserify-rsa "^4.1.0" + create-hash "^1.2.0" + create-hmac "^1.1.7" + elliptic "^6.5.4" + inherits "^2.0.4" + parse-asn1 "^5.1.6" + readable-stream "^3.6.2" + safe-buffer "^5.2.1" + +browserslist@^4.21.9: + version "4.22.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.2.tgz#704c4943072bd81ea18997f3bd2180e89c77874b" + integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== + dependencies: + caniuse-lite "^1.0.30001565" + electron-to-chromium "^1.4.601" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== + +buffer@6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +bundle-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-3.0.0.tgz#ba59bcc9ac785fb67ccdbf104a2bf60c099f0e1a" + integrity sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw== + dependencies: + run-applescript "^5.0.0" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camel-case@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" + integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== + dependencies: + pascal-case "^3.1.2" + tslib "^2.0.3" + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001565: + version "1.0.30001566" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001566.tgz#61a8e17caf3752e3e426d4239c549ebbb37fef0d" + integrity sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA== + +chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +chokidar@^3.5.2: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +cjs-module-lexer@^1.0.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" + integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +command-line-args@^5.1.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.2.1.tgz#c44c32e437a57d7c51157696893c5909e9cec42e" + integrity sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg== + dependencies: + array-back "^3.1.0" + find-replace "^3.0.0" + lodash.camelcase "^4.3.0" + typical "^4.0.0" + +command-line-usage@^6.1.0: + version "6.1.3" + resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-6.1.3.tgz#428fa5acde6a838779dfa30e44686f4b6761d957" + integrity sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw== + dependencies: + array-back "^4.0.2" + chalk "^2.4.2" + table-layout "^1.0.2" + typical "^5.2.0" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +create-ecdh@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" + integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== + dependencies: + bn.js "^4.1.0" + elliptic "^6.5.3" + +create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + +create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypto-browserify@3.12.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" + integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== + dependencies: + browserify-cipher "^1.0.0" + browserify-sign "^4.0.0" + create-ecdh "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.0" + diffie-hellman "^5.0.0" + inherits "^2.0.1" + pbkdf2 "^3.0.3" + public-encrypt "^4.0.0" + randombytes "^2.0.0" + randomfill "^1.0.3" + +debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +dedent@^1.0.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff" + integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg== + +deep-extend@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +default-browser-id@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-3.0.0.tgz#bee7bbbef1f4e75d31f98f4d3f1556a14cea790c" + integrity sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA== + dependencies: + bplist-parser "^0.2.0" + untildify "^4.0.0" + +default-browser@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-4.0.0.tgz#53c9894f8810bf86696de117a6ce9085a3cbc7da" + integrity sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA== + dependencies: + bundle-name "^3.0.0" + default-browser-id "^3.0.0" + execa "^7.1.1" + titleize "^3.0.0" + +define-lazy-prop@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" + integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +des.js@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.1.0.tgz#1d37f5766f3bbff4ee9638e871a8768c173b81da" + integrity sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg== + dependencies: + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +diffie-hellman@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== + dependencies: + bn.js "^4.1.0" + miller-rabin "^4.0.0" + randombytes "^2.0.0" + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +electron-to-chromium@^1.4.601: + version "1.4.601" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.601.tgz#cac69868548aee89961ffe63ff5a7716f0685b75" + integrity sha512-SpwUMDWe9tQu8JX5QCO1+p/hChAi9AE9UpoC3rcHVc+gdCGlbT3SGb5I1klgb952HRIyvt9wZhSz9bNBYz9swA== + +elliptic@6.5.4, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.4: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-prettier@^9.0.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz#31af3d94578645966c082fcb71a5846d3c94867f" + integrity sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw== + +eslint-plugin-jest@^27.6.0: + version "27.6.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-27.6.0.tgz#e5c0cf735b3c8cad0ef9db5b565b2fc99f5e55ed" + integrity sha512-MTlusnnDMChbElsszJvrwD1dN3x6nZl//s4JD23BxB6MgR66TZlL064su24xEIS3VACfAoHV1vgyMgPw8nkdng== + dependencies: + "@typescript-eslint/utils" "^5.10.0" + +eslint-plugin-prettier@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz#a3b399f04378f79f066379f544e42d6b73f11515" + integrity sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg== + dependencies: + prettier-linter-helpers "^1.0.0" + synckit "^0.8.5" + +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.54.0: + version "8.55.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.55.0.tgz#078cb7b847d66f2c254ea1794fa395bf8e7e03f8" + integrity sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.55.0" + "@humanwhocodes/config-array" "^0.11.13" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +ethers@^5.5.1: + version "5.7.2" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" + integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== + dependencies: + "@ethersproject/abi" "5.7.0" + "@ethersproject/abstract-provider" "5.7.0" + "@ethersproject/abstract-signer" "5.7.0" + "@ethersproject/address" "5.7.0" + "@ethersproject/base64" "5.7.0" + "@ethersproject/basex" "5.7.0" + "@ethersproject/bignumber" "5.7.0" + "@ethersproject/bytes" "5.7.0" + "@ethersproject/constants" "5.7.0" + "@ethersproject/contracts" "5.7.0" + "@ethersproject/hash" "5.7.0" + "@ethersproject/hdnode" "5.7.0" + "@ethersproject/json-wallets" "5.7.0" + "@ethersproject/keccak256" "5.7.0" + "@ethersproject/logger" "5.7.0" + "@ethersproject/networks" "5.7.1" + "@ethersproject/pbkdf2" "5.7.0" + "@ethersproject/properties" "5.7.0" + "@ethersproject/providers" "5.7.2" + "@ethersproject/random" "5.7.0" + "@ethersproject/rlp" "5.7.0" + "@ethersproject/sha2" "5.7.0" + "@ethersproject/signing-key" "5.7.0" + "@ethersproject/solidity" "5.7.0" + "@ethersproject/strings" "5.7.0" + "@ethersproject/transactions" "5.7.0" + "@ethersproject/units" "5.7.0" + "@ethersproject/wallet" "5.7.0" + "@ethersproject/web" "5.7.1" + "@ethersproject/wordlists" "5.7.0" + +evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +execa@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-7.2.0.tgz#657e75ba984f42a70f38928cedc87d6f2d4fe4e9" + integrity sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.1" + human-signals "^4.3.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^3.0.7" + strip-final-newline "^3.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + +fast-glob@^3.2.9, fast-glob@^3.3.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fast-safe-stringify@^2.0.6: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" + integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== + +fastq@^1.6.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" + integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-replace@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38" + integrity sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ== + dependencies: + array-back "^3.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.2.9" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" + integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== + +follow-redirects@^1.15.0: + version "1.15.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" + integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +forta-agent@^0.1.48: + version "0.1.48" + resolved "https://registry.yarnpkg.com/forta-agent/-/forta-agent-0.1.48.tgz#d2660eb0c8db3daf554079176836ba5bec8d3d4f" + integrity sha512-fk3mar7/Avqg/4OHFmgv01ww/azr1XM+g5KcSnwvNxZy3KDMi7aFp1jAjPCsBjs8ZyVcR03ITUlbtFpRVgZB4Q== + dependencies: + "@grpc/grpc-js" "^1.3.6" + "@grpc/proto-loader" "^0.6.4" + "@types/uuid" "^8.3.4" + async-retry "^1.3.3" + awilix "^4.3.4" + axios "^1.6.2" + base64-arraybuffer "^1.0.2" + ethers "^5.5.1" + flat-cache "^3.0.4" + form-data "^4.0.0" + jsonc "^2.0.0" + keythereum "^1.2.0" + lodash "^4.17.21" + murmurhash3js "^3.0.1" + n-readlines "^1.0.1" + prompts "^2.4.1" + python-shell "^3.0.0" + sha3 "^2.1.4" + shelljs "^0.8.4" + uuid "^8.3.2" + yargs "^17.0.1" + +fp-ts@^2.16.1: + version "2.16.1" + resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.16.1.tgz#6abc401ce42b65364ca8f0b0d995c5840c68a930" + integrity sha512-by7U5W8dkIzcvDofUcO42yl9JbnHTEDBrzu3pt5fKT+Z4Oy85I21K80EYJYdjQGC2qum4Vo55Ag57iiIK4FYuA== + +fs-extra@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2, fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0, get-stream@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@7.1.7: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.0.0, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^8.0.3: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.19.0: + version "13.23.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.23.0.tgz#ef31673c926a0976e1f61dab4dca57e0c0a8af02" + integrity sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA== + dependencies: + type-fest "^0.20.2" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hash-base@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== + dependencies: + inherits "^2.0.4" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + +hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +hasown@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" + integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== + dependencies: + function-bind "^1.1.2" + +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +human-signals@^4.3.0: + version "4.3.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" + integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== + +husky@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" + integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== + +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== + +ignore@^5.2.0, ignore@^5.2.4: + version "5.3.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78" + integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +interpret@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-core-module@^2.13.0: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + +is-docker@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-docker@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" + integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-inside-container@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" + integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== + dependencies: + is-docker "^3.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" + integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== + +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz#71e87707e8041428732518c6fb5211761753fbdf" + integrity sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.6" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.6.tgz#2544bcab4768154281a2f0870471902704ccaa1a" + integrity sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + +jest-util@^29.0.0, jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + +js-sha3@0.8.0, js-sha3@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonc@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/jsonc/-/jsonc-2.0.0.tgz#9e2a25100d164a9bb864c57517563717fa882551" + integrity sha512-B281bLCT2TRMQa+AQUQY5AGcqSOXBOKaYGP4wDzoA/+QswUfN8sODektbPEs9Baq7LGKun5jQbNFpzwGuVYKhw== + dependencies: + fast-safe-stringify "^2.0.6" + graceful-fs "^4.1.15" + mkdirp "^0.5.1" + parse-json "^4.0.0" + strip-bom "^4.0.0" + strip-json-comments "^3.0.1" + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + +keccak@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.1.tgz#ae30a0e94dbe43414f741375cff6d64c8bea0bff" + integrity sha512-epq90L9jlFWCW7+pQa6JOnKn2Xgl2mtI664seYR6MHskvI9agt7AnDqmAlp9TqU4/caMYbA08Hi5DMZAl5zdkA== + dependencies: + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + +keythereum@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/keythereum/-/keythereum-1.2.0.tgz#3e6c2d7451ef4fde011f8008348c194f7e58c86a" + integrity sha512-u3XnjIruOmjIvJ4tH1Wdr2y0X8+z8BZTQ+dqJuDMyLvNWw6VnH9XKtt0yauSE+96Bq97h6CPm4w5LbW3i28x0g== + dependencies: + crypto-browserify "3.12.0" + keccak "3.0.1" + scrypt-js "3.0.1" + secp256k1 "4.0.2" + sjcl "1.0.6" + uuid "3.0.0" + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== + +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash@^4.17.15, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + +long@^5.0.0: + version "5.2.3" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" + integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== + +lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@1.x: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +miller-rabin@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" + integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== + dependencies: + bn.js "^4.0.0" + brorand "^1.0.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mimic-fn@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" + integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== + +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== + +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +mkdirp@^0.5.1: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +murmurhash3js@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/murmurhash3js/-/murmurhash3js-3.0.1.tgz#3e983e5b47c2a06f43a713174e7e435ca044b998" + integrity sha512-KL8QYUaxq7kUbcl0Yto51rMcYt7E/4N4BG3/c96Iqw1PQrTRspu8Cpx4TZ4Nunib1d4bEkIH3gjCYlP2RLBdow== + +n-readlines@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/n-readlines/-/n-readlines-1.0.1.tgz#bbb7364d38bc31a170a199f986fcacfa76b95f6e" + integrity sha512-z4SyAIVgMy7CkgsoNw7YVz40v0g4+WWvvqy8+ZdHrCtgevcEO758WQyrYcw3XPxcLxF+//RszTz/rO48nzD0wQ== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + dependencies: + lower-case "^2.0.2" + tslib "^2.0.3" + +node-addon-api@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" + integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== + +node-gyp-build@^4.2.0: + version "4.7.1" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.7.1.tgz#cd7d2eb48e594874053150a9418ac85af83ca8f7" + integrity sha512-wTSrZ+8lsRRa3I3H8Xr65dLWSgCvY2l4AOnaeKdPA9TB/WYMPaTcrzf3rXvFoVvjKNVnu0CcWSx54qq9GKRUYg== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + +nodemon@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.0.2.tgz#222dd0de79fc7b7b3eedba422d2b9e5fc678621e" + integrity sha512-9qIN2LNTrEzpOPBaWHTm4Asy1LxXLSickZStAQ4IZe7zsoIpD/A7LWxhZV3t4Zu352uBcqVnRsDXSMR2Sc3lTA== + dependencies: + chokidar "^3.5.2" + debug "^4" + ignore-by-default "^1.0.1" + minimatch "^3.1.2" + pstree.remy "^1.1.8" + semver "^7.5.3" + simple-update-notifier "^2.0.0" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.5" + +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg== + dependencies: + abbrev "1" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +npm-run-path@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.1.0.tgz#bc62f7f3f6952d9894bd08944ba011a6ee7b7e00" + integrity sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q== + dependencies: + path-key "^4.0.0" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +onetime@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" + integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== + dependencies: + mimic-fn "^4.0.0" + +open@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/open/-/open-9.1.0.tgz#684934359c90ad25742f5a26151970ff8c6c80b6" + integrity sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg== + dependencies: + default-browser "^4.0.0" + define-lazy-prop "^3.0.0" + is-inside-container "^1.0.0" + is-wsl "^2.2.0" + +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== + dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-asn1@^5.0.0, parse-asn1@^5.1.6: + version "5.1.6" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" + integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== + dependencies: + asn1.js "^5.2.0" + browserify-aes "^1.0.0" + evp_bytestokey "^1.0.0" + pbkdf2 "^3.0.3" + safe-buffer "^5.1.1" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw== + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +pascal-case@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" + integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-key@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pbkdf2@^3.0.3: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" + integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +postinstall@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/postinstall/-/postinstall-0.8.0.tgz#4acd1d9113831055a35e8670cefb2bf57aa7f87b" + integrity sha512-onh5cnUw4ue+iBzwoyHZNfih1iopqm5abfc/0vK/A9QyYVPxCbLW0DxwrRpHFZ2/Fs5Uo7j4TiaVDNWriq0HIg== + dependencies: + "@danieldietrich/copy" "^0.4.2" + glob "^8.0.3" + minimist "^1.2.6" + resolve-from "^5.0.0" + resolve-pkg "^2.0.0" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^2.1.2, prettier@^2.3.1: + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + +prettier@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.1.0.tgz#c6d16474a5f764ea1a4a373c593b779697744d5e" + integrity sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw== + +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +prompts@^2.0.1, prompts@^2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +protobufjs@^6.11.3: + version "6.11.4" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.4.tgz#29a412c38bf70d89e537b6d02d904a6f448173aa" + integrity sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.1" + "@types/node" ">=13.7.0" + long "^4.0.0" + +protobufjs@^7.2.4: + version "7.2.5" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.5.tgz#45d5c57387a6d29a17aab6846dcc283f9b8e7f2d" + integrity sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +pstree.remy@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + +public-encrypt@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" + integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== + dependencies: + bn.js "^4.1.0" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + parse-asn1 "^5.0.0" + randombytes "^2.0.1" + safe-buffer "^5.1.2" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +pure-rand@^6.0.0: + version "6.0.4" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.4.tgz#50b737f6a925468679bff00ad20eade53f37d5c7" + integrity sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA== + +python-shell@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/python-shell/-/python-shell-3.0.1.tgz#c3d3b11536e6ebdb8d6a2602482f7180d940bb13" + integrity sha512-TWeotuxe1auhXa5bGRScxnc2J+0r41NBntSa6RYZtMBLtAEsvCboKrEbW6DvASosWQepVkhZZlT3B5Ei766G+Q== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +randomfill@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== + dependencies: + randombytes "^2.0.5" + safe-buffer "^5.1.0" + +react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + +readable-stream@^3.6.0, readable-stream@^3.6.2: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== + dependencies: + resolve "^1.1.6" + +reduce-flatten@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" + integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg/-/resolve-pkg-2.0.0.tgz#ac06991418a7623edc119084edc98b0e6bf05a41" + integrity sha512-+1lzwXehGCXSeryaISr6WujZzowloigEofRB+dj75y9RRa/obVcYgbHJd53tdYw8pvZj8GojXaaENws8Ktw/hQ== + dependencies: + resolve-from "^5.0.0" + +resolve.exports@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" + integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== + +resolve@^1.1.6, resolve@^1.20.0, resolve@^1.8.1: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +retry@0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + +run-applescript@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-5.0.0.tgz#e11e1c932e055d5c6b40d98374e0268d9b11899c" + integrity sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg== + dependencies: + execa "^5.0.0" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safer-buffer@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +scrypt-js@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" + integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== + +secp256k1@4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.2.tgz#15dd57d0f0b9fdb54ac1fa1694f40e5e9a54f4a1" + integrity sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg== + dependencies: + elliptic "^6.5.2" + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.7, semver@^7.5.3, semver@^7.5.4: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +sha3@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/sha3/-/sha3-2.1.4.tgz#000fac0fe7c2feac1f48a25e7a31b52a6492cc8f" + integrity sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg== + dependencies: + buffer "6.0.3" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shelljs@^0.8.4: + version "0.8.5" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" + integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + +signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +simple-update-notifier@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" + integrity sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w== + dependencies: + semver "^7.5.3" + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +sjcl@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/sjcl/-/sjcl-1.0.6.tgz#6415462a63cc0d4215c49baec9d3fa0c1b53520f" + integrity sha512-oUVs+hzMSWEZ3rdeDL461QilvvEU2OL9q6T42lpVi2C5Proej9obVZ1nQeY9T96NxoMy/dqw82m33MfNNEmYJg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +string-format@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string-format/-/string-format-2.0.0.tgz#f2df2e7097440d3b65de31b6d40d54c96eaffb9b" + integrity sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA== + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-final-newline@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" + integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== + +strip-json-comments@^3.0.1, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^5.3.0, supports-color@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +synckit@^0.8.5: + version "0.8.6" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.6.tgz#b69b7fbce3917c2673cbdc0d87fb324db4a5b409" + integrity sha512-laHF2savN6sMeHCjLRkheIU4wo3Zg9Ln5YOjOo7sZ5dVQW8yF5pPE5SIw1dsPhq3TRp1jisKRCdPhfs/1WMqDA== + dependencies: + "@pkgr/utils" "^2.4.2" + tslib "^2.6.2" + +table-layout@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-1.0.2.tgz#c4038a1853b0136d63365a734b6931cf4fad4a04" + integrity sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A== + dependencies: + array-back "^4.0.1" + deep-extend "~0.6.0" + typical "^5.2.0" + wordwrapjs "^4.0.0" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +titleize@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" + integrity sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ== + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +touch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== + dependencies: + nopt "~1.0.10" + +ts-api-utils@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" + integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg== + +ts-command-line-args@^2.2.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/ts-command-line-args/-/ts-command-line-args-2.5.1.tgz#e64456b580d1d4f6d948824c274cf6fa5f45f7f0" + integrity sha512-H69ZwTw3rFHb5WYpQya40YAX2/w7Ut75uUECbgBIsLmM+BNuYnxsltfyyLMxy6sEeKxgijLTnQtLd0nKd6+IYw== + dependencies: + chalk "^4.1.0" + command-line-args "^5.1.1" + command-line-usage "^6.1.0" + string-format "^2.0.0" + +ts-essentials@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-1.0.4.tgz#ce3b5dade5f5d97cf69889c11bf7d2da8555b15a" + integrity sha512-q3N1xS4vZpRouhYHDPwO0bDW3EZ6SK9CrrDHxi/D6BPReSjpVgWIOpLS2o0gSBZm+7q/wyKp6RVM1AeeW7uyfQ== + +ts-essentials@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-7.0.3.tgz#686fd155a02133eedcc5362dc8b5056cde3e5a38" + integrity sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ== + +ts-generator@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ts-generator/-/ts-generator-0.1.1.tgz#af46f2fb88a6db1f9785977e9590e7bcd79220ab" + integrity sha512-N+ahhZxTLYu1HNTQetwWcx3so8hcYbkKBHTr4b4/YgObFTIKkOSSsaa+nal12w8mfrJAyzJfETXawbNjSfP2gQ== + dependencies: + "@types/mkdirp" "^0.5.2" + "@types/prettier" "^2.1.1" + "@types/resolve" "^0.0.8" + chalk "^2.4.1" + glob "^7.1.2" + mkdirp "^0.5.1" + prettier "^2.1.2" + resolve "^1.8.1" + ts-essentials "^1.0.0" + +ts-jest@^29.1.1: + version "29.1.1" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.1.tgz#f58fe62c63caf7bfcc5cc6472082f79180f0815b" + integrity sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA== + dependencies: + bs-logger "0.x" + fast-json-stable-stringify "2.x" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "4.x" + make-error "1.x" + semver "^7.5.3" + yargs-parser "^21.0.1" + +ts-retry@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/ts-retry/-/ts-retry-4.2.4.tgz#151e813adec2e7edffa5493c2f07b47e71fd336e" + integrity sha512-r9hIgfdCyU5yBe090s3E1VdPWGifBHx6uzu5hjRn6fbK0jpV8t4TGzNBghnxZXbm2ZNsm909E5WbwPGWAHdQOQ== + +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.0, tslib@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +typechain@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/typechain/-/typechain-8.3.2.tgz#1090dd8d9c57b6ef2aed3640a516bdbf01b00d73" + integrity sha512-x/sQYr5w9K7yv3es7jo4KTX05CLxOf7TRWwoHlrjRh8H82G64g+k7VuWPJlgMo6qrjfCulOdfBjiaDtmhFYD/Q== + dependencies: + "@types/prettier" "^2.1.1" + debug "^4.3.1" + fs-extra "^7.0.0" + glob "7.1.7" + js-sha3 "^0.8.0" + lodash "^4.17.15" + mkdirp "^1.0.4" + prettier "^2.3.1" + ts-command-line-args "^2.2.0" + ts-essentials "^7.0.1" + +typescript@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43" + integrity sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ== + +typical@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" + integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw== + +typical@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066" + integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg== + +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +untildify@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" + integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== + +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +uuid@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.0.tgz#6728fc0459c450d796a99c31837569bdf672d728" + integrity sha512-rqE1LoOVLv3QrZMjb4NkF5UWlkurCfPyItVnFPNKDDGkHw4dQUdE4zMcLqx28+0Kcf3+bnUk4PisaiRJT4aiaQ== + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +v8-to-istanbul@^9.0.1: + version "9.2.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz#2ed7644a245cddd83d4e087b9b33b3e62dfd10ad" + integrity sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wordwrapjs@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-4.0.1.tgz#d9790bccfb110a0fc7836b5ebce0937b37a8b98f" + integrity sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA== + dependencies: + reduce-flatten "^2.0.0" + typical "^5.2.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +ws@7.4.6: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-parser@^21.0.1, yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yargs@^17.0.1, yargs@^17.3.1, yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/ethereum-validators-set/src/common/utils.ts b/ethereum-validators-set/src/common/utils.ts index 0812a4de..3dadfd54 100644 --- a/ethereum-validators-set/src/common/utils.ts +++ b/ethereum-validators-set/src/common/utils.ts @@ -44,8 +44,8 @@ export function etherscanNft(address: string, id: number | string): string { /** * Special wrapper under `require` function that allows to - * redefine variables from a file with the same name and `.` suffix. - * `` is a string that is passed by `FORTA_AGENT_RUN_TIER` environment variable. + * redefine variables from a file with the same name and `.` suffix. + * `` is a string that is passed by `FORTA_AGENT_RUN_TIER` environment variable. * @param module module object to get the path from. * @param path relative to module path to the main file to import. * @param mode `strict` or `merge`. Default: `strict`. @@ -56,7 +56,9 @@ export function requireWithTier( mode: RedefineMode = RedefineMode.Strict, ): T { const defaultContent = require(`${module.path}/${path}`); - if (!RUN_TIER) return defaultContent; + if (!RUN_TIER) { + return defaultContent; + } let tieredContent: any; try { tieredContent = require(`${module.path}/${path}.${RUN_TIER}`); diff --git a/lido-on-polygon/src/helpers.ts b/lido-on-polygon/src/helpers.ts index 22832e75..8e5fd1c0 100644 --- a/lido-on-polygon/src/helpers.ts +++ b/lido-on-polygon/src/helpers.ts @@ -5,11 +5,13 @@ import { ethersProvider } from "./ethers"; const SI_SYMBOL = ["", "k", "M", "G", "T", "P", "E"]; export function abbreviateNumber(number: number): string { - // what tier? (determines SI symbol) + // what tier.ts? (determines SI symbol) const tier = (Math.log10(Math.abs(number)) / 3) | 0; // if zero, we don't need a suffix - if (tier == 0) return Math.round(number).toString(); + if (tier == 0) { + return Math.round(number).toString(); + } // get suffix and determine scale const suffix = SI_SYMBOL[tier]; diff --git a/polygon/src/helpers.ts b/polygon/src/helpers.ts index 1c57ca18..bdb8c045 100644 --- a/polygon/src/helpers.ts +++ b/polygon/src/helpers.ts @@ -1,11 +1,13 @@ const SI_SYMBOL = ["", "k", "M", "G", "T", "P", "E"]; export function abbreviateNumber(number: number): string { - // what tier? (determines SI symbol) + // what tier.ts? (determines SI symbol) const tier = (Math.log10(Math.abs(number)) / 3) | 0; // if zero, we don't need a suffix - if (tier == 0) return Math.round(number).toString(); + if (tier == 0) { + return Math.round(number).toString(); + } // get suffix and determine scale const suffix = SI_SYMBOL[tier]; diff --git a/storage-watcher/src/common/utils.ts b/storage-watcher/src/common/utils.ts index 945449ed..f0a8b7cd 100644 --- a/storage-watcher/src/common/utils.ts +++ b/storage-watcher/src/common/utils.ts @@ -47,8 +47,8 @@ export enum RedefineMode { /** * Special wrapper under `require` function that allows to - * redefine variables from a file with the same name and `.` suffix. - * `` is a string that is passed by `FORTA_AGENT_RUN_TIER` environment variable. + * redefine variables from a file with the same name and `.` suffix. + * `` is a string that is passed by `FORTA_AGENT_RUN_TIER` environment variable. * @param module module object to get the path from. * @param path relative to module path to the main file to import. * @param mode `strict` or `merge`. Default: `strict`. @@ -59,7 +59,9 @@ export function requireWithTier( mode: RedefineMode = RedefineMode.Strict, ): T { const defaultContent = require(`${module.path}/${path}`); - if (!RUN_TIER) return defaultContent; + if (!RUN_TIER) { + return defaultContent; + } let tieredContent: any; try { tieredContent = require(`${module.path}/${path}.${RUN_TIER}`); diff --git a/voting-watcher/src/helpers.ts b/voting-watcher/src/helpers.ts index c75e787a..bddac998 100644 --- a/voting-watcher/src/helpers.ts +++ b/voting-watcher/src/helpers.ts @@ -15,11 +15,13 @@ export function getResultStr(quorumDistance: number, passed: boolean) { const SI_SYMBOL = ["", "k", "M", "G", "T", "P", "E"]; export function abbreviateNumber(number: number): string { - // what tier? (determines SI symbol) + // what tier.ts? (determines SI symbol) const tier = (Math.log10(Math.abs(number)) / 3) | 0; // if zero, we don't need a suffix - if (tier == 0) return Math.round(number).toString(); + if (tier == 0) { + return Math.round(number).toString(); + } // get suffix and determine scale const suffix = SI_SYMBOL[tier]; From 688160bde02ca6caa52f43dbaaeb294b46cfad01 Mon Sep 17 00:00:00 2001 From: Sergey White Date: Thu, 11 Jan 2024 17:00:46 +0300 Subject: [PATCH 02/22] feat: StethOperation Service handle block --- ethereum-steth-v2/README.md | 3 + ethereum-steth-v2/src/agent.ts | 21 +- ethereum-steth-v2/src/app.ts | 8 +- ethereum-steth-v2/src/clients/eth_provider.ts | 21 +- .../steth_operation/StethOperation.cache.ts | 37 ++- .../steth_operation/StethOperation.srv.ts | 229 ++++++++++++++++-- ethereum-steth-v2/src/utils/constants.ts | 2 + 7 files changed, 281 insertions(+), 40 deletions(-) diff --git a/ethereum-steth-v2/README.md b/ethereum-steth-v2/README.md index e858f90f..78ee615e 100644 --- a/ethereum-steth-v2/README.md +++ b/ethereum-steth-v2/README.md @@ -19,6 +19,9 @@ the cache. 1. 🚨🚨🚨 Buffered ETH drain 2. 🚨 Huge depositable ETH amount 3. ⚠️ High depositable ETH amount + 4. ⚠️ Low deposit executor balance + 5. ⚠️ Staking limit below 10% + 6. πŸ“‰ Staking limit below 30% ## Development (Forta specific) diff --git a/ethereum-steth-v2/src/agent.ts b/ethereum-steth-v2/src/agent.ts index b707c65f..30eb7eb7 100644 --- a/ethereum-steth-v2/src/agent.ts +++ b/ethereum-steth-v2/src/agent.ts @@ -1,4 +1,4 @@ -import { BlockEvent, Finding, FindingSeverity, FindingType, HandleBlock } from 'forta-agent' +import { BlockEvent, Finding, HandleBlock } from 'forta-agent' import * as process from 'process' import { argv } from 'process' import { InitializeResponse } from 'forta-agent/dist/sdk/initialize.response' @@ -6,7 +6,6 @@ import { Initialize } from 'forta-agent/dist/sdk/handlers' import * as E from 'fp-ts/Either' import { App } from './app' import { elapsedTime } from './utils/time' -import { StethOperationSrv } from './services/steth_operation/StethOperation.srv' export function initialize(): Initialize { /* const metadata: Metadata = { @@ -52,21 +51,11 @@ export const handleBlock = (): HandleBlock => { const app = await App.getInstance() const out: Finding[] = [] - const startHandleBufferedEth = new Date().getTime() - const bufferedEth = await app.StethOperationSrv.handleBufferedEth(blockEvent) - if (E.isLeft(bufferedEth)) { - const f: Finding = Finding.fromObject({ - name: `Error in ${StethOperationSrv.name}.${app.StethOperationSrv.handleBufferedEth.name}:55`, - description: `Handle handleBufferedEth is failed. Cause: ${bufferedEth.left.message}`, - alertId: 'LIDO-AGENT-ERROR', - severity: FindingSeverity.Low, - type: FindingType.Degraded, - metadata: { stack: `${bufferedEth.left.stack}` }, - }) + const startStethOpHandleBlock = new Date().getTime() + const bufferedEthFindings = await app.StethOperationSrv.handleBlock(blockEvent) + console.log(elapsedTime('app.StethOperationSrv.handleBlock', startStethOpHandleBlock)) - out.push(f) - } - console.log(elapsedTime('app.StethOperationSrv.handleBufferedEth', startHandleBufferedEth)) + out.push(...bufferedEthFindings) console.log(elapsedTime('handleBlock', startTime) + '\n') isHandleBLockRunning = false diff --git a/ethereum-steth-v2/src/app.ts b/ethereum-steth-v2/src/app.ts index 3999bc60..b6ee6b5e 100644 --- a/ethereum-steth-v2/src/app.ts +++ b/ethereum-steth-v2/src/app.ts @@ -1,6 +1,11 @@ import { StethOperationSrv } from './services/steth_operation/StethOperation.srv' import { ethers, getEthersProvider } from 'forta-agent' -import { DEPOSIT_SECURITY_ADDRESS, LIDO_STETH_ADDRESS, WITHDRAWAL_QUEUE_ADDRESS } from './utils/constants' +import { + DEPOSIT_EXECUTOR_ADDRESS, + DEPOSIT_SECURITY_ADDRESS, + LIDO_STETH_ADDRESS, + WITHDRAWAL_QUEUE_ADDRESS, +} from './utils/constants' import { StethOperationCache } from './services/steth_operation/StethOperation.cache' import { ETHProvider, IETHProvider } from './clients/eth_provider' import { FormatterWithEIP1898 } from './clients/eth_formatter' @@ -39,6 +44,7 @@ export class App { ethClient, DEPOSIT_SECURITY_ADDRESS, LIDO_STETH_ADDRESS, + DEPOSIT_EXECUTOR_ADDRESS, lidoContact, wdQueueContact, ) diff --git a/ethereum-steth-v2/src/clients/eth_provider.ts b/ethereum-steth-v2/src/clients/eth_provider.ts index 55d6f4c1..8dddd102 100644 --- a/ethereum-steth-v2/src/clients/eth_provider.ts +++ b/ethereum-steth-v2/src/clients/eth_provider.ts @@ -16,7 +16,9 @@ export abstract class IETHProvider { endBlock: number, ): Promise> - public abstract getBalance(lidoStethAddress: string, block: number): Promise> + public abstract getStethBalance(lidoStethAddress: string, block: number): Promise> + + public abstract getDepositorBalance(depositorAddress: string, block: number): Promise> } interface IEtherscanProvider { @@ -122,7 +124,7 @@ export class ETHProvider implements IETHProvider { return E.right(out) } - public async getBalance(lidoStethAddress: string, block: number): Promise> { + public async getStethBalance(lidoStethAddress: string, block: number): Promise> { try { const out = await retryAsync( async (): Promise => { @@ -136,4 +138,19 @@ export class ETHProvider implements IETHProvider { return E.left(new Error(`Could not fetch Steth balance. cause: ${e}`)) } } + + public async getDepositorBalance(depositorAddress: string, block: number): Promise> { + try { + const out = await retryAsync( + async (): Promise => { + return await this.jsonRpcProvider.getBalance(depositorAddress, block) + }, + { delay: 500, maxTry: 5 }, + ) + + return E.right(out) + } catch (e) { + return E.left(new Error(`Could not fetch depositor balance. cause: ${e}`)) + } + } } diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.cache.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.cache.ts index d0aeba94..da5fdb1a 100644 --- a/ethereum-steth-v2/src/services/steth_operation/StethOperation.cache.ts +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.cache.ts @@ -4,7 +4,10 @@ export class StethOperationCache { private _lastDepositorTxTime = 0 private _lastBufferedEth = new BigNumber(0) private _criticalDepositableAmountTime = 0 - private _lastReportedDepositableEth = 0 + private _lastReportedDepositableEthTimestamp = 0 + private _lastReportedExecutorBalanceTimestamp = 0 + private _lastReportedStakingLimit10Timestamp = 0 + private _lastReportedStakingLimit30Timestamp = 0 constructor() {} @@ -32,11 +35,35 @@ export class StethOperationCache { this._criticalDepositableAmountTime = blockTimestamp } - public getLastReportedDepositableEth(): number { - return this._lastReportedDepositableEth + public getLastReportedDepositableEthTimestamp(): number { + return this._lastReportedDepositableEthTimestamp } - public setLastReportedDepositableEth(blockTimestamp: number) { - this._lastReportedDepositableEth = blockTimestamp + public setLastReportedDepositableEthTimestamp(blockTimestamp: number) { + this._lastReportedDepositableEthTimestamp = blockTimestamp + } + + public getLastReportedExecutorBalanceTimestamp(): number { + return this._lastReportedExecutorBalanceTimestamp + } + + public setLastReportedExecutorBalanceTimestamp(blockTimestamp: number) { + this._lastReportedExecutorBalanceTimestamp = blockTimestamp + } + + public getLastReportedStakingLimit10Timestamp(): number { + return this._lastReportedStakingLimit10Timestamp + } + + public setLastReportedStakingLimit10Timestamp(blockTimestamp: number) { + this._lastReportedStakingLimit10Timestamp = blockTimestamp + } + + public getLastReportedStakingLimit30Timestamp(): number { + return this._lastReportedStakingLimit30Timestamp + } + + public setLastReportedStakingLimit30Timestamp(blockTimestamp: number) { + this._lastReportedStakingLimit30Timestamp = blockTimestamp } } diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts index 650b7278..2184b507 100644 --- a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts @@ -12,11 +12,21 @@ import { Event as EthersEvent } from 'ethers' // Formula: (60 * 60 * 72) / 13 = 19_938 const HISTORY_BLOCK_OFFSET: number = Math.floor((60 * 60 * 72) / 13) const BLOCK_CHECK_INTERVAL: number = 100 +const BLOCK_CHECK_INTERVAL_SMAll: number = 25 const MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL: number = 20_000 // 20000 ETH const REPORT_WINDOW = 60 * 60 * 24 // 24 hours const MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL_TIME = 60 * 60 // 1 hour const MAX_DEPOSITABLE_ETH_AMOUNT_MEDIUM = 10_000 // 10000 ETH const MAX_DEPOSITOR_TX_DELAY = 60 * 60 * 72 // 72 Hours +const REPORT_WINDOW_EXECUTOR_BALANCE = 60 * 60 * 4 // 4 Hours +const MIN_DEPOSIT_EXECUTOR_BALANCE = 2 // 2 ETH +const REPORT_WINDOW_STAKING_LIMIT_10 = 60 * 60 * 12 // 12 hours +const REPORT_WINDOW_STAKING_LIMIT_30 = 60 * 60 * 12 // 12 hours + +type StakingLimitInfo = { + maxStakeLimit: BigNumber + currentStakeLimit: BigNumber +} export class StethOperationSrv { private readonly name = 'StethOperation' @@ -24,6 +34,7 @@ export class StethOperationSrv { private readonly ethProvider: IETHProvider private readonly depositSecurityAddress: string private readonly lidoStethAddress: string + private readonly lidoDepositExecutorAddress: string private readonly lidoContract: LidoContract private readonly wdQueueContract: WithdrawalQueue @@ -32,6 +43,7 @@ export class StethOperationSrv { ethProvider: IETHProvider, depositSecurityAddress: string, lidoStethAddress: string, + lidoDepositExecutorAddress: string, lidoContract: LidoContract, wdQueueContract: WithdrawalQueue, ) { @@ -39,6 +51,7 @@ export class StethOperationSrv { this.ethProvider = ethProvider this.depositSecurityAddress = depositSecurityAddress this.lidoStethAddress = lidoStethAddress + this.lidoDepositExecutorAddress = lidoDepositExecutorAddress this.lidoContract = lidoContract this.wdQueueContract = wdQueueContract } @@ -65,7 +78,7 @@ export class StethOperationSrv { this.cache.setLastDepositorTxTime(depositorTxTimestamps[0]) } - const bufferedEthRaw = await this.ethProvider.getBalance(this.lidoStethAddress, currentBlock) + const bufferedEthRaw = await this.ethProvider.getStethBalance(this.lidoStethAddress, currentBlock) if (E.isLeft(bufferedEthRaw)) { return bufferedEthRaw.left } @@ -86,16 +99,18 @@ export class StethOperationSrv { public async handleBlock(blockEvent: BlockEvent) { const findings: Finding[] = [] - await Promise.all([ - // handleBufferedEth(blockEvent, findings), - // handleDepositExecutorBalance(blockEvent, findings), - // handleStakingLimit(blockEvent, findings), + const [bufferedEthFindings, depositorBalanceFindings, stakingLimitFindings] = await Promise.all([ + this.handleBufferedEth(blockEvent), + this.handleDepositExecutorBalance(blockEvent), + this.handleStakingLimit(blockEvent), ]) + findings.push(...bufferedEthFindings, ...depositorBalanceFindings, ...stakingLimitFindings) + return findings } - public async handleBufferedEth(blockEvent: BlockEvent): Promise> { + public async handleBufferedEth(blockEvent: BlockEvent): Promise { const blockNumber = blockEvent.block.number const blockTimestamp = blockEvent.block.timestamp @@ -112,7 +127,16 @@ export class StethOperationSrv { bufferedEthRaw = new BigNumber(resp.toString()) } catch (e) { - return E.left(new Error(`Could not call "lidoContract.getBufferedEther". Cause: ${e}`)) + const f: Finding = Finding.fromObject({ + name: `Error in ${StethOperationSrv.name}.${this.handleBufferedEth.name}:108`, + description: `Could not call "lidoContract.getBufferedEther. Cause ${e instanceof Error ? e.message : ''}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: e instanceof Error ? `${e.stack}` : 'null' }, + }) + + return [f] } const bufferedEth = bufferedEthRaw.div(ETH_DECIMALS).toNumber() @@ -131,18 +155,49 @@ export class StethOperationSrv { const depositableEtherRaw = new BigNumber(resp.toString()) depositableEther = depositableEtherRaw.div(ETH_DECIMALS).toNumber() } catch (e) { - return E.left(new Error(`Could not call "lidoContract.getDepositableEther". Cause: ${e}`)) + const f: Finding = Finding.fromObject({ + name: `Error in ${StethOperationSrv.name}.${this.handleBufferedEth.name}:135`, + description: `Could not call "lidoContract.getDepositableEther. Cause ${e instanceof Error ? e.message : ''}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: e instanceof Error ? `${e.stack}` : 'null' }, + }) + + return [f] } // We use shifted block number to ensure that nodes return correct values const shiftedBlockNumber = blockNumber - 3 const shifte3dBufferedEthRaw = await this.getBufferedEther(shiftedBlockNumber) if (E.isLeft(shifte3dBufferedEthRaw)) { - return E.left(new Error(`Could not call "getBufferedEther". Cause: ${shifte3dBufferedEthRaw.left}`)) + const f: Finding = Finding.fromObject({ + name: `Error in ${StethOperationSrv.name}.${this.handleBufferedEth.name}:159`, + description: `Could not call "this.getBufferedEther". Cause ${shifte3dBufferedEthRaw.left.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { + stack: `${shifte3dBufferedEthRaw.left.stack}`, + }, + }) + + return [f] } const shifte4dBufferedEthRaw = await this.getBufferedEther(shiftedBlockNumber - 1) if (E.isLeft(shifte4dBufferedEthRaw)) { - return E.left(new Error(`Could not call "getBufferedEther". Cause: ${shifte4dBufferedEthRaw.left}`)) + const f: Finding = Finding.fromObject({ + name: `Error in ${StethOperationSrv.name}.${this.handleBufferedEth.name}:174`, + description: `Could not call "this.getBufferedEther". Cause ${shifte4dBufferedEthRaw.left.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { + stack: `${shifte4dBufferedEthRaw.left.stack}`, + }, + }) + + return [f] } const out: Finding[] = [] @@ -161,7 +216,16 @@ export class StethOperationSrv { { delay: 500, maxTry: 5 }, ) } catch (e) { - return E.left(new Error(`Could not fetch unbufferedEvents. Cause: ${e}`)) + const f: Finding = Finding.fromObject({ + name: `Error in ${StethOperationSrv.name}.${this.handleBufferedEth.name}:197`, + description: `Could not fetch unbufferedEvents. Cause ${e instanceof Error ? e.message : ''}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: e instanceof Error ? `${e.stack}` : 'null' }, + }) + + return [f] } let wdReqFinalizedEvents: EthersEvent[] @@ -177,7 +241,16 @@ export class StethOperationSrv { { delay: 500, maxTry: 5 }, ) } catch (e) { - return E.left(new Error(`Could not fetch wdReqFinalizedEvents. Cause: ${e}`)) + const f: Finding = Finding.fromObject({ + name: `Error in ${StethOperationSrv.name}.${this.handleBufferedEth.name}:222`, + description: `Could not fetch wdReqFinalizedEvents. Cause ${e instanceof Error ? e.message : ''}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: e instanceof Error ? `${e.stack}` : 'null' }, + }) + + return [f] } if (unbufferedEvents.length === 0 && wdReqFinalizedEvents.length === 0) { @@ -208,7 +281,7 @@ export class StethOperationSrv { this.cache.setCriticalDepositableAmountTime(0) } - if (this.cache.getLastReportedDepositableEth() + REPORT_WINDOW < blockTimestamp) { + if (this.cache.getLastReportedDepositableEthTimestamp() + REPORT_WINDOW < blockTimestamp) { if ( depositableEther > MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL && this.cache.getCriticalDepositableAmountTime() < blockTimestamp - MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL_TIME @@ -225,7 +298,7 @@ export class StethOperationSrv { type: FindingType.Degraded, }), ) - this.cache.setLastReportedDepositableEth(blockTimestamp) + this.cache.setLastReportedDepositableEthTimestamp(blockTimestamp) } else if ( depositableEther > MAX_DEPOSITABLE_ETH_AMOUNT_MEDIUM && this.cache.getLastDepositorTxTime() < blockTimestamp - MAX_DEPOSITOR_TX_DELAY && @@ -244,12 +317,136 @@ export class StethOperationSrv { type: FindingType.Suspicious, }), ) - this.cache.setLastReportedDepositableEth(blockTimestamp) + this.cache.setLastReportedDepositableEthTimestamp(blockTimestamp) } } } - return E.right(out) + return out + } + + public async handleDepositExecutorBalance(blockEvent: BlockEvent): Promise { + const blockNumber = blockEvent.block.number + const out: Finding[] = [] + if (blockNumber % BLOCK_CHECK_INTERVAL !== 0) { + return out + } + + const currentBlockTimestamp = blockEvent.block.timestamp + if (this.cache.getLastReportedExecutorBalanceTimestamp() + REPORT_WINDOW_EXECUTOR_BALANCE < currentBlockTimestamp) { + const executorBalanceRaw = await this.ethProvider.getDepositorBalance( + this.lidoDepositExecutorAddress, + blockEvent.blockNumber, + ) + if (E.isLeft(executorBalanceRaw)) { + out.push( + Finding.fromObject({ + name: `Error in ${StethOperationSrv.name}.${this.handleDepositExecutorBalance.name}:329`, + description: `Could not fetch depositorBalance. Cause ${executorBalanceRaw.left.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: `${executorBalanceRaw.left.stack}` }, + }), + ) + + return out + } + + const executorBalance = new BigNumber(String(executorBalanceRaw.right)).div(ETH_DECIMALS).toNumber() + if (executorBalance < MIN_DEPOSIT_EXECUTOR_BALANCE) { + this.cache.setLastReportedExecutorBalanceTimestamp(currentBlockTimestamp) + out.push( + Finding.fromObject({ + name: '⚠️ Low deposit executor balance', + description: `Balance of deposit executor is ${executorBalance.toFixed(4)}. ` + `This is extremely low! 😱`, + alertId: 'LOW-DEPOSIT-EXECUTOR-BALANCE', + severity: FindingSeverity.High, + type: FindingType.Suspicious, + }), + ) + } + } + + return out + } + + public async handleStakingLimit(blockEvent: BlockEvent): Promise { + const blockNumber = blockEvent.block.number + const out: Finding[] = [] + if (blockNumber % BLOCK_CHECK_INTERVAL_SMAll !== 0) { + return out + } + + const currentBlockTimestamp = blockEvent.block.timestamp + + let stakingLimitInfo: StakingLimitInfo + try { + stakingLimitInfo = await retryAsync( + async (): Promise => { + const resp = await this.lidoContract.functions.getStakeLimitFullInfo({ + blockTag: blockNumber, + }) + + return { + currentStakeLimit: new BigNumber(String(resp.currentStakeLimit)).div(ETH_DECIMALS), + maxStakeLimit: new BigNumber(String(resp.maxStakeLimit)).div(ETH_DECIMALS), + } + }, + { delay: 500, maxTry: 5 }, + ) + } catch (e) { + const f: Finding = Finding.fromObject({ + name: `Error in ${StethOperationSrv.name}.${this.handleStakingLimit.name}:385`, + description: `Could not call "lidoContract.getStakeLimitFullInfo. Cause ${e instanceof Error ? e.message : ''}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: e instanceof Error ? `${e.stack}` : 'null' }, + }) + + return [f] + } + + const currentStakingLimit = stakingLimitInfo.currentStakeLimit + const maxStakingLimit = stakingLimitInfo.maxStakeLimit + if ( + this.cache.getLastReportedStakingLimit10Timestamp() + REPORT_WINDOW_STAKING_LIMIT_10 < currentBlockTimestamp && + currentStakingLimit.isLessThan(maxStakingLimit.times(0.1)) + ) { + out.push( + Finding.fromObject({ + name: '⚠️ Staking limit below 10%', + description: + `Current staking limit is ${currentStakingLimit.toFixed(2)} ETH ` + + `this is lower than 10% of max staking limit ` + + `${maxStakingLimit.toFixed(2)} ETH`, + alertId: 'LOW-STAKING-LIMIT', + severity: FindingSeverity.Info, + type: FindingType.Info, + }), + ) + this.cache.setLastReportedStakingLimit10Timestamp(currentBlockTimestamp) + } else if ( + this.cache.getLastReportedStakingLimit30Timestamp() + REPORT_WINDOW_STAKING_LIMIT_30 < currentBlockTimestamp && + currentStakingLimit.isLessThan(maxStakingLimit.times(0.3)) + ) { + out.push( + Finding.fromObject({ + name: 'πŸ“‰ Staking limit below 30%', + description: + `Current staking limit is ${currentStakingLimit.toFixed(2)} ETH ` + + `this is lower than 30% of max staking limit ` + + `${maxStakingLimit.toFixed(2)} ETH`, + alertId: 'LOW-STAKING-LIMIT', + severity: FindingSeverity.Info, + type: FindingType.Info, + }), + ) + this.cache.setLastReportedStakingLimit30Timestamp(currentBlockTimestamp) + } + + return out } private async getBufferedEther(blockNumber: number): Promise> { diff --git a/ethereum-steth-v2/src/utils/constants.ts b/ethereum-steth-v2/src/utils/constants.ts index 26ad9fd2..ba435e63 100644 --- a/ethereum-steth-v2/src/utils/constants.ts +++ b/ethereum-steth-v2/src/utils/constants.ts @@ -9,3 +9,5 @@ export const DEPOSIT_SECURITY_ADDRESS = '0xc77f8768774e1c9244beed705c4354f2113cf export const LIDO_STETH_ADDRESS = '0xae7ab96520de3a18e5e111b5eaab095312d7fe84' export const WITHDRAWAL_QUEUE_ADDRESS = '0x889edc2edab5f40e902b864ad4d7ade8e412f9b1' + +export const DEPOSIT_EXECUTOR_ADDRESS = '0xf82ac5937a20dc862f9bc0668779031e06000f17' From 606e324c7ee2f8d2cc2d2aa5771706d65cfa2038 Mon Sep 17 00:00:00 2001 From: Sergey White Date: Thu, 11 Jan 2024 18:20:42 +0300 Subject: [PATCH 03/22] feat: StethOperation Service handle transaction --- ethereum-steth-v2/README.md | 55 +++++++---- ethereum-steth-v2/src/agent.ts | 27 +++++- ethereum-steth-v2/src/app.ts | 8 ++ .../steth_operation/StethOperation.srv.ts | 59 +++++++++++- ethereum-steth-v2/src/utils/constants.ts | 23 +++++ .../src/utils/events/burner_events.ts | 34 +++++++ .../utils/events/deposit_security_events.ts | 81 ++++++++++++++++ .../src/utils/events/insurance_fund_events.ts | 75 +++++++++++++++ .../src/utils/events/lido_events.ts | 96 +++++++++++++++++++ ethereum-steth-v2/src/utils/tier.ts | 5 + 10 files changed, 444 insertions(+), 19 deletions(-) create mode 100644 ethereum-steth-v2/src/utils/events/burner_events.ts create mode 100644 ethereum-steth-v2/src/utils/events/deposit_security_events.ts create mode 100644 ethereum-steth-v2/src/utils/events/insurance_fund_events.ts create mode 100644 ethereum-steth-v2/src/utils/events/lido_events.ts diff --git a/ethereum-steth-v2/README.md b/ethereum-steth-v2/README.md index 78ee615e..f2142d75 100644 --- a/ethereum-steth-v2/README.md +++ b/ethereum-steth-v2/README.md @@ -1,27 +1,48 @@ -# Lido Detection Mantle Bot - -How does it work. - -The bot works on two networks: ETH mainnet and Mantle. -Here's how it operates: The bot monitors new blocks on the ETH mainnet. -Since Forta doesn't currently support Mantle, the bot reads blocks on the L2 network (Mantle) and stores the latest one -in an in-memory cache. -When the bot reads the next block on ETH, it also retrieves a segment of Mantle blocks (cachedBlock, LatestBlock) from -the cache. +# Lido ethereum Steth bot ## Supported chains -- Ethereum mainnet, Mantle network +- Ethereum mainnet ## Alerts 1. StETH operations - 1. 🚨🚨🚨 Buffered ETH drain - 2. 🚨 Huge depositable ETH amount - 3. ⚠️ High depositable ETH amount - 4. ⚠️ Low deposit executor balance - 5. ⚠️ Staking limit below 10% - 6. πŸ“‰ Staking limit below 30% + 1. HandleBlock + 1. 🚨🚨🚨 Buffered ETH drain + 2. 🚨 Huge depositable ETH amount + 3. ⚠️ High depositable ETH amount + 4. ⚠️ Low deposit executor balance + 5. ⚠️ Staking limit below 10% + 6. πŸ“‰ Staking limit below 30% + 2. HandleTransaction + 1. Deposit Security events + 1. 🚨 Deposit Security: Deposits paused + 2. βœ… Deposit Security: Deposits resumed + 3. ⚠️ Deposit Security: Guardian added + 4. ⚠️ Deposit Security: Guardian removed + 5. 🚨 Deposit Security: Guardian quorum changed + 6. ⚠️ Deposit Security: Max deposits changed + 7. ⚠️ Deposit Security: Min deposit block distance changed + 8. 🚨 Deposit Security: Owner changed + 2. Lido events + 1. 🚨🚨🚨 Lido: Stopped 🚨🚨🚨 + 2. βœ… Lido: Resumed + 3. 🚨 Lido: Staking paused + 4. βœ… Lido: Staking resumed + 5. ⚠️ Lido: Staking limit set + 6. 🚨 Lido: Staking limit removed + 7. 🚨 Lido: Locator set + 8. ℹ️ Lido: Funds recovered to vault + 9. ℹ️ Lido: Contract version set + 3. Insurance fund events + 1. ⚠️ Insurance fund: ETH transferred + 2. ⚠️ Insurance fund: ERC721 transferred + 3. 🚨 Insurance fund: ERC20 transferred + 4. ⚠️ Insurance fund: ERC1155 transferred + 5. 🚨 Insurance fund: Ownership transferred + 4. Burner events + 1. ℹ️ Lido Burner: ERC20 recovered + 2. ℹ️ Lido Burner: ERC721 recovered ## Development (Forta specific) diff --git a/ethereum-steth-v2/src/agent.ts b/ethereum-steth-v2/src/agent.ts index 30eb7eb7..090b7c0a 100644 --- a/ethereum-steth-v2/src/agent.ts +++ b/ethereum-steth-v2/src/agent.ts @@ -1,4 +1,4 @@ -import { BlockEvent, Finding, HandleBlock } from 'forta-agent' +import { BlockEvent, Finding, HandleBlock, HandleTransaction } from 'forta-agent' import * as process from 'process' import { argv } from 'process' import { InitializeResponse } from 'forta-agent/dist/sdk/initialize.response' @@ -6,6 +6,7 @@ import { Initialize } from 'forta-agent/dist/sdk/handlers' import * as E from 'fp-ts/Either' import { App } from './app' import { elapsedTime } from './utils/time' +import { TransactionEvent } from 'forta-agent/dist/sdk/transaction.event' export function initialize(): Initialize { /* const metadata: Metadata = { @@ -63,7 +64,31 @@ export const handleBlock = (): HandleBlock => { } } +let isHandleTrasactionRunning: boolean = false +export const handleTransaction = (): HandleTransaction => { + return async function (txEvent: TransactionEvent): Promise { + const startTime = new Date().getTime() + if (isHandleTrasactionRunning) { + return [] + } + isHandleTrasactionRunning = true + const app = await App.getInstance() + const out: Finding[] = [] + + const startStethOpTrx = new Date().getTime() + const stethOperationFidings = app.StethOperationSrv.handleTransaction(txEvent) + console.log(elapsedTime('app.StethOperationSrv.handleTransaction', startStethOpTrx)) + + out.push(...stethOperationFidings) + + console.log(elapsedTime('handleTransaction', startTime) + '\n') + isHandleTrasactionRunning = false + return out + } +} + export default { initialize: initialize(), handleBlock: handleBlock(), + handleTransaction: handleTransaction(), } diff --git a/ethereum-steth-v2/src/app.ts b/ethereum-steth-v2/src/app.ts index b6ee6b5e..514c109b 100644 --- a/ethereum-steth-v2/src/app.ts +++ b/ethereum-steth-v2/src/app.ts @@ -10,6 +10,10 @@ import { StethOperationCache } from './services/steth_operation/StethOperation.c import { ETHProvider, IETHProvider } from './clients/eth_provider' import { FormatterWithEIP1898 } from './clients/eth_formatter' import { Lido__factory, WithdrawalQueueERC721__factory } from './generated' +import { DEPOSIT_SECURITY_EVENTS } from './utils/events/deposit_security_events' +import { LIDO_EVENTS } from './utils/events/lido_events' +import { INSURANCE_FUND_EVENTS } from './utils/events/insurance_fund_events' +import { BURNER_EVENTS } from './utils/events/burner_events' export type Container = { ethClient: IETHProvider @@ -47,6 +51,10 @@ export class App { DEPOSIT_EXECUTOR_ADDRESS, lidoContact, wdQueueContact, + DEPOSIT_SECURITY_EVENTS, + LIDO_EVENTS, + INSURANCE_FUND_EVENTS, + BURNER_EVENTS, ) App.instance = { diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts index 2184b507..f574f339 100644 --- a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts @@ -3,11 +3,13 @@ import { StethOperationCache } from './StethOperation.cache' import { ETH_DECIMALS } from '../../utils/constants' import * as E from 'fp-ts/Either' import { IETHProvider } from '../../clients/eth_provider' -import { BlockEvent, Finding, FindingSeverity, FindingType } from 'forta-agent' +import { BlockEvent, filterLog, Finding, FindingSeverity, FindingType } from 'forta-agent' import { Lido as LidoContract, WithdrawalQueueERC721 as WithdrawalQueue } from '../../generated' import { retryAsync } from 'ts-retry' import { BigNumber as EtherBigNumber } from '@ethersproject/bignumber/lib/bignumber' import { Event as EthersEvent } from 'ethers' +import { TransactionEvent } from 'forta-agent/dist/sdk/transaction.event' +import { EventOfNotice } from '../../entity/events' // Formula: (60 * 60 * 72) / 13 = 19_938 const HISTORY_BLOCK_OFFSET: number = Math.floor((60 * 60 * 72) / 13) @@ -38,6 +40,11 @@ export class StethOperationSrv { private readonly lidoContract: LidoContract private readonly wdQueueContract: WithdrawalQueue + private readonly depositSecurityEvents: EventOfNotice[] + private readonly lidoEvents: EventOfNotice[] + private readonly insuranceFundEvents: EventOfNotice[] + private readonly burnerEvents: EventOfNotice[] + constructor( cache: StethOperationCache, ethProvider: IETHProvider, @@ -46,6 +53,10 @@ export class StethOperationSrv { lidoDepositExecutorAddress: string, lidoContract: LidoContract, wdQueueContract: WithdrawalQueue, + depositSecurityEvents: EventOfNotice[], + lidoEvents: EventOfNotice[], + insuranceFundEvents: EventOfNotice[], + burnerEvents: EventOfNotice[], ) { this.cache = cache this.ethProvider = ethProvider @@ -54,6 +65,11 @@ export class StethOperationSrv { this.lidoDepositExecutorAddress = lidoDepositExecutorAddress this.lidoContract = lidoContract this.wdQueueContract = wdQueueContract + + this.depositSecurityEvents = depositSecurityEvents + this.lidoEvents = lidoEvents + this.insuranceFundEvents = insuranceFundEvents + this.burnerEvents = burnerEvents } public async initialize(currentBlock: number): Promise { @@ -110,6 +126,47 @@ export class StethOperationSrv { return findings } + public handleTransaction(txEvent: TransactionEvent): Finding[] { + const out: Finding[] = [] + + if (txEvent.to == this.depositSecurityAddress) { + this.cache.setLastDepositorTxTime(txEvent.timestamp) + } + + const depositSecFindings = this.handleEventsOfNotice(txEvent, this.depositSecurityEvents) + const lidoFindings = this.handleEventsOfNotice(txEvent, this.lidoEvents) + const insuranceFundFindings = this.handleEventsOfNotice(txEvent, this.insuranceFundEvents) + const burnerFindings = this.handleEventsOfNotice(txEvent, this.burnerEvents) + + out.push(...depositSecFindings, ...lidoFindings, ...insuranceFundFindings, ...burnerFindings) + + return out + } + + public handleEventsOfNotice(txEvent: TransactionEvent, eventsOfNotice: EventOfNotice[]) { + const out: Finding[] = [] + for (const eventInfo of eventsOfNotice) { + if (eventInfo.address in txEvent.addresses) { + const filteredEvents = filterLog(txEvent.logs, eventInfo.event, eventInfo.address) + + for (const filteredEvent of filteredEvents) { + out.push( + Finding.fromObject({ + name: eventInfo.name, + description: eventInfo.description(filteredEvent.args), + alertId: eventInfo.alertId, + severity: eventInfo.severity, + type: eventInfo.type, + metadata: { args: String(filteredEvent.args) }, + }), + ) + } + } + } + + return out + } + public async handleBufferedEth(blockEvent: BlockEvent): Promise { const blockNumber = blockEvent.block.number const blockTimestamp = blockEvent.block.timestamp diff --git a/ethereum-steth-v2/src/utils/constants.ts b/ethereum-steth-v2/src/utils/constants.ts index ba435e63..48fe31f6 100644 --- a/ethereum-steth-v2/src/utils/constants.ts +++ b/ethereum-steth-v2/src/utils/constants.ts @@ -1,13 +1,36 @@ import BigNumber from 'bignumber.js' +export type ERC20 = { + decimals: number + name: string +} + export const RUN_TIER = process.env.FORTA_AGENT_RUN_TIER export const ETH_DECIMALS = new BigNumber(10).pow(18) export const DEPOSIT_SECURITY_ADDRESS = '0xc77f8768774e1c9244beed705c4354f2113cfc09' +export const BURNER_ADDRESS = '0xd15a672319cf0352560ee76d9e89eab0889046d3' +export const LDO_ADDRESS = '0x5a98fcbea516cf06857215779fd812ca3bef1b32' export const LIDO_STETH_ADDRESS = '0xae7ab96520de3a18e5e111b5eaab095312d7fe84' +export const WSTETH_ADDRESS = '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0' export const WITHDRAWAL_QUEUE_ADDRESS = '0x889edc2edab5f40e902b864ad4d7ade8e412f9b1' export const DEPOSIT_EXECUTOR_ADDRESS = '0xf82ac5937a20dc862f9bc0668779031e06000f17' + +export const INSURANCE_FUND_ADDRESS = '0x8b3f33234abd88493c0cd28de33d583b70bede35' + +export const DAI_ADDRESS = '0x6b175474e89094c44da98b954eedeac495271d0f' +export const USDT_ADDRESS = '0xdac17f958d2ee523a2206206994597c13d831ec7' +export const USDC_ADDRESS = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' + +export const KNOWN_ERC20 = new Map([ + [LIDO_STETH_ADDRESS, { decimals: 18, name: 'stETH' }], + [WSTETH_ADDRESS, { decimals: 18, name: 'wstETH' }], + [LDO_ADDRESS, { decimals: 18, name: 'LDO' }], + [DAI_ADDRESS, { decimals: 18, name: 'DAI' }], + [USDT_ADDRESS, { decimals: 6, name: 'USDT' }], + [USDC_ADDRESS, { decimals: 6, name: 'USDC' }], +]) diff --git a/ethereum-steth-v2/src/utils/events/burner_events.ts b/ethereum-steth-v2/src/utils/events/burner_events.ts new file mode 100644 index 00000000..5b99dcc7 --- /dev/null +++ b/ethereum-steth-v2/src/utils/events/burner_events.ts @@ -0,0 +1,34 @@ +import { EventOfNotice } from '../../entity/events' +import { etherscanAddress } from '../tier' +import { FindingSeverity, FindingType } from 'forta-agent' +import { BURNER_ADDRESS } from '../constants' +import { Result } from '@ethersproject/abi/lib' + +export const BURNER_EVENTS: EventOfNotice[] = [ + { + address: BURNER_ADDRESS, + event: 'event ERC20Recovered(address indexed requestedBy, address indexed token,uint256 amount)', + alertId: 'LIDO-BURNER-ERC20-RECOVERED', + name: 'ℹ️ Lido Burner: ERC20 recovered', + description: (args: Result) => + `ERC20 recovered:\n` + + `Requested by: ${etherscanAddress(args.requestedBy)}\n` + + `Token: ${etherscanAddress(args.token)}\n` + + `Amount: ${args.amount}`, + severity: FindingSeverity.Info, + type: FindingType.Info, + }, + { + address: BURNER_ADDRESS, + event: 'event ERC721Recovered(address indexed requestedBy, address indexed token, uint256 tokenId)', + alertId: 'LIDO-BURNE-ERC721-RECOVERED', + name: 'ℹ️ Lido Burner: ERC721 recovered', + description: (args: Result) => + `ERC721 recovered:\n` + + `Requested by: ${etherscanAddress(args.requestedBy)}\n` + + `Token: ${etherscanAddress(args.token)}\n` + + `Token ID: ${args.tokenId}`, + severity: FindingSeverity.Info, + type: FindingType.Info, + }, +] diff --git a/ethereum-steth-v2/src/utils/events/deposit_security_events.ts b/ethereum-steth-v2/src/utils/events/deposit_security_events.ts new file mode 100644 index 00000000..69e868ce --- /dev/null +++ b/ethereum-steth-v2/src/utils/events/deposit_security_events.ts @@ -0,0 +1,81 @@ +import { EventOfNotice } from '../../entity/events' +import { DEPOSIT_SECURITY_ADDRESS } from '../constants' +import { FindingSeverity, FindingType } from 'forta-agent' +import { etherscanAddress } from '../tier' +import { Result } from '@ethersproject/abi/lib' + +export const DEPOSIT_SECURITY_EVENTS: EventOfNotice[] = [ + { + address: DEPOSIT_SECURITY_ADDRESS, + event: 'event DepositsPaused(address indexed guardian, uint24 indexed stakingModuleId)', + alertId: 'LIDO-DEPOSITS-PAUSED', + name: '🚨 Deposit Security: Deposits paused', + description: (args: Result) => + `Deposits were paused by ${etherscanAddress(args.guardian)} for ${args.stakingModuleId} staking module`, + severity: FindingSeverity.Critical, + type: FindingType.Info, + }, + { + address: DEPOSIT_SECURITY_ADDRESS, + event: 'event DepositsUnpaused(uint24 indexed stakingModuleId)', + alertId: 'LIDO-DEPOSITS-UNPAUSED', + name: 'βœ… Deposit Security: Deposits resumed', + description: (args: Result) => `Deposits were resumed for ${args.stakingModuleId} staking module`, + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: DEPOSIT_SECURITY_ADDRESS, + event: 'event GuardianAdded(address guardian)', + alertId: 'LIDO-DEPOSITOR-GUARDIAN-ADDED', + name: '⚠️ Deposit Security: Guardian added', + description: (args: Result) => `New guardian added ${etherscanAddress(args.guardian)}`, + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: DEPOSIT_SECURITY_ADDRESS, + event: 'event GuardianRemoved(address guardian)', + alertId: 'LIDO-DEPOSITOR-GUARDIAN-REMOVED', + name: '⚠️ Deposit Security: Guardian removed', + description: (args: Result) => `Guardian ${etherscanAddress(args.guardian)} was removed`, + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: DEPOSIT_SECURITY_ADDRESS, + event: 'event GuardianQuorumChanged(uint256 newValue)', + alertId: 'LIDO-DEPOSITOR-GUARDIAN-QUORUM-CHANGED', + name: '🚨 Deposit Security: Guardian quorum changed', + description: (args: Result) => `New quorum size ${args.newValue}`, + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: DEPOSIT_SECURITY_ADDRESS, + event: 'event MaxDepositsChanged(uint256 newValue)', + alertId: 'LIDO-DEPOSITOR-MAX-DEPOSITS-CHANGED', + name: '⚠️ Deposit Security: Max deposits changed', + description: (args: Result) => `New value ${args.newValue}`, + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: DEPOSIT_SECURITY_ADDRESS, + event: 'event MinDepositBlockDistanceChanged(uint256 newValue)', + alertId: 'LIDO-DEPOSITOR-MIN-DEPOSITS-BLOCK-DISTANCE-CHANGED', + name: '⚠️ Deposit Security: Min deposit block distance changed', + description: (args: Result) => `New value ${args.newValue}`, + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: DEPOSIT_SECURITY_ADDRESS, + event: 'event OwnerChanged(address newValue)', + alertId: 'LIDO-DEPOSITOR-OWNER-CHANGED', + name: '🚨 Deposit Security: Owner changed', + description: (args: Result) => `New owner ${etherscanAddress(args.newValue)}`, + severity: FindingSeverity.Critical, + type: FindingType.Info, + }, +] diff --git a/ethereum-steth-v2/src/utils/events/insurance_fund_events.ts b/ethereum-steth-v2/src/utils/events/insurance_fund_events.ts new file mode 100644 index 00000000..c1a171a1 --- /dev/null +++ b/ethereum-steth-v2/src/utils/events/insurance_fund_events.ts @@ -0,0 +1,75 @@ +import { EventOfNotice } from '../../entity/events' +import { ETH_DECIMALS, INSURANCE_FUND_ADDRESS, KNOWN_ERC20 } from '../constants' +import BigNumber from 'bignumber.js' +import { etherscanAddress } from '../tier' +import { FindingSeverity, FindingType } from 'forta-agent' +import { Result } from '@ethersproject/abi/lib' + +export const INSURANCE_FUND_EVENTS: EventOfNotice[] = [ + { + address: INSURANCE_FUND_ADDRESS, + event: 'event EtherTransferred(address indexed _recipient, uint256 _amount)', + alertId: 'INS-FUND-ETH-TRANSFERRED', + name: '⚠️ Insurance fund: ETH transferred', + description: (args: Result) => + `${new BigNumber(String(args._amount)) + .div(ETH_DECIMALS) + .toFixed(2)} ETH were transferred from insurance fund to ${etherscanAddress(args._recipient)}`, + severity: FindingSeverity.Info, + type: FindingType.Info, + }, + { + address: INSURANCE_FUND_ADDRESS, + event: 'event ERC721Transferred(address indexed _token, address indexed _recipient, uint256 _tokenId, bytes _data)', + alertId: 'INS-FUND-ERC721-TRANSFERRED', + name: '⚠️ Insurance fund: ERC721 transferred', + description: (args: Result) => + `ERC721 token (address: ${etherscanAddress(args._token)}, id: ${ + args._tokenId + }) was transferred form insurance fund to ${etherscanAddress(args._recipient)}`, + severity: FindingSeverity.Info, + type: FindingType.Info, + }, + { + address: INSURANCE_FUND_ADDRESS, + event: 'event ERC20Transferred(address indexed _token, address indexed _recipient, uint256 _amount)', + alertId: 'INS-FUND-ERC20-TRANSFERRED', + name: '🚨 Insurance fund: ERC20 transferred', + description: (args: Result) => { + const tokenInfo = KNOWN_ERC20.get(args._token.toLowerCase()) || { + decimals: 18, + name: 'unknown', + } + return `${new BigNumber(String(args._amount)).div(10 ** tokenInfo.decimals).toFixed(2)} of ${etherscanAddress( + args._token, + )}(${tokenInfo.name}) were transferred from insurance fund to ${etherscanAddress(args._recipient)}` + }, + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: INSURANCE_FUND_ADDRESS, + event: + 'event ERC1155Transferred(address indexed _token, address indexed _recipient, uint256 _tokenId, uint256 _amount, bytes _data)', + alertId: 'INS-FUND-ERC1155-TRANSFERRED', + name: '⚠️ Insurance fund: ERC1155 transferred', + description: (args: Result) => + `${args._amount} of ERC1155 token (address: ${etherscanAddress(args._token)}, id: ${ + args._tokenId + }) was transferred form insurance fund to ${etherscanAddress(args._recipient)}`, + severity: FindingSeverity.Info, + type: FindingType.Info, + }, + { + address: INSURANCE_FUND_ADDRESS, + event: 'event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)', + alertId: 'INS-FUND-OWNERSHIP-TRANSFERRED', + name: '🚨 Insurance fund: Ownership transferred', + description: (args: Result) => + `Owner of the insurance fund was transferred from ${etherscanAddress(args.previousOwner)} to ${etherscanAddress( + args.newOwner, + )}`, + severity: FindingSeverity.Critical, + type: FindingType.Info, + }, +] diff --git a/ethereum-steth-v2/src/utils/events/lido_events.ts b/ethereum-steth-v2/src/utils/events/lido_events.ts new file mode 100644 index 00000000..ae77a1c0 --- /dev/null +++ b/ethereum-steth-v2/src/utils/events/lido_events.ts @@ -0,0 +1,96 @@ +import { LIDO_STETH_ADDRESS } from '../constants' +import { FindingSeverity, FindingType } from 'forta-agent' +import { etherscanAddress } from '../tier' +import { EventOfNotice } from '../../entity/events' +import { Result } from '@ethersproject/abi/lib' + +export const LIDO_EVENTS: EventOfNotice[] = [ + { + address: LIDO_STETH_ADDRESS, + event: 'event Stopped()', + alertId: 'LIDO-STOPPED', + name: '🚨🚨🚨 Lido: Stopped 🚨🚨🚨', + description: () => `Lido DAO contract was stopped`, + severity: FindingSeverity.Critical, + type: FindingType.Info, + }, + { + address: LIDO_STETH_ADDRESS, + event: 'event Resumed()', + alertId: 'LIDO-RESUMED', + name: 'βœ… Lido: Resumed', + description: () => `Lido DAO contract was resumed`, + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: LIDO_STETH_ADDRESS, + event: 'event StakingPaused()', + alertId: 'LIDO-STAKING-PAUSED', + name: '🚨 Lido: Staking paused', + description: () => `Staking was paused!`, + severity: FindingSeverity.Critical, + type: FindingType.Info, + }, + { + address: LIDO_STETH_ADDRESS, + event: 'event StakingResumed()', + alertId: 'LIDO-STAKING-RESUMED', + name: 'βœ… Lido: Staking resumed', + description: () => `Staking was resumed!`, + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: LIDO_STETH_ADDRESS, + event: 'event StakingLimitSet(uint256 maxStakeLimit, uint256 stakeLimitIncreasePerBlock)', + alertId: 'LIDO-STAKING-LIMIT-SET', + name: '⚠️ Lido: Staking limit set', + description: (args: Result) => + `Staking limit was set with:\n` + + `Max staking limit: ${args.maxStakeLimit}\n` + + `Stake limit increase per block: ${args.stakeLimitIncreasePerBlock}`, + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: LIDO_STETH_ADDRESS, + event: 'event StakingLimitRemoved()', + alertId: 'LIDO-STAKING-LIMIT-REMOVED', + name: '🚨 Lido: Staking limit removed', + description: () => `Staking limit was removed`, + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: LIDO_STETH_ADDRESS, + event: 'event LidoLocatorSet(address lidoLocator)', + alertId: 'LIDO-LOCATOR-SET', + name: '🚨 Lido: Locator set', + description: (args: Result) => `Lido locator was set to: ${etherscanAddress(args.lidoLocator)}`, + severity: FindingSeverity.Critical, + type: FindingType.Info, + }, + { + address: LIDO_STETH_ADDRESS, + event: 'event RecoverToVault(address vault, address token, uint256 amount)', + alertId: 'LIDO-RECOVER-TO-VAULT', + name: 'ℹ️ Lido: Funds recovered to vault', + description: (args: Result) => + `Funds recovered to vault:\n` + + `Vault: ${etherscanAddress(args.vault)}\n` + + `Token: ${etherscanAddress(args.token)}\n` + + `Amount: ${args.amount}`, + severity: FindingSeverity.Info, + type: FindingType.Info, + }, + { + address: LIDO_STETH_ADDRESS, + event: 'event ContractVersionSet(uint256 version)', + alertId: 'LIDO-CONTRACT-VERSION-SET', + name: 'ℹ️ Lido: Contract version set', + description: (args: Result) => `Contract version set:\n` + `Version: ${args.version}`, + severity: FindingSeverity.Info, + type: FindingType.Info, + }, +] diff --git a/ethereum-steth-v2/src/utils/tier.ts b/ethereum-steth-v2/src/utils/tier.ts index 6e0f64f8..5173f05c 100644 --- a/ethereum-steth-v2/src/utils/tier.ts +++ b/ethereum-steth-v2/src/utils/tier.ts @@ -54,3 +54,8 @@ export function requireWithTier(module: NodeModule, path: string, mode: Redef } throw new Error(`Unknown require mode: ${mode}`) } + +export function etherscanAddress(address: string): string { + const subpath = process.env.FORTA_AGENT_RUN_TIER == 'testnet' ? 'goerli.' : '' + return `[${address}](https://${subpath}etherscan.io/address/${address})` +} From 74fdd929476a670954ac1d05ead732b6e56b3512 Mon Sep 17 00:00:00 2001 From: Sergey White Date: Wed, 17 Jan 2024 15:01:30 +0300 Subject: [PATCH 04/22] feat: eth-steth bot v2 --- ethereum-steth-v2/README.md | 46 +- ethereum-steth-v2/src/abi/GateSeal.json | 1 + .../src/abi/GateSealFactory.json | 1 + .../src/abi/ValidatorsExitBusOracle.json | 1179 +++++++++++++++++ ethereum-steth-v2/src/agent.ts | 107 +- ethereum-steth-v2/src/app.ts | 100 +- .../src/clients/eth_formatter.ts | 9 +- ethereum-steth-v2/src/clients/eth_provider.ts | 380 +++++- ethereum-steth-v2/src/entity/gate_seal.ts | 8 + .../src/entity/stakingLimitInfo.ts | 7 + .../src/services/gate-seal/GateSeal.cache.ts | 22 + .../src/services/gate-seal/GateSeal.srv.ts | 308 +++++ .../steth_operation/StethOperation.srv.ts | 265 ++-- .../src/services/vault/Vault.srv.ts | 380 ++++++ .../services/withdrawals/Withdrawals.cache.ts | 158 +++ .../services/withdrawals/Withdrawals.srv.ts | 779 +++++++++++ .../src/utils/constants.testnet.ts | 47 + ethereum-steth-v2/src/utils/constants.ts | 74 +- ethereum-steth-v2/src/utils/error.ts | 14 - .../src/utils/events/burner_events.ts | 61 +- .../utils/events/deposit_security_events.ts | 155 +-- .../src/utils/events/gate_seal_events.ts | 4 + .../src/utils/events/insurance_fund_events.ts | 144 +- .../src/utils/events/lido_events.ts | 185 +-- .../src/utils/events/vault_events.ts | 7 + .../src/utils/events/withdrawals_events.ts | 43 + ethereum-steth-v2/src/utils/mutex.ts | 17 +- ethereum-steth-v2/src/utils/string.ts | 16 + ethereum-steth-v2/src/utils/tier.ts | 61 - ethereum-steth-v2/src/utils/time.ts | 17 + 30 files changed, 3998 insertions(+), 597 deletions(-) create mode 100644 ethereum-steth-v2/src/abi/GateSeal.json create mode 100644 ethereum-steth-v2/src/abi/GateSealFactory.json create mode 100644 ethereum-steth-v2/src/abi/ValidatorsExitBusOracle.json create mode 100644 ethereum-steth-v2/src/entity/gate_seal.ts create mode 100644 ethereum-steth-v2/src/entity/stakingLimitInfo.ts create mode 100644 ethereum-steth-v2/src/services/gate-seal/GateSeal.cache.ts create mode 100644 ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts create mode 100644 ethereum-steth-v2/src/services/vault/Vault.srv.ts create mode 100644 ethereum-steth-v2/src/services/withdrawals/Withdrawals.cache.ts create mode 100644 ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts create mode 100644 ethereum-steth-v2/src/utils/constants.testnet.ts delete mode 100644 ethereum-steth-v2/src/utils/error.ts create mode 100644 ethereum-steth-v2/src/utils/events/gate_seal_events.ts create mode 100644 ethereum-steth-v2/src/utils/events/vault_events.ts create mode 100644 ethereum-steth-v2/src/utils/events/withdrawals_events.ts create mode 100644 ethereum-steth-v2/src/utils/string.ts delete mode 100644 ethereum-steth-v2/src/utils/tier.ts diff --git a/ethereum-steth-v2/README.md b/ethereum-steth-v2/README.md index f2142d75..f3bd8358 100644 --- a/ethereum-steth-v2/README.md +++ b/ethereum-steth-v2/README.md @@ -8,12 +8,12 @@ 1. StETH operations 1. HandleBlock - 1. 🚨🚨🚨 Buffered ETH drain - 2. 🚨 Huge depositable ETH amount - 3. ⚠️ High depositable ETH amount - 4. ⚠️ Low deposit executor balance - 5. ⚠️ Staking limit below 10% - 6. πŸ“‰ Staking limit below 30% + 1. 🚨🚨🚨 Buffered ETH drain (checks each block) + 2. 🚨 Huge depositable ETH amount (checks every 100 blocks) + 3. ⚠️ High depositable ETH amount (checks every 100 blocks) + 4. ⚠️ Low deposit executor balance (checks every 100 blocks) + 5. ⚠️ Staking limit below 10% (checks every 25 blocks) + 6. πŸ“‰ Staking limit below 30% (checks every 25 blocks) 2. HandleTransaction 1. Deposit Security events 1. 🚨 Deposit Security: Deposits paused @@ -43,6 +43,40 @@ 4. Burner events 1. ℹ️ Lido Burner: ERC20 recovered 2. ℹ️ Lido Burner: ERC721 recovered +2. Withdrawals. + 1. HandleBlock runs on each 100-th block or one per 20 minutes + 1. ⚠️ Withdrawals: % of stake limit is drained and unfinalized queue is on par with drained stake + limit + 2. ⚠️ Withdrawals: unfinalized queue is more than 100_000 stETH + 3. ⚠️ Withdrawals: unfinalized queue wait time is too long + 4. πŸ€” Withdrawals: ${unclaimedSizeRate.times(100).toFixed(2)}% of finalized requests are unclaimed + 5. πŸ€” Withdrawals: unclaimed requests size is more than withdrawal queue balance + 2. HandleTransaction + 1. 🚨 Withdrawals: BUNKER MODE ON! 🚨 + 2. βœ… Withdrawals: BUNKER MODE OFF! βœ… + 3. ℹ️ Huge stETH withdrawal requests batch + 4. ⚠️ Withdrawals: the sum of received withdrawal requests since the last rebase greater than 150_000 stETH + 5. πŸ€” Withdrawals: claimed amount is more than requested + 6. βœ… Withdrawals: contract was unpaused + 7. 🚨 Withdrawals: contract was paused +3. GateSeal + 1. HandleBlock runs on each next block + 1. ⚠️ GateSeal: default GateSeal address in forta agent is expired + 2. ⚠️️ GateSeal: default GateSeal address in forta agent doesn't have PAUSE_ROLE for contracts + 3. 🚨GateSeal: actual address doesn't have PAUSE_ROLE for contracts + 4. 🚨🚨🚨 GateSeal: is expired! 🚨🚨🚨 + 5. ⚠️ GateSeal: is about to be expired + 2. HandleTransaction + 1. 🚨🚨🚨 GateSeal: is sealed 🚨🚨🚨 + 2. 🚨 GateSeal: new one created +4. Vaults + 1. Handleblock + 1. πŸ’΅ Withdrawal Vault Balance significant change (checks every on 100-th block) + 2. πŸ’΅ EL Vault Balance significant change + 3. 🚨 Withdrawal Vault balance mismatch + 4. 🚨 EL Vault balance mismatch + 2. HandleTransaction + 1. 🚨 Burner shares transfer ## Development (Forta specific) diff --git a/ethereum-steth-v2/src/abi/GateSeal.json b/ethereum-steth-v2/src/abi/GateSeal.json new file mode 100644 index 00000000..1358d4b6 --- /dev/null +++ b/ethereum-steth-v2/src/abi/GateSeal.json @@ -0,0 +1 @@ +[{"name":"Sealed","inputs":[{"name":"gate_seal","type":"address","indexed":false},{"name":"sealed_by","type":"address","indexed":false},{"name":"sealed_for","type":"uint256","indexed":false},{"name":"sealable","type":"address","indexed":false},{"name":"sealed_at","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"stateMutability":"nonpayable","type":"constructor","inputs":[{"name":"_sealing_committee","type":"address"},{"name":"_seal_duration_seconds","type":"uint256"},{"name":"_sealables","type":"address[]"},{"name":"_expiry_timestamp","type":"uint256"}],"outputs":[]},{"stateMutability":"view","type":"function","name":"get_sealing_committee","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"get_seal_duration_seconds","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"get_sealables","inputs":[],"outputs":[{"name":"","type":"address[]"}]},{"stateMutability":"view","type":"function","name":"get_expiry_timestamp","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"is_expired","inputs":[],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"nonpayable","type":"function","name":"seal","inputs":[{"name":"_sealables","type":"address[]"}],"outputs":[]}] diff --git a/ethereum-steth-v2/src/abi/GateSealFactory.json b/ethereum-steth-v2/src/abi/GateSealFactory.json new file mode 100644 index 00000000..1ea99898 --- /dev/null +++ b/ethereum-steth-v2/src/abi/GateSealFactory.json @@ -0,0 +1 @@ +[{"name":"GateSealCreated","inputs":[{"name":"gate_seal","type":"address","indexed":false}],"anonymous":false,"type":"event"},{"stateMutability":"nonpayable","type":"constructor","inputs":[{"name":"_blueprint","type":"address"}],"outputs":[]},{"stateMutability":"view","type":"function","name":"get_blueprint","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"nonpayable","type":"function","name":"create_gate_seal","inputs":[{"name":"_sealing_committee","type":"address"},{"name":"_seal_duration_seconds","type":"uint256"},{"name":"_sealables","type":"address[]"},{"name":"_expiry_timestamp","type":"uint256"}],"outputs":[]}] diff --git a/ethereum-steth-v2/src/abi/ValidatorsExitBusOracle.json b/ethereum-steth-v2/src/abi/ValidatorsExitBusOracle.json new file mode 100644 index 00000000..b127f236 --- /dev/null +++ b/ethereum-steth-v2/src/abi/ValidatorsExitBusOracle.json @@ -0,0 +1,1179 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "secondsPerSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "genesisTime", + "type": "uint256" + }, + { + "internalType": "address", + "name": "lidoLocator", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AddressCannotBeSame", + "type": "error" + }, + { + "inputs": [], + "name": "AddressCannotBeZero", + "type": "error" + }, + { + "inputs": [], + "name": "AdminCannotBeZero", + "type": "error" + }, + { + "inputs": [], + "name": "ArgumentOutOfBounds", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "initialRefSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "processingRefSlot", + "type": "uint256" + } + ], + "name": "InitialRefSlotCannotBeLessThanProcessingOne", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidContractVersionIncrement", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidRequestsData", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidRequestsDataLength", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidRequestsDataSortOrder", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "moduleId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nodeOpId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "prevRequestedValidatorIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "requestedValidatorIndex", + "type": "uint256" + } + ], + "name": "NodeOpValidatorIndexMustIncrease", + "type": "error" + }, + { + "inputs": [], + "name": "NonZeroContractVersionOnInit", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyConsensusContractCanSubmitReport", + "type": "error" + }, + { + "inputs": [], + "name": "PauseUntilMustBeInFuture", + "type": "error" + }, + { + "inputs": [], + "name": "PausedExpected", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "ProcessingDeadlineMissed", + "type": "error" + }, + { + "inputs": [], + "name": "RefSlotAlreadyProcessing", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "prevRefSlot", + "type": "uint256" + } + ], + "name": "RefSlotCannotDecrease", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "processingRefSlot", + "type": "uint256" + } + ], + "name": "RefSlotMustBeGreaterThanProcessingOne", + "type": "error" + }, + { + "inputs": [], + "name": "ResumedExpected", + "type": "error" + }, + { + "inputs": [], + "name": "SenderNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "UnexpectedChainConfig", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "expectedVersion", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "receivedVersion", + "type": "uint256" + } + ], + "name": "UnexpectedConsensusVersion", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "expected", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "received", + "type": "uint256" + } + ], + "name": "UnexpectedContractVersion", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "consensusHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "receivedHash", + "type": "bytes32" + } + ], + "name": "UnexpectedDataHash", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "consensusRefSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dataRefSlot", + "type": "uint256" + } + ], + "name": "UnexpectedRefSlot", + "type": "error" + }, + { + "inputs": [], + "name": "UnexpectedRequestsDataLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "format", + "type": "uint256" + } + ], + "name": "UnsupportedRequestsDataFormat", + "type": "error" + }, + { + "inputs": [], + "name": "VersionCannotBeSame", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroPauseDuration", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "prevAddr", + "type": "address" + } + ], + "name": "ConsensusHashContractSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "version", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "prevVersion", + "type": "uint256" + } + ], + "name": "ConsensusVersionSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "version", + "type": "uint256" + } + ], + "name": "ContractVersionSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "hash", + "type": "bytes32" + } + ], + "name": "ProcessingStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "hash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "processingDeadlineTime", + "type": "uint256" + } + ], + "name": "ReportSubmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "Resumed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "stakingModuleId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "nodeOperatorId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "validatorIndex", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "ValidatorExitRequest", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "requestsProcessed", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "requestsCount", + "type": "uint256" + } + ], + "name": "WarnDataIncompleteProcessing", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + } + ], + "name": "WarnProcessingMissed", + "type": "event" + }, + { + "inputs": [], + "name": "DATA_FORMAT_LIST", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "GENESIS_TIME", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MANAGE_CONSENSUS_CONTRACT_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MANAGE_CONSENSUS_VERSION_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PAUSE_INFINITELY", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PAUSE_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RESUME_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SECONDS_PER_SLOT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SUBMIT_DATA_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getConsensusContract", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getConsensusReport", + "outputs": [ + { + "internalType": "bytes32", + "name": "hash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "processingDeadlineTime", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "processingStarted", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getConsensusVersion", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getContractVersion", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLastProcessingRefSlot", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "moduleId", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "nodeOpIds", + "type": "uint256[]" + } + ], + "name": "getLastRequestedValidatorIndices", + "outputs": [ + { + "internalType": "int256[]", + "name": "", + "type": "int256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getProcessingState", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "currentFrameRefSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "processingDeadlineTime", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "dataHash", + "type": "bytes32" + }, + { + "internalType": "bool", + "name": "dataSubmitted", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "dataFormat", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "requestsCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "requestsSubmitted", + "type": "uint256" + } + ], + "internalType": "struct ValidatorsExitBusOracle.ProcessingState", + "name": "result", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getResumeSinceTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getRoleMember", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleMemberCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTotalRequestsProcessed", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "admin", + "type": "address" + }, + { + "internalType": "address", + "name": "consensusContract", + "type": "address" + }, + { + "internalType": "uint256", + "name": "consensusVersion", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastProcessingRefSlot", + "type": "uint256" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_duration", + "type": "uint256" + } + ], + "name": "pauseFor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_pauseUntilInclusive", + "type": "uint256" + } + ], + "name": "pauseUntil", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "resume", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "setConsensusContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "version", + "type": "uint256" + } + ], + "name": "setConsensusVersion", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "reportHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "submitConsensusReport", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "consensusVersion", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "requestsCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dataFormat", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct ValidatorsExitBusOracle.ReportData", + "name": "data", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "contractVersion", + "type": "uint256" + } + ], + "name": "submitReportData", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/ethereum-steth-v2/src/agent.ts b/ethereum-steth-v2/src/agent.ts index 090b7c0a..c6fc7a20 100644 --- a/ethereum-steth-v2/src/agent.ts +++ b/ethereum-steth-v2/src/agent.ts @@ -1,4 +1,4 @@ -import { BlockEvent, Finding, HandleBlock, HandleTransaction } from 'forta-agent' +import { BlockEvent, Finding, FindingSeverity, FindingType, HandleBlock, HandleTransaction } from 'forta-agent' import * as process from 'process' import { argv } from 'process' import { InitializeResponse } from 'forta-agent/dist/sdk/initialize.response' @@ -7,12 +7,15 @@ import * as E from 'fp-ts/Either' import { App } from './app' import { elapsedTime } from './utils/time' import { TransactionEvent } from 'forta-agent/dist/sdk/transaction.event' +import { Metadata } from './entity/metadata' +import Version from './utils/version' +import { ETH_DECIMALS } from './utils/constants' export function initialize(): Initialize { - /* const metadata: Metadata = { - 'version.commitHash': VERSION.commitHash, - 'version.commitMsg': VERSION.commitMsg, - }*/ + const metadata: Metadata = { + 'version.commitHash': Version.commitHash, + 'version.commitMsg': Version.commitMsg, + } return async function (): Promise { const startTime = new Date().getTime() @@ -26,17 +29,65 @@ export function initialize(): Initialize { process.exit(1) } - const startStethOperationSrv = new Date().getTime() - const err = await app.StethOperationSrv.initialize(latestBlockNumber.right) - if (err !== null) { - console.log(`Error: ${err.message}`) - console.log(`Stack: ${err.stack}`) + const [stethOperationSrvErr, withdrawalsSrvErr, gateSealSrvErr] = await Promise.all([ + app.StethOperationSrv.initialize(latestBlockNumber.right), + app.WithdrawalsSrv.initialize(latestBlockNumber.right), + app.GateSealSrv.initialize(latestBlockNumber.right), + app.VaultSrv.initialize(latestBlockNumber.right), + ]) + if (stethOperationSrvErr !== null) { + console.log(`Error: ${stethOperationSrvErr.message}`) + console.log(`Stack: ${stethOperationSrvErr.stack}`) + + process.exit(1) + } + + if (withdrawalsSrvErr !== null) { + console.log(`Error: ${withdrawalsSrvErr.message}`) + console.log(`Stack: ${withdrawalsSrvErr.stack}`) + + process.exit(1) + } + + if (gateSealSrvErr instanceof Error) { + console.log(`Error: ${gateSealSrvErr.message}`) + console.log(`Stack: ${gateSealSrvErr.stack}`) process.exit(1) + } else { + await app.findingsRW.write(gateSealSrvErr) } - console.log(elapsedTime('App.StethOperationSrv.initialize', startStethOperationSrv)) + const agents: string[] = [ + app.StethOperationSrv.getName(), + app.WithdrawalsSrv.getName(), + app.GateSealSrv.getName(), + app.VaultSrv.getName(), + ] + metadata.agents = '[' + agents.toString() + ']' + + await app.findingsRW.write([ + Finding.fromObject({ + name: 'Agent launched', + description: `Version: ${Version.desc}`, + alertId: 'LIDO-AGENT-LAUNCHED', + severity: FindingSeverity.Info, + type: FindingType.Info, + metadata, + }), + ]) + console.log(elapsedTime('Agent.initialize', startTime) + '\n') + + console.log( + `[${app.StethOperationSrv.getName()}] Last Depositor TxTime: ${app.StethOperationSrv.getStorage().getLastDepositorTxTime()}`, + ) + console.log( + `[${app.StethOperationSrv.getName()}] Buffered Eth: ${app.StethOperationSrv.getStorage() + .getLastBufferedEth() + .div(ETH_DECIMALS) + .toFixed(2)} on ${latestBlockNumber.right} block`, + ) } } @@ -52,11 +103,18 @@ export const handleBlock = (): HandleBlock => { const app = await App.getInstance() const out: Finding[] = [] - const startStethOpHandleBlock = new Date().getTime() - const bufferedEthFindings = await app.StethOperationSrv.handleBlock(blockEvent) - console.log(elapsedTime('app.StethOperationSrv.handleBlock', startStethOpHandleBlock)) + const findingsAsync = await app.findingsRW.read() + if (findingsAsync.length > 0) { + out.push(...findingsAsync) + } - out.push(...bufferedEthFindings) + const [bufferedEthFindings, withdrawalsFindings, gateSealFindings, vaultFindings] = await Promise.all([ + app.StethOperationSrv.handleBlock(blockEvent), + app.WithdrawalsSrv.handleBlock(blockEvent), + app.GateSealSrv.handleBlock(blockEvent), + app.VaultSrv.handleBlock(blockEvent), + ]) + out.push(...bufferedEthFindings, ...withdrawalsFindings, ...gateSealFindings, ...vaultFindings) console.log(elapsedTime('handleBlock', startTime) + '\n') isHandleBLockRunning = false @@ -64,25 +122,26 @@ export const handleBlock = (): HandleBlock => { } } -let isHandleTrasactionRunning: boolean = false +let isHandleTransactionRunning: boolean = false export const handleTransaction = (): HandleTransaction => { return async function (txEvent: TransactionEvent): Promise { const startTime = new Date().getTime() - if (isHandleTrasactionRunning) { + if (isHandleTransactionRunning) { return [] } - isHandleTrasactionRunning = true + isHandleTransactionRunning = true const app = await App.getInstance() const out: Finding[] = [] - const startStethOpTrx = new Date().getTime() - const stethOperationFidings = app.StethOperationSrv.handleTransaction(txEvent) - console.log(elapsedTime('app.StethOperationSrv.handleTransaction', startStethOpTrx)) + const stethOperationFindings = app.StethOperationSrv.handleTransaction(txEvent) + const withdrawalsFindings = app.WithdrawalsSrv.handleTransaction(txEvent) + const gateSealFindings = app.GateSealSrv.handleTransaction(txEvent) + const vaultFindings = app.VaultSrv.handleTransaction(txEvent) - out.push(...stethOperationFidings) + out.push(...stethOperationFindings, ...withdrawalsFindings, ...gateSealFindings, ...vaultFindings) console.log(elapsedTime('handleTransaction', startTime) + '\n') - isHandleTrasactionRunning = false + isHandleTransactionRunning = false return out } } @@ -90,5 +149,5 @@ export const handleTransaction = (): HandleTransaction => { export default { initialize: initialize(), handleBlock: handleBlock(), - handleTransaction: handleTransaction(), + // handleTransaction: handleTransaction(), } diff --git a/ethereum-steth-v2/src/app.ts b/ethereum-steth-v2/src/app.ts index 514c109b..0aa92cee 100644 --- a/ethereum-steth-v2/src/app.ts +++ b/ethereum-steth-v2/src/app.ts @@ -1,23 +1,35 @@ import { StethOperationSrv } from './services/steth_operation/StethOperation.srv' -import { ethers, getEthersProvider } from 'forta-agent' -import { - DEPOSIT_EXECUTOR_ADDRESS, - DEPOSIT_SECURITY_ADDRESS, - LIDO_STETH_ADDRESS, - WITHDRAWAL_QUEUE_ADDRESS, -} from './utils/constants' +import { ethers, Finding, getEthersProvider } from 'forta-agent' +import { Address } from './utils/constants' import { StethOperationCache } from './services/steth_operation/StethOperation.cache' import { ETHProvider, IETHProvider } from './clients/eth_provider' import { FormatterWithEIP1898 } from './clients/eth_formatter' -import { Lido__factory, WithdrawalQueueERC721__factory } from './generated' -import { DEPOSIT_SECURITY_EVENTS } from './utils/events/deposit_security_events' -import { LIDO_EVENTS } from './utils/events/lido_events' -import { INSURANCE_FUND_EVENTS } from './utils/events/insurance_fund_events' -import { BURNER_EVENTS } from './utils/events/burner_events' +import { + GateSeal__factory, + Lido__factory, + ValidatorsExitBusOracle__factory, + WithdrawalQueueERC721__factory, +} from './generated' +import { getDepositSecurityEvents } from './utils/events/deposit_security_events' +import { getLidoEvents } from './utils/events/lido_events' +import { getInsuranceFundEvents } from './utils/events/insurance_fund_events' +import { getBurnerEvents } from './utils/events/burner_events' +import { WithdrawalsSrv } from './services/withdrawals/Withdrawals.srv' +import { WithdrawalsCache } from './services/withdrawals/Withdrawals.cache' +import { getWithdrawalsEvents } from './utils/events/withdrawals_events' +import { GateSealSrv } from './services/gate-seal/GateSeal.srv' +import { DataRW } from './utils/mutex' +import { GateSealCache } from './services/gate-seal/GateSeal.cache' +import { VaultSrv } from './services/vault/vault.srv' +import { TestNetAddress } from './utils/constants.testnet' export type Container = { ethClient: IETHProvider StethOperationSrv: StethOperationSrv + WithdrawalsSrv: WithdrawalsSrv + GateSealSrv: GateSealSrv + VaultSrv: VaultSrv + findingsRW: DataRW } export class App { @@ -34,32 +46,76 @@ export class App { etherscanKey, ) + let address: Address = Address + if (process.env.FORTA_AGENT_RUN_TIER === 'testnet') { + address = TestNetAddress + } + const ethersProvider = getEthersProvider() ethersProvider.formatter = new FormatterWithEIP1898() - const ethClient = new ETHProvider(ethersProvider, etherscanProvider) + const lidoContact = Lido__factory.connect(address.LIDO_STETH_ADDRESS, ethersProvider) + const wdQueueContact = WithdrawalQueueERC721__factory.connect(address.WITHDRAWALS_QUEUE_ADDRESS, ethersProvider) - const lidoContact = Lido__factory.connect(LIDO_STETH_ADDRESS, ethersProvider) - const wdQueueContact = WithdrawalQueueERC721__factory.connect(WITHDRAWAL_QUEUE_ADDRESS, ethersProvider) + const gateSealContact = GateSeal__factory.connect(address.GATE_SEAL_DEFAULT_ADDRESS, ethersProvider) + const exitBusOracleContract = ValidatorsExitBusOracle__factory.connect( + address.EXITBUS_ORACLE_ADDRESS, + ethersProvider, + ) + const ethClient = new ETHProvider( + ethersProvider, + etherscanProvider, + lidoContact, + wdQueueContact, + gateSealContact, + exitBusOracleContract, + ) const stethOperationCache = new StethOperationCache() const stethOperationSrv = new StethOperationSrv( stethOperationCache, ethClient, - DEPOSIT_SECURITY_ADDRESS, - LIDO_STETH_ADDRESS, - DEPOSIT_EXECUTOR_ADDRESS, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, lidoContact, wdQueueContact, - DEPOSIT_SECURITY_EVENTS, - LIDO_EVENTS, - INSURANCE_FUND_EVENTS, - BURNER_EVENTS, + getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS), + getLidoEvents(address.LIDO_STETH_ADDRESS), + getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20), + getBurnerEvents(address.BURNER_ADDRESS), + ) + + const withdrawalsSrv = new WithdrawalsSrv( + ethClient, + wdQueueContact, + lidoContact, + new WithdrawalsCache(), + getWithdrawalsEvents(address.WITHDRAWALS_QUEUE_ADDRESS), + ) + + const gateSealSrv = new GateSealSrv( + ethClient, + new GateSealCache(), + address.GATE_SEAL_DEFAULT_ADDRESS, + address.GATE_SEAL_FACTORY_ADDRESS, + ) + + const vaultSrv = new VaultSrv( + ethClient, + lidoContact, + address.WITHDRAWALS_VAULT_ADDRESS, + address.EL_REWARDS_VAULT_ADDRESS, + address.BURNER_ADDRESS, ) App.instance = { ethClient: ethClient, StethOperationSrv: stethOperationSrv, + WithdrawalsSrv: withdrawalsSrv, + GateSealSrv: gateSealSrv, + VaultSrv: vaultSrv, + findingsRW: new DataRW([]), } } diff --git a/ethereum-steth-v2/src/clients/eth_formatter.ts b/ethereum-steth-v2/src/clients/eth_formatter.ts index 16be5518..caf629e5 100644 --- a/ethereum-steth-v2/src/clients/eth_formatter.ts +++ b/ethereum-steth-v2/src/clients/eth_formatter.ts @@ -5,8 +5,13 @@ export class FormatterWithEIP1898 extends Formatter { * blockTag formatter with EIP-1898 support * https://eips.ethereum.org/EIPS/eip-1898 */ - blockTag(blockTag: any): any { - if (typeof blockTag === 'object' && blockTag != null && (blockTag.blockNumber || blockTag.blockHash)) { + blockTag(blockTag: any): string { + if ( + typeof blockTag === 'object' && + blockTag != null && + (Object.prototype.hasOwnProperty.call(blockTag, 'blockNumber') || + Object.prototype.hasOwnProperty.call(blockTag, 'blockHash')) + ) { return blockTag } diff --git a/ethereum-steth-v2/src/clients/eth_provider.ts b/ethereum-steth-v2/src/clients/eth_provider.ts index 8dddd102..45163a33 100644 --- a/ethereum-steth-v2/src/clients/eth_provider.ts +++ b/ethereum-steth-v2/src/clients/eth_provider.ts @@ -4,6 +4,19 @@ import * as E from 'fp-ts/Either' import { retryAsync } from 'ts-retry' import { BlockTag } from '@ethersproject/providers' import { BigNumber as EtherBigNumber } from '@ethersproject/bignumber/lib/bignumber' +import BigNumber from 'bignumber.js' +import { ETH_DECIMALS } from '../utils/constants' +import { StakingLimitInfo } from '../entity/stakingLimitInfo' +import { + GateSeal as GateSealContract, + Lido as LidoContract, + ValidatorsExitBusOracle as ExitBusContract, + WithdrawalQueueERC721 as WithdrawalQueueContract, +} from '../generated' +import { WithdrawalQueueBase } from '../generated/WithdrawalQueueERC721' +import { GateSeal, GateSealExpiredErr } from '../entity/gate_seal' +import { ETHDistributedEvent } from '../generated/Lido' +import { DataRW } from '../utils/mutex' export abstract class IETHProvider { public abstract getTransaction(txHash: string): Promise> @@ -16,9 +29,31 @@ export abstract class IETHProvider { endBlock: number, ): Promise> - public abstract getStethBalance(lidoStethAddress: string, block: number): Promise> + public abstract getStethBalance(lidoStethAddress: string, block: number): Promise> - public abstract getDepositorBalance(depositorAddress: string, block: number): Promise> + public abstract getBalance(address: string, block: number): Promise> + + public abstract getBalanceByBlockHash(address: string, blockHash: string): Promise> + + public abstract getStakingLimitInfo(blockNumber: number): Promise> + + public abstract getUnfinalizedStETH(blockNumber: number): Promise> + + public abstract getWithdrawalStatuses( + requestsRange: number[], + currentBlock: number, + ): Promise> + + public abstract getBufferedEther(blockNumber: number): Promise> + + public abstract checkGateSeal(blockNumber: number, gateSealAddress: string): Promise> + + public abstract getExpiryTimestamp(blockNumber: number): Promise> + + public abstract getETHDistributedEvent( + fromBlockNumber: number, + toBlockNumber: number, + ): Promise> } interface IEtherscanProvider { @@ -35,9 +70,25 @@ export class ETHProvider implements IETHProvider { private jsonRpcProvider: ethers.providers.JsonRpcProvider private etherscanProvider: IEtherscanProvider - constructor(jsonRpcProvider: ethers.providers.JsonRpcProvider, etherscanProvider: IEtherscanProvider) { + private readonly lidoContract: LidoContract + private readonly wdQueueContract: WithdrawalQueueContract + private readonly exitBusContract: ExitBusContract + private gateSeal: GateSealContract + + constructor( + jsonRpcProvider: ethers.providers.JsonRpcProvider, + etherscanProvider: IEtherscanProvider, + lidoContract: LidoContract, + wdQueueContract: WithdrawalQueueContract, + gateSeal: GateSealContract, + exitBusContract: ExitBusContract, + ) { this.jsonRpcProvider = jsonRpcProvider this.etherscanProvider = etherscanProvider + this.lidoContract = lidoContract + this.wdQueueContract = wdQueueContract + this.gateSeal = gateSeal + this.exitBusContract = exitBusContract } public async getStartedBlockForApp(argv: string[]): Promise> { @@ -99,32 +150,46 @@ export class ETHProvider implements IETHProvider { startBlock: number, endBlock: number, ): Promise> { - const out: TransactionResponse[] = [] - const batchSize = 50 - - for (let i = startBlock; i <= endBlock; i += batchSize) { - const start = i - const end = Math.min(i + batchSize - 1, endBlock) - - let chunkTrxResp: TransactionResponse[] = [] + const fetchBatch = async (start: number, end: number): Promise => { try { - chunkTrxResp = await retryAsync( + return await retryAsync( async (): Promise => { return await this.etherscanProvider.getHistory(depositSecurityAddress, start, end) }, { delay: 500, maxTry: 5 }, ) } catch (e) { - return E.left(new Error(`Could not fetch transaction history for last 3 days. Cause: ${e}`)) + throw new Error(`Could not fetch transaction history between ${start} and ${end} blocks. Cause: ${e}`) } + } + + const batchPromises: Promise[] = [] + const out = new DataRW([]) + // const out: TransactionResponse[] = [] + const batchSize = 10_000 + + for (let i = startBlock; i <= endBlock; i += batchSize) { + const start = i + const end = Math.min(i + batchSize - 1, endBlock) - out.push(...chunkTrxResp) + const promise = fetchBatch(start, end).then((chunkTrxResp) => { + out.write(chunkTrxResp) + }) + + batchPromises.push(promise) } + console.log('Info: count history requests: ', batchPromises.length) - return E.right(out) + try { + await Promise.all(batchPromises) + + return E.right(await out.read()) + } catch (error) { + return E.left(new Error(`Could not fetch transaction history for last 3 days. Cause ${error}`)) + } } - public async getStethBalance(lidoStethAddress: string, block: number): Promise> { + public async getStethBalance(lidoStethAddress: string, block: number): Promise> { try { const out = await retryAsync( async (): Promise => { @@ -133,24 +198,299 @@ export class ETHProvider implements IETHProvider { { delay: 500, maxTry: 5 }, ) - return E.right(out) + return E.right(new BigNumber(String(out))) } catch (e) { return E.left(new Error(`Could not fetch Steth balance. cause: ${e}`)) } } - public async getDepositorBalance(depositorAddress: string, block: number): Promise> { + public async getBalance(address: string, block: number): Promise> { + try { + const out = await retryAsync( + async (): Promise => { + return await this.jsonRpcProvider.getBalance(address, block) + }, + { delay: 500, maxTry: 5 }, + ) + + return E.right(new BigNumber(String(out))) + } catch (e) { + return E.left(new Error(`Could not fetch balance by address ${address}. cause: ${e}`)) + } + } + + public async getBalanceByBlockHash(address: string, blockHash: string): Promise> { try { const out = await retryAsync( async (): Promise => { - return await this.jsonRpcProvider.getBalance(depositorAddress, block) + return await this.jsonRpcProvider.getBalance(address, { + blockHash: blockHash, + } as never) + }, + { delay: 500, maxTry: 5 }, + ) + + return E.right(new BigNumber(String(out))) + } catch (e) { + return E.left(new Error(`Could not fetch balance by address ${address} and blockHash ${blockHash}. cause: ${e}`)) + } + } + + public async getStakingLimitInfo(blockNumber: number): Promise> { + try { + const out = await retryAsync( + async (): Promise => { + const resp = await this.lidoContract.functions.getStakeLimitFullInfo({ + blockTag: blockNumber, + }) + + return { + currentStakeLimit: new BigNumber(String(resp.currentStakeLimit)).div(ETH_DECIMALS), + maxStakeLimit: new BigNumber(String(resp.maxStakeLimit)).div(ETH_DECIMALS), + isStakingPaused: resp.isStakingPaused, + } + }, + { delay: 500, maxTry: 5 }, + ) + + return E.right(out) + } catch (e) { + return E.left(new Error(`Could not call "lidoContract.getStakeLimitFullInfo. Cause ${e}`)) + } + } + + public async getUnfinalizedStETH(blockNumber: number): Promise> { + try { + const out = await retryAsync( + async (): Promise => { + const out = await this.wdQueueContract.unfinalizedStETH({ + blockTag: blockNumber, + }) + + return new BigNumber(String(out)) }, { delay: 500, maxTry: 5 }, ) return E.right(out) } catch (e) { - return E.left(new Error(`Could not fetch depositor balance. cause: ${e}`)) + return E.left(new Error(`Could not call "wdQueueContract.unfinalizedStETH. Cause ${e}`)) + } + } + + public async getWithdrawalStatuses( + requestsRange: number[], + currentBlock: number, + ): Promise> { + const fetchStatusesChunk = async ( + request: number[], + ): Promise => { + try { + return await retryAsync( + async (): Promise => { + const resp = await this.wdQueueContract.functions.getWithdrawalStatus(request, { + blockTag: currentBlock, + }) + + return resp.statuses + }, + { delay: 500, maxTry: 5 }, + ) + } catch (e) { + throw new Error( + `Could not call "getWithdrawalStatus between ${request[0]} and ${request[request.length - 1]}. Total: ${ + request.length + }. Cause ${e} `, + ) + } + } + + const chunkPromises: Promise[] = [] + const MAX_REQUESTS_CHUNK_SIZE = 1750 + const out = new DataRW([]) + + for (let i = 0; i < requestsRange.length; i += MAX_REQUESTS_CHUNK_SIZE) { + const requestChunk = requestsRange.slice(i, i + MAX_REQUESTS_CHUNK_SIZE) + + const promise = fetchStatusesChunk(requestChunk).then((statuses) => { + out.write(statuses) + }) + + chunkPromises.push(promise) + } + console.log('Info: count withdrawals requests: ', chunkPromises.length) + + try { + await Promise.all(chunkPromises) + return E.right(await out.read()) + } catch (error) { + return E.left(error as Error) + } + } + + public async getBufferedEther(blockNumber: number): Promise> { + try { + const resp = await retryAsync( + async (): Promise => { + return await this.lidoContract.getBufferedEther({ + blockTag: blockNumber, + }) + }, + { delay: 500, maxTry: 5 }, + ) + + return E.right(new BigNumber(resp.toString())) + } catch (e) { + return E.left(new Error(`Could not call "lidoContract.getBufferedEther". Cause: ${e}`)) + } + } + + public async checkGateSeal(blockNumber: number, gateSealAddress: string): Promise> { + const isExpired = await this.isGateSealExpired(blockNumber, gateSealAddress) + if (E.isLeft(isExpired)) { + return E.left(isExpired.left) + } + + if (isExpired.right) { + return E.left(GateSealExpiredErr) + } + + const [isGateSealHasPauseRole, isGateSealHasExitBusPauseRoleMember] = await Promise.all([ + this.isGateSealHasPauseRole(blockNumber, gateSealAddress), + this.isGateSealHasExitBusPauseRoleMember(blockNumber, gateSealAddress), + ]) + + if (E.isLeft(isGateSealHasPauseRole)) { + return E.left(isGateSealHasPauseRole.left) + } + + if (E.isLeft(isGateSealHasExitBusPauseRoleMember)) { + return E.left(isGateSealHasExitBusPauseRoleMember.left) + } + + const out: GateSeal = { + roleForWithdrawalQueue: isGateSealHasPauseRole.right, + roleForExitBus: isGateSealHasExitBusPauseRoleMember.right, + exitbusOracleAddress: this.exitBusContract.address, + withdrawalQueueAddress: this.wdQueueContract.address, + } + + return E.right(out) + } + + public async getExpiryTimestamp(blockNumber: number): Promise> { + try { + const expiryTimestamp = await retryAsync( + async (): Promise => { + const [resp] = await this.gateSeal.functions.get_expiry_timestamp({ + blockTag: blockNumber, + }) + + return new BigNumber(String(resp)) + }, + { delay: 500, maxTry: 5 }, + ) + + return E.right(expiryTimestamp) + } catch (e) { + return E.left(new Error(`Could not call "gateSeal.functions.get_expiry_timestamp". Cause: ${e}`)) + } + } + + public async getETHDistributedEvent( + fromBlockNumber: number, + toBlockNumber: number, + ): Promise> { + try { + const report = await retryAsync( + async (): Promise => { + const [resp] = await this.lidoContract.queryFilter( + this.lidoContract.filters.ETHDistributed(), + fromBlockNumber, + toBlockNumber, + ) + + if (resp === undefined) { + return null + } + + return resp + }, + { delay: 500, maxTry: 5 }, + ) + + return E.right(report) + } catch (e) { + return E.left(new Error(`Could not call "this.lidoContract.filters.ETHDistributed". Cause: ${e}`)) + } + } + + private async isGateSealExpired(blockNumber: number, gateSealAddress: string): Promise> { + this.gateSeal = this.gateSeal.attach(gateSealAddress) + + try { + const isExpired = await retryAsync( + async (): Promise => { + const [resp] = await this.gateSeal.functions.is_expired({ + blockTag: blockNumber, + }) + + return resp + }, + { delay: 500, maxTry: 5 }, + ) + + return E.right(isExpired) + } catch (e) { + return E.left(new Error(`Could not call "gateSeal.functions.is_expired". Cause: ${e}`)) + } + } + + private async isGateSealHasPauseRole( + blockNumber: number, + gateSealAddress: string, + ): Promise> { + const keccakPauseRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('PAUSE_ROLE')) + + try { + const queuePauseRoleMember = await retryAsync( + async (): Promise => { + const [resp] = await this.wdQueueContract.functions.hasRole(keccakPauseRole, gateSealAddress, { + blockTag: blockNumber, + }) + + return resp + }, + { delay: 500, maxTry: 5 }, + ) + + return E.right(queuePauseRoleMember) + } catch (e) { + return E.left(new Error(`Could not call "wdQueueContract.functions.hasRole". Cause: ${e}`)) + } + } + + private async isGateSealHasExitBusPauseRoleMember( + blockNumber: number, + gateSealAddress: string, + ): Promise> { + const keccakPauseRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('PAUSE_ROLE')) + + try { + const exitBusPauseRoleMember = await retryAsync( + async (): Promise => { + const [resp] = await this.exitBusContract.functions.hasRole(keccakPauseRole, gateSealAddress, { + blockTag: blockNumber, + }) + + return resp + }, + { delay: 500, maxTry: 5 }, + ) + + return E.right(exitBusPauseRoleMember) + } catch (e) { + return E.left(new Error(`Could not call "this.exitBusContract.functions.hasRole". Cause: ${e}`)) } } } diff --git a/ethereum-steth-v2/src/entity/gate_seal.ts b/ethereum-steth-v2/src/entity/gate_seal.ts new file mode 100644 index 00000000..98270f87 --- /dev/null +++ b/ethereum-steth-v2/src/entity/gate_seal.ts @@ -0,0 +1,8 @@ +export type GateSeal = { + roleForWithdrawalQueue: boolean + roleForExitBus: boolean + exitbusOracleAddress: string + withdrawalQueueAddress: string +} + +export const GateSealExpiredErr = new Error('GateSeal is expired') diff --git a/ethereum-steth-v2/src/entity/stakingLimitInfo.ts b/ethereum-steth-v2/src/entity/stakingLimitInfo.ts new file mode 100644 index 00000000..4cf64390 --- /dev/null +++ b/ethereum-steth-v2/src/entity/stakingLimitInfo.ts @@ -0,0 +1,7 @@ +import BigNumber from 'bignumber.js' + +export type StakingLimitInfo = { + maxStakeLimit: BigNumber + currentStakeLimit: BigNumber + isStakingPaused: boolean +} diff --git a/ethereum-steth-v2/src/services/gate-seal/GateSeal.cache.ts b/ethereum-steth-v2/src/services/gate-seal/GateSeal.cache.ts new file mode 100644 index 00000000..3377c5d5 --- /dev/null +++ b/ethereum-steth-v2/src/services/gate-seal/GateSeal.cache.ts @@ -0,0 +1,22 @@ +export class GateSealCache { + private _lastNoPauseRoleAlertTimestamp = 0 + private _lastExpiryGateSealAlertTimestamp = 0 + + constructor() {} + + public getLastNoPauseRoleAlertTimestamp(): number { + return this._lastNoPauseRoleAlertTimestamp + } + + public setLastNoPauseRoleAlertTimestamp(lastNoPauseRoleAlertTimestamp: number) { + this._lastNoPauseRoleAlertTimestamp = lastNoPauseRoleAlertTimestamp + } + + public getLastExpiryGateSealAlertTimestamp(): number { + return this._lastExpiryGateSealAlertTimestamp + } + + public setLastExpiryGateSealAlertTimestamp(lastExpiryGateSealAlertTimestamp: number) { + this._lastExpiryGateSealAlertTimestamp = lastExpiryGateSealAlertTimestamp + } +} diff --git a/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts b/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts new file mode 100644 index 00000000..5a0a6f53 --- /dev/null +++ b/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts @@ -0,0 +1,308 @@ +import { elapsedTime, formatDelay } from '../../utils/time' +import { BlockEvent, filterLog, Finding, FindingSeverity, FindingType } from 'forta-agent' +import { IETHProvider } from '../../clients/eth_provider' +import * as E from 'fp-ts/Either' +import { GateSealExpiredErr } from '../../entity/gate_seal' +import { GateSealCache } from './GateSeal.cache' +import { TransactionEvent } from 'forta-agent/dist/sdk/transaction.event' +import { GATE_SEAL_FACTORY_GATE_SEAL_CREATED_EVENT, GATE_SEAL_SEALED_EVENT } from '../../utils/events/gate_seal_events' +import { etherscanAddress } from '../../utils/string' + +const ONE_HOUR = 60 * 60 +const ONE_DAY = 24 * ONE_HOUR +const ONE_WEEK = 7 * ONE_DAY +const ONE_MONTH = ONE_WEEK * 4 +const GATE_SEAL_WITHOUT_PAUSE_ROLE_TRIGGER_EVERY = ONE_DAY +const GATE_SEAL_EXPIRY_TRIGGER_EVERY = ONE_WEEK + +const GATE_SEAL_EXPIRY_THRESHOLD = ONE_MONTH + +export class GateSealSrv { + private readonly name = 'GateSealSrv' + + private readonly ethProvider: IETHProvider + private readonly cache: GateSealCache + private readonly gateSealFactoryAddress: string + + private gateSealAddress: string | undefined + + constructor( + ethProvider: IETHProvider, + cache: GateSealCache, + gateSealAddress: string, + gateSealFactoryAddress: string, + ) { + this.ethProvider = ethProvider + this.cache = cache + this.gateSealAddress = gateSealAddress + this.gateSealFactoryAddress = gateSealFactoryAddress + } + + public async initialize(currentBlock: number): Promise { + const start = new Date().getTime() + const out: Finding[] = [] + if (this.gateSealAddress === undefined) { + return new Error(`Gate seal address is not provided`) + } + + const status = await this.ethProvider.checkGateSeal(currentBlock, this.gateSealAddress) + if (E.isLeft(status)) { + if (status.left === GateSealExpiredErr) { + const f = Finding.fromObject({ + name: '⚠️ GateSeal: default GateSeal address in forta agent is expired', + description: `GateSeal address: ${etherscanAddress(this.gateSealAddress)}]`, + alertId: 'GATE-SEAL-DEFAULT-EXPIRED', + severity: FindingSeverity.High, + type: FindingType.Info, + }) + + out.push(f) + console.log(elapsedTime(`[${this.name}.initialize]`, start) + `on block ${currentBlock}`) + + return out + } + + return status.left + } + + if (!status.right.roleForExitBus || !status.right.roleForWithdrawalQueue) { + let additionalDesc = '' + if (!status.right.roleForExitBus) { + additionalDesc += `\nNo PAUSE_ROLE for ExitBus address: ${etherscanAddress(status.right.exitbusOracleAddress)}` + } + if (!status.right.roleForWithdrawalQueue) { + additionalDesc += `\nNo PAUSE_ROLE for WithdrawalQueue address: ${etherscanAddress( + status.right.withdrawalQueueAddress, + )}` + } + + const f = Finding.fromObject({ + name: "⚠️ GateSeal: default GateSeal address in forta agent doesn't have PAUSE_ROLE for contracts", + description: `GateSeal address: ${etherscanAddress(this.gateSealAddress)}${additionalDesc}`, + alertId: 'GATE-SEAL-DEFAULT-WITHOUT-ROLE', + severity: FindingSeverity.High, + type: FindingType.Info, + }) + + out.push(f) + } + + console.log(elapsedTime(`[${this.name}.initialize]`, start) + ` on block ${currentBlock}`) + return out + } + + public getName(): string { + return this.name + } + + public async handleBlock(blockEvent: BlockEvent): Promise { + const start = new Date().getTime() + const findings: Finding[] = [] + + const [pauseRoleFindings, expiryGateSealFindings] = await Promise.all([ + this.handlePauseRole(blockEvent), + this.handleExpiryGateSeal(blockEvent), + ]) + + findings.push(...pauseRoleFindings, ...expiryGateSealFindings) + console.log(elapsedTime(GateSealSrv.name + '.' + this.handleBlock.name, start)) + + return findings + } + + public async handlePauseRole(blockEvent: BlockEvent): Promise { + const out: Finding[] = [] + if (this.gateSealAddress === undefined) { + return [] + } + + const currentBlockTimestamp = blockEvent.block.timestamp + const status = await this.ethProvider.checkGateSeal(blockEvent.blockNumber, this.gateSealAddress) + if (E.isLeft(status)) { + if (status.left === GateSealExpiredErr) { + const f = Finding.fromObject({ + name: '⚠️ GateSeal: default GateSeal address in forta agent is expired', + description: `GateSeal address: ${etherscanAddress(this.gateSealAddress)}]`, + alertId: 'GATE-SEAL-DEFAULT-EXPIRED', + severity: FindingSeverity.High, + type: FindingType.Info, + }) + + out.push(f) + + return out + } + + const f = Finding.fromObject({ + name: 'Could not check gateSeal.', + description: `Could not call "ethProvider.checkGateSeal. Cause ${status.left.message}`, + alertId: 'GATE-SEAL-DEFAULT-EXPIRED', + severity: FindingSeverity.Low, + type: FindingType.Info, + metadata: { + stack: `${status.left.stack}`, + }, + }) + + out.push(f) + + return out + } + + if (!status.right.roleForExitBus || !status.right.roleForWithdrawalQueue) { + let additionalDesc = '' + if (!status.right.roleForExitBus) { + additionalDesc += `\nNo PAUSE_ROLE for ExitBus address: ${etherscanAddress(status.right.exitbusOracleAddress)}` + } + if (!status.right.roleForWithdrawalQueue) { + additionalDesc += `\nNo PAUSE_ROLE for WithdrawalQueue address: ${etherscanAddress( + status.right.withdrawalQueueAddress, + )}` + } + if ( + currentBlockTimestamp - this.cache.getLastNoPauseRoleAlertTimestamp() > + GATE_SEAL_WITHOUT_PAUSE_ROLE_TRIGGER_EVERY + ) { + out.push( + Finding.fromObject({ + name: "🚨GateSeal: actual address doesn't have PAUSE_ROLE for contracts", + description: `GateSeal address: ${etherscanAddress(this.gateSealAddress)}${additionalDesc}`, + alertId: 'GATE-SEAL-WITHOUT-PAUSE-ROLE', + severity: FindingSeverity.Critical, + type: FindingType.Degraded, + }), + ) + + this.cache.setLastNoPauseRoleAlertTimestamp(currentBlockTimestamp) + } + } + + return out + } + + public async handleExpiryGateSeal(blockEvent: BlockEvent): Promise { + if (this.gateSealAddress === undefined) { + return [] + } + + const currentBlockTimestamp = blockEvent.block.timestamp + const expiryTimestamp = await this.ethProvider.getExpiryTimestamp(blockEvent.blockNumber) + if (E.isLeft(expiryTimestamp)) { + const f = Finding.fromObject({ + name: `Error in ${GateSealSrv.name}.${this.handleExpiryGateSeal.name}:172`, + description: `Could not call "ethProvider.getExpiryTimestamp. Cause ${expiryTimestamp.left.message}`, + alertId: 'GATE-SEAL-DEFAULT-EXPIRED', + severity: FindingSeverity.Low, + type: FindingType.Info, + metadata: { + stack: `${expiryTimestamp.left.stack}`, + }, + }) + + return [f] + } + const out: Finding[] = [] + if (expiryTimestamp.right.eq(0) || Number(expiryTimestamp.right) <= currentBlockTimestamp) { + out.push( + Finding.fromObject({ + name: '🚨🚨🚨 GateSeal: is expired! 🚨🚨🚨', + description: `GateSeal address: ${etherscanAddress(this.gateSealAddress)}}`, + alertId: 'GATE-SEAL-IS-EXPIRED', + severity: FindingSeverity.Critical, + type: FindingType.Degraded, + }), + ) + this.gateSealAddress = undefined + } else if ( + currentBlockTimestamp - this.cache.getLastExpiryGateSealAlertTimestamp() > + GATE_SEAL_EXPIRY_TRIGGER_EVERY + ) { + if (Number(expiryTimestamp) - currentBlockTimestamp <= GATE_SEAL_EXPIRY_THRESHOLD) { + out.push( + Finding.fromObject({ + name: '⚠️ GateSeal: is about to be expired', + description: `GateSeal address: ${etherscanAddress(this.gateSealAddress)}\nExpiry date ${new Date( + String(expiryTimestamp), + )}`, + alertId: 'GATE-SEAL-IS-ABOUT-TO-BE-EXPIRED', + severity: FindingSeverity.High, + type: FindingType.Degraded, + }), + ) + this.cache.setLastExpiryGateSealAlertTimestamp(currentBlockTimestamp) + } + } + + return out + } + + public handleTransaction(txEvent: TransactionEvent): Finding[] { + const findings: Finding[] = [] + + const sealedGateSealFindings = this.handleSealedGateSeal(txEvent) + const newGateSealFindings = this.handleNewGateSeal(txEvent) + + findings.push(...sealedGateSealFindings, ...newGateSealFindings) + + return findings + } + + public handleSealedGateSeal(txEvent: TransactionEvent): Finding[] { + if (this.gateSealAddress === undefined) { + return [] + } + const sealedEvents = filterLog(txEvent.logs, GATE_SEAL_SEALED_EVENT, this.gateSealAddress) + if (sealedEvents.length === 0) { + return [] + } + + const out: Finding[] = [] + for (const sealedEvent of sealedEvents) { + const { sealed_by, sealed_for, sealable } = sealedEvent.args + const duration = formatDelay(Number(sealed_for)) + out.push( + Finding.fromObject({ + name: '🚨🚨🚨 GateSeal: is sealed 🚨🚨🚨', + description: `GateSeal address: ${etherscanAddress(this.gateSealAddress)}\nSealed by: ${etherscanAddress( + sealed_by, + )}\nSealed for: ${duration}\nSealable: ${etherscanAddress(sealable)}`, + alertId: 'GATE-SEAL-IS-SEALED', + severity: FindingSeverity.Critical, + type: FindingType.Info, + }), + ) + } + + return out + } + + public handleNewGateSeal(txEvent: TransactionEvent): Finding[] { + const newGateSealEvents = filterLog( + txEvent.logs, + GATE_SEAL_FACTORY_GATE_SEAL_CREATED_EVENT, + this.gateSealFactoryAddress, + ) + if (newGateSealEvents.length === 0) { + return [] + } + + const out: Finding[] = [] + for (const newGateSealEvent of newGateSealEvents) { + const { gate_seal } = newGateSealEvent.args + out.push( + Finding.fromObject({ + name: '🚨 GateSeal: new one created', + description: `GateSeal address: ${etherscanAddress( + gate_seal, + )}\ndev: Please, update \`GATE_SEAL_DEFAULT_ADDRESS\` in code`, + alertId: 'GATE-SEAL-NEW-ONE-CREATED', + severity: FindingSeverity.High, + type: FindingType.Info, + }), + ) + this.gateSealAddress = gate_seal + } + + return out + } +} diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts index f574f339..363c2890 100644 --- a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts @@ -4,12 +4,13 @@ import { ETH_DECIMALS } from '../../utils/constants' import * as E from 'fp-ts/Either' import { IETHProvider } from '../../clients/eth_provider' import { BlockEvent, filterLog, Finding, FindingSeverity, FindingType } from 'forta-agent' -import { Lido as LidoContract, WithdrawalQueueERC721 as WithdrawalQueue } from '../../generated' +import { Lido as LidoContract, WithdrawalQueueERC721 as WithdrawalQueueContract } from '../../generated' import { retryAsync } from 'ts-retry' import { BigNumber as EtherBigNumber } from '@ethersproject/bignumber/lib/bignumber' import { Event as EthersEvent } from 'ethers' import { TransactionEvent } from 'forta-agent/dist/sdk/transaction.event' import { EventOfNotice } from '../../entity/events' +import { elapsedTime } from '../../utils/time' // Formula: (60 * 60 * 72) / 13 = 19_938 const HISTORY_BLOCK_OFFSET: number = Math.floor((60 * 60 * 72) / 13) @@ -25,20 +26,15 @@ const MIN_DEPOSIT_EXECUTOR_BALANCE = 2 // 2 ETH const REPORT_WINDOW_STAKING_LIMIT_10 = 60 * 60 * 12 // 12 hours const REPORT_WINDOW_STAKING_LIMIT_30 = 60 * 60 * 12 // 12 hours -type StakingLimitInfo = { - maxStakeLimit: BigNumber - currentStakeLimit: BigNumber -} - export class StethOperationSrv { - private readonly name = 'StethOperation' + private readonly name = 'StethOperationSrv' private readonly cache: StethOperationCache private readonly ethProvider: IETHProvider private readonly depositSecurityAddress: string private readonly lidoStethAddress: string private readonly lidoDepositExecutorAddress: string private readonly lidoContract: LidoContract - private readonly wdQueueContract: WithdrawalQueue + private readonly wdQueueContract: WithdrawalQueueContract private readonly depositSecurityEvents: EventOfNotice[] private readonly lidoEvents: EventOfNotice[] @@ -52,7 +48,7 @@ export class StethOperationSrv { lidoStethAddress: string, lidoDepositExecutorAddress: string, lidoContract: LidoContract, - wdQueueContract: WithdrawalQueue, + wdQueueContract: WithdrawalQueueContract, depositSecurityEvents: EventOfNotice[], lidoEvents: EventOfNotice[], insuranceFundEvents: EventOfNotice[], @@ -73,14 +69,14 @@ export class StethOperationSrv { } public async initialize(currentBlock: number): Promise { - console.log(`[${this.name}] started on block ${currentBlock}`) - + const start = new Date().getTime() const history = await this.ethProvider.getHistory( this.depositSecurityAddress, currentBlock - HISTORY_BLOCK_OFFSET, currentBlock - 1, ) if (E.isLeft(history)) { + console.log(elapsedTime(`[${this.name}.initialize]`, start)) return history.left } @@ -96,23 +92,22 @@ export class StethOperationSrv { const bufferedEthRaw = await this.ethProvider.getStethBalance(this.lidoStethAddress, currentBlock) if (E.isLeft(bufferedEthRaw)) { + console.log(elapsedTime(`[${this.name}.initialize]`, start)) return bufferedEthRaw.left } - this.cache.setLastBufferedEth(new BigNumber(String(bufferedEthRaw.right))) - - console.log(`[${this.name}] Last Depositor TxTime: ${this.cache.getLastDepositorTxTime()}`) - console.log( - `[${this.name}] Buffered Eth: ${this.cache - .getLastBufferedEth() - .div(ETH_DECIMALS) - .toFixed(2)} on ${currentBlock} block`, - ) + this.cache.setLastBufferedEth(bufferedEthRaw.right) + console.log(elapsedTime(`[${this.name}.initialize]`, start) + ` on block ${currentBlock}`) return null } + public getName(): string { + return this.name + } + public async handleBlock(blockEvent: BlockEvent) { + const start = new Date().getTime() const findings: Finding[] = [] const [bufferedEthFindings, depositorBalanceFindings, stakingLimitFindings] = await Promise.all([ @@ -122,6 +117,7 @@ export class StethOperationSrv { ]) findings.push(...bufferedEthFindings, ...depositorBalanceFindings, ...stakingLimitFindings) + console.log(elapsedTime(StethOperationSrv.name + '.' + this.handleBlock.name, start)) return findings } @@ -171,32 +167,21 @@ export class StethOperationSrv { const blockNumber = blockEvent.block.number const blockTimestamp = blockEvent.block.timestamp - let bufferedEthRaw: BigNumber - try { - const resp = await retryAsync( - async (): Promise => { - return await this.lidoContract.getBufferedEther({ - blockTag: blockNumber, - }) - }, - { delay: 500, maxTry: 5 }, - ) - - bufferedEthRaw = new BigNumber(resp.toString()) - } catch (e) { + const bufferedEthRaw = await this.ethProvider.getBufferedEther(blockNumber) + if (E.isLeft(bufferedEthRaw)) { const f: Finding = Finding.fromObject({ - name: `Error in ${StethOperationSrv.name}.${this.handleBufferedEth.name}:108`, - description: `Could not call "lidoContract.getBufferedEther. Cause ${e instanceof Error ? e.message : ''}`, + name: `Error in ${StethOperationSrv.name}.${this.handleBufferedEth.name}:178`, + description: `Could not call "lidoContract.getBufferedEther. Cause ${bufferedEthRaw.left.message}`, alertId: 'LIDO-AGENT-ERROR', severity: FindingSeverity.Low, type: FindingType.Degraded, - metadata: { stack: e instanceof Error ? `${e.stack}` : 'null' }, + metadata: { stack: `${bufferedEthRaw.left.stack}` }, }) return [f] } - const bufferedEth = bufferedEthRaw.div(ETH_DECIMALS).toNumber() + const bufferedEth = bufferedEthRaw.right.div(ETH_DECIMALS).toNumber() let depositableEther: number try { @@ -226,7 +211,7 @@ export class StethOperationSrv { // We use shifted block number to ensure that nodes return correct values const shiftedBlockNumber = blockNumber - 3 - const shifte3dBufferedEthRaw = await this.getBufferedEther(shiftedBlockNumber) + const shifte3dBufferedEthRaw = await this.ethProvider.getBufferedEther(shiftedBlockNumber) if (E.isLeft(shifte3dBufferedEthRaw)) { const f: Finding = Finding.fromObject({ name: `Error in ${StethOperationSrv.name}.${this.handleBufferedEth.name}:159`, @@ -241,7 +226,7 @@ export class StethOperationSrv { return [f] } - const shifte4dBufferedEthRaw = await this.getBufferedEther(shiftedBlockNumber - 1) + const shifte4dBufferedEthRaw = await this.ethProvider.getBufferedEther(shiftedBlockNumber - 1) if (E.isLeft(shifte4dBufferedEthRaw)) { const f: Finding = Finding.fromObject({ name: `Error in ${StethOperationSrv.name}.${this.handleBufferedEth.name}:174`, @@ -385,43 +370,45 @@ export class StethOperationSrv { public async handleDepositExecutorBalance(blockEvent: BlockEvent): Promise { const blockNumber = blockEvent.block.number const out: Finding[] = [] - if (blockNumber % BLOCK_CHECK_INTERVAL !== 0) { - return out - } - - const currentBlockTimestamp = blockEvent.block.timestamp - if (this.cache.getLastReportedExecutorBalanceTimestamp() + REPORT_WINDOW_EXECUTOR_BALANCE < currentBlockTimestamp) { - const executorBalanceRaw = await this.ethProvider.getDepositorBalance( - this.lidoDepositExecutorAddress, - blockEvent.blockNumber, - ) - if (E.isLeft(executorBalanceRaw)) { - out.push( - Finding.fromObject({ - name: `Error in ${StethOperationSrv.name}.${this.handleDepositExecutorBalance.name}:329`, - description: `Could not fetch depositorBalance. Cause ${executorBalanceRaw.left.message}`, - alertId: 'LIDO-AGENT-ERROR', - severity: FindingSeverity.Low, - type: FindingType.Degraded, - metadata: { stack: `${executorBalanceRaw.left.stack}` }, - }), + if (blockNumber % BLOCK_CHECK_INTERVAL) { + const currentBlockTimestamp = blockEvent.block.timestamp + if ( + this.cache.getLastReportedExecutorBalanceTimestamp() + REPORT_WINDOW_EXECUTOR_BALANCE < + currentBlockTimestamp + ) { + const executorBalanceRaw = await this.ethProvider.getBalance( + this.lidoDepositExecutorAddress, + blockEvent.blockNumber, ) + if (E.isLeft(executorBalanceRaw)) { + out.push( + Finding.fromObject({ + name: `Error in ${StethOperationSrv.name}.${this.handleDepositExecutorBalance.name}:329`, + description: `Could not fetch depositorBalance. Cause ${executorBalanceRaw.left.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: `${executorBalanceRaw.left.stack}` }, + }), + ) - return out - } + return out + } - const executorBalance = new BigNumber(String(executorBalanceRaw.right)).div(ETH_DECIMALS).toNumber() - if (executorBalance < MIN_DEPOSIT_EXECUTOR_BALANCE) { - this.cache.setLastReportedExecutorBalanceTimestamp(currentBlockTimestamp) - out.push( - Finding.fromObject({ - name: '⚠️ Low deposit executor balance', - description: `Balance of deposit executor is ${executorBalance.toFixed(4)}. ` + `This is extremely low! 😱`, - alertId: 'LOW-DEPOSIT-EXECUTOR-BALANCE', - severity: FindingSeverity.High, - type: FindingType.Suspicious, - }), - ) + const executorBalance = new BigNumber(String(executorBalanceRaw.right)).div(ETH_DECIMALS).toNumber() + if (executorBalance < MIN_DEPOSIT_EXECUTOR_BALANCE) { + this.cache.setLastReportedExecutorBalanceTimestamp(currentBlockTimestamp) + out.push( + Finding.fromObject({ + name: '⚠️ Low deposit executor balance', + description: + `Balance of deposit executor is ${executorBalance.toFixed(4)}. ` + `This is extremely low! 😱`, + alertId: 'LOW-DEPOSIT-EXECUTOR-BALANCE', + severity: FindingSeverity.High, + type: FindingType.Suspicious, + }), + ) + } } } @@ -432,94 +419,66 @@ export class StethOperationSrv { const blockNumber = blockEvent.block.number const out: Finding[] = [] if (blockNumber % BLOCK_CHECK_INTERVAL_SMAll !== 0) { - return out - } - - const currentBlockTimestamp = blockEvent.block.timestamp + const currentBlockTimestamp = blockEvent.block.timestamp - let stakingLimitInfo: StakingLimitInfo - try { - stakingLimitInfo = await retryAsync( - async (): Promise => { - const resp = await this.lidoContract.functions.getStakeLimitFullInfo({ - blockTag: blockNumber, - }) + const stakingLimitInfo = await this.ethProvider.getStakingLimitInfo(blockNumber) + if (E.isLeft(stakingLimitInfo)) { + const f: Finding = Finding.fromObject({ + name: `Error in ${StethOperationSrv.name}.${this.handleStakingLimit.name}:385`, + description: `Could not call "lidoContract.getStakeLimitFullInfo. Cause ${stakingLimitInfo.left.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: `${stakingLimitInfo.left.stack}` }, + }) - return { - currentStakeLimit: new BigNumber(String(resp.currentStakeLimit)).div(ETH_DECIMALS), - maxStakeLimit: new BigNumber(String(resp.maxStakeLimit)).div(ETH_DECIMALS), - } - }, - { delay: 500, maxTry: 5 }, - ) - } catch (e) { - const f: Finding = Finding.fromObject({ - name: `Error in ${StethOperationSrv.name}.${this.handleStakingLimit.name}:385`, - description: `Could not call "lidoContract.getStakeLimitFullInfo. Cause ${e instanceof Error ? e.message : ''}`, - alertId: 'LIDO-AGENT-ERROR', - severity: FindingSeverity.Low, - type: FindingType.Degraded, - metadata: { stack: e instanceof Error ? `${e.stack}` : 'null' }, - }) + return [f] + } - return [f] - } + const currentStakingLimit = stakingLimitInfo.right.currentStakeLimit + const maxStakingLimit = stakingLimitInfo.right.maxStakeLimit - const currentStakingLimit = stakingLimitInfo.currentStakeLimit - const maxStakingLimit = stakingLimitInfo.maxStakeLimit - if ( - this.cache.getLastReportedStakingLimit10Timestamp() + REPORT_WINDOW_STAKING_LIMIT_10 < currentBlockTimestamp && - currentStakingLimit.isLessThan(maxStakingLimit.times(0.1)) - ) { - out.push( - Finding.fromObject({ - name: '⚠️ Staking limit below 10%', - description: - `Current staking limit is ${currentStakingLimit.toFixed(2)} ETH ` + - `this is lower than 10% of max staking limit ` + - `${maxStakingLimit.toFixed(2)} ETH`, - alertId: 'LOW-STAKING-LIMIT', - severity: FindingSeverity.Info, - type: FindingType.Info, - }), - ) - this.cache.setLastReportedStakingLimit10Timestamp(currentBlockTimestamp) - } else if ( - this.cache.getLastReportedStakingLimit30Timestamp() + REPORT_WINDOW_STAKING_LIMIT_30 < currentBlockTimestamp && - currentStakingLimit.isLessThan(maxStakingLimit.times(0.3)) - ) { - out.push( - Finding.fromObject({ - name: 'πŸ“‰ Staking limit below 30%', - description: - `Current staking limit is ${currentStakingLimit.toFixed(2)} ETH ` + - `this is lower than 30% of max staking limit ` + - `${maxStakingLimit.toFixed(2)} ETH`, - alertId: 'LOW-STAKING-LIMIT', - severity: FindingSeverity.Info, - type: FindingType.Info, - }), - ) - this.cache.setLastReportedStakingLimit30Timestamp(currentBlockTimestamp) + if ( + this.cache.getLastReportedStakingLimit10Timestamp() + REPORT_WINDOW_STAKING_LIMIT_10 < currentBlockTimestamp && + currentStakingLimit.isLessThan(maxStakingLimit.times(0.1)) + ) { + out.push( + Finding.fromObject({ + name: '⚠️ Staking limit below 10%', + description: + `Current staking limit is ${currentStakingLimit.toFixed(2)} ETH ` + + `this is lower than 10% of max staking limit ` + + `${maxStakingLimit.toFixed(2)} ETH`, + alertId: 'LOW-STAKING-LIMIT', + severity: FindingSeverity.Info, + type: FindingType.Info, + }), + ) + this.cache.setLastReportedStakingLimit10Timestamp(currentBlockTimestamp) + } else if ( + this.cache.getLastReportedStakingLimit30Timestamp() + REPORT_WINDOW_STAKING_LIMIT_30 < currentBlockTimestamp && + currentStakingLimit.isLessThan(maxStakingLimit.times(0.3)) + ) { + out.push( + Finding.fromObject({ + name: 'πŸ“‰ Staking limit below 30%', + description: + `Current staking limit is ${currentStakingLimit.toFixed(2)} ETH ` + + `this is lower than 30% of max staking limit ` + + `${maxStakingLimit.toFixed(2)} ETH`, + alertId: 'LOW-STAKING-LIMIT', + severity: FindingSeverity.Info, + type: FindingType.Info, + }), + ) + this.cache.setLastReportedStakingLimit30Timestamp(currentBlockTimestamp) + } } return out } - private async getBufferedEther(blockNumber: number): Promise> { - try { - const resp = await retryAsync( - async (): Promise => { - return await this.lidoContract.getBufferedEther({ - blockTag: blockNumber, - }) - }, - { delay: 500, maxTry: 5 }, - ) - - return E.right(new BigNumber(resp.toString())) - } catch (e) { - return E.left(new Error(`Could not call "lidoContract.getBufferedEther". Cause: ${e}`)) - } + public getStorage(): StethOperationCache { + return this.cache } } diff --git a/ethereum-steth-v2/src/services/vault/Vault.srv.ts b/ethereum-steth-v2/src/services/vault/Vault.srv.ts new file mode 100644 index 00000000..5cafdca3 --- /dev/null +++ b/ethereum-steth-v2/src/services/vault/Vault.srv.ts @@ -0,0 +1,380 @@ +import BigNumber from 'bignumber.js' +import { ETH_DECIMALS } from '../../utils/constants' +import * as E from 'fp-ts/Either' +import { IETHProvider } from '../../clients/eth_provider' +import { BlockEvent, Finding, FindingSeverity, FindingType } from 'forta-agent' +import { Lido as LidoContract } from '../../generated' +import { elapsedTime } from '../../utils/time' +import { toEthString } from '../../utils/string' +import { ETHDistributedEvent } from '../../generated/Lido' +import { TransactionEvent } from 'forta-agent/dist/sdk/transaction.event' +import { TRANSFER_SHARES_EVENT } from '../../utils/events/vault_events' + +const WITHDRAWAL_VAULT_BALANCE_BLOCK_INTERVAL = 100 +const WITHDRAWAL_VAULT_BALANCE_DIFF_INFO = ETH_DECIMALS.times(1000) +const EL_VAULT_BALANCE_DIFF_INFO = ETH_DECIMALS.times(50) + +export class VaultSrv { + private readonly name = 'VaultSrv' + private readonly ethProvider: IETHProvider + + private readonly lidoContract: LidoContract + + private readonly withdrawalsVaultAddress: string + private readonly elRewardsVaultAddress: string + + private readonly burnerAddress: string + + constructor( + ethProvider: IETHProvider, + lidoContract: LidoContract, + withdrawalsVaultAddress: string, + elRewardsVaultAddress: string, + burnerAddress: string, + ) { + this.ethProvider = ethProvider + this.lidoContract = lidoContract + this.elRewardsVaultAddress = elRewardsVaultAddress + this.withdrawalsVaultAddress = withdrawalsVaultAddress + this.burnerAddress = burnerAddress + } + + public initialize(currentBlock: number): null { + const start = new Date().getTime() + console.log(elapsedTime(`[${this.name}.initialize]`, start) + ` on block ${currentBlock}`) + + return null + } + + public getName(): string { + return this.name + } + + public async handleBlock(blockEvent: BlockEvent) { + const start = new Date().getTime() + const findings: Finding[] = [] + + const currentBlock = blockEvent.blockNumber + const prevBlockWithdrawalVaultBalance = await this.ethProvider.getBalanceByBlockHash( + this.withdrawalsVaultAddress, + blockEvent.block.parentHash, + ) + if (E.isLeft(prevBlockWithdrawalVaultBalance)) { + const f: Finding = Finding.fromObject({ + name: `Error in ${VaultSrv.name}.${this.handleBlock.name}:56`, + description: `Could not call "ethProvider.getBalanceByBlockTag. Cause ${prevBlockWithdrawalVaultBalance.left.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: `${prevBlockWithdrawalVaultBalance.left.stack}` }, + }) + + return [f] + } + const prevBlockElVaultBalance = await this.ethProvider.getBalanceByBlockHash( + this.elRewardsVaultAddress, + blockEvent.block.parentHash, + ) + if (E.isLeft(prevBlockElVaultBalance)) { + const f: Finding = Finding.fromObject({ + name: `Error in ${VaultSrv.name}.${this.handleBlock.name}:72`, + description: `Could not call "ethProvider.getBalanceByBlockTag. Cause ${prevBlockElVaultBalance.left.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: `${prevBlockElVaultBalance.left.stack}` }, + }) + + return [f] + } + + const report = await this.ethProvider.getETHDistributedEvent(currentBlock, currentBlock) + if (E.isLeft(report)) { + const f: Finding = Finding.fromObject({ + name: `Error in ${VaultSrv.name}.${this.handleBlock.name}:81`, + description: `Could not call "ethProvider.getETHDistributedEvent. Cause ${report.left.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: `${report.left.stack}` }, + }) + + return [f] + } + + const [ + withdrawalVaultBalanceFindings, + elVaultBalanceFindings, + noWithdrawalVaultDrainsFindings, + noELVaultDrainsFindings, + ] = await Promise.all([ + this.handleWithdrawalVaultBalance(currentBlock), + this.handleELVaultBalance(currentBlock, prevBlockElVaultBalance.right), + this.handleNoWithdrawalVaultDrains(currentBlock, prevBlockWithdrawalVaultBalance.right, report.right), + this.handleNoELVaultDrains(currentBlock, prevBlockElVaultBalance.right, report.right), + ]) + + findings.push( + ...withdrawalVaultBalanceFindings, + ...elVaultBalanceFindings, + ...noWithdrawalVaultDrainsFindings, + ...noELVaultDrainsFindings, + ) + + console.log(elapsedTime(VaultSrv.name + '.' + this.handleBlock.name, start)) + + return findings + } + + public async handleWithdrawalVaultBalance(blockNumber: number): Promise { + const out: Finding[] = [] + if (blockNumber % WITHDRAWAL_VAULT_BALANCE_BLOCK_INTERVAL === 0) { + const report = await this.ethProvider.getETHDistributedEvent( + blockNumber - WITHDRAWAL_VAULT_BALANCE_BLOCK_INTERVAL, + blockNumber, + ) + if (E.isLeft(report)) { + const f: Finding = Finding.fromObject({ + name: `Error in ${VaultSrv.name}.${this.handleBlock.name}:125`, + description: `Could not call "ethProvider.getETHDistributedEvent. Cause ${report.left.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: `${report.left.stack}` }, + }) + + return [f] + } + + const prevWithdrawalVaultBalance = await this.ethProvider.getBalance( + this.withdrawalsVaultAddress, + blockNumber - WITHDRAWAL_VAULT_BALANCE_BLOCK_INTERVAL, + ) + if (E.isLeft(prevWithdrawalVaultBalance)) { + const f: Finding = Finding.fromObject({ + name: `Error in ${VaultSrv.name}.${this.handleBlock.name}:143`, + description: `Could not call "ethProvider.getBalance. Cause ${prevWithdrawalVaultBalance.left.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: `${prevWithdrawalVaultBalance.left.stack}` }, + }) + + return [f] + } + + const withdrawalVaultBalance = await this.ethProvider.getBalance(this.withdrawalsVaultAddress, blockNumber) + if (E.isLeft(withdrawalVaultBalance)) { + const f: Finding = Finding.fromObject({ + name: `Error in ${VaultSrv.name}.${this.handleBlock.name}:159`, + description: `Could not call "ethProvider.getBalance. Cause ${withdrawalVaultBalance.left.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: `${withdrawalVaultBalance.left.stack}` }, + }) + + return [f] + } + + let withdrawalsWithdrawn = new BigNumber(0) + if (report.right !== null) { + withdrawalsWithdrawn = new BigNumber(String(report.right.args.withdrawalsWithdrawn)) + } + + const withdrawalVaultBalanceDiff = withdrawalVaultBalance.right + .minus(prevWithdrawalVaultBalance.right) + .plus(withdrawalsWithdrawn) + + if (withdrawalVaultBalanceDiff.gte(WITHDRAWAL_VAULT_BALANCE_DIFF_INFO)) { + out.push( + Finding.fromObject({ + name: 'πŸ’΅ Withdrawal Vault Balance significant change', + description: `Withdrawal Vault Balance has increased by ${toEthString( + withdrawalVaultBalanceDiff, + )} during the last ${WITHDRAWAL_VAULT_BALANCE_BLOCK_INTERVAL} blocks`, + alertId: 'WITHDRAWAL-VAULT-BALANCE-CHANGE', + type: FindingType.Info, + severity: FindingSeverity.Info, + }), + ) + } + } + + return out + } + + public async handleELVaultBalance(blockNumber: number, prevBalance: BigNumber): Promise { + const elVaultBalance = await this.ethProvider.getBalance(this.elRewardsVaultAddress, blockNumber) + if (E.isLeft(elVaultBalance)) { + const f: Finding = Finding.fromObject({ + name: `Error in ${VaultSrv.name}.${this.handleBlock.name}:190`, + description: `Could not call "ethProvider.getBalance. Cause ${elVaultBalance.left.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: `${elVaultBalance.left.stack}` }, + }) + + return [f] + } + + const elVaultBalanceDiff = elVaultBalance.right.minus(prevBalance) + + const out: Finding[] = [] + if (elVaultBalanceDiff.gte(EL_VAULT_BALANCE_DIFF_INFO)) { + out.push( + Finding.fromObject({ + name: 'πŸ’΅ EL Vault Balance significant change', + description: `EL Vault Balance has increased by ${toEthString(elVaultBalanceDiff)}`, + alertId: 'EL-VAULT-BALANCE-CHANGE', + type: FindingType.Info, + severity: FindingSeverity.Info, + }), + ) + } + + return out + } + + public async handleNoWithdrawalVaultDrains( + currentBlock: number, + prevBalance: BigNumber, + report: ETHDistributedEvent | null, + ): Promise { + const out: Finding[] = [] + const currentBalance = await this.ethProvider.getBalance(this.withdrawalsVaultAddress, currentBlock) + if (E.isLeft(currentBalance)) { + const f: Finding = Finding.fromObject({ + name: `Error in ${VaultSrv.name}.${this.handleBlock.name}:228`, + description: `Could not call "ethProvider.getBalance. Cause ${currentBalance.left.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: `${currentBalance.left.stack}` }, + }) + + return [f] + } + + if (report === null) { + if (currentBalance.right.lt(prevBalance)) { + out.push( + Finding.fromObject({ + name: '🚨 Withdrawal Vault balance mismatch', + description: `Withdrawal Vault Balance has decreased by ${toEthString( + prevBalance.minus(currentBalance.right), + )} without Oracle report`, + alertId: 'WITHDRAWAL-VAULT-BALANCE-DRAIN', + severity: FindingSeverity.Critical, + type: FindingType.Suspicious, + }), + ) + } + + return out + } + + const withdrawalsWithdrawn = new BigNumber(String(report.args.withdrawalsWithdrawn)) + const expectedBalance = prevBalance.minus(withdrawalsWithdrawn) + + if (currentBalance.right.lt(expectedBalance)) { + out.push( + Finding.fromObject({ + name: '🚨 Withdrawal Vault balance mismatch', + description: `Withdrawal Vault Balance has decreased by ${toEthString( + expectedBalance.minus(currentBalance.right), + )} but Oracle report shows ${toEthString(withdrawalsWithdrawn)}`, + alertId: 'WITHDRAWAL-VAULT-BALANCE-DRAIN', + severity: FindingSeverity.Critical, + type: FindingType.Suspicious, + }), + ) + } + + return out + } + + public async handleNoELVaultDrains( + currentBlock: number, + prevBalance: BigNumber, + report: ETHDistributedEvent | null, + ): Promise { + const currentBalance = await this.ethProvider.getBalance(this.elRewardsVaultAddress, currentBlock) + if (E.isLeft(currentBalance)) { + const f: Finding = Finding.fromObject({ + name: `Error in ${VaultSrv.name}.${this.handleBlock.name}:291`, + description: `Could not call "ethProvider.getBalance. Cause ${currentBalance.left.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: `${currentBalance.left.stack}` }, + }) + + return [f] + } + + const out: Finding[] = [] + if (report === null) { + if (currentBalance.right.lt(prevBalance)) { + out.push( + Finding.fromObject({ + name: '🚨 EL Vault balance mismatch', + description: `EL Vault Balance has decreased by ${toEthString( + prevBalance.minus(currentBalance.right), + )} without Oracle report`, + alertId: 'EL-VAULT-BALANCE-DRAIN', + severity: FindingSeverity.Critical, + type: FindingType.Suspicious, + }), + ) + } + + return out + } + + const executionLayerRewardsWithdrawn = new BigNumber(String(report.args.executionLayerRewardsWithdrawn)) + const expectedBalance = prevBalance.minus(executionLayerRewardsWithdrawn) + + if (currentBalance.right.lt(expectedBalance)) { + out.push( + Finding.fromObject({ + name: '🚨 EL Vault balance mismatch', + description: `EL Vault Balance has decreased by ${toEthString( + expectedBalance.minus(currentBalance.right), + )} but Oracle report shows ${toEthString(executionLayerRewardsWithdrawn)}`, + alertId: 'EL-VAULT-BALANCE-DRAIN', + severity: FindingSeverity.Critical, + type: FindingType.Suspicious, + }), + ) + } + + return out + } + + public handleTransaction(txEvent: TransactionEvent): Finding[] { + return this.handleBurnerSharesTx(txEvent) + } + + public handleBurnerSharesTx(txEvent: TransactionEvent): Finding[] { + const events = txEvent + .filterLog(TRANSFER_SHARES_EVENT, this.lidoContract.address) + .filter((e) => e.args.from.toLowerCase() === this.burnerAddress.toLowerCase()) + + const out: Finding[] = [] + for (const event of events) { + out.push( + Finding.fromObject({ + name: '🚨 Burner shares transfer', + description: `Burner shares transfer to ${event.args.to} has occurred`, + alertId: 'BURNER-SHARES-TRANSFER', + severity: FindingSeverity.High, + type: FindingType.Suspicious, + }), + ) + } + + return out + } +} diff --git a/ethereum-steth-v2/src/services/withdrawals/Withdrawals.cache.ts b/ethereum-steth-v2/src/services/withdrawals/Withdrawals.cache.ts new file mode 100644 index 00000000..f88d9a93 --- /dev/null +++ b/ethereum-steth-v2/src/services/withdrawals/Withdrawals.cache.ts @@ -0,0 +1,158 @@ +import BigNumber from 'bignumber.js' + +export type WithdrawalRequest = { + id: number + amount: BigNumber | undefined + claimed: boolean + timestamp: string +} + +export class WithdrawalsCache { + private _bunkerModeEnabledSinceTimestamp = 0 + private _isBunkerMode = false + private _lastFinalizedRequestId = 0 + private _lastFinalizedTimestamp = 0 + private _firstUnfinalizedRequestTimestamp: number + private readonly _finalizedWithdrawalRequestsMap: Map + + private _lastQueueOnParStakeLimitAlertTimestamp = 0 + private _lastBigUnfinalizedQueueAlertTimestamp = 0 + private _lastLongUnfinalizedQueueAlertTimestamp = 0 + private _lastUnclaimedRequestsAlertTimestamp = 0 + private _lastUnclaimedMoreThanBalanceAlertTimestamp = 0 + private _lastBigRequestAfterRebaseAlertTimestamp = 0 + private _lastTokenRebaseTimestamp = 0 + private _lastClaimedAmountMoreThanRequestedAlertTimestamp = 0 + private _claimedAmountMoreThanRequestedAlertsCount = 0 + + private _amountOfRequestedStETHSinceLastTokenRebase = new BigNumber(0) + + constructor() { + this._finalizedWithdrawalRequestsMap = new Map() + this._firstUnfinalizedRequestTimestamp = 0 + } + + public getBunkerModeEnabledSinceTimestamp(): number { + return this._bunkerModeEnabledSinceTimestamp + } + + public setBunkerModeEnabledSinceTimestamp(blockTimestamp: number) { + this._bunkerModeEnabledSinceTimestamp = blockTimestamp + } + + public getIsBunkerMode(): boolean { + return this._isBunkerMode + } + + public setIsBunkerMode(isBunkerMode: boolean) { + this._isBunkerMode = isBunkerMode + } + + public getLastFinalizedRequestId(): number { + return this._lastFinalizedRequestId + } + + public setLastFinalizedRequestId(lastFinalizedRequestId: number) { + this._lastFinalizedRequestId = lastFinalizedRequestId + } + + public getLastFinalizedTimestamp(): number { + return this._lastFinalizedTimestamp + } + + public setLastFinalizedTimestamp(lastFinalizedTimestamp: number) { + this._lastFinalizedTimestamp = lastFinalizedTimestamp + } + + public getFinalizedWithdrawalRequestsMap(): Map { + return this._finalizedWithdrawalRequestsMap + } + + public getFirstUnfinalizedRequestTimestamp(): number { + return this._firstUnfinalizedRequestTimestamp + } + + public setFirstUnfinalizedRequestTimestamp(firstUnfinalizedRequestTimestamp: number) { + this._firstUnfinalizedRequestTimestamp = firstUnfinalizedRequestTimestamp + } + + public getLastQueueOnParStakeLimitAlertTimestamp(): number { + return this._lastQueueOnParStakeLimitAlertTimestamp + } + + public setLastQueueOnParStakeLimitAlertTimestamp(lastQueueOnParStakeLimitAlertTimestamp: number) { + this._lastQueueOnParStakeLimitAlertTimestamp = lastQueueOnParStakeLimitAlertTimestamp + } + + public getLastBigUnfinalizedQueueAlertTimestamp(): number { + return this._lastBigUnfinalizedQueueAlertTimestamp + } + + public setLastBigUnfinalizedQueueAlertTimestamp(lastBigUnfinalizedQueueAlertTimestamp: number) { + this._lastBigUnfinalizedQueueAlertTimestamp = lastBigUnfinalizedQueueAlertTimestamp + } + + public getLastLongUnfinalizedQueueAlertTimestamp(): number { + return this._lastLongUnfinalizedQueueAlertTimestamp + } + + public setLastLongUnfinalizedQueueAlertTimestamp(lastLongUnfinalizedQueueAlertTimestamp: number) { + this._lastLongUnfinalizedQueueAlertTimestamp = lastLongUnfinalizedQueueAlertTimestamp + } + + public getLastUnclaimedRequestsAlertTimestamp(): number { + return this._lastUnclaimedRequestsAlertTimestamp + } + + public setLastUnclaimedRequestsAlertTimestamp(lastUnclaimedRequestsAlertTimestamp: number) { + this._lastUnclaimedRequestsAlertTimestamp = lastUnclaimedRequestsAlertTimestamp + } + + public getLastUnclaimedMoreThanBalanceAlertTimestamp(): number { + return this._lastUnclaimedMoreThanBalanceAlertTimestamp + } + + public setLastUnclaimedMoreThanBalanceAlertTimestamp(lastUnclaimedMoreThanBalanceAlertTimestamp: number) { + this._lastUnclaimedMoreThanBalanceAlertTimestamp = lastUnclaimedMoreThanBalanceAlertTimestamp + } + + public getAmountOfRequestedStETHSinceLastTokenRebase(): BigNumber { + return this._amountOfRequestedStETHSinceLastTokenRebase + } + + public setAmountOfRequestedStETHSinceLastTokenRebase(amountOfRequestedStETHSinceLastTokenRebase: BigNumber) { + this._amountOfRequestedStETHSinceLastTokenRebase = amountOfRequestedStETHSinceLastTokenRebase + } + + public getLastBigRequestAfterRebaseAlertTimestamp(): number { + return this._lastBigRequestAfterRebaseAlertTimestamp + } + + public setLastBigRequestAfterRebaseAlertTimestamp(lastBigRequestAfterRebaseAlertTimestamp: number) { + this._lastBigRequestAfterRebaseAlertTimestamp = lastBigRequestAfterRebaseAlertTimestamp + } + + public getLastTokenRebaseTimestamp(): number { + return this._lastTokenRebaseTimestamp + } + + public setLastTokenRebaseTimestamp(lastTokenRebaseTimestamp: number) { + this._lastTokenRebaseTimestamp = lastTokenRebaseTimestamp + } + + public getLastClaimedAmountMoreThanRequestedAlertTimestamp(): number { + return this._lastClaimedAmountMoreThanRequestedAlertTimestamp + } + + public setLastClaimedAmountMoreThanRequestedAlertTimestamp(lastClaimedAmountMoreThanRequestedAlertTimestamp: number) { + this._lastClaimedAmountMoreThanRequestedAlertTimestamp = lastClaimedAmountMoreThanRequestedAlertTimestamp + } + + public getClaimedAmountMoreThanRequestedAlertsCount(): number { + return this._claimedAmountMoreThanRequestedAlertsCount + } + + public setClaimedAmountMoreThanRequestedAlertsCount(claimedAmountMoreThanRequestedAlertsCount: number) { + this._claimedAmountMoreThanRequestedAlertsCount = claimedAmountMoreThanRequestedAlertsCount + } +} diff --git a/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts b/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts new file mode 100644 index 00000000..cbf00bb1 --- /dev/null +++ b/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts @@ -0,0 +1,779 @@ +import { Lido as LidoContract, WithdrawalQueueERC721 as WithdrawalQueueContract } from '../../generated' +import BigNumber from 'bignumber.js' +import { retryAsync } from 'ts-retry' +import { WithdrawalRequest, WithdrawalsCache } from './Withdrawals.cache' +import { BlockEvent, filterLog, Finding, FindingSeverity, FindingType } from 'forta-agent' +import { IETHProvider } from '../../clients/eth_provider' +import * as E from 'fp-ts/Either' +import { ETH_DECIMALS } from '../../utils/constants' +import { elapsedTime, formatDelay } from '../../utils/time' +import { TransactionEvent } from 'forta-agent/dist/sdk/transaction.event' +import { + LIDO_TOKEN_REBASED_EVENT, + WITHDRAWAL_QUEUE_WITHDRAWAL_CLAIMED_EVENT, + WITHDRAWAL_QUEUE_WITHDRAWAL_REQUESTED_EVENT, + WITHDRAWAL_QUEUE_WITHDRAWALS_FINALIZED_EVENT, + WITHDRAWALS_BUNKER_MODE_DISABLED_EVENT, + WITHDRAWALS_BUNKER_MODE_ENABLED_EVENT, +} from '../../utils/events/withdrawals_events' +import { etherscanAddress, etherscanNft } from '../../utils/string' +import { EventOfNotice } from '../../entity/events' + +const ONE_HOUR = 60 * 60 +const ONE_DAY = ONE_HOUR * 24 + +const BLOCK_CHECK_INTERVAL = 100 // ~20 minutes +const QUEUE_ON_PAR_STAKE_LIMIT_TRIGGER_EVERY = ONE_DAY +const QUEUE_ON_PAR_STAKE_LIMIT_RATE_THRESHOLD = 0.95 +const BIG_UNFINALIZED_QUEUE_TRIGGER_EVERY = ONE_DAY + +const LONG_UNFINALIZED_QUEUE_THRESHOLD = 5 * ONE_DAY // 5 days +const LONG_UNFINALIZED_QUEUE_TRIGGER_EVERY = ONE_DAY +const UNCLAIMED_REQUESTS_TIME_WINDOW = 14 * ONE_DAY // 2 weeks +const UNCLAIMED_REQUESTS_SIZE_RATE_TRIGGER_EVERY = ONE_DAY +const UNCLAIMED_REQUESTS_SIZE_RATE_THRESHOLD = 0.2 +const UNCLAIMED_REQUESTS_MORE_THAN_BALANCE_TRIGGER_EVERY = ONE_DAY + +const BIG_UNFINALIZED_QUEUE_THRESHOLD = new BigNumber(100_000) +const BIG_WITHDRAWAL_REQUEST_THRESHOLD = new BigNumber(5_000) +const BIG_WITHDRAWAL_REQUEST_AFTER_REBASE_THRESHOLD = new BigNumber(150_000) + +const CLAIMED_AMOUNT_MORE_THAN_REQUESTED_MAX_ALERTS_PER_HOUR = 5 + +export class WithdrawalsSrv { + private name = `WithdrawalsSrv` + + private readonly lidoContract: LidoContract + private readonly wdQueueContract: WithdrawalQueueContract + private readonly cache: WithdrawalsCache + private readonly ethProvider: IETHProvider + private readonly withdrawalsEvents: EventOfNotice[] + + constructor( + ethProvider: IETHProvider, + wdQueueContract: WithdrawalQueueContract, + lidoContract: LidoContract, + cache: WithdrawalsCache, + withdrawalsEvents: EventOfNotice[], + ) { + this.ethProvider = ethProvider + this.wdQueueContract = wdQueueContract + this.lidoContract = lidoContract + this.cache = cache + this.withdrawalsEvents = withdrawalsEvents + } + + async initialize(currentBlock: number): Promise { + const start = new Date().getTime() + + try { + const isBunkerMode = await retryAsync( + async (): Promise => { + const [isBunkerMode] = await this.wdQueueContract.functions.isBunkerModeActive({ + blockTag: currentBlock, + }) + + return isBunkerMode + }, + { delay: 500, maxTry: 5 }, + ) + + this.cache.setIsBunkerMode(isBunkerMode) + } catch (e) { + console.log(elapsedTime(`[${this.name}.initialize]`, start)) + return new Error(`Could not call "isBunkerModeActive. Cause ${e}`) + } + + if (this.cache.getIsBunkerMode()) { + let bunkerModeSinceTimestamp: number + try { + bunkerModeSinceTimestamp = await retryAsync( + async (): Promise => { + const [resp] = await this.wdQueueContract.functions.bunkerModeSinceTimestamp({ + blockTag: currentBlock, + }) + + return Number(resp) + }, + { delay: 500, maxTry: 5 }, + ) + + this.cache.setBunkerModeEnabledSinceTimestamp(bunkerModeSinceTimestamp) + } catch (e) { + console.log(elapsedTime(`[${this.name}.initialize]`, start)) + return new Error(`Could not call "bunkerModeSinceTimestamp. Cause ${e}`) + } + } + + let lastRequestId: number + try { + lastRequestId = await retryAsync( + async (): Promise => { + const [resp] = await this.wdQueueContract.functions.getLastRequestId({ + blockTag: currentBlock, + }) + + return Number(resp) + }, + { delay: 500, maxTry: 5 }, + ) + } catch (e) { + console.log(elapsedTime(`[${this.name}.initialize]`, start)) + return new Error(`Could not call "getLastRequestId. Cause ${e}`) + } + + if (lastRequestId !== 0) { + try { + const lastFinalizedRequestId = await retryAsync( + async (): Promise => { + const [resp] = await this.wdQueueContract.functions.getLastFinalizedRequestId({ + blockTag: currentBlock, + }) + + return Number(resp) + }, + { delay: 500, maxTry: 5 }, + ) + + this.cache.setLastFinalizedRequestId(lastFinalizedRequestId) + } catch (e) { + console.log(elapsedTime(`[${this.name}.initialize]`, start)) + return new Error(`Could not call "getLastFinalizedRequestId. Cause ${e}`) + } + + if (this.cache.getLastFinalizedRequestId() !== 0) { + try { + const lastFinalizedTimestamp = await retryAsync( + async (): Promise => { + const resp = await this.wdQueueContract.functions.getWithdrawalStatus( + [this.cache.getLastFinalizedRequestId()], + { + blockTag: currentBlock, + }, + ) + + return Number(resp.statuses[0].timestamp) + }, + { delay: 500, maxTry: 5 }, + ) + + this.cache.setLastFinalizedTimestamp(lastFinalizedTimestamp) + } catch (e) { + console.log(elapsedTime(`[${this.name}.initialize]`, start)) + + return new Error(`Could not call "getWithdrawalStatus. Cause ${e}`) + } + } + + const diff = lastRequestId - this.cache.getLastFinalizedRequestId() + let endValue = this.cache.getLastFinalizedRequestId() + if (diff > 0) { + endValue += 1 + } + + const requestsRange: number[] = [] + for (let i = 1; i <= endValue; i++) { + requestsRange.push(i) + } + + const requestsStatuses = await this.ethProvider.getWithdrawalStatuses(requestsRange, currentBlock) + if (E.isLeft(requestsStatuses)) { + console.log(elapsedTime(`[${this.name}.initialize]`, start)) + + return requestsStatuses.left + } + + for (const [index, reqStatus] of requestsStatuses.right.entries()) { + const reqId = index + 1 + if (reqStatus.isFinalized) { + this.cache.getFinalizedWithdrawalRequestsMap().set(reqId, { + id: reqId, + amount: new BigNumber(String(reqStatus.amountOfStETH)), + claimed: reqStatus.isClaimed, + timestamp: String(reqStatus.timestamp), + }) + } + } + if (diff > 0) { + this.cache.setFirstUnfinalizedRequestTimestamp( + requestsStatuses.right[requestsRange.length - 1].timestamp.toNumber(), + ) + } + } + + console.log(elapsedTime(`[${this.name}.initialize]`, start) + ` on block ${currentBlock}`) + return null + } + + public getName(): string { + return this.name + } + + async handleBlock(blockEvent: BlockEvent): Promise { + const start = new Date().getTime() + const findings: Finding[] = [] + + if (blockEvent.block.number % BLOCK_CHECK_INTERVAL == 0) { + const [queueOnParWithStakeLimitFindings, unfinalizedRequestNumberFindings, unclaimedRequestsFindings] = + await Promise.all([ + this.handleQueueOnParWithStakeLimit(blockEvent), + this.handleUnfinalizedRequestNumber(blockEvent), + this.handleUnclaimedRequests(blockEvent), + ]) + + findings.push( + ...queueOnParWithStakeLimitFindings, + ...unfinalizedRequestNumberFindings, + ...unclaimedRequestsFindings, + ) + } + + console.log(elapsedTime(WithdrawalsSrv.name + '.' + this.handleBlock.name, start)) + + return findings + } + + public handleTransaction(txEvent: TransactionEvent): Finding[] { + const out: Finding[] = [] + + const bunkerStatusFindings = this.handleBunkerStatus(txEvent) + this.handleLastTokenRebase(txEvent) + this.handleWithdrawalFinalized(txEvent) + const withdrawalRequestFindings = this.handleWithdrawalRequest(txEvent) + const withdrawalClaimedFindings = this.handleWithdrawalClaimed(txEvent) + const withdrawalsEventsFindings = this.handleEventsOfNotice(txEvent, this.withdrawalsEvents) + + out.push( + ...bunkerStatusFindings, + ...withdrawalRequestFindings, + ...withdrawalClaimedFindings, + ...withdrawalsEventsFindings, + ) + + return out + } + + public async handleQueueOnParWithStakeLimit(blockEvent: BlockEvent): Promise { + const blockTimestamp = blockEvent.block.timestamp + + if ( + blockTimestamp - this.cache.getLastQueueOnParStakeLimitAlertTimestamp() <= + QUEUE_ON_PAR_STAKE_LIMIT_TRIGGER_EVERY + ) { + return [] + } + + const stakeLimitFullInfo = await this.ethProvider.getStakingLimitInfo(blockEvent.blockNumber) + if (E.isLeft(stakeLimitFullInfo)) { + const f: Finding = Finding.fromObject({ + name: `Error in ${WithdrawalsSrv.name}.${this.handleQueueOnParWithStakeLimit.name}:213`, + description: `Could not call "ethProvider.getStakingLimitInfo. Cause ${stakeLimitFullInfo.left.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: `${stakeLimitFullInfo.left.stack}` }, + }) + + return [f] + } + + const unfinalizedStETH = await this.ethProvider.getUnfinalizedStETH(blockEvent.blockNumber) + if (E.isLeft(unfinalizedStETH)) { + const f: Finding = Finding.fromObject({ + name: `Error in ${WithdrawalsSrv.name}.${this.handleQueueOnParWithStakeLimit.name}:232`, + description: `Could not call "wdQueueContract.unfinalizedStETH. Cause ${unfinalizedStETH.left.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: `${unfinalizedStETH.left.stack}` }, + }) + + return [f] + } + + if (stakeLimitFullInfo.right.isStakingPaused || unfinalizedStETH.right.eq(0)) { + return [] + } + const drainedStakeLimit = stakeLimitFullInfo.right.maxStakeLimit.minus(stakeLimitFullInfo.right.currentStakeLimit) + const drainedStakeLimitRate = drainedStakeLimit.div(stakeLimitFullInfo.right.maxStakeLimit) + const thresholdStakeLimit = stakeLimitFullInfo.right.maxStakeLimit.times(QUEUE_ON_PAR_STAKE_LIMIT_RATE_THRESHOLD) + + const findings: Finding[] = [] + if (drainedStakeLimit.gte(thresholdStakeLimit) && unfinalizedStETH.right.gte(thresholdStakeLimit)) { + findings.push( + Finding.fromObject({ + name: `⚠️ Withdrawals: ${drainedStakeLimitRate.times( + 100, + )}% of stake limit is drained and unfinalized queue is on par with drained stake limit`, + description: `Unfinalized queue: ${unfinalizedStETH.right + .div(ETH_DECIMALS) + .toFixed(2)} stETH\nDrained stake limit: ${drainedStakeLimit.div(ETH_DECIMALS).toFixed(2)} stETH`, + alertId: 'WITHDRAWALS-UNFINALIZED-QUEUE-AND-STAKE-LIMIT', + severity: FindingSeverity.High, + type: FindingType.Suspicious, + }), + ) + this.cache.setLastQueueOnParStakeLimitAlertTimestamp(blockTimestamp) + } + + return findings + } + + public async handleUnfinalizedRequestNumber(blockEvent: BlockEvent): Promise { + const currentBlockTimestamp = blockEvent.block.timestamp + + let unfinalizedStETH = new BigNumber(0) + + const out: Finding[] = [] + if (currentBlockTimestamp >= this.cache.getLastFinalizedTimestamp()) { + const unfinalizedStETHraw = await this.ethProvider.getUnfinalizedStETH(blockEvent.blockNumber) + if (E.isLeft(unfinalizedStETHraw)) { + const f: Finding = Finding.fromObject({ + name: `Error in ${WithdrawalsSrv.name}.${this.handleUnfinalizedRequestNumber.name}:292`, + description: `Could not call "wdQueueContract.unfinalizedStETH. Cause ${unfinalizedStETHraw.left.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: `${unfinalizedStETHraw.left.stack}` }, + }) + + return [f] + } + + unfinalizedStETH = unfinalizedStETHraw.right.div(ETH_DECIMALS) + if ( + currentBlockTimestamp - this.cache.getLastBigUnfinalizedQueueAlertTimestamp() > + BIG_UNFINALIZED_QUEUE_TRIGGER_EVERY + ) { + if (unfinalizedStETH.gte(BIG_UNFINALIZED_QUEUE_THRESHOLD)) { + // if alert hasn't been sent after last finalized batch + // and unfinalized queue is more than `BIG_UNFINALIZED_QUEUE_THRESHOLD` StETH + out.push( + Finding.fromObject({ + name: `⚠️ Withdrawals: unfinalized queue is more than ${BIG_UNFINALIZED_QUEUE_THRESHOLD} stETH`, + description: `Unfinalized queue is ${unfinalizedStETH.toFixed(2)} stETH`, + alertId: 'WITHDRAWALS-BIG-UNFINALIZED-QUEUE', + severity: FindingSeverity.Medium, + type: FindingType.Info, + }), + ) + + this.cache.setLastBigUnfinalizedQueueAlertTimestamp(currentBlockTimestamp) + } + } + } + + if (!this.cache.getIsBunkerMode() && unfinalizedStETH.gt(0)) { + if (currentBlockTimestamp - LONG_UNFINALIZED_QUEUE_THRESHOLD > this.cache.getFirstUnfinalizedRequestTimestamp()) { + if ( + currentBlockTimestamp - this.cache.getLastLongUnfinalizedQueueAlertTimestamp() > + LONG_UNFINALIZED_QUEUE_TRIGGER_EVERY + ) { + // if we are in turbo mode and unfinalized queue is not finalized for 5 days + // and alert hasn't been sent for 1 day + out.push( + Finding.fromObject({ + name: '⚠️ Withdrawals: unfinalized queue wait time is too long', + description: `Unfinalized queue wait time is ${formatDelay( + currentBlockTimestamp - this.cache.getFirstUnfinalizedRequestTimestamp(), + )}`, + alertId: 'WITHDRAWALS-LONG-UNFINALIZED-QUEUE', + severity: FindingSeverity.Medium, + type: FindingType.Info, + }), + ) + + this.cache.setLastLongUnfinalizedQueueAlertTimestamp(currentBlockTimestamp) + } + } + } + + return out + } + + public async handleUnclaimedRequests(blockEvent: BlockEvent): Promise { + const out: Finding[] = [] + const currentBlockTimestamp = blockEvent.block.timestamp + + const outdatedClaimedReqIds: number[] = [] + let unclaimedStETH = new BigNumber(0) + let claimedStETH = new BigNumber(0) + + const unclaimedReqIds: number[] = [] + for (const [id, req] of this.cache.getFinalizedWithdrawalRequestsMap().entries()) { + if (!req.claimed) { + unclaimedReqIds.push(id) + } + } + if (unclaimedReqIds.length == 0) { + return [] + } + const unclaimedRequestsStatuses = await this.ethProvider.getWithdrawalStatuses( + unclaimedReqIds, + blockEvent.blockNumber, + ) + if (E.isLeft(unclaimedRequestsStatuses)) { + const f: Finding = Finding.fromObject({ + name: `Error in ${WithdrawalsSrv.name}.${this.handleUnclaimedRequests.name}:363`, + description: `Could not call "wdQueueContract.getWithdrawalStatuses. Cause ${unclaimedRequestsStatuses.left.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: `${unclaimedRequestsStatuses.left.stack}` }, + }) + + return [f] + } + + for (const [index, reqStatus] of unclaimedRequestsStatuses.right.entries()) { + const reqId = unclaimedReqIds[index] + const curr = this.cache.getFinalizedWithdrawalRequestsMap().get(reqId) + if (curr === undefined) { + const f: Finding = Finding.fromObject({ + name: `Error in ${WithdrawalsSrv.name}.${this.handleUnclaimedRequests.name}:377`, + description: `FinalizedWithdrawalRequestsMap is broken. FinalizedWithdrawalRequestsMap(${reqId}) does not contain value.`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.High, + type: FindingType.Unknown, + }) + + return [f] + } + + const withdrawalRequest: WithdrawalRequest = { + id: curr.id, + amount: new BigNumber(String(reqStatus.amountOfStETH)), + claimed: reqStatus.isClaimed, + timestamp: curr.timestamp, + } + + this.cache.getFinalizedWithdrawalRequestsMap().set(reqId, withdrawalRequest) + } + + for (const [id, req] of this.cache.getFinalizedWithdrawalRequestsMap()) { + const isOutdated = currentBlockTimestamp - Number(req.timestamp) > UNCLAIMED_REQUESTS_TIME_WINDOW + if (isOutdated) { + if (req.claimed) { + outdatedClaimedReqIds.push(id) + } + } + if (!isOutdated && req.claimed) { + claimedStETH = claimedStETH.plus(req.amount as BigNumber) + } + if (!req.claimed) { + unclaimedStETH = unclaimedStETH.plus(req.amount as BigNumber) + } + } + + for (const id of outdatedClaimedReqIds) { + this.cache.getFinalizedWithdrawalRequestsMap().delete(id) + } + + const totalFinalizedSize = claimedStETH.plus(unclaimedStETH) + const unclaimedSizeRate = unclaimedStETH.div(totalFinalizedSize) + if ( + currentBlockTimestamp - this.cache.getLastUnclaimedRequestsAlertTimestamp() > + UNCLAIMED_REQUESTS_SIZE_RATE_TRIGGER_EVERY + ) { + if (unclaimedSizeRate.gte(UNCLAIMED_REQUESTS_SIZE_RATE_THRESHOLD)) { + out.push( + Finding.fromObject({ + name: `πŸ€” Withdrawals: ${unclaimedSizeRate.times(100).toFixed(2)}% of finalized requests are unclaimed`, + description: `Unclaimed (for all time): ${unclaimedStETH + .div(ETH_DECIMALS) + .toFixed(2)} stETH\nClaimed (for 2 weeks): ${claimedStETH + .div(ETH_DECIMALS) + .toFixed(2)} stETH\nTotal finalized: ${totalFinalizedSize.div(ETH_DECIMALS).toFixed(2)} stETH`, + alertId: 'WITHDRAWALS-UNCLAIMED-REQUESTS', + severity: FindingSeverity.Info, + type: FindingType.Suspicious, + }), + ) + this.cache.setLastUnclaimedRequestsAlertTimestamp(currentBlockTimestamp) + } + } + if ( + currentBlockTimestamp - this.cache.getLastUnclaimedMoreThanBalanceAlertTimestamp() > + UNCLAIMED_REQUESTS_MORE_THAN_BALANCE_TRIGGER_EVERY + ) { + const withdrawalQueueBalance = await this.ethProvider.getBalance( + this.wdQueueContract.address, + blockEvent.block.number, + ) + if (E.isLeft(withdrawalQueueBalance)) { + const f: Finding = Finding.fromObject({ + name: `Error in ${WithdrawalsSrv.name}.${this.handleUnclaimedRequests.name}:452`, + description: `Could not get withdrawalQueueBalance. Cause ${withdrawalQueueBalance.left.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: `${withdrawalQueueBalance.left.stack}` }, + }) + + return [f] + } + + if (unclaimedStETH.gt(withdrawalQueueBalance.right)) { + out.push( + Finding.fromObject({ + name: `πŸ€” Withdrawals: unclaimed requests size is more than withdrawal queue balance`, + description: `Unclaimed: ${unclaimedStETH + .div(ETH_DECIMALS) + .toFixed(2)} stETH\nWithdrawal queue balance: ${withdrawalQueueBalance.right + .div(ETH_DECIMALS) + .toFixed(2)} ETH\nDifference: ${unclaimedStETH.minus(withdrawalQueueBalance.right)} wei`, + alertId: 'WITHDRAWALS-UNCLAIMED-REQUESTS-MORE-THAN-BALANCE', + severity: FindingSeverity.Critical, + type: FindingType.Suspicious, + }), + ) + this.cache.setLastUnclaimedMoreThanBalanceAlertTimestamp(currentBlockTimestamp) + } + } + + return out + } + + public handleBunkerStatus(txEvent: TransactionEvent): Finding[] { + const [bunkerEnabled] = filterLog(txEvent.logs, WITHDRAWALS_BUNKER_MODE_ENABLED_EVENT, this.wdQueueContract.address) + + const out: Finding[] = [] + + if (bunkerEnabled) { + this.cache.setIsBunkerMode(true) + this.cache.setBunkerModeEnabledSinceTimestamp(bunkerEnabled.args._sinceTimestamp) + + out.push( + Finding.fromObject({ + name: '🚨 Withdrawals: BUNKER MODE ON! 🚨', + description: `Started from ${new Date( + String(this.cache.getBunkerModeEnabledSinceTimestamp()), + ).toUTCString()}`, + alertId: 'WITHDRAWALS-BUNKER-ENABLED', + severity: FindingSeverity.Critical, + type: FindingType.Degraded, + }), + ) + + return out + } + + const [bunkerDisabled] = filterLog( + txEvent.logs, + WITHDRAWALS_BUNKER_MODE_DISABLED_EVENT, + this.wdQueueContract.address, + ) + if (bunkerDisabled) { + this.cache.setIsBunkerMode(false) + const delay = formatDelay(txEvent.block.timestamp - Number(this.cache.getBunkerModeEnabledSinceTimestamp())) + out.push( + Finding.fromObject({ + name: 'βœ… Withdrawals: BUNKER MODE OFF! βœ…', + description: `Bunker lasted ${delay}`, + alertId: 'WITHDRAWALS-BUNKER-DISABLED', + severity: FindingSeverity.High, + type: FindingType.Info, + }), + ) + return out + } + + return out + } + + public handleWithdrawalRequest(txEvent: TransactionEvent): Finding[] { + const requestEvents = filterLog( + txEvent.logs, + WITHDRAWAL_QUEUE_WITHDRAWAL_REQUESTED_EVENT, + this.wdQueueContract.address, + ) + if (!requestEvents) { + return [] + } + + if ( + this.cache.getFirstUnfinalizedRequestTimestamp() < this.cache.getLastFinalizedTimestamp() && + txEvent.timestamp >= this.cache.getLastFinalizedTimestamp() + ) { + this.cache.setFirstUnfinalizedRequestTimestamp(txEvent.timestamp) + } + + const perRequesterAmounts = new Map() + for (const event of requestEvents) { + perRequesterAmounts.set( + event.args.requestor, + (perRequesterAmounts.get(event.args.requestor) || new BigNumber(0)).plus( + new BigNumber(String(event.args.amountOfStETH)).div(ETH_DECIMALS), + ), + ) + } + + const out: Finding[] = [] + for (const [requester, amounts] of perRequesterAmounts.entries()) { + if (amounts.gte(BIG_WITHDRAWAL_REQUEST_THRESHOLD)) { + out.push( + Finding.fromObject({ + name: `ℹ️ Huge stETH withdrawal requests batch`, + description: `Requester: ${etherscanAddress(requester)}\nAmount: ${amounts.toFixed(2)} stETH`, + alertId: 'WITHDRAWALS-BIG-WITHDRAWAL-REQUEST-BATCH', + severity: FindingSeverity.Info, + type: FindingType.Info, + }), + ) + } + + this.cache.setAmountOfRequestedStETHSinceLastTokenRebase( + this.cache.getAmountOfRequestedStETHSinceLastTokenRebase().plus(amounts), + ) + } + + if (this.cache.getAmountOfRequestedStETHSinceLastTokenRebase().gte(BIG_WITHDRAWAL_REQUEST_AFTER_REBASE_THRESHOLD)) { + if (this.cache.getLastBigRequestAfterRebaseAlertTimestamp() < this.cache.getLastTokenRebaseTimestamp()) { + out.push( + Finding.fromObject({ + name: `⚠️ Withdrawals: the sum of received withdrawal requests since the last rebase greater than ${BIG_WITHDRAWAL_REQUEST_AFTER_REBASE_THRESHOLD} stETH`, + description: `Amount: ${this.cache.getAmountOfRequestedStETHSinceLastTokenRebase().toFixed(2)} stETH`, + alertId: 'WITHDRAWALS-BIG-WITHDRAWAL-REQUEST-AFTER-REBASE', + severity: FindingSeverity.High, + type: FindingType.Info, + }), + ) + + this.cache.setLastBigRequestAfterRebaseAlertTimestamp(txEvent.timestamp) + } + } + + return out + } + + public handleLastTokenRebase(txEvent: TransactionEvent): void { + const [rebaseEvent] = filterLog(txEvent.logs, LIDO_TOKEN_REBASED_EVENT, this.lidoContract.address) + if (!rebaseEvent) { + return + } + + this.cache.setLastTokenRebaseTimestamp(txEvent.timestamp) + this.cache.setAmountOfRequestedStETHSinceLastTokenRebase(new BigNumber(0)) + } + + public handleWithdrawalFinalized(txEvent: TransactionEvent): void { + const [withdrawalEvent] = filterLog( + txEvent.logs, + WITHDRAWAL_QUEUE_WITHDRAWALS_FINALIZED_EVENT, + this.wdQueueContract.address, + ) + if (!withdrawalEvent) { + return + } + + const finalizedIds: number[] = [] + for (let i = Number(withdrawalEvent.args.from); i <= Number(withdrawalEvent.args.to); i++) { + finalizedIds.push(i) + } + + for (const reqId of finalizedIds) { + if (!this.cache.getFinalizedWithdrawalRequestsMap().has(reqId)) { + this.cache.getFinalizedWithdrawalRequestsMap().set(reqId, { + id: reqId, + amount: undefined, // will be set in `handleUnclaimedRequests` + claimed: false, + timestamp: String(withdrawalEvent.args.timestamp), + }) + } + } + + this.cache.setLastFinalizedRequestId(Number(withdrawalEvent.args.to)) + this.cache.setLastFinalizedTimestamp(Number(withdrawalEvent.args.timestamp)) + } + + public handleWithdrawalClaimed(txEvent: TransactionEvent): Finding[] { + const claimedEvents = filterLog( + txEvent.logs, + WITHDRAWAL_QUEUE_WITHDRAWAL_CLAIMED_EVENT, + this.wdQueueContract.address, + ) + if (claimedEvents.length === 0) { + return [] + } + + const currentBlockTimestamp = txEvent.block.timestamp + if (currentBlockTimestamp - this.cache.getLastClaimedAmountMoreThanRequestedAlertTimestamp() > ONE_HOUR) { + this.cache.setClaimedAmountMoreThanRequestedAlertsCount(0) + } + + if ( + this.cache.getClaimedAmountMoreThanRequestedAlertsCount() >= + CLAIMED_AMOUNT_MORE_THAN_REQUESTED_MAX_ALERTS_PER_HOUR + ) { + return [] + } + + const out: Finding[] = [] + for (const event of claimedEvents) { + const reqId = Number(event.args.requestId) + if (this.cache.getFinalizedWithdrawalRequestsMap().has(reqId)) { + const curr = this.cache.getFinalizedWithdrawalRequestsMap().get(reqId) as WithdrawalRequest + const claimedAmount = new BigNumber(String(event.args.amountOfETH)) + + const currAmount = curr.amount === undefined ? new BigNumber(0) : curr.amount + + if (claimedAmount.gt(currAmount)) { + out.push( + Finding.fromObject({ + name: `πŸ€” Withdrawals: claimed amount is more than requested`, + description: `Request ID: ${etherscanNft(this.wdQueueContract.address, reqId)}\nClaimed: ${claimedAmount + .div(ETH_DECIMALS) + .toFixed(2)} ETH\nRequested: ${(curr.amount as BigNumber) + .div(ETH_DECIMALS) + .toFixed(2)} stETH\nDifference: ${claimedAmount.minus( + curr.amount as BigNumber, + )} wei\nOwner: ${etherscanAddress(event.args.owner)}\nReceiver: ${etherscanAddress(event.args.receiver)}`, + alertId: 'WITHDRAWALS-CLAIMED-AMOUNT-MORE-THAN-REQUESTED', + severity: FindingSeverity.Critical, + type: FindingType.Suspicious, + }), + ) + + this.cache.setClaimedAmountMoreThanRequestedAlertsCount( + this.cache.getClaimedAmountMoreThanRequestedAlertsCount() + 1, + ) + + this.cache.setLastClaimedAmountMoreThanRequestedAlertTimestamp(currentBlockTimestamp) + } + + this.cache.getFinalizedWithdrawalRequestsMap().set(reqId, { + id: curr.id, + amount: new BigNumber(String(event.args.amountOfETH)), + claimed: true, + timestamp: curr.timestamp, + }) + } + } + + return out + } + + public handleEventsOfNotice(txEvent: TransactionEvent, eventsOfNotice: EventOfNotice[]): Finding[] { + const out: Finding[] = [] + for (const eventInfo of eventsOfNotice) { + if (eventInfo.address in txEvent.addresses) { + const filteredEvents = filterLog(txEvent.logs, eventInfo.event, eventInfo.address) + + for (const filteredEvent of filteredEvents) { + out.push( + Finding.fromObject({ + name: eventInfo.name, + description: eventInfo.description(filteredEvent.args), + alertId: eventInfo.alertId, + severity: eventInfo.severity, + type: eventInfo.type, + metadata: { args: String(filteredEvent.args) }, + }), + ) + } + } + } + + return out + } +} diff --git a/ethereum-steth-v2/src/utils/constants.testnet.ts b/ethereum-steth-v2/src/utils/constants.testnet.ts new file mode 100644 index 00000000..cdc16c9b --- /dev/null +++ b/ethereum-steth-v2/src/utils/constants.testnet.ts @@ -0,0 +1,47 @@ +import { Address, ERC20 } from './constants' + +const DEPOSIT_SECURITY_ADDRESS: string = '0xe57025e250275ca56f92d76660decfc490c7e79a' +const BURNER_ADDRESS: string = '0x20c61c07c2e2fab04bf5b4e12ce45a459a18f3b1' +const LDO_ADDRESS: string = '0x56340274fB5a72af1A3C6609061c451De7961Bd4' +const LIDO_STETH_ADDRESS: string = '0x1643E812aE58766192Cf7D2Cf9567dF2C37e9B7F' +const WSTETH_ADDRESS: string = '0x6320cd32aa674d2898a68ec82e869385fc5f7e2f' +const WITHDRAWALS_QUEUE_ADDRESS: string = '0xcf117961421ca9e546cd7f50bc73abcdb3039533' +const DEPOSIT_EXECUTOR_ADDRESS: string = '0x745ad85f7c20ea6f3c85b830208394e0d70a31ea' +const INSURANCE_FUND_ADDRESS: string = '0x2fae4d2d86efb17249f24c9fb70855d4c58585a5' +const DAI_ADDRESS: string = '0x6B175474E89094C44Da98b954EedeAC495271d0F' +const USDT_ADDRESS: string = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +const USDC_ADDRESS: string = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' +const GATE_SEAL_DEFAULT_ADDRESS: string = '0xee656d0c8e9f0646b505a13295c188164a3d8eac' +const EXITBUS_ORACLE_ADDRESS: string = '0xb75a55efab5a8f5224ae93b34b25741edd3da98b' +const GATE_SEAL_FACTORY_ADDRESS: string = '0x248c4a34645921c70a591ccc200cb75d6a4c5091' +const WITHDRAWALS_VAULT_ADDRESS: string = '0xdc62f9e8C34be08501Cdef4EBDE0a280f576D762' +const EL_REWARDS_VAULT_ADDRESS: string = '0x94750381bE1AbA0504C666ee1DB118F68f0780D4' + +const KNOWN_ERC20 = new Map([ + [LIDO_STETH_ADDRESS, { decimals: 18, name: 'stETH' }], + [WSTETH_ADDRESS, { decimals: 18, name: 'wstETH' }], + [LDO_ADDRESS, { decimals: 18, name: 'LDO' }], + [DAI_ADDRESS, { decimals: 18, name: 'DAI' }], + [USDT_ADDRESS, { decimals: 6, name: 'USDT' }], + [USDC_ADDRESS, { decimals: 6, name: 'USDC' }], +]) + +export const TestNetAddress: Address = { + DEPOSIT_SECURITY_ADDRESS: DEPOSIT_SECURITY_ADDRESS, + BURNER_ADDRESS: BURNER_ADDRESS, + LDO_ADDRESS: LDO_ADDRESS, + LIDO_STETH_ADDRESS: LIDO_STETH_ADDRESS, + WSTETH_ADDRESS: WSTETH_ADDRESS, + WITHDRAWALS_QUEUE_ADDRESS: WITHDRAWALS_QUEUE_ADDRESS, + DEPOSIT_EXECUTOR_ADDRESS: DEPOSIT_EXECUTOR_ADDRESS, + INSURANCE_FUND_ADDRESS: INSURANCE_FUND_ADDRESS, + DAI_ADDRESS: DAI_ADDRESS, + USDT_ADDRESS: USDT_ADDRESS, + USDC_ADDRESS: USDC_ADDRESS, + GATE_SEAL_DEFAULT_ADDRESS: GATE_SEAL_DEFAULT_ADDRESS, + EXITBUS_ORACLE_ADDRESS: EXITBUS_ORACLE_ADDRESS, + GATE_SEAL_FACTORY_ADDRESS: GATE_SEAL_FACTORY_ADDRESS, + WITHDRAWALS_VAULT_ADDRESS: WITHDRAWALS_VAULT_ADDRESS, + EL_REWARDS_VAULT_ADDRESS: EL_REWARDS_VAULT_ADDRESS, + KNOWN_ERC20: KNOWN_ERC20, +} diff --git a/ethereum-steth-v2/src/utils/constants.ts b/ethereum-steth-v2/src/utils/constants.ts index 48fe31f6..6982b2a8 100644 --- a/ethereum-steth-v2/src/utils/constants.ts +++ b/ethereum-steth-v2/src/utils/constants.ts @@ -5,28 +5,46 @@ export type ERC20 = { name: string } -export const RUN_TIER = process.env.FORTA_AGENT_RUN_TIER +export type Address = { + DEPOSIT_SECURITY_ADDRESS: string + BURNER_ADDRESS: string + LDO_ADDRESS: string + LIDO_STETH_ADDRESS: string + WSTETH_ADDRESS: string + WITHDRAWALS_QUEUE_ADDRESS: string + DEPOSIT_EXECUTOR_ADDRESS: string + INSURANCE_FUND_ADDRESS: string + DAI_ADDRESS: string + USDT_ADDRESS: string + USDC_ADDRESS: string + GATE_SEAL_DEFAULT_ADDRESS: string + EXITBUS_ORACLE_ADDRESS: string + GATE_SEAL_FACTORY_ADDRESS: string + WITHDRAWALS_VAULT_ADDRESS: string + EL_REWARDS_VAULT_ADDRESS: string + KNOWN_ERC20: Map +} export const ETH_DECIMALS = new BigNumber(10).pow(18) -export const DEPOSIT_SECURITY_ADDRESS = '0xc77f8768774e1c9244beed705c4354f2113cfc09' - -export const BURNER_ADDRESS = '0xd15a672319cf0352560ee76d9e89eab0889046d3' -export const LDO_ADDRESS = '0x5a98fcbea516cf06857215779fd812ca3bef1b32' -export const LIDO_STETH_ADDRESS = '0xae7ab96520de3a18e5e111b5eaab095312d7fe84' -export const WSTETH_ADDRESS = '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0' - -export const WITHDRAWAL_QUEUE_ADDRESS = '0x889edc2edab5f40e902b864ad4d7ade8e412f9b1' - -export const DEPOSIT_EXECUTOR_ADDRESS = '0xf82ac5937a20dc862f9bc0668779031e06000f17' +const DEPOSIT_SECURITY_ADDRESS: string = '0xc77f8768774e1c9244beed705c4354f2113cfc09' +const BURNER_ADDRESS: string = '0xd15a672319cf0352560ee76d9e89eab0889046d3' +const LDO_ADDRESS: string = '0x5a98fcbea516cf06857215779fd812ca3bef1b32' +const LIDO_STETH_ADDRESS: string = '0xae7ab96520de3a18e5e111b5eaab095312d7fe84' +const WSTETH_ADDRESS: string = '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0' +const WITHDRAWALS_QUEUE_ADDRESS: string = '0x889edc2edab5f40e902b864ad4d7ade8e412f9b1' +const DEPOSIT_EXECUTOR_ADDRESS: string = '0xf82ac5937a20dc862f9bc0668779031e06000f17' +const INSURANCE_FUND_ADDRESS: string = '0x8b3f33234abd88493c0cd28de33d583b70bede35' +const DAI_ADDRESS: string = '0x6b175474e89094c44da98b954eedeac495271d0f' +const USDT_ADDRESS: string = '0xdac17f958d2ee523a2206206994597c13d831ec7' +const USDC_ADDRESS: string = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' +const GATE_SEAL_DEFAULT_ADDRESS: string = '0x1ad5cb2955940f998081c1ef5f5f00875431aa90' +const EXITBUS_ORACLE_ADDRESS: string = '0x0de4ea0184c2ad0baca7183356aea5b8d5bf5c6e' +const GATE_SEAL_FACTORY_ADDRESS: string = '0x6c82877cac5a7a739f16ca0a89c0a328b8764a24' +const WITHDRAWALS_VAULT_ADDRESS: string = '0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f' +const EL_REWARDS_VAULT_ADDRESS: string = '0x388c818ca8b9251b393131c08a736a67ccb19297' -export const INSURANCE_FUND_ADDRESS = '0x8b3f33234abd88493c0cd28de33d583b70bede35' - -export const DAI_ADDRESS = '0x6b175474e89094c44da98b954eedeac495271d0f' -export const USDT_ADDRESS = '0xdac17f958d2ee523a2206206994597c13d831ec7' -export const USDC_ADDRESS = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' - -export const KNOWN_ERC20 = new Map([ +const KNOWN_ERC20 = new Map([ [LIDO_STETH_ADDRESS, { decimals: 18, name: 'stETH' }], [WSTETH_ADDRESS, { decimals: 18, name: 'wstETH' }], [LDO_ADDRESS, { decimals: 18, name: 'LDO' }], @@ -34,3 +52,23 @@ export const KNOWN_ERC20 = new Map([ [USDT_ADDRESS, { decimals: 6, name: 'USDT' }], [USDC_ADDRESS, { decimals: 6, name: 'USDC' }], ]) + +export const Address: Address = { + DEPOSIT_SECURITY_ADDRESS: DEPOSIT_SECURITY_ADDRESS, + BURNER_ADDRESS: BURNER_ADDRESS, + LDO_ADDRESS: LDO_ADDRESS, + LIDO_STETH_ADDRESS: LIDO_STETH_ADDRESS, + WSTETH_ADDRESS: WSTETH_ADDRESS, + WITHDRAWALS_QUEUE_ADDRESS: WITHDRAWALS_QUEUE_ADDRESS, + DEPOSIT_EXECUTOR_ADDRESS: DEPOSIT_EXECUTOR_ADDRESS, + INSURANCE_FUND_ADDRESS: INSURANCE_FUND_ADDRESS, + DAI_ADDRESS: DAI_ADDRESS, + USDT_ADDRESS: USDT_ADDRESS, + USDC_ADDRESS: USDC_ADDRESS, + GATE_SEAL_DEFAULT_ADDRESS: GATE_SEAL_DEFAULT_ADDRESS, + EXITBUS_ORACLE_ADDRESS: EXITBUS_ORACLE_ADDRESS, + GATE_SEAL_FACTORY_ADDRESS: GATE_SEAL_FACTORY_ADDRESS, + WITHDRAWALS_VAULT_ADDRESS: WITHDRAWALS_VAULT_ADDRESS, + EL_REWARDS_VAULT_ADDRESS: EL_REWARDS_VAULT_ADDRESS, + KNOWN_ERC20: KNOWN_ERC20, +} diff --git a/ethereum-steth-v2/src/utils/error.ts b/ethereum-steth-v2/src/utils/error.ts deleted file mode 100644 index 865f75b2..00000000 --- a/ethereum-steth-v2/src/utils/error.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Finding, FindingSeverity, FindingType } from 'forta-agent' - -export function errorToFinding(e: unknown, className: string, fnName: string): Finding { - const err: Error = e instanceof Error ? e : new Error(`non-Error thrown: ${e}`) - - return Finding.fromObject({ - name: `Error in ${className}.${fnName}`, - description: `${err.message}`, - alertId: 'LIDO-AGENT-ERROR', - severity: FindingSeverity.High, - type: FindingType.Degraded, - metadata: { stack: `${err.stack}` }, - }) -} diff --git a/ethereum-steth-v2/src/utils/events/burner_events.ts b/ethereum-steth-v2/src/utils/events/burner_events.ts index 5b99dcc7..7a508224 100644 --- a/ethereum-steth-v2/src/utils/events/burner_events.ts +++ b/ethereum-steth-v2/src/utils/events/burner_events.ts @@ -1,34 +1,35 @@ import { EventOfNotice } from '../../entity/events' -import { etherscanAddress } from '../tier' import { FindingSeverity, FindingType } from 'forta-agent' -import { BURNER_ADDRESS } from '../constants' import { Result } from '@ethersproject/abi/lib' +import { etherscanAddress } from '../string' -export const BURNER_EVENTS: EventOfNotice[] = [ - { - address: BURNER_ADDRESS, - event: 'event ERC20Recovered(address indexed requestedBy, address indexed token,uint256 amount)', - alertId: 'LIDO-BURNER-ERC20-RECOVERED', - name: 'ℹ️ Lido Burner: ERC20 recovered', - description: (args: Result) => - `ERC20 recovered:\n` + - `Requested by: ${etherscanAddress(args.requestedBy)}\n` + - `Token: ${etherscanAddress(args.token)}\n` + - `Amount: ${args.amount}`, - severity: FindingSeverity.Info, - type: FindingType.Info, - }, - { - address: BURNER_ADDRESS, - event: 'event ERC721Recovered(address indexed requestedBy, address indexed token, uint256 tokenId)', - alertId: 'LIDO-BURNE-ERC721-RECOVERED', - name: 'ℹ️ Lido Burner: ERC721 recovered', - description: (args: Result) => - `ERC721 recovered:\n` + - `Requested by: ${etherscanAddress(args.requestedBy)}\n` + - `Token: ${etherscanAddress(args.token)}\n` + - `Token ID: ${args.tokenId}`, - severity: FindingSeverity.Info, - type: FindingType.Info, - }, -] +export function getBurnerEvents(BURNER_ADDRESS: string): EventOfNotice[] { + return [ + { + address: BURNER_ADDRESS, + event: 'event ERC20Recovered(address indexed requestedBy, address indexed token,uint256 amount)', + alertId: 'LIDO-BURNER-ERC20-RECOVERED', + name: 'ℹ️ Lido Burner: ERC20 recovered', + description: (args: Result) => + `ERC20 recovered:\n` + + `Requested by: ${etherscanAddress(args.requestedBy)}\n` + + `Token: ${etherscanAddress(args.token)}\n` + + `Amount: ${args.amount}`, + severity: FindingSeverity.Info, + type: FindingType.Info, + }, + { + address: BURNER_ADDRESS, + event: 'event ERC721Recovered(address indexed requestedBy, address indexed token, uint256 tokenId)', + alertId: 'LIDO-BURNER-ERC721-RECOVERED', + name: 'ℹ️ Lido Burner: ERC721 recovered', + description: (args: Result) => + `ERC721 recovered:\n` + + `Requested by: ${etherscanAddress(args.requestedBy)}\n` + + `Token: ${etherscanAddress(args.token)}\n` + + `Token ID: ${args.tokenId}`, + severity: FindingSeverity.Info, + type: FindingType.Info, + }, + ] +} diff --git a/ethereum-steth-v2/src/utils/events/deposit_security_events.ts b/ethereum-steth-v2/src/utils/events/deposit_security_events.ts index 69e868ce..2d52c142 100644 --- a/ethereum-steth-v2/src/utils/events/deposit_security_events.ts +++ b/ethereum-steth-v2/src/utils/events/deposit_security_events.ts @@ -1,81 +1,82 @@ import { EventOfNotice } from '../../entity/events' -import { DEPOSIT_SECURITY_ADDRESS } from '../constants' import { FindingSeverity, FindingType } from 'forta-agent' -import { etherscanAddress } from '../tier' import { Result } from '@ethersproject/abi/lib' +import { etherscanAddress } from '../string' -export const DEPOSIT_SECURITY_EVENTS: EventOfNotice[] = [ - { - address: DEPOSIT_SECURITY_ADDRESS, - event: 'event DepositsPaused(address indexed guardian, uint24 indexed stakingModuleId)', - alertId: 'LIDO-DEPOSITS-PAUSED', - name: '🚨 Deposit Security: Deposits paused', - description: (args: Result) => - `Deposits were paused by ${etherscanAddress(args.guardian)} for ${args.stakingModuleId} staking module`, - severity: FindingSeverity.Critical, - type: FindingType.Info, - }, - { - address: DEPOSIT_SECURITY_ADDRESS, - event: 'event DepositsUnpaused(uint24 indexed stakingModuleId)', - alertId: 'LIDO-DEPOSITS-UNPAUSED', - name: 'βœ… Deposit Security: Deposits resumed', - description: (args: Result) => `Deposits were resumed for ${args.stakingModuleId} staking module`, - severity: FindingSeverity.High, - type: FindingType.Info, - }, - { - address: DEPOSIT_SECURITY_ADDRESS, - event: 'event GuardianAdded(address guardian)', - alertId: 'LIDO-DEPOSITOR-GUARDIAN-ADDED', - name: '⚠️ Deposit Security: Guardian added', - description: (args: Result) => `New guardian added ${etherscanAddress(args.guardian)}`, - severity: FindingSeverity.High, - type: FindingType.Info, - }, - { - address: DEPOSIT_SECURITY_ADDRESS, - event: 'event GuardianRemoved(address guardian)', - alertId: 'LIDO-DEPOSITOR-GUARDIAN-REMOVED', - name: '⚠️ Deposit Security: Guardian removed', - description: (args: Result) => `Guardian ${etherscanAddress(args.guardian)} was removed`, - severity: FindingSeverity.High, - type: FindingType.Info, - }, - { - address: DEPOSIT_SECURITY_ADDRESS, - event: 'event GuardianQuorumChanged(uint256 newValue)', - alertId: 'LIDO-DEPOSITOR-GUARDIAN-QUORUM-CHANGED', - name: '🚨 Deposit Security: Guardian quorum changed', - description: (args: Result) => `New quorum size ${args.newValue}`, - severity: FindingSeverity.High, - type: FindingType.Info, - }, - { - address: DEPOSIT_SECURITY_ADDRESS, - event: 'event MaxDepositsChanged(uint256 newValue)', - alertId: 'LIDO-DEPOSITOR-MAX-DEPOSITS-CHANGED', - name: '⚠️ Deposit Security: Max deposits changed', - description: (args: Result) => `New value ${args.newValue}`, - severity: FindingSeverity.High, - type: FindingType.Info, - }, - { - address: DEPOSIT_SECURITY_ADDRESS, - event: 'event MinDepositBlockDistanceChanged(uint256 newValue)', - alertId: 'LIDO-DEPOSITOR-MIN-DEPOSITS-BLOCK-DISTANCE-CHANGED', - name: '⚠️ Deposit Security: Min deposit block distance changed', - description: (args: Result) => `New value ${args.newValue}`, - severity: FindingSeverity.High, - type: FindingType.Info, - }, - { - address: DEPOSIT_SECURITY_ADDRESS, - event: 'event OwnerChanged(address newValue)', - alertId: 'LIDO-DEPOSITOR-OWNER-CHANGED', - name: '🚨 Deposit Security: Owner changed', - description: (args: Result) => `New owner ${etherscanAddress(args.newValue)}`, - severity: FindingSeverity.Critical, - type: FindingType.Info, - }, -] +export function getDepositSecurityEvents(DEPOSIT_SECURITY_ADDRESS: string): EventOfNotice[] { + return [ + { + address: DEPOSIT_SECURITY_ADDRESS, + event: 'event DepositsPaused(address indexed guardian, uint24 indexed stakingModuleId)', + alertId: 'LIDO-DEPOSITS-PAUSED', + name: '🚨 Deposit Security: Deposits paused', + description: (args: Result) => + `Deposits were paused by ${etherscanAddress(args.guardian)} for ${args.stakingModuleId} staking module`, + severity: FindingSeverity.Critical, + type: FindingType.Info, + }, + { + address: DEPOSIT_SECURITY_ADDRESS, + event: 'event DepositsUnpaused(uint24 indexed stakingModuleId)', + alertId: 'LIDO-DEPOSITS-UNPAUSED', + name: 'βœ… Deposit Security: Deposits resumed', + description: (args: Result) => `Deposits were resumed for ${args.stakingModuleId} staking module`, + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: DEPOSIT_SECURITY_ADDRESS, + event: 'event GuardianAdded(address guardian)', + alertId: 'LIDO-DEPOSITOR-GUARDIAN-ADDED', + name: '⚠️ Deposit Security: Guardian added', + description: (args: Result) => `New guardian added ${etherscanAddress(args.guardian)}`, + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: DEPOSIT_SECURITY_ADDRESS, + event: 'event GuardianRemoved(address guardian)', + alertId: 'LIDO-DEPOSITOR-GUARDIAN-REMOVED', + name: '⚠️ Deposit Security: Guardian removed', + description: (args: Result) => `Guardian ${etherscanAddress(args.guardian)} was removed`, + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: DEPOSIT_SECURITY_ADDRESS, + event: 'event GuardianQuorumChanged(uint256 newValue)', + alertId: 'LIDO-DEPOSITOR-GUARDIAN-QUORUM-CHANGED', + name: '🚨 Deposit Security: Guardian quorum changed', + description: (args: Result) => `New quorum size ${args.newValue}`, + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: DEPOSIT_SECURITY_ADDRESS, + event: 'event MaxDepositsChanged(uint256 newValue)', + alertId: 'LIDO-DEPOSITOR-MAX-DEPOSITS-CHANGED', + name: '⚠️ Deposit Security: Max deposits changed', + description: (args: Result) => `New value ${args.newValue}`, + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: DEPOSIT_SECURITY_ADDRESS, + event: 'event MinDepositBlockDistanceChanged(uint256 newValue)', + alertId: 'LIDO-DEPOSITOR-MIN-DEPOSITS-BLOCK-DISTANCE-CHANGED', + name: '⚠️ Deposit Security: Min deposit block distance changed', + description: (args: Result) => `New value ${args.newValue}`, + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: DEPOSIT_SECURITY_ADDRESS, + event: 'event OwnerChanged(address newValue)', + alertId: 'LIDO-DEPOSITOR-OWNER-CHANGED', + name: '🚨 Deposit Security: Owner changed', + description: (args: Result) => `New owner ${etherscanAddress(args.newValue)}`, + severity: FindingSeverity.Critical, + type: FindingType.Info, + }, + ] +} diff --git a/ethereum-steth-v2/src/utils/events/gate_seal_events.ts b/ethereum-steth-v2/src/utils/events/gate_seal_events.ts new file mode 100644 index 00000000..84b2db24 --- /dev/null +++ b/ethereum-steth-v2/src/utils/events/gate_seal_events.ts @@ -0,0 +1,4 @@ +export const GATE_SEAL_SEALED_EVENT = + 'event Sealed (address gate_seal, address sealed_by, uint256 sealed_for, address sealable, uint256 sealed_at)' + +export const GATE_SEAL_FACTORY_GATE_SEAL_CREATED_EVENT = 'event GateSealCreated (address gate_seal)' diff --git a/ethereum-steth-v2/src/utils/events/insurance_fund_events.ts b/ethereum-steth-v2/src/utils/events/insurance_fund_events.ts index c1a171a1..6dd750f6 100644 --- a/ethereum-steth-v2/src/utils/events/insurance_fund_events.ts +++ b/ethereum-steth-v2/src/utils/events/insurance_fund_events.ts @@ -1,75 +1,81 @@ import { EventOfNotice } from '../../entity/events' -import { ETH_DECIMALS, INSURANCE_FUND_ADDRESS, KNOWN_ERC20 } from '../constants' +import { ERC20, ETH_DECIMALS } from '../constants' import BigNumber from 'bignumber.js' -import { etherscanAddress } from '../tier' import { FindingSeverity, FindingType } from 'forta-agent' import { Result } from '@ethersproject/abi/lib' +import { etherscanAddress } from '../string' -export const INSURANCE_FUND_EVENTS: EventOfNotice[] = [ - { - address: INSURANCE_FUND_ADDRESS, - event: 'event EtherTransferred(address indexed _recipient, uint256 _amount)', - alertId: 'INS-FUND-ETH-TRANSFERRED', - name: '⚠️ Insurance fund: ETH transferred', - description: (args: Result) => - `${new BigNumber(String(args._amount)) - .div(ETH_DECIMALS) - .toFixed(2)} ETH were transferred from insurance fund to ${etherscanAddress(args._recipient)}`, - severity: FindingSeverity.Info, - type: FindingType.Info, - }, - { - address: INSURANCE_FUND_ADDRESS, - event: 'event ERC721Transferred(address indexed _token, address indexed _recipient, uint256 _tokenId, bytes _data)', - alertId: 'INS-FUND-ERC721-TRANSFERRED', - name: '⚠️ Insurance fund: ERC721 transferred', - description: (args: Result) => - `ERC721 token (address: ${etherscanAddress(args._token)}, id: ${ - args._tokenId - }) was transferred form insurance fund to ${etherscanAddress(args._recipient)}`, - severity: FindingSeverity.Info, - type: FindingType.Info, - }, - { - address: INSURANCE_FUND_ADDRESS, - event: 'event ERC20Transferred(address indexed _token, address indexed _recipient, uint256 _amount)', - alertId: 'INS-FUND-ERC20-TRANSFERRED', - name: '🚨 Insurance fund: ERC20 transferred', - description: (args: Result) => { - const tokenInfo = KNOWN_ERC20.get(args._token.toLowerCase()) || { - decimals: 18, - name: 'unknown', - } - return `${new BigNumber(String(args._amount)).div(10 ** tokenInfo.decimals).toFixed(2)} of ${etherscanAddress( - args._token, - )}(${tokenInfo.name}) were transferred from insurance fund to ${etherscanAddress(args._recipient)}` +export function getInsuranceFundEvents( + INSURANCE_FUND_ADDRESS: string, + KNOWN_ERC20: Map, +): EventOfNotice[] { + return [ + { + address: INSURANCE_FUND_ADDRESS, + event: 'event EtherTransferred(address indexed _recipient, uint256 _amount)', + alertId: 'INS-FUND-ETH-TRANSFERRED', + name: '⚠️ Insurance fund: ETH transferred', + description: (args: Result) => + `${new BigNumber(String(args._amount)) + .div(ETH_DECIMALS) + .toFixed(2)} ETH were transferred from insurance fund to ${etherscanAddress(args._recipient)}`, + severity: FindingSeverity.Info, + type: FindingType.Info, }, - severity: FindingSeverity.High, - type: FindingType.Info, - }, - { - address: INSURANCE_FUND_ADDRESS, - event: - 'event ERC1155Transferred(address indexed _token, address indexed _recipient, uint256 _tokenId, uint256 _amount, bytes _data)', - alertId: 'INS-FUND-ERC1155-TRANSFERRED', - name: '⚠️ Insurance fund: ERC1155 transferred', - description: (args: Result) => - `${args._amount} of ERC1155 token (address: ${etherscanAddress(args._token)}, id: ${ - args._tokenId - }) was transferred form insurance fund to ${etherscanAddress(args._recipient)}`, - severity: FindingSeverity.Info, - type: FindingType.Info, - }, - { - address: INSURANCE_FUND_ADDRESS, - event: 'event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)', - alertId: 'INS-FUND-OWNERSHIP-TRANSFERRED', - name: '🚨 Insurance fund: Ownership transferred', - description: (args: Result) => - `Owner of the insurance fund was transferred from ${etherscanAddress(args.previousOwner)} to ${etherscanAddress( - args.newOwner, - )}`, - severity: FindingSeverity.Critical, - type: FindingType.Info, - }, -] + { + address: INSURANCE_FUND_ADDRESS, + event: + 'event ERC721Transferred(address indexed _token, address indexed _recipient, uint256 _tokenId, bytes _data)', + alertId: 'INS-FUND-ERC721-TRANSFERRED', + name: '⚠️ Insurance fund: ERC721 transferred', + description: (args: Result) => + `ERC721 token (address: ${etherscanAddress(args._token)}, id: ${ + args._tokenId + }) was transferred form insurance fund to ${etherscanAddress(args._recipient)}`, + severity: FindingSeverity.Info, + type: FindingType.Info, + }, + { + address: INSURANCE_FUND_ADDRESS, + event: 'event ERC20Transferred(address indexed _token, address indexed _recipient, uint256 _amount)', + alertId: 'INS-FUND-ERC20-TRANSFERRED', + name: '🚨 Insurance fund: ERC20 transferred', + description: (args: Result) => { + const tokenInfo = KNOWN_ERC20.get(args._token.toLowerCase()) || { + decimals: 18, + name: 'unknown', + } + return `${new BigNumber(String(args._amount)).div(10 ** tokenInfo.decimals).toFixed(2)} of ${etherscanAddress( + args._token, + )}(${tokenInfo.name}) were transferred from insurance fund to ${etherscanAddress(args._recipient)}` + }, + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: INSURANCE_FUND_ADDRESS, + event: + 'event ERC1155Transferred(address indexed _token, address indexed _recipient, uint256 _tokenId, uint256 _amount, bytes _data)', + alertId: 'INS-FUND-ERC1155-TRANSFERRED', + name: '⚠️ Insurance fund: ERC1155 transferred', + description: (args: Result) => + `${args._amount} of ERC1155 token (address: ${etherscanAddress(args._token)}, id: ${ + args._tokenId + }) was transferred form insurance fund to ${etherscanAddress(args._recipient)}`, + severity: FindingSeverity.Info, + type: FindingType.Info, + }, + { + address: INSURANCE_FUND_ADDRESS, + event: 'event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)', + alertId: 'INS-FUND-OWNERSHIP-TRANSFERRED', + name: '🚨 Insurance fund: Ownership transferred', + description: (args: Result) => + `Owner of the insurance fund was transferred from ${etherscanAddress(args.previousOwner)} to ${etherscanAddress( + args.newOwner, + )}`, + severity: FindingSeverity.Critical, + type: FindingType.Info, + }, + ] +} diff --git a/ethereum-steth-v2/src/utils/events/lido_events.ts b/ethereum-steth-v2/src/utils/events/lido_events.ts index ae77a1c0..d4db37dc 100644 --- a/ethereum-steth-v2/src/utils/events/lido_events.ts +++ b/ethereum-steth-v2/src/utils/events/lido_events.ts @@ -1,96 +1,97 @@ -import { LIDO_STETH_ADDRESS } from '../constants' import { FindingSeverity, FindingType } from 'forta-agent' -import { etherscanAddress } from '../tier' import { EventOfNotice } from '../../entity/events' import { Result } from '@ethersproject/abi/lib' +import { etherscanAddress } from '../string' -export const LIDO_EVENTS: EventOfNotice[] = [ - { - address: LIDO_STETH_ADDRESS, - event: 'event Stopped()', - alertId: 'LIDO-STOPPED', - name: '🚨🚨🚨 Lido: Stopped 🚨🚨🚨', - description: () => `Lido DAO contract was stopped`, - severity: FindingSeverity.Critical, - type: FindingType.Info, - }, - { - address: LIDO_STETH_ADDRESS, - event: 'event Resumed()', - alertId: 'LIDO-RESUMED', - name: 'βœ… Lido: Resumed', - description: () => `Lido DAO contract was resumed`, - severity: FindingSeverity.High, - type: FindingType.Info, - }, - { - address: LIDO_STETH_ADDRESS, - event: 'event StakingPaused()', - alertId: 'LIDO-STAKING-PAUSED', - name: '🚨 Lido: Staking paused', - description: () => `Staking was paused!`, - severity: FindingSeverity.Critical, - type: FindingType.Info, - }, - { - address: LIDO_STETH_ADDRESS, - event: 'event StakingResumed()', - alertId: 'LIDO-STAKING-RESUMED', - name: 'βœ… Lido: Staking resumed', - description: () => `Staking was resumed!`, - severity: FindingSeverity.High, - type: FindingType.Info, - }, - { - address: LIDO_STETH_ADDRESS, - event: 'event StakingLimitSet(uint256 maxStakeLimit, uint256 stakeLimitIncreasePerBlock)', - alertId: 'LIDO-STAKING-LIMIT-SET', - name: '⚠️ Lido: Staking limit set', - description: (args: Result) => - `Staking limit was set with:\n` + - `Max staking limit: ${args.maxStakeLimit}\n` + - `Stake limit increase per block: ${args.stakeLimitIncreasePerBlock}`, - severity: FindingSeverity.High, - type: FindingType.Info, - }, - { - address: LIDO_STETH_ADDRESS, - event: 'event StakingLimitRemoved()', - alertId: 'LIDO-STAKING-LIMIT-REMOVED', - name: '🚨 Lido: Staking limit removed', - description: () => `Staking limit was removed`, - severity: FindingSeverity.High, - type: FindingType.Info, - }, - { - address: LIDO_STETH_ADDRESS, - event: 'event LidoLocatorSet(address lidoLocator)', - alertId: 'LIDO-LOCATOR-SET', - name: '🚨 Lido: Locator set', - description: (args: Result) => `Lido locator was set to: ${etherscanAddress(args.lidoLocator)}`, - severity: FindingSeverity.Critical, - type: FindingType.Info, - }, - { - address: LIDO_STETH_ADDRESS, - event: 'event RecoverToVault(address vault, address token, uint256 amount)', - alertId: 'LIDO-RECOVER-TO-VAULT', - name: 'ℹ️ Lido: Funds recovered to vault', - description: (args: Result) => - `Funds recovered to vault:\n` + - `Vault: ${etherscanAddress(args.vault)}\n` + - `Token: ${etherscanAddress(args.token)}\n` + - `Amount: ${args.amount}`, - severity: FindingSeverity.Info, - type: FindingType.Info, - }, - { - address: LIDO_STETH_ADDRESS, - event: 'event ContractVersionSet(uint256 version)', - alertId: 'LIDO-CONTRACT-VERSION-SET', - name: 'ℹ️ Lido: Contract version set', - description: (args: Result) => `Contract version set:\n` + `Version: ${args.version}`, - severity: FindingSeverity.Info, - type: FindingType.Info, - }, -] +export function getLidoEvents(LIDO_STETH_ADDRESS: string): EventOfNotice[] { + return [ + { + address: LIDO_STETH_ADDRESS, + event: 'event Stopped()', + alertId: 'LIDO-STOPPED', + name: '🚨🚨🚨 Lido: Stopped 🚨🚨🚨', + description: () => `Lido DAO contract was stopped`, + severity: FindingSeverity.Critical, + type: FindingType.Info, + }, + { + address: LIDO_STETH_ADDRESS, + event: 'event Resumed()', + alertId: 'LIDO-RESUMED', + name: 'βœ… Lido: Resumed', + description: () => `Lido DAO contract was resumed`, + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: LIDO_STETH_ADDRESS, + event: 'event StakingPaused()', + alertId: 'LIDO-STAKING-PAUSED', + name: '🚨 Lido: Staking paused', + description: () => `Staking was paused!`, + severity: FindingSeverity.Critical, + type: FindingType.Info, + }, + { + address: LIDO_STETH_ADDRESS, + event: 'event StakingResumed()', + alertId: 'LIDO-STAKING-RESUMED', + name: 'βœ… Lido: Staking resumed', + description: () => `Staking was resumed!`, + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: LIDO_STETH_ADDRESS, + event: 'event StakingLimitSet(uint256 maxStakeLimit, uint256 stakeLimitIncreasePerBlock)', + alertId: 'LIDO-STAKING-LIMIT-SET', + name: '⚠️ Lido: Staking limit set', + description: (args: Result) => + `Staking limit was set with:\n` + + `Max staking limit: ${args.maxStakeLimit}\n` + + `Stake limit increase per block: ${args.stakeLimitIncreasePerBlock}`, + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: LIDO_STETH_ADDRESS, + event: 'event StakingLimitRemoved()', + alertId: 'LIDO-STAKING-LIMIT-REMOVED', + name: '🚨 Lido: Staking limit removed', + description: () => `Staking limit was removed`, + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: LIDO_STETH_ADDRESS, + event: 'event LidoLocatorSet(address lidoLocator)', + alertId: 'LIDO-LOCATOR-SET', + name: '🚨 Lido: Locator set', + description: (args: Result) => `Lido locator was set to: ${etherscanAddress(args.lidoLocator)}`, + severity: FindingSeverity.Critical, + type: FindingType.Info, + }, + { + address: LIDO_STETH_ADDRESS, + event: 'event RecoverToVault(address vault, address token, uint256 amount)', + alertId: 'LIDO-RECOVER-TO-VAULT', + name: 'ℹ️ Lido: Funds recovered to vault', + description: (args: Result) => + `Funds recovered to vault:\n` + + `Vault: ${etherscanAddress(args.vault)}\n` + + `Token: ${etherscanAddress(args.token)}\n` + + `Amount: ${args.amount}`, + severity: FindingSeverity.Info, + type: FindingType.Info, + }, + { + address: LIDO_STETH_ADDRESS, + event: 'event ContractVersionSet(uint256 version)', + alertId: 'LIDO-CONTRACT-VERSION-SET', + name: 'ℹ️ Lido: Contract version set', + description: (args: Result) => `Contract version set:\n` + `Version: ${args.version}`, + severity: FindingSeverity.Info, + type: FindingType.Info, + }, + ] +} diff --git a/ethereum-steth-v2/src/utils/events/vault_events.ts b/ethereum-steth-v2/src/utils/events/vault_events.ts new file mode 100644 index 00000000..16b733ce --- /dev/null +++ b/ethereum-steth-v2/src/utils/events/vault_events.ts @@ -0,0 +1,7 @@ +export const TRANSFER_SHARES_EVENT = ` + event TransferShares( + address indexed from, + address indexed to, + uint256 sharesValue + ) +` diff --git a/ethereum-steth-v2/src/utils/events/withdrawals_events.ts b/ethereum-steth-v2/src/utils/events/withdrawals_events.ts new file mode 100644 index 00000000..caa9d70c --- /dev/null +++ b/ethereum-steth-v2/src/utils/events/withdrawals_events.ts @@ -0,0 +1,43 @@ +import { FindingSeverity, FindingType } from 'forta-agent' +import BigNumber from 'bignumber.js' +import { Result } from '@ethersproject/abi/lib' +import { EventOfNotice } from '../../entity/events' + +export const WITHDRAWALS_BUNKER_MODE_ENABLED_EVENT = 'event BunkerModeEnabled(uint256 _sinceTimestamp)' + +export const WITHDRAWALS_BUNKER_MODE_DISABLED_EVENT = 'event BunkerModeDisabled()' + +export const WITHDRAWAL_QUEUE_WITHDRAWAL_REQUESTED_EVENT = + 'event WithdrawalRequested(uint256 indexed requestId, address indexed requestor, address indexed owner, uint256 amountOfStETH, uint256 amountOfShares)' + +export const LIDO_TOKEN_REBASED_EVENT = + 'event TokenRebased(uint256 indexed reportTimestamp, uint256 timeElapsed, uint256 preTotalShares, uint256 preTotalEther, uint256 postTotalShares, uint256 postTotalEther, uint256 sharesMintedAsFees)' + +export const WITHDRAWAL_QUEUE_WITHDRAWALS_FINALIZED_EVENT = + 'event WithdrawalsFinalized(uint256 indexed from, uint256 indexed to, uint256 amountOfETHLocked, uint256 sharesToBurn, uint256 timestamp)' + +export const WITHDRAWAL_QUEUE_WITHDRAWAL_CLAIMED_EVENT = + 'event WithdrawalClaimed(uint256 indexed requestId, address indexed owner, address indexed receiver, uint256 amountOfETH)' + +export function getWithdrawalsEvents(WITHDRAWAL_QUEUE_ADDRESS: string): EventOfNotice[] { + return [ + { + address: WITHDRAWAL_QUEUE_ADDRESS, + event: 'event Resumed()', + alertId: 'WITHDRAWALS-UNPAUSED', + name: 'βœ… Withdrawals: contract was unpaused', + description: () => 'Contract was resumed', + severity: FindingSeverity.High, + type: FindingType.Info, + }, + { + address: WITHDRAWAL_QUEUE_ADDRESS, + event: 'event Paused(uint256 duration)', + alertId: 'WITHDRAWALS-PAUSED', + name: '🚨 Withdrawals: contract was paused', + description: (args: Result) => `For ${new BigNumber(args.duration).div(60 * 60)} hours`, + severity: FindingSeverity.Critical, + type: FindingType.Info, + }, + ] +} diff --git a/ethereum-steth-v2/src/utils/mutex.ts b/ethereum-steth-v2/src/utils/mutex.ts index 45f80341..377f148f 100644 --- a/ethereum-steth-v2/src/utils/mutex.ts +++ b/ethereum-steth-v2/src/utils/mutex.ts @@ -1,16 +1,15 @@ -import { Mutex } from 'async-mutex' -import { Finding } from 'forta-agent' +import { Mutex, MutexInterface, withTimeout } from 'async-mutex' -export class FindingsRW { - private mutex: Mutex - private value: Finding[] +export class DataRW { + private mutex: MutexInterface + private value: T[] - constructor(initialValue: Finding[]) { - this.mutex = new Mutex() + constructor(initialValue: T[]) { + this.mutex = withTimeout(new Mutex(), 100) this.value = initialValue } - async read(): Promise { + async read(): Promise { await this.mutex.acquire() try { const out = this.value @@ -22,7 +21,7 @@ export class FindingsRW { } } - async write(newValue: Finding[]): Promise { + async write(newValue: T[]): Promise { await this.mutex.acquire() try { this.value.push(...newValue) diff --git a/ethereum-steth-v2/src/utils/string.ts b/ethereum-steth-v2/src/utils/string.ts new file mode 100644 index 00000000..c21c9f65 --- /dev/null +++ b/ethereum-steth-v2/src/utils/string.ts @@ -0,0 +1,16 @@ +import { ETH_DECIMALS } from './constants' +import BigNumber from 'bignumber.js' + +export function etherscanNft(address: string, id: number | string): string { + const subpath = process.env.FORTA_AGENT_RUN_TEAR == 'testnet' ? 'goerli.' : '' + return `[${id}](https://${subpath}etherscan.io/nft/${address}/${id})` +} + +export function etherscanAddress(address: string): string { + const subpath = process.env.FORTA_AGENT_RUN_TIER == 'testnet' ? 'goerli.' : '' + return `[${address}](https://${subpath}etherscan.io/address/${address})` +} + +export function toEthString(wei: BigNumber): string { + return wei.dividedBy(ETH_DECIMALS).toFixed(3) + ' ETH' +} \ No newline at end of file diff --git a/ethereum-steth-v2/src/utils/tier.ts b/ethereum-steth-v2/src/utils/tier.ts deleted file mode 100644 index 5173f05c..00000000 --- a/ethereum-steth-v2/src/utils/tier.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { RUN_TIER } from './constants' - -export enum RedefineMode { - Strict = 'strict', - Merge = 'merge', -} - -/** - * Special wrapper under `require` function that allows to - * redefine variables from a file with the same name and `.` suffix. - * `` is a string that is passed by `FORTA_AGENT_RUN_TIER` environment variable. - * @param module module object to get the path from. - * @param path relative to module path to the main file to import. - * @param mode `strict` or `merge`. Default: `strict`. - */ -export function requireWithTier(module: NodeModule, path: string, mode: RedefineMode = RedefineMode.Strict): T { - const defaultContent = require(`${module.path}/${path}`) - if (!RUN_TIER) { - return defaultContent - } - let tieredContent: any - try { - tieredContent = require(`${module.path}/${path}.${RUN_TIER}`) - module.exports.__tier__ = RUN_TIER - } catch (e) { - return defaultContent - } - if (mode == RedefineMode.Strict) { - const valid = (key: string) => { - return key in tieredContent && typeof defaultContent[key] == typeof tieredContent[key] - } - if (Object.keys(defaultContent).every((key) => valid(key))) { - return tieredContent - } else { - throw new Error( - `Failed to import module: '${module.path}/${path}.${RUN_TIER}' doesn't contain all keys or unmatched types - with '${module.path}/${path}'`, - ) - } - } - if (mode == RedefineMode.Merge) { - const valid = (key: string) => { - if (key in defaultContent) { - return typeof defaultContent[key] == typeof tieredContent[key] - } else { - return true - } - } - if (Object.keys(tieredContent).every((key) => valid(key))) { - return { ...defaultContent, ...tieredContent } - } else { - throw new Error(`Failed to import module: '${path}.${RUN_TIER}' unmatched types with '${path}'`) - } - } - throw new Error(`Unknown require mode: ${mode}`) -} - -export function etherscanAddress(address: string): string { - const subpath = process.env.FORTA_AGENT_RUN_TIER == 'testnet' ? 'goerli.' : '' - return `[${address}](https://${subpath}etherscan.io/address/${address})` -} diff --git a/ethereum-steth-v2/src/utils/time.ts b/ethereum-steth-v2/src/utils/time.ts index acd3cb72..2b2433be 100644 --- a/ethereum-steth-v2/src/utils/time.ts +++ b/ethereum-steth-v2/src/utils/time.ts @@ -13,3 +13,20 @@ export function elapsedTime(methodName: string, startTime: number): string { function formatTimeToHumanReadable(date: Date): string { return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}` } + +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 + } + return ( + (sign == 1 ? '' : '-') + + (delayHours > 0 ? `${delayHours} hrs ` : '') + + (delayMin > 0 ? `${delayMin} min ` : '') + + `${delaySec} sec` + ) +} From d8b391c833fef8da84c69fb6a98dfaa03b159124 Mon Sep 17 00:00:00 2001 From: Sergey White Date: Wed, 17 Jan 2024 15:13:42 +0300 Subject: [PATCH 05/22] feat: eth-steth bot v2. fix linter warnings --- ethereum-steth-v2/README.md | 134 +++++++++--------- .../src/clients/eth_formatter.ts | 4 +- ethereum-steth-v2/src/utils/string.ts | 2 +- 3 files changed, 71 insertions(+), 69 deletions(-) diff --git a/ethereum-steth-v2/README.md b/ethereum-steth-v2/README.md index f3bd8358..a8b1f423 100644 --- a/ethereum-steth-v2/README.md +++ b/ethereum-steth-v2/README.md @@ -7,76 +7,76 @@ ## Alerts 1. StETH operations - 1. HandleBlock - 1. 🚨🚨🚨 Buffered ETH drain (checks each block) - 2. 🚨 Huge depositable ETH amount (checks every 100 blocks) - 3. ⚠️ High depositable ETH amount (checks every 100 blocks) - 4. ⚠️ Low deposit executor balance (checks every 100 blocks) - 5. ⚠️ Staking limit below 10% (checks every 25 blocks) - 6. πŸ“‰ Staking limit below 30% (checks every 25 blocks) - 2. HandleTransaction - 1. Deposit Security events - 1. 🚨 Deposit Security: Deposits paused - 2. βœ… Deposit Security: Deposits resumed - 3. ⚠️ Deposit Security: Guardian added - 4. ⚠️ Deposit Security: Guardian removed - 5. 🚨 Deposit Security: Guardian quorum changed - 6. ⚠️ Deposit Security: Max deposits changed - 7. ⚠️ Deposit Security: Min deposit block distance changed - 8. 🚨 Deposit Security: Owner changed - 2. Lido events - 1. 🚨🚨🚨 Lido: Stopped 🚨🚨🚨 - 2. βœ… Lido: Resumed - 3. 🚨 Lido: Staking paused - 4. βœ… Lido: Staking resumed - 5. ⚠️ Lido: Staking limit set - 6. 🚨 Lido: Staking limit removed - 7. 🚨 Lido: Locator set - 8. ℹ️ Lido: Funds recovered to vault - 9. ℹ️ Lido: Contract version set - 3. Insurance fund events - 1. ⚠️ Insurance fund: ETH transferred - 2. ⚠️ Insurance fund: ERC721 transferred - 3. 🚨 Insurance fund: ERC20 transferred - 4. ⚠️ Insurance fund: ERC1155 transferred - 5. 🚨 Insurance fund: Ownership transferred - 4. Burner events - 1. ℹ️ Lido Burner: ERC20 recovered - 2. ℹ️ Lido Burner: ERC721 recovered + 1. HandleBlock + 1. 🚨🚨🚨 Buffered ETH drain (checks each block) + 2. 🚨 Huge depositable ETH amount (checks every 100 blocks) + 3. ⚠️ High depositable ETH amount (checks every 100 blocks) + 4. ⚠️ Low deposit executor balance (checks every 100 blocks) + 5. ⚠️ Staking limit below 10% (checks every 25 blocks) + 6. πŸ“‰ Staking limit below 30% (checks every 25 blocks) + 2. HandleTransaction + 1. Deposit Security events + 1. 🚨 Deposit Security: Deposits paused + 2. βœ… Deposit Security: Deposits resumed + 3. ⚠️ Deposit Security: Guardian added + 4. ⚠️ Deposit Security: Guardian removed + 5. 🚨 Deposit Security: Guardian quorum changed + 6. ⚠️ Deposit Security: Max deposits changed + 7. ⚠️ Deposit Security: Min deposit block distance changed + 8. 🚨 Deposit Security: Owner changed + 2. Lido events + 1. 🚨🚨🚨 Lido: Stopped 🚨🚨🚨 + 2. βœ… Lido: Resumed + 3. 🚨 Lido: Staking paused + 4. βœ… Lido: Staking resumed + 5. ⚠️ Lido: Staking limit set + 6. 🚨 Lido: Staking limit removed + 7. 🚨 Lido: Locator set + 8. ℹ️ Lido: Funds recovered to vault + 9. ℹ️ Lido: Contract version set + 3. Insurance fund events + 1. ⚠️ Insurance fund: ETH transferred + 2. ⚠️ Insurance fund: ERC721 transferred + 3. 🚨 Insurance fund: ERC20 transferred + 4. ⚠️ Insurance fund: ERC1155 transferred + 5. 🚨 Insurance fund: Ownership transferred + 4. Burner events + 1. ℹ️ Lido Burner: ERC20 recovered + 2. ℹ️ Lido Burner: ERC721 recovered 2. Withdrawals. - 1. HandleBlock runs on each 100-th block or one per 20 minutes - 1. ⚠️ Withdrawals: % of stake limit is drained and unfinalized queue is on par with drained stake - limit - 2. ⚠️ Withdrawals: unfinalized queue is more than 100_000 stETH - 3. ⚠️ Withdrawals: unfinalized queue wait time is too long - 4. πŸ€” Withdrawals: ${unclaimedSizeRate.times(100).toFixed(2)}% of finalized requests are unclaimed - 5. πŸ€” Withdrawals: unclaimed requests size is more than withdrawal queue balance - 2. HandleTransaction - 1. 🚨 Withdrawals: BUNKER MODE ON! 🚨 - 2. βœ… Withdrawals: BUNKER MODE OFF! βœ… - 3. ℹ️ Huge stETH withdrawal requests batch - 4. ⚠️ Withdrawals: the sum of received withdrawal requests since the last rebase greater than 150_000 stETH - 5. πŸ€” Withdrawals: claimed amount is more than requested - 6. βœ… Withdrawals: contract was unpaused - 7. 🚨 Withdrawals: contract was paused + 1. HandleBlock runs on each 100-th block or one per 20 minutes + 1. ⚠️ Withdrawals: % of stake limit is drained and unfinalized queue is on par with drained stake + limit + 2. ⚠️ Withdrawals: unfinalized queue is more than 100_000 stETH + 3. ⚠️ Withdrawals: unfinalized queue wait time is too long + 4. πŸ€” Withdrawals: ${unclaimedSizeRate.times(100).toFixed(2)}% of finalized requests are unclaimed + 5. πŸ€” Withdrawals: unclaimed requests size is more than withdrawal queue balance + 2. HandleTransaction + 1. 🚨 Withdrawals: BUNKER MODE ON! 🚨 + 2. βœ… Withdrawals: BUNKER MODE OFF! βœ… + 3. ℹ️ Huge stETH withdrawal requests batch + 4. ⚠️ Withdrawals: the sum of received withdrawal requests since the last rebase greater than 150_000 stETH + 5. πŸ€” Withdrawals: claimed amount is more than requested + 6. βœ… Withdrawals: contract was unpaused + 7. 🚨 Withdrawals: contract was paused 3. GateSeal - 1. HandleBlock runs on each next block - 1. ⚠️ GateSeal: default GateSeal address in forta agent is expired - 2. ⚠️️ GateSeal: default GateSeal address in forta agent doesn't have PAUSE_ROLE for contracts - 3. 🚨GateSeal: actual address doesn't have PAUSE_ROLE for contracts - 4. 🚨🚨🚨 GateSeal: is expired! 🚨🚨🚨 - 5. ⚠️ GateSeal: is about to be expired - 2. HandleTransaction - 1. 🚨🚨🚨 GateSeal: is sealed 🚨🚨🚨 - 2. 🚨 GateSeal: new one created + 1. HandleBlock runs on each next block + 1. ⚠️ GateSeal: default GateSeal address in forta agent is expired + 2. ⚠️️ GateSeal: default GateSeal address in forta agent doesn't have PAUSE_ROLE for contracts + 3. 🚨GateSeal: actual address doesn't have PAUSE_ROLE for contracts + 4. 🚨🚨🚨 GateSeal: is expired! 🚨🚨🚨 + 5. ⚠️ GateSeal: is about to be expired + 2. HandleTransaction + 1. 🚨🚨🚨 GateSeal: is sealed 🚨🚨🚨 + 2. 🚨 GateSeal: new one created 4. Vaults - 1. Handleblock - 1. πŸ’΅ Withdrawal Vault Balance significant change (checks every on 100-th block) - 2. πŸ’΅ EL Vault Balance significant change - 3. 🚨 Withdrawal Vault balance mismatch - 4. 🚨 EL Vault balance mismatch - 2. HandleTransaction - 1. 🚨 Burner shares transfer + 1. Handleblock + 1. πŸ’΅ Withdrawal Vault Balance significant change (checks every on 100-th block) + 2. πŸ’΅ EL Vault Balance significant change + 3. 🚨 Withdrawal Vault balance mismatch + 4. 🚨 EL Vault balance mismatch + 2. HandleTransaction + 1. 🚨 Burner shares transfer ## Development (Forta specific) diff --git a/ethereum-steth-v2/src/clients/eth_formatter.ts b/ethereum-steth-v2/src/clients/eth_formatter.ts index caf629e5..f4f0950a 100644 --- a/ethereum-steth-v2/src/clients/eth_formatter.ts +++ b/ethereum-steth-v2/src/clients/eth_formatter.ts @@ -5,7 +5,9 @@ export class FormatterWithEIP1898 extends Formatter { * blockTag formatter with EIP-1898 support * https://eips.ethereum.org/EIPS/eip-1898 */ - blockTag(blockTag: any): string { + + // eslint-disable-next-line + public blockTag(blockTag: any): string { if ( typeof blockTag === 'object' && blockTag != null && diff --git a/ethereum-steth-v2/src/utils/string.ts b/ethereum-steth-v2/src/utils/string.ts index c21c9f65..c4d0d4d6 100644 --- a/ethereum-steth-v2/src/utils/string.ts +++ b/ethereum-steth-v2/src/utils/string.ts @@ -13,4 +13,4 @@ export function etherscanAddress(address: string): string { export function toEthString(wei: BigNumber): string { return wei.dividedBy(ETH_DECIMALS).toFixed(3) + ' ETH' -} \ No newline at end of file +} From 8fef2e328aca4272b1b89c6f4c738aa29b0527ca Mon Sep 17 00:00:00 2001 From: Sergey White Date: Wed, 17 Jan 2024 15:40:09 +0300 Subject: [PATCH 06/22] feat: eth-steth bot v2. fix linter warnings --- ethereum-steth-v2/src/agent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum-steth-v2/src/agent.ts b/ethereum-steth-v2/src/agent.ts index c6fc7a20..8a0ada8c 100644 --- a/ethereum-steth-v2/src/agent.ts +++ b/ethereum-steth-v2/src/agent.ts @@ -149,5 +149,5 @@ export const handleTransaction = (): HandleTransaction => { export default { initialize: initialize(), handleBlock: handleBlock(), - // handleTransaction: handleTransaction(), + handleTransaction: handleTransaction(), } From 4e5e20cc6eadf3978594875254363ebe06c67818 Mon Sep 17 00:00:00 2001 From: Sergey White Date: Wed, 17 Jan 2024 17:38:29 +0300 Subject: [PATCH 07/22] feat: eth-steth bot v2. update packages --- ethereum-steth-v2/yarn.lock | 542 +++++++++++++----------------------- 1 file changed, 198 insertions(+), 344 deletions(-) diff --git a/ethereum-steth-v2/yarn.lock b/ethereum-steth-v2/yarn.lock index dc40ad02..1a50a913 100644 --- a/ethereum-steth-v2/yarn.lock +++ b/ethereum-steth-v2/yarn.lock @@ -23,50 +23,50 @@ "@babel/highlight" "^7.23.4" chalk "^2.4.2" -"@babel/compat-data@^7.22.9": +"@babel/compat-data@^7.23.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== "@babel/core@^7.11.6", "@babel/core@^7.12.3": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.5.tgz#6e23f2acbcb77ad283c5ed141f824fd9f70101c7" - integrity sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g== + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.7.tgz#4d8016e06a14b5f92530a13ed0561730b5c6483f" + integrity sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.23.5" - "@babel/generator" "^7.23.5" - "@babel/helper-compilation-targets" "^7.22.15" + "@babel/generator" "^7.23.6" + "@babel/helper-compilation-targets" "^7.23.6" "@babel/helper-module-transforms" "^7.23.3" - "@babel/helpers" "^7.23.5" - "@babel/parser" "^7.23.5" + "@babel/helpers" "^7.23.7" + "@babel/parser" "^7.23.6" "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.5" - "@babel/types" "^7.23.5" + "@babel/traverse" "^7.23.7" + "@babel/types" "^7.23.6" convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.23.5", "@babel/generator@^7.7.2": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.5.tgz#17d0a1ea6b62f351d281350a5f80b87a810c4755" - integrity sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA== +"@babel/generator@^7.23.6", "@babel/generator@^7.7.2": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e" + integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw== dependencies: - "@babel/types" "^7.23.5" + "@babel/types" "^7.23.6" "@jridgewell/gen-mapping" "^0.3.2" "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" -"@babel/helper-compilation-targets@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" - integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== +"@babel/helper-compilation-targets@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" + integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== dependencies: - "@babel/compat-data" "^7.22.9" - "@babel/helper-validator-option" "^7.22.15" - browserslist "^4.21.9" + "@babel/compat-data" "^7.23.5" + "@babel/helper-validator-option" "^7.23.5" + browserslist "^4.22.2" lru-cache "^5.1.1" semver "^6.3.1" @@ -137,19 +137,19 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== -"@babel/helper-validator-option@^7.22.15": +"@babel/helper-validator-option@^7.23.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== -"@babel/helpers@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.5.tgz#52f522840df8f1a848d06ea6a79b79eefa72401e" - integrity sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg== +"@babel/helpers@^7.23.7": + version "7.23.8" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.8.tgz#fc6b2d65b16847fd50adddbd4232c76378959e34" + integrity sha512-KDqYz4PiOWvDFrdHLPhKtCThtIcKVy6avWD2oG4GEvyQ+XDZwHD4YQd+H2vNMnq2rkdxsDkU82T+Vk8U/WXHRQ== dependencies: "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.5" - "@babel/types" "^7.23.5" + "@babel/traverse" "^7.23.7" + "@babel/types" "^7.23.6" "@babel/highlight@^7.23.4": version "7.23.4" @@ -160,10 +160,10 @@ chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.5.tgz#37dee97c4752af148e1d38c34b856b2507660563" - integrity sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b" + integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -272,26 +272,26 @@ "@babel/parser" "^7.22.15" "@babel/types" "^7.22.15" -"@babel/traverse@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.5.tgz#f546bf9aba9ef2b042c0e00d245990c15508e7ec" - integrity sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w== +"@babel/traverse@^7.23.7": + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305" + integrity sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg== dependencies: "@babel/code-frame" "^7.23.5" - "@babel/generator" "^7.23.5" + "@babel/generator" "^7.23.6" "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-function-name" "^7.23.0" "@babel/helper-hoist-variables" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.23.5" - "@babel/types" "^7.23.5" - debug "^4.1.0" + "@babel/parser" "^7.23.6" + "@babel/types" "^7.23.6" + debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.5", "@babel/types@^7.3.3": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.5.tgz#48d730a00c95109fa4393352705954d74fb5b602" - integrity sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w== +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.6", "@babel/types@^7.3.3": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.6.tgz#be33fdb151e1f5a56877d704492c240fc71c7ccd" + integrity sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg== dependencies: "@babel/helper-string-parser" "^7.23.4" "@babel/helper-validator-identifier" "^7.22.20" @@ -334,10 +334,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.55.0": - version "8.55.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.55.0.tgz#b721d52060f369aa259cf97392403cb9ce892ec6" - integrity sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA== +"@eslint/js@8.56.0": + version "8.56.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.56.0.tgz#ef20350fec605a7f7035a01764731b2de0f3782b" + integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A== "@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.0", "@ethersproject/abi@^5.7.0": version "5.7.0" @@ -682,9 +682,9 @@ "@ethersproject/strings" "^5.7.0" "@grpc/grpc-js@^1.3.6": - version "1.9.12" - resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.9.12.tgz#a45b23a7d9ee1eadc9fa8fe480e27edbc6544cdd" - integrity sha512-Um5MBuge32TS3lAKX02PGCnFM4xPT996yLgZNb5H03pn6NyJ4Iwn5YcPq6Jj9yxGRk7WOgaZFtVRH5iTdYBeUg== + version "1.9.14" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.9.14.tgz#236378822876cbf7903f9d61a0330410e8dcc5a1" + integrity sha512-nOpuzZ2G3IuMFN+UPPpKrC6NsLmWsTqSsm66IRfnBt1D4pwTqE27lmbpcPM+l2Ua4gE7PfjRHI6uedAy7hoXUw== dependencies: "@grpc/proto-loader" "^0.7.8" "@types/node" ">=12.12.47" @@ -711,12 +711,12 @@ yargs "^17.7.2" "@humanwhocodes/config-array@^0.11.13": - version "0.11.13" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.13.tgz#075dc9684f40a531d9b26b0822153c1e832ee297" - integrity sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ== + 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.1" - debug "^4.1.1" + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" minimatch "^3.0.5" "@humanwhocodes/module-importer@^1.0.1": @@ -724,10 +724,10 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== -"@humanwhocodes/object-schema@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz#e5211452df060fa8522b55c7b3c0c4d1981cb044" - integrity sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw== +"@humanwhocodes/object-schema@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" + integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" @@ -962,9 +962,9 @@ integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.20" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" - integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== + version "0.3.21" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.21.tgz#5dc1df7b3dc4a6209e503a924e1ca56097a2bb15" + integrity sha512-SRfKmRe1KvYnxjEMtxEr+J4HIeMX5YBg/qhRHpxEIGjhX1rshcHlnFUE9K0GazhVKWM7B+nARSkV8LuvJdJ5/g== dependencies: "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" @@ -990,17 +990,10 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@pkgr/utils@^2.4.2": - version "2.4.2" - resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.4.2.tgz#9e638bbe9a6a6f165580dc943f138fd3309a2cbc" - integrity sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw== - dependencies: - cross-spawn "^7.0.3" - fast-glob "^3.3.0" - is-glob "^4.0.3" - open "^9.1.0" - picocolors "^1.0.0" - tslib "^2.6.0" +"@pkgr/core@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" + integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" @@ -1099,9 +1092,9 @@ "@types/babel__traverse" "*" "@types/babel__generator@*": - version "7.6.7" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.7.tgz#a7aebf15c7bc0eb9abd638bdb5c0b8700399c9d0" - integrity sha512-6Sfsq+EaaLrw4RmdFWE9Onp63TOUue71AWb4Gpa6JxzgTYtimbM086WnYTy2U67AofR++QKCo08ZP6pwx8YFHQ== + version "7.6.8" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" + integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== dependencies: "@babel/types" "^7.0.0" @@ -1114,9 +1107,9 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.20.4" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.4.tgz#ec2c06fed6549df8bc0eb4615b683749a4a92e1b" - integrity sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA== + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.5.tgz#7b7502be0aa80cc4ef22978846b983edaafcd4dd" + integrity sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ== dependencies: "@babel/types" "^7.20.7" @@ -1147,9 +1140,9 @@ "@types/istanbul-lib-report" "*" "@types/jest@^29.5.10": - version "29.5.10" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.10.tgz#a10fc5bab9e426081c12b2ef73d24d4f0c9b7f50" - integrity sha512-tE4yxKEphEyxj9s4inideLHktW/x6DwesIwWZ9NN1FKf9zbJYsnhBoA9vrHA/IuIOKwPa5PcFBNV4lpMIOEzyQ== + version "29.5.11" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.11.tgz#0c13aa0da7d0929f078ab080ae5d4ced80fa2f2c" + integrity sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ== dependencies: expect "^29.0.0" pretty-format "^29.0.0" @@ -1177,9 +1170,9 @@ "@types/node" "*" "@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0": - version "20.10.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.3.tgz#4900adcc7fc189d5af5bb41da8f543cea6962030" - integrity sha512-XJavIpZqiXID5Yxnxv3RUDKTN5b81ddNC3ecsA0SoFXz/QU8OGBwZGMomiq0zw+uuqbL/krztv/DINAQ/EV4gg== + version "20.11.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.5.tgz#be10c622ca7fcaa3cf226cf80166abc31389d86e" + integrity sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w== dependencies: undici-types "~5.26.4" @@ -1237,15 +1230,15 @@ "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^6.12.0": - version "6.13.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.1.tgz#f98bd887bf95551203c917e734d113bf8d527a0c" - integrity sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA== + version "6.19.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.0.tgz#db03f3313b57a30fbbdad2e6929e88fc7feaf9ba" + integrity sha512-DUCUkQNklCQYnrBSSikjVChdc84/vMPDQSgJTHBZ64G9bA9w0Crc0rd2diujKbTdp6w2J47qkeHQLoi0rpLCdg== dependencies: "@eslint-community/regexpp" "^4.5.1" - "@typescript-eslint/scope-manager" "6.13.1" - "@typescript-eslint/type-utils" "6.13.1" - "@typescript-eslint/utils" "6.13.1" - "@typescript-eslint/visitor-keys" "6.13.1" + "@typescript-eslint/scope-manager" "6.19.0" + "@typescript-eslint/type-utils" "6.19.0" + "@typescript-eslint/utils" "6.19.0" + "@typescript-eslint/visitor-keys" "6.19.0" debug "^4.3.4" graphemer "^1.4.0" ignore "^5.2.4" @@ -1254,14 +1247,14 @@ ts-api-utils "^1.0.1" "@typescript-eslint/parser@^6.12.0": - version "6.13.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.13.1.tgz#29d6d4e5fab4669e58bc15f6904b67da65567487" - integrity sha512-fs2XOhWCzRhqMmQf0eicLa/CWSaYss2feXsy7xBD/pLyWke/jCIVc2s1ikEAtSW7ina1HNhv7kONoEfVNEcdDQ== - dependencies: - "@typescript-eslint/scope-manager" "6.13.1" - "@typescript-eslint/types" "6.13.1" - "@typescript-eslint/typescript-estree" "6.13.1" - "@typescript-eslint/visitor-keys" "6.13.1" + version "6.19.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.19.0.tgz#80344086f362181890ade7e94fc35fe0480bfdf5" + integrity sha512-1DyBLG5SH7PYCd00QlroiW60YJ4rWMuUGa/JBV0iZuqi4l4IK3twKPq5ZkEebmGqRjXWVgsUzfd3+nZveewgow== + dependencies: + "@typescript-eslint/scope-manager" "6.19.0" + "@typescript-eslint/types" "6.19.0" + "@typescript-eslint/typescript-estree" "6.19.0" + "@typescript-eslint/visitor-keys" "6.19.0" debug "^4.3.4" "@typescript-eslint/scope-manager@5.62.0": @@ -1272,21 +1265,21 @@ "@typescript-eslint/types" "5.62.0" "@typescript-eslint/visitor-keys" "5.62.0" -"@typescript-eslint/scope-manager@6.13.1": - version "6.13.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz#58c7c37c6a957d3d9f59bc4f64c2888e0cac1d70" - integrity sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ== +"@typescript-eslint/scope-manager@6.19.0": + version "6.19.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.19.0.tgz#b6d2abb825b29ab70cb542d220e40c61c1678116" + integrity sha512-dO1XMhV2ehBI6QN8Ufi7I10wmUovmLU0Oru3n5LVlM2JuzB4M+dVphCPLkVpKvGij2j/pHBWuJ9piuXx+BhzxQ== dependencies: - "@typescript-eslint/types" "6.13.1" - "@typescript-eslint/visitor-keys" "6.13.1" + "@typescript-eslint/types" "6.19.0" + "@typescript-eslint/visitor-keys" "6.19.0" -"@typescript-eslint/type-utils@6.13.1": - version "6.13.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.13.1.tgz#e6e5885e387841cae9c38fc0638fd8b7561973d6" - integrity sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ== +"@typescript-eslint/type-utils@6.19.0": + version "6.19.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.19.0.tgz#522a494ef0d3e9fdc5e23a7c22c9331bbade0101" + integrity sha512-mcvS6WSWbjiSxKCwBcXtOM5pRkPQ6kcDds/juxcy/727IQr3xMEcwr/YLHW2A2+Fp5ql6khjbKBzOyjuPqGi/w== dependencies: - "@typescript-eslint/typescript-estree" "6.13.1" - "@typescript-eslint/utils" "6.13.1" + "@typescript-eslint/typescript-estree" "6.19.0" + "@typescript-eslint/utils" "6.19.0" debug "^4.3.4" ts-api-utils "^1.0.1" @@ -1295,10 +1288,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== -"@typescript-eslint/types@6.13.1": - version "6.13.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.13.1.tgz#b56f26130e7eb8fa1e429c75fb969cae6ad7bb5c" - integrity sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg== +"@typescript-eslint/types@6.19.0": + version "6.19.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.19.0.tgz#689b0498c436272a6a2059b09f44bcbd90de294a" + integrity sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A== "@typescript-eslint/typescript-estree@5.62.0": version "5.62.0" @@ -1313,30 +1306,31 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@6.13.1": - version "6.13.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz#d01dda78d2487434d1c503853fa00291c566efa4" - integrity sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ== +"@typescript-eslint/typescript-estree@6.19.0": + version "6.19.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.0.tgz#0813ba364a409afb4d62348aec0202600cb468fa" + integrity sha512-o/zefXIbbLBZ8YJ51NlkSAt2BamrK6XOmuxSR3hynMIzzyMY33KuJ9vuMdFSXW+H0tVvdF9qBPTHA91HDb4BIQ== dependencies: - "@typescript-eslint/types" "6.13.1" - "@typescript-eslint/visitor-keys" "6.13.1" + "@typescript-eslint/types" "6.19.0" + "@typescript-eslint/visitor-keys" "6.19.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" + minimatch "9.0.3" semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/utils@6.13.1": - version "6.13.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.13.1.tgz#925b3a2453a71ada914ae329b7bb7e7d96634b2f" - integrity sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw== +"@typescript-eslint/utils@6.19.0": + version "6.19.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.19.0.tgz#557b72c3eeb4f73bef8037c85dae57b21beb1a4b" + integrity sha512-QR41YXySiuN++/dC9UArYOg4X86OAYP83OWTewpVx5ct1IZhjjgTLocj7QNxGhWoTqknsgpl7L+hGygCO+sdYw== dependencies: "@eslint-community/eslint-utils" "^4.4.0" "@types/json-schema" "^7.0.12" "@types/semver" "^7.5.0" - "@typescript-eslint/scope-manager" "6.13.1" - "@typescript-eslint/types" "6.13.1" - "@typescript-eslint/typescript-estree" "6.13.1" + "@typescript-eslint/scope-manager" "6.19.0" + "@typescript-eslint/types" "6.19.0" + "@typescript-eslint/typescript-estree" "6.19.0" semver "^7.5.4" "@typescript-eslint/utils@^5.10.0": @@ -1361,12 +1355,12 @@ "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" -"@typescript-eslint/visitor-keys@6.13.1": - version "6.13.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz#c4b692dcc23a4fc60685b718f10fde789d65a540" - integrity sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ== +"@typescript-eslint/visitor-keys@6.19.0": + version "6.19.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.0.tgz#4565e0ecd63ca1f81b96f1dd76e49f746c6b2b49" + integrity sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ== dependencies: - "@typescript-eslint/types" "6.13.1" + "@typescript-eslint/types" "6.19.0" eslint-visitor-keys "^3.4.1" "@ungap/structured-clone@^1.2.0": @@ -1385,9 +1379,9 @@ acorn-jsx@^5.3.2: integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn@^8.9.0: - version "8.11.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" - integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== aes-js@3.0.0: version "3.0.0" @@ -1508,11 +1502,11 @@ awilix@^4.3.4: glob "^7.1.6" axios@^1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2" - integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A== + version "1.6.5" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.5.tgz#2c090da14aeeab3770ad30c3a1461bc970fb0cd8" + integrity sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg== dependencies: - follow-redirects "^1.15.0" + follow-redirects "^1.15.4" form-data "^4.0.0" proxy-from-env "^1.1.0" @@ -1596,11 +1590,6 @@ bech32@1.1.4: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== -big-integer@^1.6.44: - version "1.6.52" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85" - integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg== - bignumber.js@^9.1.2: version "9.1.2" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" @@ -1621,13 +1610,6 @@ bn.js@^5.0.0, bn.js@^5.2.1: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== -bplist-parser@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.2.0.tgz#43a9d183e5bf9d545200ceac3e712f79ebbe8d0e" - integrity sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw== - dependencies: - big-integer "^1.6.44" - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -1709,7 +1691,7 @@ browserify-sign@^4.0.0: readable-stream "^3.6.2" safe-buffer "^5.2.1" -browserslist@^4.21.9: +browserslist@^4.22.2: version "4.22.2" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.2.tgz#704c4943072bd81ea18997f3bd2180e89c77874b" integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== @@ -1751,13 +1733,6 @@ buffer@6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" -bundle-name@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-3.0.0.tgz#ba59bcc9ac785fb67ccdbf104a2bf60c099f0e1a" - integrity sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw== - dependencies: - run-applescript "^5.0.0" - callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -1782,9 +1757,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001565: - version "1.0.30001566" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001566.tgz#61a8e17caf3752e3e426d4239c549ebbb37fef0d" - integrity sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA== + version "1.0.30001578" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001578.tgz#11741580434ce60aae4b4a9abee9f9f8d7bf5be5" + integrity sha512-J/jkFgsQ3NEl4w2lCoM9ZPxrD+FoBNJ7uJUpGVjIg/j0OwJosWM36EPDv+Yyi0V4twBk9pPmlFS+PLykgEvUmg== chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" @@ -2027,29 +2002,6 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== -default-browser-id@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-3.0.0.tgz#bee7bbbef1f4e75d31f98f4d3f1556a14cea790c" - integrity sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA== - dependencies: - bplist-parser "^0.2.0" - untildify "^4.0.0" - -default-browser@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-4.0.0.tgz#53c9894f8810bf86696de117a6ce9085a3cbc7da" - integrity sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA== - dependencies: - bundle-name "^3.0.0" - default-browser-id "^3.0.0" - execa "^7.1.1" - titleize "^3.0.0" - -define-lazy-prop@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" - integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -2097,9 +2049,9 @@ doctrine@^3.0.0: esutils "^2.0.2" electron-to-chromium@^1.4.601: - version "1.4.601" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.601.tgz#cac69868548aee89961ffe63ff5a7716f0685b75" - integrity sha512-SpwUMDWe9tQu8JX5QCO1+p/hChAi9AE9UpoC3rcHVc+gdCGlbT3SGb5I1klgb952HRIyvt9wZhSz9bNBYz9swA== + version "1.4.635" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.635.tgz#e4e064b8711a98827652ce17cc11b0e0184c40d1" + integrity sha512-iu/2D0zolKU3iDGXXxdOzNf72Jnokn+K1IN6Kk4iV6l1Tr2g/qy+mvmtfAiBwZe5S3aB5r92vp+zSZ69scYRrg== elliptic@6.5.4, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.4: version "6.5.4" @@ -2157,19 +2109,19 @@ eslint-config-prettier@^9.0.0: integrity sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw== eslint-plugin-jest@^27.6.0: - version "27.6.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-27.6.0.tgz#e5c0cf735b3c8cad0ef9db5b565b2fc99f5e55ed" - integrity sha512-MTlusnnDMChbElsszJvrwD1dN3x6nZl//s4JD23BxB6MgR66TZlL064su24xEIS3VACfAoHV1vgyMgPw8nkdng== + version "27.6.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-27.6.3.tgz#8acb8b1e45597fe1f4d4cf25163d90119efc12be" + integrity sha512-+YsJFVH6R+tOiO3gCJon5oqn4KWc+mDq2leudk8mrp8RFubLOo9CVyi3cib4L7XMpxExmkmBZQTPDYVBzgpgOA== dependencies: "@typescript-eslint/utils" "^5.10.0" eslint-plugin-prettier@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz#a3b399f04378f79f066379f544e42d6b73f11515" - integrity sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg== + version "5.1.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz#17cfade9e732cef32b5f5be53bd4e07afd8e67e1" + integrity sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw== dependencies: prettier-linter-helpers "^1.0.0" - synckit "^0.8.5" + synckit "^0.8.6" eslint-scope@^5.1.1: version "5.1.1" @@ -2193,14 +2145,14 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== eslint@^8.54.0: - version "8.55.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.55.0.tgz#078cb7b847d66f2c254ea1794fa395bf8e7e03f8" - integrity sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA== + version "8.56.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.56.0.tgz#4957ce8da409dc0809f99ab07a1b94832ab74b15" + integrity sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.6.1" "@eslint/eslintrc" "^2.1.4" - "@eslint/js" "8.55.0" + "@eslint/js" "8.56.0" "@humanwhocodes/config-array" "^0.11.13" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -2338,21 +2290,6 @@ execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -execa@^7.1.1: - version "7.2.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-7.2.0.tgz#657e75ba984f42a70f38928cedc87d6f2d4fe4e9" - integrity sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.1" - human-signals "^4.3.0" - is-stream "^3.0.0" - merge-stream "^2.0.0" - npm-run-path "^5.1.0" - onetime "^6.0.0" - signal-exit "^3.0.7" - strip-final-newline "^3.0.0" - exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -2379,7 +2316,7 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== -fast-glob@^3.2.9, fast-glob@^3.3.0: +fast-glob@^3.2.9: version "3.3.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== @@ -2406,9 +2343,9 @@ fast-safe-stringify@^2.0.6: integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== fastq@^1.6.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" - integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== + version "1.16.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.16.0.tgz#83b9a9375692db77a822df081edb6a9cf6839320" + integrity sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA== dependencies: reusify "^1.0.4" @@ -2470,10 +2407,10 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== -follow-redirects@^1.15.0: - version "1.15.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" - integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== +follow-redirects@^1.15.4: + version "1.15.5" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" + integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== form-data@^4.0.0: version "4.0.0" @@ -2512,9 +2449,9 @@ forta-agent@^0.1.48: yargs "^17.0.1" fp-ts@^2.16.1: - version "2.16.1" - resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.16.1.tgz#6abc401ce42b65364ca8f0b0d995c5840c68a930" - integrity sha512-by7U5W8dkIzcvDofUcO42yl9JbnHTEDBrzu3pt5fKT+Z4Oy85I21K80EYJYdjQGC2qum4Vo55Ag57iiIK4FYuA== + version "2.16.2" + resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.16.2.tgz#7faa90f6fc2e8cf84c711d2c4e606afe2be9e342" + integrity sha512-CkqAjnIKFqvo3sCyoBTqgJvF+bHrSik584S9nhTjtBESLx26cbtVMR/T9a6ApChOcSDAaM3JydDmWDUn4EEXng== fs-extra@^7.0.0: version "7.0.1" @@ -2555,7 +2492,7 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-stream@^6.0.0, get-stream@^6.0.1: +get-stream@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== @@ -2615,9 +2552,9 @@ globals@^11.1.0: integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.19.0: - version "13.23.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.23.0.tgz#ef31673c926a0976e1f61dab4dca57e0c0a8af02" - integrity sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA== + 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" @@ -2696,11 +2633,6 @@ 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== -human-signals@^4.3.0: - version "4.3.1" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" - integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== - husky@^8.0.3: version "8.0.3" resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" @@ -2779,16 +2711,6 @@ is-core-module@^2.13.0: dependencies: hasown "^2.0.0" -is-docker@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-docker@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" - integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -2811,13 +2733,6 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-inside-container@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" - integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== - dependencies: - is-docker "^3.0.0" - is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -2833,18 +2748,6 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== -is-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" - integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== - -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -3526,11 +3429,6 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -mimic-fn@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" - integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== - minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -3541,6 +3439,13 @@ 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: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -3606,9 +3511,9 @@ node-addon-api@^2.0.0: integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== node-gyp-build@^4.2.0: - version "4.7.1" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.7.1.tgz#cd7d2eb48e594874053150a9418ac85af83ca8f7" - integrity sha512-wTSrZ+8lsRRa3I3H8Xr65dLWSgCvY2l4AOnaeKdPA9TB/WYMPaTcrzf3rXvFoVvjKNVnu0CcWSx54qq9GKRUYg== + version "4.8.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.0.tgz#3fee9c1731df4581a3f9ead74664369ff00d26dd" + integrity sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og== node-int64@^0.4.0: version "0.4.0" @@ -3621,9 +3526,9 @@ node-releases@^2.0.14: integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== nodemon@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.0.2.tgz#222dd0de79fc7b7b3eedba422d2b9e5fc678621e" - integrity sha512-9qIN2LNTrEzpOPBaWHTm4Asy1LxXLSickZStAQ4IZe7zsoIpD/A7LWxhZV3t4Zu352uBcqVnRsDXSMR2Sc3lTA== + version "3.0.3" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.0.3.tgz#244a62d1c690eece3f6165c6cdb0db03ebd80b76" + integrity sha512-7jH/NXbFPxVaMwmBCC2B9F/V6X1VkEdNgx3iu9jji8WxWcvhMWkmhNWhI5077zknOnZnBzba9hZP6bCPJLSReQ== dependencies: chokidar "^3.5.2" debug "^4" @@ -3655,13 +3560,6 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -npm-run-path@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.1.0.tgz#bc62f7f3f6952d9894bd08944ba011a6ee7b7e00" - integrity sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q== - dependencies: - path-key "^4.0.0" - once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -3676,23 +3574,6 @@ onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -onetime@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" - integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== - dependencies: - mimic-fn "^4.0.0" - -open@^9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/open/-/open-9.1.0.tgz#684934359c90ad25742f5a26151970ff8c6c80b6" - integrity sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg== - dependencies: - default-browser "^4.0.0" - define-lazy-prop "^3.0.0" - is-inside-container "^1.0.0" - is-wsl "^2.2.0" - optionator@^0.9.3: version "0.9.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" @@ -3797,11 +3678,6 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-key@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" - integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== - path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" @@ -3874,9 +3750,9 @@ prettier@^2.1.2, prettier@^2.3.1: integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== prettier@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.1.0.tgz#c6d16474a5f764ea1a4a373c593b779697744d5e" - integrity sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw== + version "3.2.4" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.4.tgz#4723cadeac2ce7c9227de758e5ff9b14e075f283" + integrity sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ== pretty-format@^29.0.0, pretty-format@^29.7.0: version "29.7.0" @@ -3915,9 +3791,9 @@ protobufjs@^6.11.3: long "^4.0.0" protobufjs@^7.2.4: - version "7.2.5" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.5.tgz#45d5c57387a6d29a17aab6846dcc283f9b8e7f2d" - integrity sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A== + version "7.2.6" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.6.tgz#4a0ccd79eb292717aacf07530a07e0ed20278215" + integrity sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -4090,13 +3966,6 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" -run-applescript@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-5.0.0.tgz#e11e1c932e055d5c6b40d98374e0268d9b11899c" - integrity sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg== - dependencies: - execa "^5.0.0" - run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -4274,11 +4143,6 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-final-newline@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" - integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== - strip-json-comments@^3.0.1, strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -4310,12 +4174,12 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -synckit@^0.8.5: - version "0.8.6" - resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.6.tgz#b69b7fbce3917c2673cbdc0d87fb324db4a5b409" - integrity sha512-laHF2savN6sMeHCjLRkheIU4wo3Zg9Ln5YOjOo7sZ5dVQW8yF5pPE5SIw1dsPhq3TRp1jisKRCdPhfs/1WMqDA== +synckit@^0.8.6: + version "0.8.8" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.8.tgz#fe7fe446518e3d3d49f5e429f443cf08b6edfcd7" + integrity sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ== dependencies: - "@pkgr/utils" "^2.4.2" + "@pkgr/core" "^0.1.0" tslib "^2.6.2" table-layout@^1.0.2: @@ -4342,11 +4206,6 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== -titleize@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" - integrity sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ== - tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -4435,7 +4294,7 @@ tslib@^1.8.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.0, tslib@^2.6.2: +tslib@^2.0.3, tslib@^2.4.0, tslib@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== @@ -4486,9 +4345,9 @@ typechain@^8.3.2: ts-essentials "^7.0.1" typescript@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43" - integrity sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ== + version "5.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== typical@^4.0.0: version "4.0.0" @@ -4515,11 +4374,6 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -untildify@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" - integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== - update-browserslist-db@^1.0.13: version "1.0.13" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" From 7d24e0fb5324646e5cbd3ff8bb3ca6ad0e381004 Mon Sep 17 00:00:00 2001 From: Sergey White Date: Mon, 22 Jan 2024 20:36:27 +0300 Subject: [PATCH 08/22] feat: cover by tests StethOperationSrv initialize, handleBufferedEth handlers --- ethereum-steth-v2/.eslintrc.json | 12 +- ethereum-steth-v2/.gitignore | 1 + ethereum-steth-v2/jest.config.js | 6 + ethereum-steth-v2/package.json | 10 +- ethereum-steth-v2/src/abi/GateSeal.json | 68 +- .../src/abi/GateSealFactory.json | 34 +- ethereum-steth-v2/src/abi/Lido.json | 872 ++++++++++++++++- .../src/abi/WithdrawalQueueERC721.json | 891 +++++++++++++++++- ethereum-steth-v2/src/app.ts | 7 + .../src/clients/mocks/eth_provider_mock.ts | 18 + .../src/services/gate-seal/GateSeal.srv.ts | 2 +- .../steth_operation/StethOperation.cache.ts | 4 +- .../steth_operation/StethOperation.spec.ts | 635 +++++++++++++ .../steth_operation/StethOperation.srv.ts | 86 +- .../src/services/steth_operation/contracts.ts | 36 + .../src/services/vault/Vault.srv.ts | 2 +- .../services/withdrawals/Withdrawals.srv.ts | 2 +- .../utils/contract_mocks/contract_mocks.ts | 44 + ethereum-steth-v2/tsconfig.json | 10 +- ethereum-steth-v2/yarn.lock | 176 +++- 20 files changed, 2849 insertions(+), 67 deletions(-) create mode 100644 ethereum-steth-v2/jest.config.js create mode 100644 ethereum-steth-v2/src/clients/mocks/eth_provider_mock.ts create mode 100644 ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts create mode 100644 ethereum-steth-v2/src/services/steth_operation/contracts.ts create mode 100644 ethereum-steth-v2/src/utils/contract_mocks/contract_mocks.ts diff --git a/ethereum-steth-v2/.eslintrc.json b/ethereum-steth-v2/.eslintrc.json index 867579eb..f2b913ec 100644 --- a/ethereum-steth-v2/.eslintrc.json +++ b/ethereum-steth-v2/.eslintrc.json @@ -9,5 +9,13 @@ "rules": { "prettier/prettier": "error", "curly": "error" - } -} + }, + "overrides": [ + { + "files": ["tests/**/*", "*.spec.ts"], + "env": { + "jest": true + } + } + ] +} \ No newline at end of file diff --git a/ethereum-steth-v2/.gitignore b/ethereum-steth-v2/.gitignore index f73222b7..1edf63a5 100644 --- a/ethereum-steth-v2/.gitignore +++ b/ethereum-steth-v2/.gitignore @@ -6,3 +6,4 @@ forta.config.json *.log version.json .DS_Store +/coverage/ diff --git a/ethereum-steth-v2/jest.config.js b/ethereum-steth-v2/jest.config.js new file mode 100644 index 00000000..9dfeb5d2 --- /dev/null +++ b/ethereum-steth-v2/jest.config.js @@ -0,0 +1,6 @@ +/** @type {import("ts-jest").JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testPathIgnorePatterns: ['dist'], +} diff --git a/ethereum-steth-v2/package.json b/ethereum-steth-v2/package.json index a422db6e..6abf5c69 100644 --- a/ethereum-steth-v2/package.json +++ b/ethereum-steth-v2/package.json @@ -54,14 +54,18 @@ "forta-agent": "^0.1.48", "fp-ts": "^2.16.1", "lodash": "^4.17.21", - "ts-retry": "^4.2.4" + "ts-log": "^2.2.5", + "ts-retry": "^4.2.4", + "winston": "^3.11.0" }, "devDependencies": { "@ethersproject/abi": "^5.0.0", "@ethersproject/providers": "^5.0.0", + "@faker-js/faker": "^8.3.1", + "@jest/globals": "^29.7.0", "@tsconfig/node20": "^20.1.2", "@typechain/ethers-v5": "^11.1.2", - "@types/jest": "^29.5.10", + "@types/jest": "^29.5.11", "@types/nodemon": "^1.19.0", "@types/ws": "^8.5.10", "@typescript-eslint/eslint-plugin": "^6.12.0", @@ -76,7 +80,7 @@ "postinstall": "^0.8.0", "prettier": "^3.1.0", "ts-generator": "^0.1.1", - "ts-jest": "^29.1.1", + "ts-jest": "^29.1.2", "typechain": "^8.3.2", "typescript": "^5.3.2" }, diff --git a/ethereum-steth-v2/src/abi/GateSeal.json b/ethereum-steth-v2/src/abi/GateSeal.json index 1358d4b6..8243451b 100644 --- a/ethereum-steth-v2/src/abi/GateSeal.json +++ b/ethereum-steth-v2/src/abi/GateSeal.json @@ -1 +1,67 @@ -[{"name":"Sealed","inputs":[{"name":"gate_seal","type":"address","indexed":false},{"name":"sealed_by","type":"address","indexed":false},{"name":"sealed_for","type":"uint256","indexed":false},{"name":"sealable","type":"address","indexed":false},{"name":"sealed_at","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"stateMutability":"nonpayable","type":"constructor","inputs":[{"name":"_sealing_committee","type":"address"},{"name":"_seal_duration_seconds","type":"uint256"},{"name":"_sealables","type":"address[]"},{"name":"_expiry_timestamp","type":"uint256"}],"outputs":[]},{"stateMutability":"view","type":"function","name":"get_sealing_committee","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"get_seal_duration_seconds","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"get_sealables","inputs":[],"outputs":[{"name":"","type":"address[]"}]},{"stateMutability":"view","type":"function","name":"get_expiry_timestamp","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"is_expired","inputs":[],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"nonpayable","type":"function","name":"seal","inputs":[{"name":"_sealables","type":"address[]"}],"outputs":[]}] +[ + { + "name": "Sealed", + "inputs": [ + { "name": "gate_seal", "type": "address", "indexed": false }, + { "name": "sealed_by", "type": "address", "indexed": false }, + { "name": "sealed_for", "type": "uint256", "indexed": false }, + { "name": "sealable", "type": "address", "indexed": false }, + { "name": "sealed_at", "type": "uint256", "indexed": false } + ], + "anonymous": false, + "type": "event" + }, + { + "stateMutability": "nonpayable", + "type": "constructor", + "inputs": [ + { "name": "_sealing_committee", "type": "address" }, + { "name": "_seal_duration_seconds", "type": "uint256" }, + { "name": "_sealables", "type": "address[]" }, + { "name": "_expiry_timestamp", "type": "uint256" } + ], + "outputs": [] + }, + { + "stateMutability": "view", + "type": "function", + "name": "get_sealing_committee", + "inputs": [], + "outputs": [{ "name": "", "type": "address" }] + }, + { + "stateMutability": "view", + "type": "function", + "name": "get_seal_duration_seconds", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256" }] + }, + { + "stateMutability": "view", + "type": "function", + "name": "get_sealables", + "inputs": [], + "outputs": [{ "name": "", "type": "address[]" }] + }, + { + "stateMutability": "view", + "type": "function", + "name": "get_expiry_timestamp", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256" }] + }, + { + "stateMutability": "view", + "type": "function", + "name": "is_expired", + "inputs": [], + "outputs": [{ "name": "", "type": "bool" }] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "seal", + "inputs": [{ "name": "_sealables", "type": "address[]" }], + "outputs": [] + } +] diff --git a/ethereum-steth-v2/src/abi/GateSealFactory.json b/ethereum-steth-v2/src/abi/GateSealFactory.json index 1ea99898..2045e1eb 100644 --- a/ethereum-steth-v2/src/abi/GateSealFactory.json +++ b/ethereum-steth-v2/src/abi/GateSealFactory.json @@ -1 +1,33 @@ -[{"name":"GateSealCreated","inputs":[{"name":"gate_seal","type":"address","indexed":false}],"anonymous":false,"type":"event"},{"stateMutability":"nonpayable","type":"constructor","inputs":[{"name":"_blueprint","type":"address"}],"outputs":[]},{"stateMutability":"view","type":"function","name":"get_blueprint","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"nonpayable","type":"function","name":"create_gate_seal","inputs":[{"name":"_sealing_committee","type":"address"},{"name":"_seal_duration_seconds","type":"uint256"},{"name":"_sealables","type":"address[]"},{"name":"_expiry_timestamp","type":"uint256"}],"outputs":[]}] +[ + { + "name": "GateSealCreated", + "inputs": [{ "name": "gate_seal", "type": "address", "indexed": false }], + "anonymous": false, + "type": "event" + }, + { + "stateMutability": "nonpayable", + "type": "constructor", + "inputs": [{ "name": "_blueprint", "type": "address" }], + "outputs": [] + }, + { + "stateMutability": "view", + "type": "function", + "name": "get_blueprint", + "inputs": [], + "outputs": [{ "name": "", "type": "address" }] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "create_gate_seal", + "inputs": [ + { "name": "_sealing_committee", "type": "address" }, + { "name": "_seal_duration_seconds", "type": "uint256" }, + { "name": "_sealables", "type": "address[]" }, + { "name": "_expiry_timestamp", "type": "uint256" } + ], + "outputs": [] + } +] diff --git a/ethereum-steth-v2/src/abi/Lido.json b/ethereum-steth-v2/src/abi/Lido.json index 41247452..0d2c2946 100644 --- a/ethereum-steth-v2/src/abi/Lido.json +++ b/ethereum-steth-v2/src/abi/Lido.json @@ -1 +1,871 @@ -[{"constant":false,"inputs":[],"name":"resume","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[],"name":"stop","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"STAKING_CONTROL_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ethAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStakingPaused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_maxStakeLimit","type":"uint256"},{"name":"_stakeLimitIncreasePerBlock","type":"uint256"}],"name":"setStakingLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"RESUME_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_lidoLocator","type":"address"},{"name":"_eip712StETH","type":"address"}],"name":"finalizeUpgrade_v2","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_newDepositedValidators","type":"uint256"}],"name":"unsafeChangeDepositedValidators","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTreasury","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBufferedEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_lidoLocator","type":"address"},{"name":"_eip712StETH","type":"address"}],"name":"initialize","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"receiveELRewards","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentStakeLimit","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getStakeLimitFullInfo","outputs":[{"name":"isStakingPaused","type":"bool"},{"name":"isStakingLimitSet","type":"bool"},{"name":"currentStakeLimit","type":"uint256"},{"name":"maxStakeLimit","type":"uint256"},{"name":"maxStakeLimitGrowthBlocks","type":"uint256"},{"name":"prevStakeLimit","type":"uint256"},{"name":"prevStakeBlockNumber","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferSharesFrom","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"resumeStaking","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFeeDistribution","outputs":[{"name":"treasuryFeeBasisPoints","type":"uint16"},{"name":"insuranceFeeBasisPoints","type":"uint16"},{"name":"operatorsFeeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"receiveWithdrawals","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"nonces","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOracle","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"eip712Domain","outputs":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getContractVersion","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getEIP712StETH","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_referral","type":"address"}],"name":"submit","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_maxDepositsCount","type":"uint256"},{"name":"_stakingModuleId","type":"uint256"},{"name":"_depositCalldata","type":"bytes"}],"name":"deposit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"UNSAFE_CHANGE_DEPOSITED_VALIDATORS_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBeaconStat","outputs":[{"name":"depositedValidators","type":"uint256"},{"name":"beaconValidators","type":"uint256"},{"name":"beaconBalance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"removeStakingLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_reportTimestamp","type":"uint256"},{"name":"_timeElapsed","type":"uint256"},{"name":"_clValidators","type":"uint256"},{"name":"_clBalance","type":"uint256"},{"name":"_withdrawalVaultBalance","type":"uint256"},{"name":"_elRewardsVaultBalance","type":"uint256"},{"name":"_sharesRequestedToBurn","type":"uint256"},{"name":"_withdrawalFinalizationBatches","type":"uint256[]"},{"name":"_simulatedShareRate","type":"uint256"}],"name":"handleOracleReport","outputs":[{"name":"postRebaseAmounts","type":"uint256[4]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFee","outputs":[{"name":"totalFee","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_deadline","type":"uint256"},{"name":"_v","type":"uint8"},{"name":"_r","type":"bytes32"},{"name":"_s","type":"bytes32"}],"name":"permit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getLidoLocator","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"canDeposit","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"STAKING_PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getDepositableEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"pauseStaking","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTotalELRewardsCollected","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[],"name":"StakingPaused","type":"event"},{"anonymous":false,"inputs":[],"name":"StakingResumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"maxStakeLimit","type":"uint256"},{"indexed":false,"name":"stakeLimitIncreasePerBlock","type":"uint256"}],"name":"StakingLimitSet","type":"event"},{"anonymous":false,"inputs":[],"name":"StakingLimitRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"reportTimestamp","type":"uint256"},{"indexed":false,"name":"preCLValidators","type":"uint256"},{"indexed":false,"name":"postCLValidators","type":"uint256"}],"name":"CLValidatorsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"depositedValidators","type":"uint256"}],"name":"DepositedValidatorsChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"reportTimestamp","type":"uint256"},{"indexed":false,"name":"preCLBalance","type":"uint256"},{"indexed":false,"name":"postCLBalance","type":"uint256"},{"indexed":false,"name":"withdrawalsWithdrawn","type":"uint256"},{"indexed":false,"name":"executionLayerRewardsWithdrawn","type":"uint256"},{"indexed":false,"name":"postBufferedEther","type":"uint256"}],"name":"ETHDistributed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"reportTimestamp","type":"uint256"},{"indexed":false,"name":"timeElapsed","type":"uint256"},{"indexed":false,"name":"preTotalShares","type":"uint256"},{"indexed":false,"name":"preTotalEther","type":"uint256"},{"indexed":false,"name":"postTotalShares","type":"uint256"},{"indexed":false,"name":"postTotalEther","type":"uint256"},{"indexed":false,"name":"sharesMintedAsFees","type":"uint256"}],"name":"TokenRebased","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"lidoLocator","type":"address"}],"name":"LidoLocatorSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"ELRewardsReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"WithdrawalsReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"referral","type":"address"}],"name":"Submitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Unbuffered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"eip712StETH","type":"address"}],"name":"EIP712StETHInitialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"sharesValue","type":"uint256"}],"name":"TransferShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":false,"name":"preRebaseTokenAmount","type":"uint256"},{"indexed":false,"name":"postRebaseTokenAmount","type":"uint256"},{"indexed":false,"name":"sharesAmount","type":"uint256"}],"name":"SharesBurnt","type":"event"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"}] +[ + { + "constant": false, + "inputs": [], + "name": "resume", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [{ "name": "", "type": "string" }], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "stop", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "hasInitialized", + "outputs": [{ "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_spender", "type": "address" }, + { "name": "_amount", "type": "uint256" } + ], + "name": "approve", + "outputs": [{ "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "STAKING_CONTROL_ROLE", + "outputs": [{ "name": "", "type": "bytes32" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [{ "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [{ "name": "_ethAmount", "type": "uint256" }], + "name": "getSharesByPooledEth", + "outputs": [{ "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isStakingPaused", + "outputs": [{ "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_sender", "type": "address" }, + { "name": "_recipient", "type": "address" }, + { "name": "_amount", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [{ "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [{ "name": "_script", "type": "bytes" }], + "name": "getEVMScriptExecutor", + "outputs": [{ "name": "", "type": "address" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_maxStakeLimit", "type": "uint256" }, + { "name": "_stakeLimitIncreasePerBlock", "type": "uint256" } + ], + "name": "setStakingLimit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "RESUME_ROLE", + "outputs": [{ "name": "", "type": "bytes32" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_lidoLocator", "type": "address" }, + { "name": "_eip712StETH", "type": "address" } + ], + "name": "finalizeUpgrade_v2", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [{ "name": "", "type": "uint8" }], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getRecoveryVault", + "outputs": [{ "name": "", "type": "address" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [{ "name": "", "type": "bytes32" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getTotalPooledEther", + "outputs": [{ "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [{ "name": "_newDepositedValidators", "type": "uint256" }], + "name": "unsafeChangeDepositedValidators", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "PAUSE_ROLE", + "outputs": [{ "name": "", "type": "bytes32" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_spender", "type": "address" }, + { "name": "_addedValue", "type": "uint256" } + ], + "name": "increaseAllowance", + "outputs": [{ "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getTreasury", + "outputs": [{ "name": "", "type": "address" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isStopped", + "outputs": [{ "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getBufferedEther", + "outputs": [{ "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_lidoLocator", "type": "address" }, + { "name": "_eip712StETH", "type": "address" } + ], + "name": "initialize", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "receiveELRewards", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getWithdrawalCredentials", + "outputs": [{ "name": "", "type": "bytes32" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getCurrentStakeLimit", + "outputs": [{ "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getStakeLimitFullInfo", + "outputs": [ + { "name": "isStakingPaused", "type": "bool" }, + { "name": "isStakingLimitSet", "type": "bool" }, + { "name": "currentStakeLimit", "type": "uint256" }, + { "name": "maxStakeLimit", "type": "uint256" }, + { "name": "maxStakeLimitGrowthBlocks", "type": "uint256" }, + { "name": "prevStakeLimit", "type": "uint256" }, + { "name": "prevStakeBlockNumber", "type": "uint256" } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_sender", "type": "address" }, + { "name": "_recipient", "type": "address" }, + { "name": "_sharesAmount", "type": "uint256" } + ], + "name": "transferSharesFrom", + "outputs": [{ "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [{ "name": "_account", "type": "address" }], + "name": "balanceOf", + "outputs": [{ "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "resumeStaking", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getFeeDistribution", + "outputs": [ + { "name": "treasuryFeeBasisPoints", "type": "uint16" }, + { "name": "insuranceFeeBasisPoints", "type": "uint16" }, + { "name": "operatorsFeeBasisPoints", "type": "uint16" } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "receiveWithdrawals", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [{ "name": "_sharesAmount", "type": "uint256" }], + "name": "getPooledEthByShares", + "outputs": [{ "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [{ "name": "token", "type": "address" }], + "name": "allowRecoverability", + "outputs": [{ "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [{ "name": "owner", "type": "address" }], + "name": "nonces", + "outputs": [{ "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "appId", + "outputs": [{ "name": "", "type": "bytes32" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getOracle", + "outputs": [{ "name": "", "type": "address" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getContractVersion", + "outputs": [{ "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getInitializationBlock", + "outputs": [{ "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_recipient", "type": "address" }, + { "name": "_sharesAmount", "type": "uint256" } + ], + "name": "transferShares", + "outputs": [{ "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [{ "name": "", "type": "string" }], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getEIP712StETH", + "outputs": [{ "name": "", "type": "address" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [{ "name": "", "type": "address" }], + "name": "transferToVault", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { "name": "_sender", "type": "address" }, + { "name": "_role", "type": "bytes32" }, + { "name": "_params", "type": "uint256[]" } + ], + "name": "canPerform", + "outputs": [{ "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [{ "name": "_referral", "type": "address" }], + "name": "submit", + "outputs": [{ "name": "", "type": "uint256" }], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_spender", "type": "address" }, + { "name": "_subtractedValue", "type": "uint256" } + ], + "name": "decreaseAllowance", + "outputs": [{ "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getEVMScriptRegistry", + "outputs": [{ "name": "", "type": "address" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_recipient", "type": "address" }, + { "name": "_amount", "type": "uint256" } + ], + "name": "transfer", + "outputs": [{ "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_maxDepositsCount", "type": "uint256" }, + { "name": "_stakingModuleId", "type": "uint256" }, + { "name": "_depositCalldata", "type": "bytes" } + ], + "name": "deposit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "UNSAFE_CHANGE_DEPOSITED_VALIDATORS_ROLE", + "outputs": [{ "name": "", "type": "bytes32" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getBeaconStat", + "outputs": [ + { "name": "depositedValidators", "type": "uint256" }, + { "name": "beaconValidators", "type": "uint256" }, + { "name": "beaconBalance", "type": "uint256" } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "removeStakingLimit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_reportTimestamp", "type": "uint256" }, + { "name": "_timeElapsed", "type": "uint256" }, + { "name": "_clValidators", "type": "uint256" }, + { "name": "_clBalance", "type": "uint256" }, + { "name": "_withdrawalVaultBalance", "type": "uint256" }, + { "name": "_elRewardsVaultBalance", "type": "uint256" }, + { "name": "_sharesRequestedToBurn", "type": "uint256" }, + { "name": "_withdrawalFinalizationBatches", "type": "uint256[]" }, + { "name": "_simulatedShareRate", "type": "uint256" } + ], + "name": "handleOracleReport", + "outputs": [{ "name": "postRebaseAmounts", "type": "uint256[4]" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getFee", + "outputs": [{ "name": "totalFee", "type": "uint16" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "kernel", + "outputs": [{ "name": "", "type": "address" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getTotalShares", + "outputs": [{ "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_owner", "type": "address" }, + { "name": "_spender", "type": "address" }, + { "name": "_value", "type": "uint256" }, + { "name": "_deadline", "type": "uint256" }, + { "name": "_v", "type": "uint8" }, + { "name": "_r", "type": "bytes32" }, + { "name": "_s", "type": "bytes32" } + ], + "name": "permit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { "name": "_owner", "type": "address" }, + { "name": "_spender", "type": "address" } + ], + "name": "allowance", + "outputs": [{ "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isPetrified", + "outputs": [{ "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getLidoLocator", + "outputs": [{ "name": "", "type": "address" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "canDeposit", + "outputs": [{ "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "STAKING_PAUSE_ROLE", + "outputs": [{ "name": "", "type": "bytes32" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getDepositableEther", + "outputs": [{ "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [{ "name": "_account", "type": "address" }], + "name": "sharesOf", + "outputs": [{ "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "pauseStaking", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getTotalELRewardsCollected", + "outputs": [{ "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { "payable": true, "stateMutability": "payable", "type": "fallback" }, + { "anonymous": false, "inputs": [], "name": "StakingPaused", "type": "event" }, + { "anonymous": false, "inputs": [], "name": "StakingResumed", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "name": "maxStakeLimit", "type": "uint256" }, + { "indexed": false, "name": "stakeLimitIncreasePerBlock", "type": "uint256" } + ], + "name": "StakingLimitSet", + "type": "event" + }, + { "anonymous": false, "inputs": [], "name": "StakingLimitRemoved", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "name": "reportTimestamp", "type": "uint256" }, + { "indexed": false, "name": "preCLValidators", "type": "uint256" }, + { "indexed": false, "name": "postCLValidators", "type": "uint256" } + ], + "name": "CLValidatorsUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "name": "depositedValidators", "type": "uint256" }], + "name": "DepositedValidatorsChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "name": "reportTimestamp", "type": "uint256" }, + { "indexed": false, "name": "preCLBalance", "type": "uint256" }, + { "indexed": false, "name": "postCLBalance", "type": "uint256" }, + { "indexed": false, "name": "withdrawalsWithdrawn", "type": "uint256" }, + { "indexed": false, "name": "executionLayerRewardsWithdrawn", "type": "uint256" }, + { "indexed": false, "name": "postBufferedEther", "type": "uint256" } + ], + "name": "ETHDistributed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "name": "reportTimestamp", "type": "uint256" }, + { "indexed": false, "name": "timeElapsed", "type": "uint256" }, + { "indexed": false, "name": "preTotalShares", "type": "uint256" }, + { "indexed": false, "name": "preTotalEther", "type": "uint256" }, + { "indexed": false, "name": "postTotalShares", "type": "uint256" }, + { "indexed": false, "name": "postTotalEther", "type": "uint256" }, + { "indexed": false, "name": "sharesMintedAsFees", "type": "uint256" } + ], + "name": "TokenRebased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "name": "lidoLocator", "type": "address" }], + "name": "LidoLocatorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "name": "amount", "type": "uint256" }], + "name": "ELRewardsReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "name": "amount", "type": "uint256" }], + "name": "WithdrawalsReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "name": "sender", "type": "address" }, + { "indexed": false, "name": "amount", "type": "uint256" }, + { "indexed": false, "name": "referral", "type": "address" } + ], + "name": "Submitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "name": "amount", "type": "uint256" }], + "name": "Unbuffered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "name": "executor", "type": "address" }, + { "indexed": false, "name": "script", "type": "bytes" }, + { "indexed": false, "name": "input", "type": "bytes" }, + { "indexed": false, "name": "returnData", "type": "bytes" } + ], + "name": "ScriptResult", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "name": "vault", "type": "address" }, + { "indexed": true, "name": "token", "type": "address" }, + { "indexed": false, "name": "amount", "type": "uint256" } + ], + "name": "RecoverToVault", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "name": "eip712StETH", "type": "address" }], + "name": "EIP712StETHInitialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "name": "from", "type": "address" }, + { "indexed": true, "name": "to", "type": "address" }, + { "indexed": false, "name": "sharesValue", "type": "uint256" } + ], + "name": "TransferShares", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "name": "account", "type": "address" }, + { "indexed": false, "name": "preRebaseTokenAmount", "type": "uint256" }, + { "indexed": false, "name": "postRebaseTokenAmount", "type": "uint256" }, + { "indexed": false, "name": "sharesAmount", "type": "uint256" } + ], + "name": "SharesBurnt", + "type": "event" + }, + { "anonymous": false, "inputs": [], "name": "Stopped", "type": "event" }, + { "anonymous": false, "inputs": [], "name": "Resumed", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "name": "from", "type": "address" }, + { "indexed": true, "name": "to", "type": "address" }, + { "indexed": false, "name": "value", "type": "uint256" } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "name": "owner", "type": "address" }, + { "indexed": true, "name": "spender", "type": "address" }, + { "indexed": false, "name": "value", "type": "uint256" } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "name": "version", "type": "uint256" }], + "name": "ContractVersionSet", + "type": "event" + } +] diff --git a/ethereum-steth-v2/src/abi/WithdrawalQueueERC721.json b/ethereum-steth-v2/src/abi/WithdrawalQueueERC721.json index 2c1dd832..3f04ff26 100644 --- a/ethereum-steth-v2/src/abi/WithdrawalQueueERC721.json +++ b/ethereum-steth-v2/src/abi/WithdrawalQueueERC721.json @@ -1 +1,890 @@ -[{"inputs":[{"internalType":"address","name":"_wstETH","type":"address"},{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AdminZeroAddress","type":"error"},{"inputs":[],"name":"ApprovalToOwner","type":"error"},{"inputs":[],"name":"ApproveToCaller","type":"error"},{"inputs":[{"internalType":"uint256","name":"_firstArrayLength","type":"uint256"},{"internalType":"uint256","name":"_secondArrayLength","type":"uint256"}],"name":"ArraysLengthMismatch","type":"error"},{"inputs":[],"name":"BatchesAreNotSorted","type":"error"},{"inputs":[],"name":"CantSendValueRecipientMayHaveReverted","type":"error"},{"inputs":[],"name":"EmptyBatches","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[{"internalType":"uint256","name":"_hint","type":"uint256"}],"name":"InvalidHint","type":"error"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"InvalidOwnerAddress","type":"error"},{"inputs":[],"name":"InvalidReportTimestamp","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"InvalidRequestId","type":"error"},{"inputs":[{"internalType":"uint256","name":"startId","type":"uint256"},{"internalType":"uint256","name":"endId","type":"uint256"}],"name":"InvalidRequestIdRange","type":"error"},{"inputs":[],"name":"InvalidState","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"NotEnoughEther","type":"error"},{"inputs":[{"internalType":"address","name":"_sender","type":"address"},{"internalType":"address","name":"_owner","type":"address"}],"name":"NotOwner","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"NotOwnerOrApproved","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"NotOwnerOrApprovedForAll","type":"error"},{"inputs":[],"name":"PauseUntilMustBeInFuture","type":"error"},{"inputs":[],"name":"PausedExpected","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"RequestAlreadyClaimed","type":"error"},{"inputs":[{"internalType":"uint256","name":"_amountOfStETH","type":"uint256"}],"name":"RequestAmountTooLarge","type":"error"},{"inputs":[{"internalType":"uint256","name":"_amountOfStETH","type":"uint256"}],"name":"RequestAmountTooSmall","type":"error"},{"inputs":[],"name":"RequestIdsNotSorted","type":"error"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"RequestNotFoundOrNotFinalized","type":"error"},{"inputs":[],"name":"ResumedExpected","type":"error"},{"inputs":[{"internalType":"string","name":"str","type":"string"}],"name":"StringTooLong","type":"error"},{"inputs":[{"internalType":"uint256","name":"sent","type":"uint256"},{"internalType":"uint256","name":"maxExpected","type":"uint256"}],"name":"TooMuchEtherToFinalize","type":"error"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"realOwner","type":"address"}],"name":"TransferFromIncorrectOwner","type":"error"},{"inputs":[],"name":"TransferFromZeroAddress","type":"error"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"TransferToNonIERC721Receiver","type":"error"},{"inputs":[],"name":"TransferToThemselves","type":"error"},{"inputs":[],"name":"TransferToZeroAddress","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[],"name":"ZeroAmountOfETH","type":"error"},{"inputs":[],"name":"ZeroMetadata","type":"error"},{"inputs":[],"name":"ZeroPauseDuration","type":"error"},{"inputs":[],"name":"ZeroRecipient","type":"error"},{"inputs":[],"name":"ZeroShareRate","type":"error"},{"inputs":[],"name":"ZeroTimestamp","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"baseURI","type":"string"}],"name":"BaseURISet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_fromTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_toTokenId","type":"uint256"}],"name":"BatchMetadataUpdate","type":"event"},{"anonymous":false,"inputs":[],"name":"BunkerModeDisabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_sinceTimestamp","type":"uint256"}],"name":"BunkerModeEnabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_admin","type":"address"}],"name":"InitializedV1","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"MetadataUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"nftDescriptorAddress","type":"address"}],"name":"NftDescriptorAddressSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"duration","type":"uint256"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfETH","type":"uint256"}],"name":"WithdrawalClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"address","name":"requestor","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfShares","type":"uint256"}],"name":"WithdrawalRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"from","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"to","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfETHLocked","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sharesToBurn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"WithdrawalsFinalized","type":"event"},{"inputs":[],"name":"BUNKER_MODE_DISABLED_TIMESTAMP","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FINALIZE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_TOKEN_URI_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_BATCHES_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STETH_WITHDRAWAL_AMOUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_STETH_WITHDRAWAL_AMOUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ORACLE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSE_INFINITELY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RESUME_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STETH","outputs":[{"internalType":"contract IStETH","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WSTETH","outputs":[{"internalType":"contract IWstETH","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bunkerModeSinceTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxShareRate","type":"uint256"},{"internalType":"uint256","name":"_maxTimestamp","type":"uint256"},{"internalType":"uint256","name":"_maxRequestsPerCall","type":"uint256"},{"components":[{"internalType":"uint256","name":"remainingEthBudget","type":"uint256"},{"internalType":"bool","name":"finished","type":"bool"},{"internalType":"uint256[36]","name":"batches","type":"uint256[36]"},{"internalType":"uint256","name":"batchesLength","type":"uint256"}],"internalType":"struct WithdrawalQueueBase.BatchesCalculationState","name":"_state","type":"tuple"}],"name":"calculateFinalizationBatches","outputs":[{"components":[{"internalType":"uint256","name":"remainingEthBudget","type":"uint256"},{"internalType":"bool","name":"finished","type":"bool"},{"internalType":"uint256[36]","name":"batches","type":"uint256[36]"},{"internalType":"uint256","name":"batchesLength","type":"uint256"}],"internalType":"struct WithdrawalQueueBase.BatchesCalculationState","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"claimWithdrawal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_hints","type":"uint256[]"}],"name":"claimWithdrawals","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_hints","type":"uint256[]"},{"internalType":"address","name":"_recipient","type":"address"}],"name":"claimWithdrawalsTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_lastRequestIdToBeFinalized","type":"uint256"},{"internalType":"uint256","name":"_maxShareRate","type":"uint256"}],"name":"finalize","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"},{"internalType":"uint256","name":"_firstIndex","type":"uint256"},{"internalType":"uint256","name":"_lastIndex","type":"uint256"}],"name":"findCheckpointHints","outputs":[{"internalType":"uint256[]","name":"hintIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBaseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_hints","type":"uint256[]"}],"name":"getClaimableEther","outputs":[{"internalType":"uint256[]","name":"claimableEthValues","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastCheckpointIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastFinalizedRequestId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastRequestId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLockedEtherAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getNFTDescriptorAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getResumeSinceTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"getWithdrawalRequests","outputs":[{"internalType":"uint256[]","name":"requestsIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"}],"name":"getWithdrawalStatus","outputs":[{"components":[{"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"internalType":"uint256","name":"amountOfShares","type":"uint256"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bool","name":"isFinalized","type":"bool"},{"internalType":"bool","name":"isClaimed","type":"bool"}],"internalType":"struct WithdrawalQueueBase.WithdrawalRequestStatus[]","name":"statuses","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isBunkerModeActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"_isBunkerModeNow","type":"bool"},{"internalType":"uint256","name":"_bunkerStartTimestamp","type":"uint256"},{"internalType":"uint256","name":"_currentReportTimestamp","type":"uint256"}],"name":"onOracleReport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_duration","type":"uint256"}],"name":"pauseFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pauseUntilInclusive","type":"uint256"}],"name":"pauseUntil","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_batches","type":"uint256[]"},{"internalType":"uint256","name":"_maxShareRate","type":"uint256"}],"name":"prefinalize","outputs":[{"internalType":"uint256","name":"ethToLock","type":"uint256"},{"internalType":"uint256","name":"sharesToBurn","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"}],"name":"requestWithdrawals","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"},{"components":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct WithdrawalQueue.PermitInput","name":"_permit","type":"tuple"}],"name":"requestWithdrawalsWithPermit","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"}],"name":"requestWithdrawalsWstETH","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"},{"internalType":"address","name":"_owner","type":"address"},{"components":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct WithdrawalQueue.PermitInput","name":"_permit","type":"tuple"}],"name":"requestWithdrawalsWstETHWithPermit","outputs":[{"internalType":"uint256[]","name":"requestIds","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"resume","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_requestId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_operator","type":"address"},{"internalType":"bool","name":"_approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_baseURI","type":"string"}],"name":"setBaseURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_nftDescriptorAddress","type":"address"}],"name":"setNFTDescriptorAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unfinalizedRequestNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unfinalizedStETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] +[ + { + "inputs": [ + { "internalType": "address", "name": "_wstETH", "type": "address" }, + { "internalType": "string", "name": "_name", "type": "string" }, + { "internalType": "string", "name": "_symbol", "type": "string" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { "inputs": [], "name": "AdminZeroAddress", "type": "error" }, + { "inputs": [], "name": "ApprovalToOwner", "type": "error" }, + { "inputs": [], "name": "ApproveToCaller", "type": "error" }, + { + "inputs": [ + { "internalType": "uint256", "name": "_firstArrayLength", "type": "uint256" }, + { "internalType": "uint256", "name": "_secondArrayLength", "type": "uint256" } + ], + "name": "ArraysLengthMismatch", + "type": "error" + }, + { "inputs": [], "name": "BatchesAreNotSorted", "type": "error" }, + { "inputs": [], "name": "CantSendValueRecipientMayHaveReverted", "type": "error" }, + { "inputs": [], "name": "EmptyBatches", "type": "error" }, + { "inputs": [], "name": "InvalidContractVersionIncrement", "type": "error" }, + { + "inputs": [{ "internalType": "uint256", "name": "_hint", "type": "uint256" }], + "name": "InvalidHint", + "type": "error" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "InvalidOwnerAddress", + "type": "error" + }, + { "inputs": [], "name": "InvalidReportTimestamp", "type": "error" }, + { + "inputs": [{ "internalType": "uint256", "name": "_requestId", "type": "uint256" }], + "name": "InvalidRequestId", + "type": "error" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "startId", "type": "uint256" }, + { "internalType": "uint256", "name": "endId", "type": "uint256" } + ], + "name": "InvalidRequestIdRange", + "type": "error" + }, + { "inputs": [], "name": "InvalidState", "type": "error" }, + { "inputs": [], "name": "NonZeroContractVersionOnInit", "type": "error" }, + { "inputs": [], "name": "NotEnoughEther", "type": "error" }, + { + "inputs": [ + { "internalType": "address", "name": "_sender", "type": "address" }, + { "internalType": "address", "name": "_owner", "type": "address" } + ], + "name": "NotOwner", + "type": "error" + }, + { + "inputs": [{ "internalType": "address", "name": "sender", "type": "address" }], + "name": "NotOwnerOrApproved", + "type": "error" + }, + { + "inputs": [{ "internalType": "address", "name": "sender", "type": "address" }], + "name": "NotOwnerOrApprovedForAll", + "type": "error" + }, + { "inputs": [], "name": "PauseUntilMustBeInFuture", "type": "error" }, + { "inputs": [], "name": "PausedExpected", "type": "error" }, + { + "inputs": [{ "internalType": "uint256", "name": "_requestId", "type": "uint256" }], + "name": "RequestAlreadyClaimed", + "type": "error" + }, + { + "inputs": [{ "internalType": "uint256", "name": "_amountOfStETH", "type": "uint256" }], + "name": "RequestAmountTooLarge", + "type": "error" + }, + { + "inputs": [{ "internalType": "uint256", "name": "_amountOfStETH", "type": "uint256" }], + "name": "RequestAmountTooSmall", + "type": "error" + }, + { "inputs": [], "name": "RequestIdsNotSorted", "type": "error" }, + { + "inputs": [{ "internalType": "uint256", "name": "_requestId", "type": "uint256" }], + "name": "RequestNotFoundOrNotFinalized", + "type": "error" + }, + { "inputs": [], "name": "ResumedExpected", "type": "error" }, + { + "inputs": [{ "internalType": "string", "name": "str", "type": "string" }], + "name": "StringTooLong", + "type": "error" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "sent", "type": "uint256" }, + { "internalType": "uint256", "name": "maxExpected", "type": "uint256" } + ], + "name": "TooMuchEtherToFinalize", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "from", "type": "address" }, + { "internalType": "address", "name": "realOwner", "type": "address" } + ], + "name": "TransferFromIncorrectOwner", + "type": "error" + }, + { "inputs": [], "name": "TransferFromZeroAddress", "type": "error" }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "TransferToNonIERC721Receiver", + "type": "error" + }, + { "inputs": [], "name": "TransferToThemselves", "type": "error" }, + { "inputs": [], "name": "TransferToZeroAddress", "type": "error" }, + { + "inputs": [ + { "internalType": "uint256", "name": "expected", "type": "uint256" }, + { "internalType": "uint256", "name": "received", "type": "uint256" } + ], + "name": "UnexpectedContractVersion", + "type": "error" + }, + { "inputs": [], "name": "ZeroAmountOfETH", "type": "error" }, + { "inputs": [], "name": "ZeroMetadata", "type": "error" }, + { "inputs": [], "name": "ZeroPauseDuration", "type": "error" }, + { "inputs": [], "name": "ZeroRecipient", "type": "error" }, + { "inputs": [], "name": "ZeroShareRate", "type": "error" }, + { "inputs": [], "name": "ZeroTimestamp", "type": "error" }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "approved", "type": "address" }, + { "indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256" } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "operator", "type": "address" }, + { "indexed": false, "internalType": "bool", "name": "approved", "type": "bool" } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "string", "name": "baseURI", "type": "string" }], + "name": "BaseURISet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "uint256", "name": "_fromTokenId", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "_toTokenId", "type": "uint256" } + ], + "name": "BatchMetadataUpdate", + "type": "event" + }, + { "anonymous": false, "inputs": [], "name": "BunkerModeDisabled", "type": "event" }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "uint256", "name": "_sinceTimestamp", "type": "uint256" }], + "name": "BunkerModeEnabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "uint256", "name": "version", "type": "uint256" }], + "name": "ContractVersionSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "address", "name": "_admin", "type": "address" }], + "name": "InitializedV1", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "uint256", "name": "_tokenId", "type": "uint256" }], + "name": "MetadataUpdate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "address", "name": "nftDescriptorAddress", "type": "address" }], + "name": "NftDescriptorAddressSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "uint256", "name": "duration", "type": "uint256" }], + "name": "Paused", + "type": "event" + }, + { "anonymous": false, "inputs": [], "name": "Resumed", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "indexed": true, "internalType": "bytes32", "name": "previousAdminRole", "type": "bytes32" }, + { "indexed": true, "internalType": "bytes32", "name": "newAdminRole", "type": "bytes32" } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "indexed": true, "internalType": "address", "name": "account", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "sender", "type": "address" } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "indexed": true, "internalType": "address", "name": "account", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "sender", "type": "address" } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "from", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "to", "type": "address" }, + { "indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256" } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "uint256", "name": "requestId", "type": "uint256" }, + { "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "receiver", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "amountOfETH", "type": "uint256" } + ], + "name": "WithdrawalClaimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "uint256", "name": "requestId", "type": "uint256" }, + { "indexed": true, "internalType": "address", "name": "requestor", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "amountOfStETH", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "amountOfShares", "type": "uint256" } + ], + "name": "WithdrawalRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "uint256", "name": "from", "type": "uint256" }, + { "indexed": true, "internalType": "uint256", "name": "to", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "amountOfETHLocked", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "sharesToBurn", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "timestamp", "type": "uint256" } + ], + "name": "WithdrawalsFinalized", + "type": "event" + }, + { + "inputs": [], + "name": "BUNKER_MODE_DISABLED_TIMESTAMP", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "FINALIZE_ROLE", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MANAGE_TOKEN_URI_ROLE", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_BATCHES_LENGTH", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_STETH_WITHDRAWAL_AMOUNT", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_STETH_WITHDRAWAL_AMOUNT", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ORACLE_ROLE", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PAUSE_INFINITELY", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PAUSE_ROLE", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RESUME_ROLE", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "STETH", + "outputs": [{ "internalType": "contract IStETH", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "WSTETH", + "outputs": [{ "internalType": "contract IWstETH", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_requestId", "type": "uint256" } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "_owner", "type": "address" }], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "bunkerModeSinceTimestamp", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_maxShareRate", "type": "uint256" }, + { "internalType": "uint256", "name": "_maxTimestamp", "type": "uint256" }, + { "internalType": "uint256", "name": "_maxRequestsPerCall", "type": "uint256" }, + { + "components": [ + { "internalType": "uint256", "name": "remainingEthBudget", "type": "uint256" }, + { "internalType": "bool", "name": "finished", "type": "bool" }, + { "internalType": "uint256[36]", "name": "batches", "type": "uint256[36]" }, + { "internalType": "uint256", "name": "batchesLength", "type": "uint256" } + ], + "internalType": "struct WithdrawalQueueBase.BatchesCalculationState", + "name": "_state", + "type": "tuple" + } + ], + "name": "calculateFinalizationBatches", + "outputs": [ + { + "components": [ + { "internalType": "uint256", "name": "remainingEthBudget", "type": "uint256" }, + { "internalType": "bool", "name": "finished", "type": "bool" }, + { "internalType": "uint256[36]", "name": "batches", "type": "uint256[36]" }, + { "internalType": "uint256", "name": "batchesLength", "type": "uint256" } + ], + "internalType": "struct WithdrawalQueueBase.BatchesCalculationState", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "_requestId", "type": "uint256" }], + "name": "claimWithdrawal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256[]", "name": "_requestIds", "type": "uint256[]" }, + { "internalType": "uint256[]", "name": "_hints", "type": "uint256[]" } + ], + "name": "claimWithdrawals", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256[]", "name": "_requestIds", "type": "uint256[]" }, + { "internalType": "uint256[]", "name": "_hints", "type": "uint256[]" }, + { "internalType": "address", "name": "_recipient", "type": "address" } + ], + "name": "claimWithdrawalsTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_lastRequestIdToBeFinalized", "type": "uint256" }, + { "internalType": "uint256", "name": "_maxShareRate", "type": "uint256" } + ], + "name": "finalize", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256[]", "name": "_requestIds", "type": "uint256[]" }, + { "internalType": "uint256", "name": "_firstIndex", "type": "uint256" }, + { "internalType": "uint256", "name": "_lastIndex", "type": "uint256" } + ], + "name": "findCheckpointHints", + "outputs": [{ "internalType": "uint256[]", "name": "hintIds", "type": "uint256[]" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "_requestId", "type": "uint256" }], + "name": "getApproved", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getBaseURI", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256[]", "name": "_requestIds", "type": "uint256[]" }, + { "internalType": "uint256[]", "name": "_hints", "type": "uint256[]" } + ], + "name": "getClaimableEther", + "outputs": [{ "internalType": "uint256[]", "name": "claimableEthValues", "type": "uint256[]" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getContractVersion", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLastCheckpointIndex", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLastFinalizedRequestId", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLastRequestId", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLockedEtherAmount", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getNFTDescriptorAddress", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getResumeSinceTimestamp", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "bytes32", "name": "role", "type": "bytes32" }], + "name": "getRoleAdmin", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "uint256", "name": "index", "type": "uint256" } + ], + "name": "getRoleMember", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "bytes32", "name": "role", "type": "bytes32" }], + "name": "getRoleMemberCount", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "_owner", "type": "address" }], + "name": "getWithdrawalRequests", + "outputs": [{ "internalType": "uint256[]", "name": "requestsIds", "type": "uint256[]" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256[]", "name": "_requestIds", "type": "uint256[]" }], + "name": "getWithdrawalStatus", + "outputs": [ + { + "components": [ + { "internalType": "uint256", "name": "amountOfStETH", "type": "uint256" }, + { "internalType": "uint256", "name": "amountOfShares", "type": "uint256" }, + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "internalType": "bool", "name": "isFinalized", "type": "bool" }, + { "internalType": "bool", "name": "isClaimed", "type": "bool" } + ], + "internalType": "struct WithdrawalQueueBase.WithdrawalRequestStatus[]", + "name": "statuses", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "hasRole", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "_admin", "type": "address" }], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_owner", "type": "address" }, + { "internalType": "address", "name": "_operator", "type": "address" } + ], + "name": "isApprovedForAll", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isBunkerModeActive", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isPaused", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bool", "name": "_isBunkerModeNow", "type": "bool" }, + { "internalType": "uint256", "name": "_bunkerStartTimestamp", "type": "uint256" }, + { "internalType": "uint256", "name": "_currentReportTimestamp", "type": "uint256" } + ], + "name": "onOracleReport", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "_requestId", "type": "uint256" }], + "name": "ownerOf", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "_duration", "type": "uint256" }], + "name": "pauseFor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "_pauseUntilInclusive", "type": "uint256" }], + "name": "pauseUntil", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256[]", "name": "_batches", "type": "uint256[]" }, + { "internalType": "uint256", "name": "_maxShareRate", "type": "uint256" } + ], + "name": "prefinalize", + "outputs": [ + { "internalType": "uint256", "name": "ethToLock", "type": "uint256" }, + { "internalType": "uint256", "name": "sharesToBurn", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256[]", "name": "_amounts", "type": "uint256[]" }, + { "internalType": "address", "name": "_owner", "type": "address" } + ], + "name": "requestWithdrawals", + "outputs": [{ "internalType": "uint256[]", "name": "requestIds", "type": "uint256[]" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256[]", "name": "_amounts", "type": "uint256[]" }, + { "internalType": "address", "name": "_owner", "type": "address" }, + { + "components": [ + { "internalType": "uint256", "name": "value", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "internalType": "struct WithdrawalQueue.PermitInput", + "name": "_permit", + "type": "tuple" + } + ], + "name": "requestWithdrawalsWithPermit", + "outputs": [{ "internalType": "uint256[]", "name": "requestIds", "type": "uint256[]" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256[]", "name": "_amounts", "type": "uint256[]" }, + { "internalType": "address", "name": "_owner", "type": "address" } + ], + "name": "requestWithdrawalsWstETH", + "outputs": [{ "internalType": "uint256[]", "name": "requestIds", "type": "uint256[]" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256[]", "name": "_amounts", "type": "uint256[]" }, + { "internalType": "address", "name": "_owner", "type": "address" }, + { + "components": [ + { "internalType": "uint256", "name": "value", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "internalType": "struct WithdrawalQueue.PermitInput", + "name": "_permit", + "type": "tuple" + } + ], + "name": "requestWithdrawalsWstETHWithPermit", + "outputs": [{ "internalType": "uint256[]", "name": "requestIds", "type": "uint256[]" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { "inputs": [], "name": "resume", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_from", "type": "address" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_requestId", "type": "uint256" } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_from", "type": "address" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_requestId", "type": "uint256" }, + { "internalType": "bytes", "name": "_data", "type": "bytes" } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_operator", "type": "address" }, + { "internalType": "bool", "name": "_approved", "type": "bool" } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "string", "name": "_baseURI", "type": "string" }], + "name": "setBaseURI", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "_nftDescriptorAddress", "type": "address" }], + "name": "setNFTDescriptorAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" }], + "name": "supportsInterface", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "_requestId", "type": "uint256" }], + "name": "tokenURI", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_from", "type": "address" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_requestId", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unfinalizedRequestNumber", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "unfinalizedStETH", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + } +] diff --git a/ethereum-steth-v2/src/app.ts b/ethereum-steth-v2/src/app.ts index 0aa92cee..8ce64a30 100644 --- a/ethereum-steth-v2/src/app.ts +++ b/ethereum-steth-v2/src/app.ts @@ -22,6 +22,7 @@ import { DataRW } from './utils/mutex' import { GateSealCache } from './services/gate-seal/GateSeal.cache' import { VaultSrv } from './services/vault/vault.srv' import { TestNetAddress } from './utils/constants.testnet' +import * as Winston from 'winston' export type Container = { ethClient: IETHProvider @@ -71,8 +72,14 @@ export class App { exitBusOracleContract, ) + const logger: Winston.Logger = Winston.createLogger({ + format: Winston.format.simple(), + transports: [new Winston.transports.Console()], + }) + const stethOperationCache = new StethOperationCache() const stethOperationSrv = new StethOperationSrv( + logger, stethOperationCache, ethClient, address.DEPOSIT_SECURITY_ADDRESS, diff --git a/ethereum-steth-v2/src/clients/mocks/eth_provider_mock.ts b/ethereum-steth-v2/src/clients/mocks/eth_provider_mock.ts new file mode 100644 index 00000000..4dad35a2 --- /dev/null +++ b/ethereum-steth-v2/src/clients/mocks/eth_provider_mock.ts @@ -0,0 +1,18 @@ +// Define a factory function to create mock instances +import { IETHProvider } from '../eth_provider' + +export const ETHProviderMock = (): jest.Mocked => ({ + getTransaction: jest.fn(), + getStartedBlockForApp: jest.fn(), + getHistory: jest.fn(), + getStethBalance: jest.fn(), + getBalance: jest.fn(), + getBalanceByBlockHash: jest.fn(), + getStakingLimitInfo: jest.fn(), + getUnfinalizedStETH: jest.fn(), + getWithdrawalStatuses: jest.fn(), + getBufferedEther: jest.fn(), + checkGateSeal: jest.fn(), + getExpiryTimestamp: jest.fn(), + getETHDistributedEvent: jest.fn(), +}) diff --git a/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts b/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts index 5a0a6f53..e2cd7e5f 100644 --- a/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts +++ b/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts @@ -87,7 +87,7 @@ export class GateSealSrv { out.push(f) } - console.log(elapsedTime(`[${this.name}.initialize]`, start) + ` on block ${currentBlock}`) + console.log(elapsedTime(`[${this.name}.initialize]`, start)) return out } diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.cache.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.cache.ts index da5fdb1a..a0cab1aa 100644 --- a/ethereum-steth-v2/src/services/steth_operation/StethOperation.cache.ts +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.cache.ts @@ -27,11 +27,11 @@ export class StethOperationCache { this._lastBufferedEth = value } - public getCriticalDepositableAmountTime(): number { + public getCriticalDepositableAmountTimestamp(): number { return this._criticalDepositableAmountTime } - public setCriticalDepositableAmountTime(blockTimestamp: number) { + public setCriticalDepositableAmountTimestamp(blockTimestamp: number) { this._criticalDepositableAmountTime = blockTimestamp } diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts new file mode 100644 index 00000000..6168b25a --- /dev/null +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts @@ -0,0 +1,635 @@ +import { + MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL, + MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL_TIME, + MAX_DEPOSITABLE_ETH_AMOUNT_MEDIUM, + MAX_DEPOSITOR_TX_DELAY, + StethOperationSrv, +} from './StethOperation.srv' +import { IETHProvider } from '../../clients/eth_provider' +import { StethOperationCache } from './StethOperation.cache' +import * as E from 'fp-ts/Either' +import { Address, ETH_DECIMALS } from '../../utils/constants' +import { getDepositSecurityEvents } from '../../utils/events/deposit_security_events' +import { getLidoEvents } from '../../utils/events/lido_events' +import { getInsuranceFundEvents } from '../../utils/events/insurance_fund_events' +import { getBurnerEvents } from '../../utils/events/burner_events' +import { ETHProviderMock } from '../../clients/mocks/eth_provider_mock' +import { + LidoContractMock, + TypedEventMock, + WithdrawalQueueContractMock, +} from '../../utils/contract_mocks/contract_mocks' +import { LidoContract, WithdrawalQueueContract } from './contracts' +import { expect } from '@jest/globals' +import { TransactionResponse } from '@ethersproject/abstract-provider' +import { faker } from '@faker-js/faker' +import { BigNumber as EtherBigNumber } from 'ethers' +import BigNumber from 'bignumber.js' +import { Finding, FindingSeverity, FindingType } from 'forta-agent' +import * as Winston from 'winston' +import { TypedEvent } from '../../generated/common' + +describe('StethOperationSrv', () => { + let ethProviderMock: jest.Mocked + let lidoContractMock: jest.Mocked + let wdQueueContractMock: jest.Mocked + const logger: Winston.Logger = Winston.createLogger({ + format: Winston.format.simple(), + transports: [new Winston.transports.Console()], + }) + + const address = Address + beforeEach(() => { + ethProviderMock = ETHProviderMock() + lidoContractMock = LidoContractMock() + wdQueueContractMock = WithdrawalQueueContractMock() + }) + + describe('initialize function tests', () => { + test(`ethProvider.getHistory error`, async () => { + const want = new Error(`getHistory error`) + ethProviderMock.getHistory.mockResolvedValue(E.left(want)) + + const srv = new StethOperationSrv( + logger, + new StethOperationCache(), + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS), + getLidoEvents(address.LIDO_STETH_ADDRESS), + getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20), + getBurnerEvents(address.BURNER_ADDRESS), + ) + + const currentBlock = 19061449 + const result = await srv.initialize(currentBlock) + + expect(result).toStrictEqual(want) + }) + + test(`ethProvider.getStethBalance error`, async () => { + const want = new Error(`getStethBalance error`) + + const TransactionResponseMock: TransactionResponse[] = [ + { + nonce: faker.number.int(), + hash: faker.string.hexadecimal(), + gasLimit: EtherBigNumber.from(faker.number.int()), + confirmations: faker.number.int(), + data: faker.string.hexadecimal(), + value: EtherBigNumber.from(faker.number.int()), + chainId: 1, + from: faker.string.hexadecimal(), + wait: jest.fn(), + }, + ] + ethProviderMock.getHistory.mockResolvedValue(E.right(TransactionResponseMock)) + ethProviderMock.getStethBalance.mockResolvedValue(E.left(want)) + + const srv = new StethOperationSrv( + logger, + new StethOperationCache(), + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS), + getLidoEvents(address.LIDO_STETH_ADDRESS), + getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20), + getBurnerEvents(address.BURNER_ADDRESS), + ) + + const currentBlock = 19061449 + const result = await srv.initialize(currentBlock) + + expect(result).toStrictEqual(want) + }) + + test(`success`, async () => { + const want = null + + const depositorTxTimeEarlier = 1702939609549 + const depositorTxTimeLater = 1702939619549 + const TransactionResponseMock: TransactionResponse[] = [ + { + nonce: faker.number.int(), + hash: faker.string.hexadecimal(), + gasLimit: EtherBigNumber.from(faker.number.int()), + confirmations: faker.number.int(), + data: faker.string.hexadecimal(), + value: EtherBigNumber.from(faker.number.int()), + chainId: 1, + from: faker.string.hexadecimal(), + wait: jest.fn(), + timestamp: depositorTxTimeEarlier, + }, + { + nonce: faker.number.int(), + hash: faker.string.hexadecimal(), + gasLimit: EtherBigNumber.from(faker.number.int()), + confirmations: faker.number.int(), + data: faker.string.hexadecimal(), + value: EtherBigNumber.from(faker.number.int()), + chainId: 1, + from: faker.string.hexadecimal(), + wait: jest.fn(), + timestamp: depositorTxTimeLater, + }, + ] + ethProviderMock.getHistory.mockResolvedValue(E.right(TransactionResponseMock)) + + const stethBalanceMock = new BigNumber(faker.number.bigInt().toString()) + ethProviderMock.getStethBalance.mockResolvedValue(E.right(stethBalanceMock)) + + const cache = new StethOperationCache() + const srv = new StethOperationSrv( + logger, + cache, + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS), + getLidoEvents(address.LIDO_STETH_ADDRESS), + getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20), + getBurnerEvents(address.BURNER_ADDRESS), + ) + + const currentBlock = 19061449 + const result = await srv.initialize(currentBlock) + + expect(result).toStrictEqual(want) + expect(cache.getLastBufferedEth()).toEqual(stethBalanceMock) + expect(cache.getLastDepositorTxTime()).toEqual(depositorTxTimeLater) + }) + }) + + describe('handleBufferedEth function tests', () => { + test(`ethProvider.getBufferedEther error`, async () => { + const getBufferedEtherErr = new Error(`getBufferedEther error`) + ethProviderMock.getBufferedEther.mockResolvedValue(E.left(getBufferedEtherErr)) + + const srv = new StethOperationSrv( + logger, + new StethOperationCache(), + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS), + getLidoEvents(address.LIDO_STETH_ADDRESS), + getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20), + getBurnerEvents(address.BURNER_ADDRESS), + ) + + const currentBlock = 19061449 + const currentBlockTimestamp = faker.date.past().getTime() + const result = await srv.handleBufferedEth(currentBlock, currentBlockTimestamp) + + const expected = Finding.fromObject({ + alertId: 'LIDO-AGENT-ERROR', + description: 'Could not call "ethProvider.getBufferedEther. Cause getBufferedEther error', + name: 'Error in StethOperationSrv.handleBufferedEth:174', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + }) + + expect(result.length).toEqual(1) + expect(result[0].alertId).toEqual(expected.alertId) + expect(result[0].description).toEqual(expected.description) + expect(result[0].name).toEqual(expected.name) + expect(result[0].severity).toEqual(expected.severity) + expect(result[0].type).toEqual(expected.type) + }) + + test(`lidoContract.getDepositableEther error`, async () => { + const getBufferedEther = new BigNumber(faker.number.int()) + ethProviderMock.getBufferedEther.mockResolvedValue(E.right(getBufferedEther)) + lidoContractMock.getDepositableEther.mockRejectedValue(new Error('getDepositableEtherErr')) + + const srv = new StethOperationSrv( + logger, + new StethOperationCache(), + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS), + getLidoEvents(address.LIDO_STETH_ADDRESS), + getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20), + getBurnerEvents(address.BURNER_ADDRESS), + ) + + const currentBlock = 19061449 + const currentBlockTimestamp = faker.date.past().getTime() + const result = await srv.handleBufferedEth(currentBlock, currentBlockTimestamp) + + const expected = Finding.fromObject({ + alertId: 'LIDO-AGENT-ERROR', + description: 'Could not call "lidoContract.getDepositableEther. Cause getDepositableEtherErr', + name: 'Error in StethOperationSrv.handleBufferedEth:189', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + }) + + expect(result.length).toEqual(1) + expect(result[0].alertId).toEqual(expected.alertId) + expect(result[0].description).toEqual(expected.description) + expect(result[0].name).toEqual(expected.name) + expect(result[0].severity).toEqual(expected.severity) + expect(result[0].type).toEqual(expected.type) + }) + test(`lidoContract.shifte3dBufferedEthRaw error`, async () => { + const getBufferedEther = new BigNumber(faker.number.int()) + ethProviderMock.getBufferedEther.mockResolvedValueOnce(E.right(getBufferedEther)) + lidoContractMock.getDepositableEther.mockResolvedValue(EtherBigNumber.from(faker.number.int())) + + // shifte3dBufferedEthRaw + ethProviderMock.getBufferedEther.mockResolvedValue(E.left(new Error('shifte3dBufferedEthRawErr'))) + + const srv = new StethOperationSrv( + logger, + new StethOperationCache(), + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS), + getLidoEvents(address.LIDO_STETH_ADDRESS), + getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20), + getBurnerEvents(address.BURNER_ADDRESS), + ) + + const currentBlock = 19061449 + const currentBlockTimestamp = faker.date.past().getTime() + const result = await srv.handleBufferedEth(currentBlock, currentBlockTimestamp) + + const expected = Finding.fromObject({ + alertId: 'LIDO-AGENT-ERROR', + description: 'Could not call "ethProvider.getBufferedEther". Cause shifte3dBufferedEthRawErr', + name: 'Error in StethOperationSrv.handleBufferedEth:215', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + }) + + expect(result.length).toEqual(1) + expect(result[0].alertId).toEqual(expected.alertId) + expect(result[0].description).toEqual(expected.description) + expect(result[0].name).toEqual(expected.name) + expect(result[0].severity).toEqual(expected.severity) + expect(result[0].type).toEqual(expected.type) + }) + test(`lidoContract.shifte4dBufferedEthRaw error`, async () => { + const getBufferedEther = new BigNumber(faker.number.int()) + ethProviderMock.getBufferedEther.mockResolvedValueOnce(E.right(getBufferedEther)) + lidoContractMock.getDepositableEther.mockResolvedValue(EtherBigNumber.from(faker.number.int())) + + // shifte3dBufferedEthRaw + ethProviderMock.getBufferedEther.mockResolvedValueOnce(E.right(new BigNumber(faker.number.int()))) + // shifte4dBufferedEthRaw + ethProviderMock.getBufferedEther.mockResolvedValue(E.left(new Error('shifte4dBufferedEthRawErr'))) + + const srv = new StethOperationSrv( + logger, + new StethOperationCache(), + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS), + getLidoEvents(address.LIDO_STETH_ADDRESS), + getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20), + getBurnerEvents(address.BURNER_ADDRESS), + ) + + const currentBlock = 19061449 + const currentBlockTimestamp = faker.date.past().getTime() + const result = await srv.handleBufferedEth(currentBlock, currentBlockTimestamp) + + const expected = Finding.fromObject({ + alertId: 'LIDO-AGENT-ERROR', + description: 'Could not call "ethProvider.getBufferedEther". Cause shifte4dBufferedEthRawErr', + name: 'Error in StethOperationSrv.handleBufferedEth:230', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + }) + + expect(result.length).toEqual(1) + expect(result[0].alertId).toEqual(expected.alertId) + expect(result[0].description).toEqual(expected.description) + expect(result[0].name).toEqual(expected.name) + expect(result[0].severity).toEqual(expected.severity) + expect(result[0].type).toEqual(expected.type) + }) + + test(`unbufferedEventsErr error`, async () => { + const getBufferedEther = new BigNumber(faker.number.int()) + ethProviderMock.getBufferedEther.mockResolvedValueOnce(E.right(getBufferedEther)) + lidoContractMock.getDepositableEther.mockResolvedValueOnce(EtherBigNumber.from(faker.number.int())) + + // shifte3dBufferedEthRaw + const shifte3dBufferedEthRaw = new BigNumber(faker.number.int()) + const shifte4dBufferedEthRaw = new BigNumber(shifte3dBufferedEthRaw.plus(faker.number.int())) + ethProviderMock.getBufferedEther.mockResolvedValueOnce(E.right(shifte3dBufferedEthRaw)) + // shifte4dBufferedEthRaw + ethProviderMock.getBufferedEther.mockResolvedValue(E.right(shifte4dBufferedEthRaw)) + + // lidoContractMock.filters.Unbuffered.mockResolvedValue() + lidoContractMock.queryFilter.mockRejectedValue(new Error('UnbufferedEventsErr')) + + const srv = new StethOperationSrv( + logger, + new StethOperationCache(), + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS), + getLidoEvents(address.LIDO_STETH_ADDRESS), + getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20), + getBurnerEvents(address.BURNER_ADDRESS), + ) + + const currentBlock = 19061449 + const currentBlockTimestamp = faker.date.past().getTime() + const result = await srv.handleBufferedEth(currentBlock, currentBlockTimestamp) + + const expected = Finding.fromObject({ + alertId: 'LIDO-AGENT-ERROR', + description: 'Could not fetch unbufferedEvents. Cause UnbufferedEventsErr', + name: 'Error in StethOperationSrv.handleBufferedEth:251', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + }) + + expect(result.length).toEqual(1) + expect(result[0].alertId).toEqual(expected.alertId) + expect(result[0].description).toEqual(expected.description) + expect(result[0].name).toEqual(expected.name) + expect(result[0].severity).toEqual(expected.severity) + expect(result[0].type).toEqual(expected.type) + }) + + test(`wdReqFinalizedEvents error`, async () => { + const getBufferedEther = new BigNumber(faker.number.int()) + ethProviderMock.getBufferedEther.mockResolvedValueOnce(E.right(getBufferedEther)) + lidoContractMock.getDepositableEther.mockResolvedValueOnce(EtherBigNumber.from(faker.number.int())) + + // shifte3dBufferedEthRaw + const shifte3dBufferedEthRaw = new BigNumber(faker.number.int()) + const shifte4dBufferedEthRaw = new BigNumber(shifte3dBufferedEthRaw.plus(faker.number.int())) + ethProviderMock.getBufferedEther.mockResolvedValueOnce(E.right(shifte3dBufferedEthRaw)) + // shifte4dBufferedEthRaw + ethProviderMock.getBufferedEther.mockResolvedValue(E.right(shifte4dBufferedEthRaw)) + + const unbufferedEvents: TypedEvent[] = [TypedEventMock(), TypedEventMock()] + + lidoContractMock.queryFilter.mockResolvedValue(unbufferedEvents) + + wdQueueContractMock.queryFilter.mockRejectedValue(new Error('wdReqFinalizedEventsErr')) + + const srv = new StethOperationSrv( + logger, + new StethOperationCache(), + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS), + getLidoEvents(address.LIDO_STETH_ADDRESS), + getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20), + getBurnerEvents(address.BURNER_ADDRESS), + ) + + const currentBlock = 19061449 + const currentBlockTimestamp = faker.date.past().getTime() + const result = await srv.handleBufferedEth(currentBlock, currentBlockTimestamp) + + const expected = Finding.fromObject({ + alertId: 'LIDO-AGENT-ERROR', + description: 'Could not fetch wdReqFinalizedEvents. Cause wdReqFinalizedEventsErr', + name: 'Error in StethOperationSrv.handleBufferedEth:222', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + }) + + expect(result.length).toEqual(1) + expect(result[0].alertId).toEqual(expected.alertId) + expect(result[0].description).toEqual(expected.description) + expect(result[0].name).toEqual(expected.name) + expect(result[0].severity).toEqual(expected.severity) + expect(result[0].type).toEqual(expected.type) + }) + + test(`unbufferedEvents.length === 0 && wdReqFinalizedEvents.length === 0`, async () => { + const getBufferedEther = new BigNumber(faker.number.int()) + ethProviderMock.getBufferedEther.mockResolvedValueOnce(E.right(getBufferedEther)) + lidoContractMock.getDepositableEther.mockResolvedValueOnce(EtherBigNumber.from(faker.number.int())) + + // shifte3dBufferedEthRaw + const shifte3dBufferedEthRaw = new BigNumber(faker.number.int()) + const shifte4dBufferedEthRaw = new BigNumber(shifte3dBufferedEthRaw.plus(faker.number.int())) + ethProviderMock.getBufferedEther.mockResolvedValueOnce(E.right(shifte3dBufferedEthRaw)) + // shifte4dBufferedEthRaw + ethProviderMock.getBufferedEther.mockResolvedValue(E.right(shifte4dBufferedEthRaw)) + + const unbufferedEvents: TypedEvent[] = [] + lidoContractMock.queryFilter.mockResolvedValue(unbufferedEvents) + const wdReqFinalizedEvents: TypedEvent[] = [] + wdQueueContractMock.queryFilter.mockResolvedValue(wdReqFinalizedEvents) + + const srv = new StethOperationSrv( + logger, + new StethOperationCache(), + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS), + getLidoEvents(address.LIDO_STETH_ADDRESS), + getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20), + getBurnerEvents(address.BURNER_ADDRESS), + ) + + const currentBlock = 19061449 + const currentBlockTimestamp = faker.date.past().getTime() + const result = await srv.handleBufferedEth(currentBlock, currentBlockTimestamp) + + const shiftedBlockNumber = currentBlock - 3 + const expected = Finding.fromObject({ + alertId: 'BUFFERED-ETH-DRAIN', + description: + `Buffered ETH amount decreased from ` + + `${shifte4dBufferedEthRaw.div(ETH_DECIMALS).toFixed(2)} ` + + `to ${shifte3dBufferedEthRaw.div(ETH_DECIMALS).toFixed(2)} ` + + `without Unbuffered or WithdrawalsFinalized events\n\nNote: actual handled block number is ${shiftedBlockNumber}`, + name: '🚨🚨🚨 Buffered ETH drain', + severity: FindingSeverity.Critical, + type: FindingType.Suspicious, + }) + + expect(result.length).toEqual(1) + expect(result[0].alertId).toEqual(expected.alertId) + expect(result[0].description).toEqual(expected.description) + expect(result[0].name).toEqual(expected.name) + expect(result[0].severity).toEqual(expected.severity) + expect(result[0].type).toEqual(expected.type) + }) + + test(`⚠️ High depositable ETH amount`, async () => { + const bufferedEther = new BigNumber(180).multipliedBy(ETH_DECIMALS) + ethProviderMock.getBufferedEther.mockResolvedValueOnce(E.right(bufferedEther)) + + const mockDepositableEther = EtherBigNumber.from(MAX_DEPOSITABLE_ETH_AMOUNT_MEDIUM + 1).mul( + EtherBigNumber.from(ETH_DECIMALS.toString()), + ) + lidoContractMock.getDepositableEther.mockResolvedValueOnce(EtherBigNumber.from(mockDepositableEther.toString())) + + // shifte3dBufferedEthRaw + const shifte3dBufferedEthRaw = new BigNumber(200) + const shifte4dBufferedEthRaw = new BigNumber(100) + ethProviderMock.getBufferedEther.mockResolvedValueOnce(E.right(shifte3dBufferedEthRaw)) + // shifte4dBufferedEthRaw + ethProviderMock.getBufferedEther.mockResolvedValue(E.right(shifte4dBufferedEthRaw)) + + const unbufferedEvents: TypedEvent[] = [TypedEventMock()] + lidoContractMock.queryFilter.mockResolvedValue(unbufferedEvents) + const wdReqFinalizedEvents: TypedEvent[] = [TypedEventMock()] + wdQueueContractMock.queryFilter.mockResolvedValue(wdReqFinalizedEvents) + + const currentBlock = 19061500 + const date = new Date('2024-01-22') + const currentBlockTimestamp = date.getTime() + + const cache = new StethOperationCache() + + cache.setLastDepositorTxTime(date.setHours(-(MAX_DEPOSITOR_TX_DELAY + 1))) + const srv = new StethOperationSrv( + logger, + cache, + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS), + getLidoEvents(address.LIDO_STETH_ADDRESS), + getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20), + getBurnerEvents(address.BURNER_ADDRESS), + ) + + const result = await srv.handleBufferedEth(currentBlock, currentBlockTimestamp) + + const bufferedEth = bufferedEther.div(ETH_DECIMALS).toNumber() + const expected = Finding.fromObject({ + alertId: 'HIGH-DEPOSITABLE-ETH', + description: + `There are ${bufferedEth.toFixed(2)} ` + + `depositable ETH in DAO and there are more than ` + + `${Math.floor(MAX_DEPOSITOR_TX_DELAY / (60 * 60))} ` + + `hours since last Depositor TX`, + name: '⚠️ High depositable ETH amount', + severity: FindingSeverity.Medium, + type: FindingType.Suspicious, + }) + + expect(result.length).toEqual(1) + expect(result[0].alertId).toEqual(expected.alertId) + expect(result[0].description).toEqual(expected.description) + expect(result[0].name).toEqual(expected.name) + expect(result[0].severity).toEqual(expected.severity) + expect(result[0].type).toEqual(expected.type) + + expect(cache.getLastReportedDepositableEthTimestamp()).toEqual(currentBlockTimestamp) + }) + + test(`🚨 Huge depositable ETH amount`, async () => { + const bufferedEther = new BigNumber(180).multipliedBy(ETH_DECIMALS) + ethProviderMock.getBufferedEther.mockResolvedValueOnce(E.right(bufferedEther)) + + const mockDepositableEther = EtherBigNumber.from(MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL + 1).mul( + EtherBigNumber.from(ETH_DECIMALS.toString()), + ) + lidoContractMock.getDepositableEther.mockResolvedValueOnce(EtherBigNumber.from(mockDepositableEther.toString())) + + // shifte3dBufferedEthRaw + const shifte3dBufferedEthRaw = new BigNumber(200) + const shifte4dBufferedEthRaw = new BigNumber(100) + ethProviderMock.getBufferedEther.mockResolvedValueOnce(E.right(shifte3dBufferedEthRaw)) + // shifte4dBufferedEthRaw + ethProviderMock.getBufferedEther.mockResolvedValue(E.right(shifte4dBufferedEthRaw)) + + const unbufferedEvents: TypedEvent[] = [TypedEventMock()] + lidoContractMock.queryFilter.mockResolvedValue(unbufferedEvents) + const wdReqFinalizedEvents: TypedEvent[] = [TypedEventMock()] + wdQueueContractMock.queryFilter.mockResolvedValue(wdReqFinalizedEvents) + + const currentBlock = 19061500 + const date = new Date('2024-01-22') + const currentBlockTimestamp = date.getTime() + + const cache = new StethOperationCache() + + cache.setCriticalDepositableAmountTimestamp(date.setHours(-26)) + + const srv = new StethOperationSrv( + logger, + cache, + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS), + getLidoEvents(address.LIDO_STETH_ADDRESS), + getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20), + getBurnerEvents(address.BURNER_ADDRESS), + ) + + const result = await srv.handleBufferedEth(currentBlock, currentBlockTimestamp) + + const expected = Finding.fromObject({ + alertId: 'HUGE-DEPOSITABLE-ETH', + description: + `There are 20001.00 depositable ETH in DAO for more than ` + + `${Math.floor(MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL_TIME / (60 * 60))} hour(s)`, + name: '🚨 Huge depositable ETH amount', + severity: FindingSeverity.High, + type: FindingType.Suspicious, + }) + + expect(result.length).toEqual(1) + expect(result[0].alertId).toEqual(expected.alertId) + expect(result[0].description).toEqual(expected.description) + expect(result[0].name).toEqual(expected.name) + expect(result[0].severity).toEqual(expected.severity) + expect(result[0].type).toEqual(expected.type) + + expect(cache.getLastReportedDepositableEthTimestamp()).toEqual(currentBlockTimestamp) + }) + }) +}) diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts index 363c2890..9e5a30f8 100644 --- a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts @@ -4,23 +4,26 @@ import { ETH_DECIMALS } from '../../utils/constants' import * as E from 'fp-ts/Either' import { IETHProvider } from '../../clients/eth_provider' import { BlockEvent, filterLog, Finding, FindingSeverity, FindingType } from 'forta-agent' -import { Lido as LidoContract, WithdrawalQueueERC721 as WithdrawalQueueContract } from '../../generated' import { retryAsync } from 'ts-retry' import { BigNumber as EtherBigNumber } from '@ethersproject/bignumber/lib/bignumber' import { Event as EthersEvent } from 'ethers' import { TransactionEvent } from 'forta-agent/dist/sdk/transaction.event' import { EventOfNotice } from '../../entity/events' import { elapsedTime } from '../../utils/time' +import { LidoContract, WithdrawalQueueContract } from './contracts' +import { Logger } from 'winston' +import { TypedEvent } from '../../generated/common' // Formula: (60 * 60 * 72) / 13 = 19_938 const HISTORY_BLOCK_OFFSET: number = Math.floor((60 * 60 * 72) / 13) const BLOCK_CHECK_INTERVAL: number = 100 const BLOCK_CHECK_INTERVAL_SMAll: number = 25 -const MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL: number = 20_000 // 20000 ETH +export const MAX_DEPOSITABLE_ETH_AMOUNT_MEDIUM = 10_000 // 10000 ETH +export const MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL: number = 20_000 // 20000 ETH const REPORT_WINDOW = 60 * 60 * 24 // 24 hours -const MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL_TIME = 60 * 60 // 1 hour -const MAX_DEPOSITABLE_ETH_AMOUNT_MEDIUM = 10_000 // 10000 ETH -const MAX_DEPOSITOR_TX_DELAY = 60 * 60 * 72 // 72 Hours +export const MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL_TIME = 60 * 60 // 1 hour + +export const MAX_DEPOSITOR_TX_DELAY = 60 * 60 * 72 // 72 Hours const REPORT_WINDOW_EXECUTOR_BALANCE = 60 * 60 * 4 // 4 Hours const MIN_DEPOSIT_EXECUTOR_BALANCE = 2 // 2 ETH const REPORT_WINDOW_STAKING_LIMIT_10 = 60 * 60 * 12 // 12 hours @@ -28,6 +31,7 @@ const REPORT_WINDOW_STAKING_LIMIT_30 = 60 * 60 * 12 // 12 hours export class StethOperationSrv { private readonly name = 'StethOperationSrv' + private readonly logger: Logger private readonly cache: StethOperationCache private readonly ethProvider: IETHProvider private readonly depositSecurityAddress: string @@ -42,6 +46,7 @@ export class StethOperationSrv { private readonly burnerEvents: EventOfNotice[] constructor( + logger: Logger, cache: StethOperationCache, ethProvider: IETHProvider, depositSecurityAddress: string, @@ -54,6 +59,7 @@ export class StethOperationSrv { insuranceFundEvents: EventOfNotice[], burnerEvents: EventOfNotice[], ) { + this.logger = logger this.cache = cache this.ethProvider = ethProvider this.depositSecurityAddress = depositSecurityAddress @@ -76,7 +82,7 @@ export class StethOperationSrv { currentBlock - 1, ) if (E.isLeft(history)) { - console.log(elapsedTime(`[${this.name}.initialize]`, start)) + this.logger.info(elapsedTime(`[${this.name}.initialize]`, start)) return history.left } @@ -92,13 +98,13 @@ export class StethOperationSrv { const bufferedEthRaw = await this.ethProvider.getStethBalance(this.lidoStethAddress, currentBlock) if (E.isLeft(bufferedEthRaw)) { - console.log(elapsedTime(`[${this.name}.initialize]`, start)) + this.logger.info(elapsedTime(`[${this.name}.initialize]`, start)) return bufferedEthRaw.left } this.cache.setLastBufferedEth(bufferedEthRaw.right) - console.log(elapsedTime(`[${this.name}.initialize]`, start) + ` on block ${currentBlock}`) + this.logger.info(elapsedTime(`[${this.name}.initialize]`, start)) return null } @@ -111,13 +117,13 @@ export class StethOperationSrv { const findings: Finding[] = [] const [bufferedEthFindings, depositorBalanceFindings, stakingLimitFindings] = await Promise.all([ - this.handleBufferedEth(blockEvent), + this.handleBufferedEth(blockEvent.block.number, blockEvent.block.timestamp), this.handleDepositExecutorBalance(blockEvent), this.handleStakingLimit(blockEvent), ]) findings.push(...bufferedEthFindings, ...depositorBalanceFindings, ...stakingLimitFindings) - console.log(elapsedTime(StethOperationSrv.name + '.' + this.handleBlock.name, start)) + this.logger.info(elapsedTime(StethOperationSrv.name + '.' + this.handleBlock.name, start)) return findings } @@ -163,15 +169,12 @@ export class StethOperationSrv { return out } - public async handleBufferedEth(blockEvent: BlockEvent): Promise { - const blockNumber = blockEvent.block.number - const blockTimestamp = blockEvent.block.timestamp - + public async handleBufferedEth(blockNumber: number, blockTimestamp: number): Promise { const bufferedEthRaw = await this.ethProvider.getBufferedEther(blockNumber) if (E.isLeft(bufferedEthRaw)) { const f: Finding = Finding.fromObject({ - name: `Error in ${StethOperationSrv.name}.${this.handleBufferedEth.name}:178`, - description: `Could not call "lidoContract.getBufferedEther. Cause ${bufferedEthRaw.left.message}`, + name: `Error in ${StethOperationSrv.name}.${this.handleBufferedEth.name}:174`, + description: `Could not call "ethProvider.getBufferedEther. Cause ${bufferedEthRaw.left.message}`, alertId: 'LIDO-AGENT-ERROR', severity: FindingSeverity.Low, type: FindingType.Degraded, @@ -181,7 +184,6 @@ export class StethOperationSrv { return [f] } - const bufferedEth = bufferedEthRaw.right.div(ETH_DECIMALS).toNumber() let depositableEther: number try { @@ -198,7 +200,7 @@ export class StethOperationSrv { depositableEther = depositableEtherRaw.div(ETH_DECIMALS).toNumber() } catch (e) { const f: Finding = Finding.fromObject({ - name: `Error in ${StethOperationSrv.name}.${this.handleBufferedEth.name}:135`, + name: `Error in ${StethOperationSrv.name}.${this.handleBufferedEth.name}:189`, description: `Could not call "lidoContract.getDepositableEther. Cause ${e instanceof Error ? e.message : ''}`, alertId: 'LIDO-AGENT-ERROR', severity: FindingSeverity.Low, @@ -214,8 +216,8 @@ export class StethOperationSrv { const shifte3dBufferedEthRaw = await this.ethProvider.getBufferedEther(shiftedBlockNumber) if (E.isLeft(shifte3dBufferedEthRaw)) { const f: Finding = Finding.fromObject({ - name: `Error in ${StethOperationSrv.name}.${this.handleBufferedEth.name}:159`, - description: `Could not call "this.getBufferedEther". Cause ${shifte3dBufferedEthRaw.left.message}`, + name: `Error in ${StethOperationSrv.name}.${this.handleBufferedEth.name}:215`, + description: `Could not call "ethProvider.getBufferedEther". Cause ${shifte3dBufferedEthRaw.left.message}`, alertId: 'LIDO-AGENT-ERROR', severity: FindingSeverity.Low, type: FindingType.Degraded, @@ -229,8 +231,8 @@ export class StethOperationSrv { const shifte4dBufferedEthRaw = await this.ethProvider.getBufferedEther(shiftedBlockNumber - 1) if (E.isLeft(shifte4dBufferedEthRaw)) { const f: Finding = Finding.fromObject({ - name: `Error in ${StethOperationSrv.name}.${this.handleBufferedEth.name}:174`, - description: `Could not call "this.getBufferedEther". Cause ${shifte4dBufferedEthRaw.left.message}`, + name: `Error in ${StethOperationSrv.name}.${this.handleBufferedEth.name}:230`, + description: `Could not call "ethProvider.getBufferedEther". Cause ${shifte4dBufferedEthRaw.left.message}`, alertId: 'LIDO-AGENT-ERROR', severity: FindingSeverity.Low, type: FindingType.Degraded, @@ -244,22 +246,20 @@ export class StethOperationSrv { const out: Finding[] = [] if (shifte3dBufferedEthRaw.right.lt(shifte4dBufferedEthRaw.right)) { - let unbufferedEvents: EthersEvent[] + let unbufferedEvents: TypedEvent[] try { - unbufferedEvents = await retryAsync( - async (): Promise => { - return await this.lidoContract.queryFilter( - this.lidoContract.filters.Unbuffered(), - shiftedBlockNumber, - shiftedBlockNumber, - ) + unbufferedEvents = await retryAsync( + async (): Promise => { + const filter = this.lidoContract.filters.Unbuffered() + + return await this.lidoContract.queryFilter(filter, shiftedBlockNumber, shiftedBlockNumber) }, { delay: 500, maxTry: 5 }, ) } catch (e) { const f: Finding = Finding.fromObject({ - name: `Error in ${StethOperationSrv.name}.${this.handleBufferedEth.name}:197`, + name: `Error in ${StethOperationSrv.name}.${this.handleBufferedEth.name}:251`, description: `Could not fetch unbufferedEvents. Cause ${e instanceof Error ? e.message : ''}`, alertId: 'LIDO-AGENT-ERROR', severity: FindingSeverity.Low, @@ -274,11 +274,9 @@ export class StethOperationSrv { try { wdReqFinalizedEvents = await retryAsync( async (): Promise => { - return await this.wdQueueContract.queryFilter( - this.wdQueueContract.filters.WithdrawalsFinalized(), - shiftedBlockNumber, - shiftedBlockNumber, - ) + const filter = this.wdQueueContract.filters.WithdrawalsFinalized() + + return await this.wdQueueContract.queryFilter(filter, shiftedBlockNumber, shiftedBlockNumber) }, { delay: 500, maxTry: 5 }, ) @@ -315,18 +313,18 @@ export class StethOperationSrv { if (blockNumber % BLOCK_CHECK_INTERVAL === 0) { // Keep track of buffer size above MAX_BUFFERED_ETH_AMOUNT_CRITICAL if (depositableEther > MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL) { - if (this.cache.getCriticalDepositableAmountTime() === 0) { - this.cache.setCriticalDepositableAmountTime(blockTimestamp) + if (this.cache.getCriticalDepositableAmountTimestamp() === 0) { + this.cache.setCriticalDepositableAmountTimestamp(blockTimestamp) } } else { // reset counter if buffered amount goes below MAX_BUFFERED_ETH_AMOUNT_CRITICAL - this.cache.setCriticalDepositableAmountTime(0) + this.cache.setCriticalDepositableAmountTimestamp(0) } if (this.cache.getLastReportedDepositableEthTimestamp() + REPORT_WINDOW < blockTimestamp) { if ( depositableEther > MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL && - this.cache.getCriticalDepositableAmountTime() < blockTimestamp - MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL_TIME + this.cache.getCriticalDepositableAmountTimestamp() + MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL_TIME < blockTimestamp ) { out.push( Finding.fromObject({ @@ -337,15 +335,17 @@ export class StethOperationSrv { `${Math.floor(MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL_TIME / (60 * 60))} hour(s)`, alertId: 'HUGE-DEPOSITABLE-ETH', severity: FindingSeverity.High, - type: FindingType.Degraded, + type: FindingType.Suspicious, }), ) this.cache.setLastReportedDepositableEthTimestamp(blockTimestamp) } else if ( depositableEther > MAX_DEPOSITABLE_ETH_AMOUNT_MEDIUM && - this.cache.getLastDepositorTxTime() < blockTimestamp - MAX_DEPOSITOR_TX_DELAY && - this.cache.getLastDepositorTxTime() !== 0 + this.cache.getLastDepositorTxTime() !== 0 && + this.cache.getLastDepositorTxTime() + MAX_DEPOSITOR_TX_DELAY < blockTimestamp ) { + const bufferedEth = bufferedEthRaw.right.div(ETH_DECIMALS).toNumber() + out.push( Finding.fromObject({ name: '⚠️ High depositable ETH amount', diff --git a/ethereum-steth-v2/src/services/steth_operation/contracts.ts b/ethereum-steth-v2/src/services/steth_operation/contracts.ts new file mode 100644 index 00000000..0703785b --- /dev/null +++ b/ethereum-steth-v2/src/services/steth_operation/contracts.ts @@ -0,0 +1,36 @@ +import type { BigNumber, BigNumberish, CallOverrides } from 'ethers' +import type { TypedEvent, TypedEventFilter } from '../../generated/common' +import { UnbufferedEventFilter } from '../../generated/Lido' +import { WithdrawalsFinalizedEventFilter } from '../../generated/WithdrawalQueueERC721' + +export interface LidoContract { + getDepositableEther(overrides?: CallOverrides): Promise + + queryFilter( + event: TypedEventFilter, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined, + ): Promise> + + filters: { + Unbuffered(amount?: null): UnbufferedEventFilter + } +} + +export interface WithdrawalQueueContract { + queryFilter( + event: TypedEventFilter, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined, + ): Promise> + + filters: { + WithdrawalsFinalized( + from?: BigNumberish | null, + to?: BigNumberish | null, + amountOfETHLocked?: null, + sharesToBurn?: null, + timestamp?: null, + ): WithdrawalsFinalizedEventFilter + } +} diff --git a/ethereum-steth-v2/src/services/vault/Vault.srv.ts b/ethereum-steth-v2/src/services/vault/Vault.srv.ts index 5cafdca3..56ddc5b9 100644 --- a/ethereum-steth-v2/src/services/vault/Vault.srv.ts +++ b/ethereum-steth-v2/src/services/vault/Vault.srv.ts @@ -41,7 +41,7 @@ export class VaultSrv { public initialize(currentBlock: number): null { const start = new Date().getTime() - console.log(elapsedTime(`[${this.name}.initialize]`, start) + ` on block ${currentBlock}`) + console.log(elapsedTime(`[${this.name}.initialize]`, start)) return null } diff --git a/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts b/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts index cbf00bb1..06f1df23 100644 --- a/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts +++ b/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts @@ -201,7 +201,7 @@ export class WithdrawalsSrv { } } - console.log(elapsedTime(`[${this.name}.initialize]`, start) + ` on block ${currentBlock}`) + console.log(elapsedTime(`[${this.name}.initialize]`, start)) return null } diff --git a/ethereum-steth-v2/src/utils/contract_mocks/contract_mocks.ts b/ethereum-steth-v2/src/utils/contract_mocks/contract_mocks.ts new file mode 100644 index 00000000..843ec340 --- /dev/null +++ b/ethereum-steth-v2/src/utils/contract_mocks/contract_mocks.ts @@ -0,0 +1,44 @@ +import { LidoContract, WithdrawalQueueContract } from '../../services/steth_operation/contracts' +import { TypedEvent } from '../../generated/common' + +export const LidoContractMock = (): jest.Mocked => ({ + getDepositableEther: jest.fn(), + queryFilter: jest.fn(), + filters: { + Unbuffered: () => { + return { + address: '0xae7ab96520de3a18e5e111b5eaab095312d7fe84', + topics: ['0x76a397bea5768d4fca97ef47792796e35f98dc81b16c1de84e28a818e1f97108'], + } + }, + }, +}) + +export const WithdrawalQueueContractMock = (): jest.Mocked => ({ + queryFilter: jest.fn(), + filters: { + WithdrawalsFinalized: () => { + return { + address: '0x889edc2edab5f40e902b864ad4d7ade8e412f9b1', + topics: ['0x197874c72af6a06fb0aa4fab45fd39c7cb61ac0992159872dc3295207da7e9eb'], + } + }, + }, +}) + +export const TypedEventMock = (): jest.Mocked => ({ + address: '', + args: undefined, + blockHash: '', + blockNumber: 0, + data: '', + getBlock: jest.fn(), + getTransaction: jest.fn(), + getTransactionReceipt: jest.fn(), + logIndex: 0, + removeListener: jest.fn(), + removed: false, + topics: [], + transactionHash: '', + transactionIndex: 0, +}) diff --git a/ethereum-steth-v2/tsconfig.json b/ethereum-steth-v2/tsconfig.json index 631cc43b..d943778f 100644 --- a/ethereum-steth-v2/tsconfig.json +++ b/ethereum-steth-v2/tsconfig.json @@ -8,5 +8,11 @@ "noImplicitAny": true, "resolveJsonModule": true }, - "exclude": ["e2e", "node_modules"] -} + "exclude": [ + "e2e", + "node_modules", + "test", + "dist", + "**/*spec.ts" + ] +} \ No newline at end of file diff --git a/ethereum-steth-v2/yarn.lock b/ethereum-steth-v2/yarn.lock index 1a50a913..80a1c15b 100644 --- a/ethereum-steth-v2/yarn.lock +++ b/ethereum-steth-v2/yarn.lock @@ -302,6 +302,20 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@colors/colors@1.6.0", "@colors/colors@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.6.0.tgz#ec6cd237440700bc23ca23087f513c75508958b0" + integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA== + +"@dabh/diagnostics@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a" + integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA== + dependencies: + colorspace "1.1.x" + enabled "2.0.x" + kuler "^2.0.0" + "@danieldietrich/copy@^0.4.2": version "0.4.2" resolved "https://registry.yarnpkg.com/@danieldietrich/copy/-/copy-0.4.2.tgz#c1cabfa499d8b473ba95413c446c1c1efae64d24" @@ -681,6 +695,11 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" +"@faker-js/faker@^8.3.1": + version "8.3.1" + resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-8.3.1.tgz#7753df0cb88d7649becf984a96dd1bd0a26f43e3" + integrity sha512-FdgpFxY6V6rLZE9mmIBb9hM0xpfvQOSNOLnzolzKwsE1DH+gC7lEKV1p1IbR0lAYyvYd5a4u3qWJzowUkw1bIw== + "@grpc/grpc-js@^1.3.6": version "1.9.14" resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.9.14.tgz#236378822876cbf7903f9d61a0330410e8dcc5a1" @@ -1139,7 +1158,7 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^29.5.10": +"@types/jest@^29.5.11": version "29.5.11" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.11.tgz#0c13aa0da7d0929f078ab080ae5d4ced80fa2f2c" integrity sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ== @@ -1205,6 +1224,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== +"@types/triple-beam@^1.3.2": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.5.tgz#74fef9ffbaa198eb8b588be029f38b00299caa2c" + integrity sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw== + "@types/uuid@^8.3.4": version "8.3.4" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" @@ -1488,6 +1512,11 @@ async-retry@^1.3.3: dependencies: retry "0.13.1" +async@^3.2.3: + version "3.2.5" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" + integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -1844,7 +1873,7 @@ collect-v8-coverage@^1.0.0: resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== -color-convert@^1.9.0: +color-convert@^1.9.0, color-convert@^1.9.3: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== @@ -1863,11 +1892,35 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== -color-name@~1.1.4: +color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-string@^1.6.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^3.1.3: + version "3.2.1" + resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" + integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== + dependencies: + color-convert "^1.9.3" + color-string "^1.6.0" + +colorspace@1.1.x: + version "1.1.4" + resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243" + integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w== + dependencies: + color "^3.1.3" + text-hex "1.0.x" + combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -2076,6 +2129,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== +enabled@2.0.x: + version "2.0.0" + resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" + integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -2356,6 +2414,11 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fecha@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" + integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -2407,6 +2470,11 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== +fn.name@1.x.x: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" + integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== + follow-redirects@^1.15.4: version "1.15.5" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" @@ -2697,6 +2765,11 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -3275,6 +3348,11 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +kuler@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" + integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== + leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -3327,6 +3405,18 @@ lodash@^4.17.15, lodash@^4.17.21: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +logform@^2.3.2, logform@^2.4.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/logform/-/logform-2.6.0.tgz#8c82a983f05d6eaeb2d75e3decae7a768b2bf9b5" + integrity sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ== + dependencies: + "@colors/colors" "1.6.0" + "@types/triple-beam" "^1.3.2" + fecha "^4.2.0" + ms "^2.1.1" + safe-stable-stringify "^2.3.1" + triple-beam "^1.3.0" + long@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" @@ -3482,6 +3572,11 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + murmurhash3js@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/murmurhash3js/-/murmurhash3js-3.0.1.tgz#3e983e5b47c2a06f43a713174e7e435ca044b998" @@ -3567,6 +3662,13 @@ once@^1.3.0: dependencies: wrappy "1" +one-time@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" + integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== + dependencies: + fn.name "1.x.x" + onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" @@ -3870,7 +3972,7 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== -readable-stream@^3.6.0, readable-stream@^3.6.2: +readable-stream@^3.4.0, readable-stream@^3.6.0, readable-stream@^3.6.2: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -3978,6 +4080,11 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +safe-stable-stringify@^2.3.1: + version "2.4.3" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" + integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== + safer-buffer@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -4050,6 +4157,13 @@ 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== +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" + simple-update-notifier@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" @@ -4090,6 +4204,11 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== +stack-trace@0.0.x: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== + stack-utils@^2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" @@ -4201,6 +4320,11 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +text-hex@1.0.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" + integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -4230,6 +4354,11 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" +triple-beam@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984" + integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== + ts-api-utils@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" @@ -4270,10 +4399,10 @@ ts-generator@^0.1.1: resolve "^1.8.1" ts-essentials "^1.0.0" -ts-jest@^29.1.1: - version "29.1.1" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.1.tgz#f58fe62c63caf7bfcc5cc6472082f79180f0815b" - integrity sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA== +ts-jest@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.2.tgz#7613d8c81c43c8cb312c6904027257e814c40e09" + integrity sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g== dependencies: bs-logger "0.x" fast-json-stable-stringify "2.x" @@ -4284,6 +4413,11 @@ ts-jest@^29.1.1: semver "^7.5.3" yargs-parser "^21.0.1" +ts-log@^2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/ts-log/-/ts-log-2.2.5.tgz#aef3252f1143d11047e2cb6f7cfaac7408d96623" + integrity sha512-PGcnJoTBnVGy6yYNFxWVNkdcAuAMstvutN9MgDJIV6L0oG8fB+ZNNy1T+wJzah8RPGor1mZuPQkVfXNDpy9eHA== + ts-retry@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/ts-retry/-/ts-retry-4.2.4.tgz#151e813adec2e7edffa5493c2f07b47e71fd336e" @@ -4427,6 +4561,32 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +winston-transport@^4.5.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.6.0.tgz#f1c1a665ad1b366df72199e27892721832a19e1b" + integrity sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg== + dependencies: + logform "^2.3.2" + readable-stream "^3.6.0" + triple-beam "^1.3.0" + +winston@^3.11.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.11.0.tgz#2d50b0a695a2758bb1c95279f0a88e858163ed91" + integrity sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g== + dependencies: + "@colors/colors" "^1.6.0" + "@dabh/diagnostics" "^2.0.2" + async "^3.2.3" + is-stream "^2.0.0" + logform "^2.4.0" + one-time "^1.0.0" + readable-stream "^3.4.0" + safe-stable-stringify "^2.3.1" + stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.5.0" + wordwrapjs@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-4.0.1.tgz#d9790bccfb110a0fc7836b5ebce0937b37a8b98f" From 8f4721a13210a1ccfde7f0ff488e650b951207fc Mon Sep 17 00:00:00 2001 From: Sergey White Date: Wed, 24 Jan 2024 19:11:49 +0300 Subject: [PATCH 09/22] feat: ethereum-steth-v2 moved functional tests --- ethereum-steth-v2/.eslintrc.json | 15 +- ethereum-steth-v2/package.json | 1 - ethereum-steth-v2/src/agent.ts | 6 +- ethereum-steth-v2/src/app.ts | 5 +- ethereum-steth-v2/src/clients/contracts.ts | 16 + ethereum-steth-v2/src/clients/eth_provider.ts | 14 +- .../src/services/gate-seal/GateSeal.srv.ts | 10 +- .../steth_operation/StethOperation.spec.ts | 304 +++++++++++++++++- .../steth_operation/StethOperation.srv.ts | 94 +++--- .../src/services/steth_operation/contracts.ts | 11 + .../src/services/vault/Vault.srv.ts | 8 +- .../services/withdrawals/Withdrawals.srv.ts | 20 +- .../utils/contract_mocks/contract_mocks.ts | 15 +- .../src/utils/events/burner_events.ts | 28 +- .../utils/events/deposit_security_events.ts | 29 +- .../src/utils/events/helper/event_helper.ts | 19 ++ .../src/utils/events/insurance_fund_events.ts | 42 ++- .../src/utils/events/lido_events.ts | 37 ++- .../src/utils/events/withdrawals_events.ts | 22 +- ethereum-steth-v2/tests/.gitignore | 1 + .../tests/e2e/agent-steth-ops.spec.ts | 180 +++++++++++ .../tests/e2e/agent-vaults.spec.ts | 33 ++ ethereum-steth-v2/tests/e2e/utils.ts | 128 ++++++++ ethereum-steth-v2/tsconfig.json | 12 +- ethereum-steth-v2/yarn.lock | 139 ++++---- 25 files changed, 1000 insertions(+), 189 deletions(-) create mode 100644 ethereum-steth-v2/src/clients/contracts.ts create mode 100644 ethereum-steth-v2/src/utils/events/helper/event_helper.ts create mode 100644 ethereum-steth-v2/tests/.gitignore create mode 100644 ethereum-steth-v2/tests/e2e/agent-steth-ops.spec.ts create mode 100644 ethereum-steth-v2/tests/e2e/agent-vaults.spec.ts create mode 100644 ethereum-steth-v2/tests/e2e/utils.ts diff --git a/ethereum-steth-v2/.eslintrc.json b/ethereum-steth-v2/.eslintrc.json index f2b913ec..1262ce13 100644 --- a/ethereum-steth-v2/.eslintrc.json +++ b/ethereum-steth-v2/.eslintrc.json @@ -8,14 +8,7 @@ }, "rules": { "prettier/prettier": "error", - "curly": "error" - }, - "overrides": [ - { - "files": ["tests/**/*", "*.spec.ts"], - "env": { - "jest": true - } - } - ] -} \ No newline at end of file + "curly": "error", + "semi": "off" + } +} diff --git a/ethereum-steth-v2/package.json b/ethereum-steth-v2/package.json index 6abf5c69..ae8402d5 100644 --- a/ethereum-steth-v2/package.json +++ b/ethereum-steth-v2/package.json @@ -54,7 +54,6 @@ "forta-agent": "^0.1.48", "fp-ts": "^2.16.1", "lodash": "^4.17.21", - "ts-log": "^2.2.5", "ts-retry": "^4.2.4", "winston": "^3.11.0" }, diff --git a/ethereum-steth-v2/src/agent.ts b/ethereum-steth-v2/src/agent.ts index 8a0ada8c..7cc560af 100644 --- a/ethereum-steth-v2/src/agent.ts +++ b/ethereum-steth-v2/src/agent.ts @@ -80,7 +80,9 @@ export function initialize(): Initialize { console.log(elapsedTime('Agent.initialize', startTime) + '\n') console.log( - `[${app.StethOperationSrv.getName()}] Last Depositor TxTime: ${app.StethOperationSrv.getStorage().getLastDepositorTxTime()}`, + `[${app.StethOperationSrv.getName()}] Last Depositor TxTime: ${new Date( + app.StethOperationSrv.getStorage().getLastDepositorTxTime() * 1000, + ).toUTCString()}`, ) console.log( `[${app.StethOperationSrv.getName()}] Buffered Eth: ${app.StethOperationSrv.getStorage() @@ -149,5 +151,5 @@ export const handleTransaction = (): HandleTransaction => { export default { initialize: initialize(), handleBlock: handleBlock(), - handleTransaction: handleTransaction(), + // handleTransaction: handleTransaction(), } diff --git a/ethereum-steth-v2/src/app.ts b/ethereum-steth-v2/src/app.ts index 8ce64a30..6f557528 100644 --- a/ethereum-steth-v2/src/app.ts +++ b/ethereum-steth-v2/src/app.ts @@ -20,9 +20,9 @@ import { getWithdrawalsEvents } from './utils/events/withdrawals_events' import { GateSealSrv } from './services/gate-seal/GateSeal.srv' import { DataRW } from './utils/mutex' import { GateSealCache } from './services/gate-seal/GateSeal.cache' -import { VaultSrv } from './services/vault/vault.srv' import { TestNetAddress } from './utils/constants.testnet' import * as Winston from 'winston' +import { VaultSrv } from './services/vault/Vault.srv' export type Container = { ethClient: IETHProvider @@ -94,6 +94,7 @@ export class App { ) const withdrawalsSrv = new WithdrawalsSrv( + logger, ethClient, wdQueueContact, lidoContact, @@ -102,6 +103,7 @@ export class App { ) const gateSealSrv = new GateSealSrv( + logger, ethClient, new GateSealCache(), address.GATE_SEAL_DEFAULT_ADDRESS, @@ -109,6 +111,7 @@ export class App { ) const vaultSrv = new VaultSrv( + logger, ethClient, lidoContact, address.WITHDRAWALS_VAULT_ADDRESS, diff --git a/ethereum-steth-v2/src/clients/contracts.ts b/ethereum-steth-v2/src/clients/contracts.ts new file mode 100644 index 00000000..e5a1f200 --- /dev/null +++ b/ethereum-steth-v2/src/clients/contracts.ts @@ -0,0 +1,16 @@ +import { BlockTag } from '@ethersproject/providers' +import { TransactionResponse } from '@ethersproject/abstract-provider' +import { BigNumber as EtherBigNumber } from '@ethersproject/bignumber/lib/bignumber' + +export abstract class IEtherscanProvider { + abstract getHistory( + addressOrName: string | Promise, + startBlock?: BlockTag, + endBlock?: BlockTag, + ): Promise> + + abstract getBalance( + addressOrName: string | Promise, + blockTag?: BlockTag | Promise, + ): Promise +} diff --git a/ethereum-steth-v2/src/clients/eth_provider.ts b/ethereum-steth-v2/src/clients/eth_provider.ts index 45163a33..e58f5e21 100644 --- a/ethereum-steth-v2/src/clients/eth_provider.ts +++ b/ethereum-steth-v2/src/clients/eth_provider.ts @@ -2,7 +2,6 @@ import { TransactionResponse } from '@ethersproject/abstract-provider' import { ethers } from 'forta-agent' import * as E from 'fp-ts/Either' import { retryAsync } from 'ts-retry' -import { BlockTag } from '@ethersproject/providers' import { BigNumber as EtherBigNumber } from '@ethersproject/bignumber/lib/bignumber' import BigNumber from 'bignumber.js' import { ETH_DECIMALS } from '../utils/constants' @@ -17,6 +16,7 @@ import { WithdrawalQueueBase } from '../generated/WithdrawalQueueERC721' import { GateSeal, GateSealExpiredErr } from '../entity/gate_seal' import { ETHDistributedEvent } from '../generated/Lido' import { DataRW } from '../utils/mutex' +import { IEtherscanProvider } from './contracts' export abstract class IETHProvider { public abstract getTransaction(txHash: string): Promise> @@ -56,16 +56,6 @@ export abstract class IETHProvider { ): Promise> } -interface IEtherscanProvider { - getHistory( - addressOrName: string | Promise, - startBlock?: BlockTag, - endBlock?: BlockTag, - ): Promise> - - getBalance(addressOrName: string | Promise, blockTag?: BlockTag | Promise): Promise -} - export class ETHProvider implements IETHProvider { private jsonRpcProvider: ethers.providers.JsonRpcProvider private etherscanProvider: IEtherscanProvider @@ -178,7 +168,6 @@ export class ETHProvider implements IETHProvider { batchPromises.push(promise) } - console.log('Info: count history requests: ', batchPromises.length) try { await Promise.all(batchPromises) @@ -318,7 +307,6 @@ export class ETHProvider implements IETHProvider { chunkPromises.push(promise) } - console.log('Info: count withdrawals requests: ', chunkPromises.length) try { await Promise.all(chunkPromises) diff --git a/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts b/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts index e2cd7e5f..a6ddfb4f 100644 --- a/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts +++ b/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts @@ -7,6 +7,7 @@ import { GateSealCache } from './GateSeal.cache' import { TransactionEvent } from 'forta-agent/dist/sdk/transaction.event' import { GATE_SEAL_FACTORY_GATE_SEAL_CREATED_EVENT, GATE_SEAL_SEALED_EVENT } from '../../utils/events/gate_seal_events' import { etherscanAddress } from '../../utils/string' +import { Logger } from 'winston' const ONE_HOUR = 60 * 60 const ONE_DAY = 24 * ONE_HOUR @@ -19,6 +20,7 @@ const GATE_SEAL_EXPIRY_THRESHOLD = ONE_MONTH export class GateSealSrv { private readonly name = 'GateSealSrv' + private readonly logger: Logger private readonly ethProvider: IETHProvider private readonly cache: GateSealCache @@ -27,11 +29,13 @@ export class GateSealSrv { private gateSealAddress: string | undefined constructor( + logger: Logger, ethProvider: IETHProvider, cache: GateSealCache, gateSealAddress: string, gateSealFactoryAddress: string, ) { + this.logger = logger this.ethProvider = ethProvider this.cache = cache this.gateSealAddress = gateSealAddress @@ -57,7 +61,7 @@ export class GateSealSrv { }) out.push(f) - console.log(elapsedTime(`[${this.name}.initialize]`, start) + `on block ${currentBlock}`) + this.logger.info(elapsedTime(`[${this.name}.initialize]`, start) + `on block ${currentBlock}`) return out } @@ -87,7 +91,7 @@ export class GateSealSrv { out.push(f) } - console.log(elapsedTime(`[${this.name}.initialize]`, start)) + this.logger.info(elapsedTime(`[${this.name}.initialize]`, start)) return out } @@ -105,7 +109,7 @@ export class GateSealSrv { ]) findings.push(...pauseRoleFindings, ...expiryGateSealFindings) - console.log(elapsedTime(GateSealSrv.name + '.' + this.handleBlock.name, start)) + this.logger.info(elapsedTime(GateSealSrv.name + '.' + this.handleBlock.name, start)) return findings } diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts index 6168b25a..472ce03f 100644 --- a/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts @@ -3,19 +3,24 @@ import { MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL_TIME, MAX_DEPOSITABLE_ETH_AMOUNT_MEDIUM, MAX_DEPOSITOR_TX_DELAY, + MIN_DEPOSIT_EXECUTOR_BALANCE, StethOperationSrv, } from './StethOperation.srv' import { IETHProvider } from '../../clients/eth_provider' import { StethOperationCache } from './StethOperation.cache' import * as E from 'fp-ts/Either' import { Address, ETH_DECIMALS } from '../../utils/constants' -import { getDepositSecurityEvents } from '../../utils/events/deposit_security_events' -import { getLidoEvents } from '../../utils/events/lido_events' -import { getInsuranceFundEvents } from '../../utils/events/insurance_fund_events' -import { getBurnerEvents } from '../../utils/events/burner_events' +import { + getDepositSecurityEvents, + getFilteredDepositSecurityEventsMock, +} from '../../utils/events/deposit_security_events' +import { getFilteredLidoEventsMock, getLidoEvents } from '../../utils/events/lido_events' +import { getFilteredInsuranceFundEventsMock, getInsuranceFundEvents } from '../../utils/events/insurance_fund_events' +import { getBurnerEvents, getFilteredBurnerEventsMock } from '../../utils/events/burner_events' import { ETHProviderMock } from '../../clients/mocks/eth_provider_mock' import { LidoContractMock, + TransactionEventContractMock, TypedEventMock, WithdrawalQueueContractMock, } from '../../utils/contract_mocks/contract_mocks' @@ -25,9 +30,10 @@ import { TransactionResponse } from '@ethersproject/abstract-provider' import { faker } from '@faker-js/faker' import { BigNumber as EtherBigNumber } from 'ethers' import BigNumber from 'bignumber.js' -import { Finding, FindingSeverity, FindingType } from 'forta-agent' +import { Finding, FindingSeverity, FindingType, LogDescription } from 'forta-agent' import * as Winston from 'winston' import { TypedEvent } from '../../generated/common' +import { StakingLimitInfo } from '../../entity/stakingLimitInfo' describe('StethOperationSrv', () => { let ethProviderMock: jest.Mocked @@ -45,7 +51,7 @@ describe('StethOperationSrv', () => { wdQueueContractMock = WithdrawalQueueContractMock() }) - describe('initialize function tests', () => { + describe('initialize', () => { test(`ethProvider.getHistory error`, async () => { const want = new Error(`getHistory error`) ethProviderMock.getHistory.mockResolvedValue(E.left(want)) @@ -172,7 +178,7 @@ describe('StethOperationSrv', () => { }) }) - describe('handleBufferedEth function tests', () => { + describe('handleBufferedEth', () => { test(`ethProvider.getBufferedEther error`, async () => { const getBufferedEtherErr = new Error(`getBufferedEther error`) ethProviderMock.getBufferedEther.mockResolvedValue(E.left(getBufferedEtherErr)) @@ -232,7 +238,7 @@ describe('StethOperationSrv', () => { getBurnerEvents(address.BURNER_ADDRESS), ) - const currentBlock = 19061449 + const currentBlock = 19061500 const currentBlockTimestamp = faker.date.past().getTime() const result = await srv.handleBufferedEth(currentBlock, currentBlockTimestamp) @@ -632,4 +638,286 @@ describe('StethOperationSrv', () => { expect(cache.getLastReportedDepositableEthTimestamp()).toEqual(currentBlockTimestamp) }) }) + + describe('handleDepositExecutorBalance', () => { + test('getBalanceErr', async () => { + const executorBalanceRaw = new Error('getBalanceErr') + ethProviderMock.getBalance.mockResolvedValueOnce(E.left(executorBalanceRaw)) + + const cache = new StethOperationCache() + const srv = new StethOperationSrv( + logger, + cache, + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS), + getLidoEvents(address.LIDO_STETH_ADDRESS), + getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20), + getBurnerEvents(address.BURNER_ADDRESS), + ) + + const blockNumber = 19061500 + const currentBlockDate = new Date('2022-01-21') + const result = await srv.handleDepositExecutorBalance(blockNumber, currentBlockDate.getTime()) + + const expected = Finding.fromObject({ + alertId: 'LIDO-AGENT-ERROR', + description: `Could not fetch depositorBalance. Cause getBalanceErr`, + name: 'Error in StethOperationSrv.handleDepositExecutorBalance:376', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + }) + + expect(result.length).toEqual(1) + expect(result[0].alertId).toEqual(expected.alertId) + expect(result[0].description).toEqual(expected.description) + expect(result[0].name).toEqual(expected.name) + expect(result[0].severity).toEqual(expected.severity) + expect(result[0].type).toEqual(expected.type) + }) + + test('⚠️ Low deposit executor balance', async () => { + const executorBalanceRaw = new BigNumber(MIN_DEPOSIT_EXECUTOR_BALANCE - 1).multipliedBy(ETH_DECIMALS) + ethProviderMock.getBalance.mockResolvedValueOnce(E.right(executorBalanceRaw)) + + const cache = new StethOperationCache() + const srv = new StethOperationSrv( + logger, + cache, + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS), + getLidoEvents(address.LIDO_STETH_ADDRESS), + getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20), + getBurnerEvents(address.BURNER_ADDRESS), + ) + + const blockNumber = 19061500 + const currentBlockDate = new Date('2022-01-21') + const result = await srv.handleDepositExecutorBalance(blockNumber, currentBlockDate.getTime()) + + const expected = Finding.fromObject({ + alertId: 'LOW-DEPOSIT-EXECUTOR-BALANCE', + description: `Balance of deposit executor is 1.0000. This is extremely low! 😱`, + name: '⚠️ Low deposit executor balance', + severity: FindingSeverity.High, + type: FindingType.Suspicious, + }) + + expect(result.length).toEqual(1) + expect(result[0].alertId).toEqual(expected.alertId) + expect(result[0].description).toEqual(expected.description) + expect(result[0].name).toEqual(expected.name) + expect(result[0].severity).toEqual(expected.severity) + expect(result[0].type).toEqual(expected.type) + + expect(cache.getLastReportedExecutorBalanceTimestamp()).toEqual(currentBlockDate.getTime()) + }) + }) + + describe('handleStakingLimit', () => { + test('getStakingLimitInfoErr', async () => { + const getStakingLimitInfo = new Error('getStakingLimitInfoErr') + ethProviderMock.getStakingLimitInfo.mockResolvedValueOnce(E.left(getStakingLimitInfo)) + + const cache = new StethOperationCache() + const srv = new StethOperationSrv( + logger, + cache, + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS), + getLidoEvents(address.LIDO_STETH_ADDRESS), + getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20), + getBurnerEvents(address.BURNER_ADDRESS), + ) + + const blockNumber = 19061500 + const currentBlockDate = new Date('2022-01-21') + const result = await srv.handleStakingLimit(blockNumber, currentBlockDate.getTime()) + + const expected = Finding.fromObject({ + alertId: 'LIDO-AGENT-ERROR', + description: `Could not call "lidoContract.getStakeLimitFullInfo. Cause getStakingLimitInfoErr`, + name: 'Error in StethOperationSrv.handleStakingLimit:418', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + }) + + expect(result.length).toEqual(1) + expect(result[0].alertId).toEqual(expected.alertId) + expect(result[0].description).toEqual(expected.description) + expect(result[0].name).toEqual(expected.name) + expect(result[0].severity).toEqual(expected.severity) + expect(result[0].type).toEqual(expected.type) + }) + + test('⚠️ Staking limit below 10%', async () => { + const getStakingLimitInfo: StakingLimitInfo = { + currentStakeLimit: new BigNumber(9), + isStakingPaused: false, + maxStakeLimit: new BigNumber(100), + } + ethProviderMock.getStakingLimitInfo.mockResolvedValueOnce(E.right(getStakingLimitInfo)) + + const cache = new StethOperationCache() + const srv = new StethOperationSrv( + logger, + cache, + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS), + getLidoEvents(address.LIDO_STETH_ADDRESS), + getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20), + getBurnerEvents(address.BURNER_ADDRESS), + ) + + const blockNumber = 19061500 + const currentBlockDate = new Date('2022-01-21') + const result = await srv.handleStakingLimit(blockNumber, currentBlockDate.getTime()) + + const expected = Finding.fromObject({ + alertId: 'LOW-STAKING-LIMIT', + description: `Current staking limit is 9.00 ETH this is lower than 10% of max staking limit 100.00 ETH`, + name: '⚠️ Staking limit below 10%', + severity: FindingSeverity.Info, + type: FindingType.Info, + }) + + expect(result.length).toEqual(1) + expect(result[0].alertId).toEqual(expected.alertId) + expect(result[0].description).toEqual(expected.description) + expect(result[0].name).toEqual(expected.name) + expect(result[0].severity).toEqual(expected.severity) + expect(result[0].type).toEqual(expected.type) + + expect(cache.getLastReportedStakingLimit10Timestamp()).toEqual(currentBlockDate.getTime()) + }) + + test('⚠️ Staking limit below 30%', async () => { + const getStakingLimitInfo: StakingLimitInfo = { + currentStakeLimit: new BigNumber(250), + isStakingPaused: false, + maxStakeLimit: new BigNumber(1000), + } + ethProviderMock.getStakingLimitInfo.mockResolvedValueOnce(E.right(getStakingLimitInfo)) + + const cache = new StethOperationCache() + const srv = new StethOperationSrv( + logger, + cache, + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS), + getLidoEvents(address.LIDO_STETH_ADDRESS), + getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20), + getBurnerEvents(address.BURNER_ADDRESS), + ) + + const blockNumber = 19061500 + const currentBlockDate = new Date('2022-01-21') + const result = await srv.handleStakingLimit(blockNumber, currentBlockDate.getTime()) + + const expected = Finding.fromObject({ + alertId: 'LOW-STAKING-LIMIT', + description: `Current staking limit is 250.00 ETH this is lower than 30% of max staking limit 1000.00 ETH`, + name: 'πŸ“‰ Staking limit below 30%', + severity: FindingSeverity.Info, + type: FindingType.Info, + }) + + expect(result.length).toEqual(1) + expect(result[0].alertId).toEqual(expected.alertId) + expect(result[0].description).toEqual(expected.description) + expect(result[0].name).toEqual(expected.name) + expect(result[0].severity).toEqual(expected.severity) + expect(result[0].type).toEqual(expected.type) + + expect(srv.getStorage().getLastReportedStakingLimit30Timestamp()).toEqual(currentBlockDate.getTime()) + }) + }) + + describe('handleTransaction', () => { + test('success', async () => { + const cache = new StethOperationCache() + + const depositEvents = getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS) + const lidoEvents = getLidoEvents(address.LIDO_STETH_ADDRESS) + const insuranceFundEvents = getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20) + const burnerEvents = getBurnerEvents(address.BURNER_ADDRESS) + + const events = [...depositEvents, ...lidoEvents, ...insuranceFundEvents, ...burnerEvents] + + const srv = new StethOperationSrv( + logger, + cache, + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + depositEvents, + lidoEvents, + insuranceFundEvents, + burnerEvents, + ) + + const txEventMock = TransactionEventContractMock() + txEventMock.addresses = { + [address.DEPOSIT_SECURITY_ADDRESS]: true, + [address.LIDO_STETH_ADDRESS]: true, + [address.INSURANCE_FUND_ADDRESS]: true, + [address.BURNER_ADDRESS]: true, + } + + txEventMock.to = address.DEPOSIT_SECURITY_ADDRESS + + const filteredEvents: LogDescription[] = [] + for (const logDescription of [ + ...getFilteredDepositSecurityEventsMock(), + ...getFilteredLidoEventsMock(), + ...getFilteredInsuranceFundEventsMock(), + ...getFilteredBurnerEventsMock(), + ]) { + filteredEvents.push(logDescription) + txEventMock.filterLog.mockReturnValueOnce([logDescription]) + } + + const result = srv.handleTransaction(txEventMock) + + for (let i = 0; i < result.length; i++) { + const expected: Finding = Finding.fromObject({ + name: events[i].name, + description: events[i].description(filteredEvents[i].args), + alertId: events[i].alertId, + severity: events[i].severity, + type: events[i].type, + metadata: { args: String(filteredEvents[i].args) }, + }) + + expect(result[i]).toEqual(expected) + } + }) + }) }) diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts index 9e5a30f8..aefca95e 100644 --- a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts @@ -3,14 +3,13 @@ import { StethOperationCache } from './StethOperation.cache' import { ETH_DECIMALS } from '../../utils/constants' import * as E from 'fp-ts/Either' import { IETHProvider } from '../../clients/eth_provider' -import { BlockEvent, filterLog, Finding, FindingSeverity, FindingType } from 'forta-agent' +import { BlockEvent, Finding, FindingSeverity, FindingType } from 'forta-agent' import { retryAsync } from 'ts-retry' import { BigNumber as EtherBigNumber } from '@ethersproject/bignumber/lib/bignumber' import { Event as EthersEvent } from 'ethers' -import { TransactionEvent } from 'forta-agent/dist/sdk/transaction.event' import { EventOfNotice } from '../../entity/events' import { elapsedTime } from '../../utils/time' -import { LidoContract, WithdrawalQueueContract } from './contracts' +import { LidoContract, TransactionEventContract, WithdrawalQueueContract } from './contracts' import { Logger } from 'winston' import { TypedEvent } from '../../generated/common' @@ -25,7 +24,7 @@ export const MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL_TIME = 60 * 60 // 1 hour export const MAX_DEPOSITOR_TX_DELAY = 60 * 60 * 72 // 72 Hours const REPORT_WINDOW_EXECUTOR_BALANCE = 60 * 60 * 4 // 4 Hours -const MIN_DEPOSIT_EXECUTOR_BALANCE = 2 // 2 ETH +export const MIN_DEPOSIT_EXECUTOR_BALANCE = 2 // 2 ETH const REPORT_WINDOW_STAKING_LIMIT_10 = 60 * 60 * 12 // 12 hours const REPORT_WINDOW_STAKING_LIMIT_30 = 60 * 60 * 12 // 12 hours @@ -118,8 +117,8 @@ export class StethOperationSrv { const [bufferedEthFindings, depositorBalanceFindings, stakingLimitFindings] = await Promise.all([ this.handleBufferedEth(blockEvent.block.number, blockEvent.block.timestamp), - this.handleDepositExecutorBalance(blockEvent), - this.handleStakingLimit(blockEvent), + this.handleDepositExecutorBalance(blockEvent.block.number, blockEvent.block.timestamp), + this.handleStakingLimit(blockEvent.block.number, blockEvent.block.timestamp), ]) findings.push(...bufferedEthFindings, ...depositorBalanceFindings, ...stakingLimitFindings) @@ -128,7 +127,7 @@ export class StethOperationSrv { return findings } - public handleTransaction(txEvent: TransactionEvent): Finding[] { + public handleTransaction(txEvent: TransactionEventContract): Finding[] { const out: Finding[] = [] if (txEvent.to == this.depositSecurityAddress) { @@ -145,11 +144,11 @@ export class StethOperationSrv { return out } - public handleEventsOfNotice(txEvent: TransactionEvent, eventsOfNotice: EventOfNotice[]) { + public handleEventsOfNotice(txEvent: TransactionEventContract, eventsOfNotice: EventOfNotice[]) { const out: Finding[] = [] for (const eventInfo of eventsOfNotice) { if (eventInfo.address in txEvent.addresses) { - const filteredEvents = filterLog(txEvent.logs, eventInfo.event, eventInfo.address) + const filteredEvents = txEvent.filterLog(eventInfo.event, eventInfo.address) for (const filteredEvent of filteredEvents) { out.push( @@ -184,33 +183,6 @@ export class StethOperationSrv { return [f] } - let depositableEther: number - - try { - const resp = await retryAsync( - async (): Promise => { - return await this.lidoContract.getDepositableEther({ - blockTag: blockNumber, - }) - }, - { delay: 500, maxTry: 5 }, - ) - - const depositableEtherRaw = new BigNumber(resp.toString()) - depositableEther = depositableEtherRaw.div(ETH_DECIMALS).toNumber() - } catch (e) { - const f: Finding = Finding.fromObject({ - name: `Error in ${StethOperationSrv.name}.${this.handleBufferedEth.name}:189`, - description: `Could not call "lidoContract.getDepositableEther. Cause ${e instanceof Error ? e.message : ''}`, - alertId: 'LIDO-AGENT-ERROR', - severity: FindingSeverity.Low, - type: FindingType.Degraded, - metadata: { stack: e instanceof Error ? `${e.stack}` : 'null' }, - }) - - return [f] - } - // We use shifted block number to ensure that nodes return correct values const shiftedBlockNumber = blockNumber - 3 const shifte3dBufferedEthRaw = await this.ethProvider.getBufferedEther(shiftedBlockNumber) @@ -311,6 +283,32 @@ export class StethOperationSrv { } if (blockNumber % BLOCK_CHECK_INTERVAL === 0) { + let depositableEther: number + + try { + const resp = await retryAsync( + async (): Promise => { + return await this.lidoContract.getDepositableEther({ + blockTag: blockNumber, + }) + }, + { delay: 500, maxTry: 5 }, + ) + + const depositableEtherRaw = new BigNumber(resp.toString()) + depositableEther = depositableEtherRaw.div(ETH_DECIMALS).toNumber() + } catch (e) { + const f: Finding = Finding.fromObject({ + name: `Error in ${StethOperationSrv.name}.${this.handleBufferedEth.name}:189`, + description: `Could not call "lidoContract.getDepositableEther. Cause ${e instanceof Error ? e.message : ''}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { stack: e instanceof Error ? `${e.stack}` : 'null' }, + }) + + return [f] + } // Keep track of buffer size above MAX_BUFFERED_ETH_AMOUNT_CRITICAL if (depositableEther > MAX_DEPOSITABLE_ETH_AMOUNT_CRITICAL) { if (this.cache.getCriticalDepositableAmountTimestamp() === 0) { @@ -367,23 +365,18 @@ export class StethOperationSrv { return out } - public async handleDepositExecutorBalance(blockEvent: BlockEvent): Promise { - const blockNumber = blockEvent.block.number + public async handleDepositExecutorBalance(blockNumber: number, currentBlockTimestamp: number): Promise { const out: Finding[] = [] - if (blockNumber % BLOCK_CHECK_INTERVAL) { - const currentBlockTimestamp = blockEvent.block.timestamp + if (blockNumber % BLOCK_CHECK_INTERVAL === 0) { if ( this.cache.getLastReportedExecutorBalanceTimestamp() + REPORT_WINDOW_EXECUTOR_BALANCE < currentBlockTimestamp ) { - const executorBalanceRaw = await this.ethProvider.getBalance( - this.lidoDepositExecutorAddress, - blockEvent.blockNumber, - ) + const executorBalanceRaw = await this.ethProvider.getBalance(this.lidoDepositExecutorAddress, blockNumber) if (E.isLeft(executorBalanceRaw)) { out.push( Finding.fromObject({ - name: `Error in ${StethOperationSrv.name}.${this.handleDepositExecutorBalance.name}:329`, + name: `Error in ${StethOperationSrv.name}.${this.handleDepositExecutorBalance.name}:376`, description: `Could not fetch depositorBalance. Cause ${executorBalanceRaw.left.message}`, alertId: 'LIDO-AGENT-ERROR', severity: FindingSeverity.Low, @@ -395,7 +388,7 @@ export class StethOperationSrv { return out } - const executorBalance = new BigNumber(String(executorBalanceRaw.right)).div(ETH_DECIMALS).toNumber() + const executorBalance = executorBalanceRaw.right.div(ETH_DECIMALS).toNumber() if (executorBalance < MIN_DEPOSIT_EXECUTOR_BALANCE) { this.cache.setLastReportedExecutorBalanceTimestamp(currentBlockTimestamp) out.push( @@ -415,16 +408,13 @@ export class StethOperationSrv { return out } - public async handleStakingLimit(blockEvent: BlockEvent): Promise { - const blockNumber = blockEvent.block.number + public async handleStakingLimit(blockNumber: number, currentBlockTimestamp: number): Promise { const out: Finding[] = [] - if (blockNumber % BLOCK_CHECK_INTERVAL_SMAll !== 0) { - const currentBlockTimestamp = blockEvent.block.timestamp - + if (blockNumber % BLOCK_CHECK_INTERVAL_SMAll === 0) { const stakingLimitInfo = await this.ethProvider.getStakingLimitInfo(blockNumber) if (E.isLeft(stakingLimitInfo)) { const f: Finding = Finding.fromObject({ - name: `Error in ${StethOperationSrv.name}.${this.handleStakingLimit.name}:385`, + name: `Error in ${StethOperationSrv.name}.${this.handleStakingLimit.name}:418`, description: `Could not call "lidoContract.getStakeLimitFullInfo. Cause ${stakingLimitInfo.left.message}`, alertId: 'LIDO-AGENT-ERROR', severity: FindingSeverity.Low, diff --git a/ethereum-steth-v2/src/services/steth_operation/contracts.ts b/ethereum-steth-v2/src/services/steth_operation/contracts.ts index 0703785b..7b918313 100644 --- a/ethereum-steth-v2/src/services/steth_operation/contracts.ts +++ b/ethereum-steth-v2/src/services/steth_operation/contracts.ts @@ -2,6 +2,7 @@ import type { BigNumber, BigNumberish, CallOverrides } from 'ethers' import type { TypedEvent, TypedEventFilter } from '../../generated/common' import { UnbufferedEventFilter } from '../../generated/Lido' import { WithdrawalsFinalizedEventFilter } from '../../generated/WithdrawalQueueERC721' +import { Log, LogDescription } from 'forta-agent' export interface LidoContract { getDepositableEther(overrides?: CallOverrides): Promise @@ -34,3 +35,13 @@ export interface WithdrawalQueueContract { ): WithdrawalsFinalizedEventFilter } } + +export type TransactionEventContract = { + addresses: { + [key: string]: boolean + } + logs: Log[] + filterLog: (eventAbi: string | string[], contractAddress?: string | string[]) => LogDescription[] + to: string | null + timestamp: number +} diff --git a/ethereum-steth-v2/src/services/vault/Vault.srv.ts b/ethereum-steth-v2/src/services/vault/Vault.srv.ts index 56ddc5b9..978b635a 100644 --- a/ethereum-steth-v2/src/services/vault/Vault.srv.ts +++ b/ethereum-steth-v2/src/services/vault/Vault.srv.ts @@ -9,12 +9,14 @@ import { toEthString } from '../../utils/string' import { ETHDistributedEvent } from '../../generated/Lido' import { TransactionEvent } from 'forta-agent/dist/sdk/transaction.event' import { TRANSFER_SHARES_EVENT } from '../../utils/events/vault_events' +import { Logger } from 'winston' const WITHDRAWAL_VAULT_BALANCE_BLOCK_INTERVAL = 100 const WITHDRAWAL_VAULT_BALANCE_DIFF_INFO = ETH_DECIMALS.times(1000) const EL_VAULT_BALANCE_DIFF_INFO = ETH_DECIMALS.times(50) export class VaultSrv { + private readonly logger: Logger private readonly name = 'VaultSrv' private readonly ethProvider: IETHProvider @@ -26,12 +28,14 @@ export class VaultSrv { private readonly burnerAddress: string constructor( + logger: Logger, ethProvider: IETHProvider, lidoContract: LidoContract, withdrawalsVaultAddress: string, elRewardsVaultAddress: string, burnerAddress: string, ) { + this.logger = logger this.ethProvider = ethProvider this.lidoContract = lidoContract this.elRewardsVaultAddress = elRewardsVaultAddress @@ -41,7 +45,7 @@ export class VaultSrv { public initialize(currentBlock: number): null { const start = new Date().getTime() - console.log(elapsedTime(`[${this.name}.initialize]`, start)) + this.logger.info(elapsedTime(`[${this.name}.initialize] on ${currentBlock}`, start)) return null } @@ -121,7 +125,7 @@ export class VaultSrv { ...noELVaultDrainsFindings, ) - console.log(elapsedTime(VaultSrv.name + '.' + this.handleBlock.name, start)) + this.logger.info(elapsedTime(VaultSrv.name + '.' + this.handleBlock.name, start)) return findings } diff --git a/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts b/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts index 06f1df23..a643852f 100644 --- a/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts +++ b/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts @@ -18,6 +18,7 @@ import { } from '../../utils/events/withdrawals_events' import { etherscanAddress, etherscanNft } from '../../utils/string' import { EventOfNotice } from '../../entity/events' +import { Logger } from 'winston' const ONE_HOUR = 60 * 60 const ONE_DAY = ONE_HOUR * 24 @@ -43,6 +44,7 @@ const CLAIMED_AMOUNT_MORE_THAN_REQUESTED_MAX_ALERTS_PER_HOUR = 5 export class WithdrawalsSrv { private name = `WithdrawalsSrv` + private readonly logger: Logger private readonly lidoContract: LidoContract private readonly wdQueueContract: WithdrawalQueueContract private readonly cache: WithdrawalsCache @@ -50,12 +52,14 @@ export class WithdrawalsSrv { private readonly withdrawalsEvents: EventOfNotice[] constructor( + logger: Logger, ethProvider: IETHProvider, wdQueueContract: WithdrawalQueueContract, lidoContract: LidoContract, cache: WithdrawalsCache, withdrawalsEvents: EventOfNotice[], ) { + this.logger = logger this.ethProvider = ethProvider this.wdQueueContract = wdQueueContract this.lidoContract = lidoContract @@ -80,7 +84,7 @@ export class WithdrawalsSrv { this.cache.setIsBunkerMode(isBunkerMode) } catch (e) { - console.log(elapsedTime(`[${this.name}.initialize]`, start)) + this.logger.info(elapsedTime(`[${this.name}.initialize]`, start)) return new Error(`Could not call "isBunkerModeActive. Cause ${e}`) } @@ -100,7 +104,7 @@ export class WithdrawalsSrv { this.cache.setBunkerModeEnabledSinceTimestamp(bunkerModeSinceTimestamp) } catch (e) { - console.log(elapsedTime(`[${this.name}.initialize]`, start)) + this.logger.info(elapsedTime(`[${this.name}.initialize]`, start)) return new Error(`Could not call "bunkerModeSinceTimestamp. Cause ${e}`) } } @@ -118,7 +122,7 @@ export class WithdrawalsSrv { { delay: 500, maxTry: 5 }, ) } catch (e) { - console.log(elapsedTime(`[${this.name}.initialize]`, start)) + this.logger.info(elapsedTime(`[${this.name}.initialize]`, start)) return new Error(`Could not call "getLastRequestId. Cause ${e}`) } @@ -137,7 +141,7 @@ export class WithdrawalsSrv { this.cache.setLastFinalizedRequestId(lastFinalizedRequestId) } catch (e) { - console.log(elapsedTime(`[${this.name}.initialize]`, start)) + this.logger.info(elapsedTime(`[${this.name}.initialize]`, start)) return new Error(`Could not call "getLastFinalizedRequestId. Cause ${e}`) } @@ -159,7 +163,7 @@ export class WithdrawalsSrv { this.cache.setLastFinalizedTimestamp(lastFinalizedTimestamp) } catch (e) { - console.log(elapsedTime(`[${this.name}.initialize]`, start)) + this.logger.info(elapsedTime(`[${this.name}.initialize]`, start)) return new Error(`Could not call "getWithdrawalStatus. Cause ${e}`) } @@ -178,7 +182,7 @@ export class WithdrawalsSrv { const requestsStatuses = await this.ethProvider.getWithdrawalStatuses(requestsRange, currentBlock) if (E.isLeft(requestsStatuses)) { - console.log(elapsedTime(`[${this.name}.initialize]`, start)) + this.logger.info(elapsedTime(`[${this.name}.initialize]`, start)) return requestsStatuses.left } @@ -201,7 +205,7 @@ export class WithdrawalsSrv { } } - console.log(elapsedTime(`[${this.name}.initialize]`, start)) + this.logger.info(elapsedTime(`[${this.name}.initialize]`, start)) return null } @@ -228,7 +232,7 @@ export class WithdrawalsSrv { ) } - console.log(elapsedTime(WithdrawalsSrv.name + '.' + this.handleBlock.name, start)) + this.logger.info(elapsedTime(WithdrawalsSrv.name + '.' + this.handleBlock.name, start)) return findings } diff --git a/ethereum-steth-v2/src/utils/contract_mocks/contract_mocks.ts b/ethereum-steth-v2/src/utils/contract_mocks/contract_mocks.ts index 843ec340..3f44c623 100644 --- a/ethereum-steth-v2/src/utils/contract_mocks/contract_mocks.ts +++ b/ethereum-steth-v2/src/utils/contract_mocks/contract_mocks.ts @@ -1,5 +1,10 @@ -import { LidoContract, WithdrawalQueueContract } from '../../services/steth_operation/contracts' +import { + LidoContract, + TransactionEventContract, + WithdrawalQueueContract, +} from '../../services/steth_operation/contracts' import { TypedEvent } from '../../generated/common' +import { faker } from '@faker-js/faker' export const LidoContractMock = (): jest.Mocked => ({ getDepositableEther: jest.fn(), @@ -42,3 +47,11 @@ export const TypedEventMock = (): jest.Mocked => ({ transactionHash: '', transactionIndex: 0, }) + +export const TransactionEventContractMock = (): jest.Mocked => ({ + addresses: {}, + logs: [], + filterLog: jest.fn(), + to: faker.finance.ethereumAddress(), + timestamp: faker.date.past().getTime(), +}) diff --git a/ethereum-steth-v2/src/utils/events/burner_events.ts b/ethereum-steth-v2/src/utils/events/burner_events.ts index 7a508224..189dd779 100644 --- a/ethereum-steth-v2/src/utils/events/burner_events.ts +++ b/ethereum-steth-v2/src/utils/events/burner_events.ts @@ -1,7 +1,9 @@ import { EventOfNotice } from '../../entity/events' -import { FindingSeverity, FindingType } from 'forta-agent' +import { FindingSeverity, FindingType, LogDescription } from 'forta-agent' import { Result } from '@ethersproject/abi/lib' import { etherscanAddress } from '../string' +import { faker } from '@faker-js/faker' +import { createLogDescriptionMock } from './helper/event_helper' export function getBurnerEvents(BURNER_ADDRESS: string): EventOfNotice[] { return [ @@ -33,3 +35,27 @@ export function getBurnerEvents(BURNER_ADDRESS: string): EventOfNotice[] { }, ] } + +export function getFilteredBurnerEventsMock(): LogDescription[] { + const descriptions = [ + { + ['requestedBy']: faker.finance.ethereumAddress(), + ['token']: faker.finance.ethereumAddress(), + ['amount']: faker.number.int(), + }, + { + ['requestedBy']: faker.finance.ethereumAddress(), + ['token']: faker.finance.ethereumAddress(), + ['tokenId']: faker.number.int(), + }, + ] + + const out: LogDescription[] = [] + for (const desc of descriptions) { + // eslint-disable-next-line + // @ts-expect-error + out.push(createLogDescriptionMock(desc)) + } + + return out +} diff --git a/ethereum-steth-v2/src/utils/events/deposit_security_events.ts b/ethereum-steth-v2/src/utils/events/deposit_security_events.ts index 2d52c142..fa17f483 100644 --- a/ethereum-steth-v2/src/utils/events/deposit_security_events.ts +++ b/ethereum-steth-v2/src/utils/events/deposit_security_events.ts @@ -1,7 +1,9 @@ import { EventOfNotice } from '../../entity/events' -import { FindingSeverity, FindingType } from 'forta-agent' +import { FindingSeverity, FindingType, LogDescription } from 'forta-agent' import { Result } from '@ethersproject/abi/lib' import { etherscanAddress } from '../string' +import { faker } from '@faker-js/faker' +import { createLogDescriptionMock } from './helper/event_helper' export function getDepositSecurityEvents(DEPOSIT_SECURITY_ADDRESS: string): EventOfNotice[] { return [ @@ -80,3 +82,28 @@ export function getDepositSecurityEvents(DEPOSIT_SECURITY_ADDRESS: string): Even }, ] } + +export function getFilteredDepositSecurityEventsMock(): LogDescription[] { + const descriptions = [ + { + ['guardian']: faker.finance.ethereumAddress(), + ['stakingModuleId']: 1, + }, + { ['stakingModuleId']: 1 }, + { ['guardian']: faker.finance.ethereumAddress() }, + { ['newValue']: faker.number.int() }, + { ['newValue']: faker.number.int() }, + { ['newValue']: faker.number.int() }, + { ['newValue']: faker.number.int() }, + { ['newValue']: faker.number.int() }, + ] + + const out: LogDescription[] = [] + for (const desc of descriptions) { + // eslint-disable-next-line + // @ts-expect-error + out.push(createLogDescriptionMock(desc)) + } + + return out +} diff --git a/ethereum-steth-v2/src/utils/events/helper/event_helper.ts b/ethereum-steth-v2/src/utils/events/helper/event_helper.ts new file mode 100644 index 00000000..cc4e05db --- /dev/null +++ b/ethereum-steth-v2/src/utils/events/helper/event_helper.ts @@ -0,0 +1,19 @@ +import { LogDescription } from 'forta-agent' +import { faker } from '@faker-js/faker' +import { Result } from '@ethersproject/abi/lib' + +export function createLogDescriptionMock(args?: Result): jest.Mocked { + return { + address: faker.finance.ethereumAddress(), + // eslint-disable-next-line + // @ts-expect-error + args: args ?? {}, + // eslint-disable-next-line + // @ts-expect-error + eventFragment: jest.fn(), + logIndex: faker.number.int(), + name: faker.string.uuid(), + signature: faker.finance.ethereumAddress(), + topic: faker.finance.ethereumAddress(), + } +} diff --git a/ethereum-steth-v2/src/utils/events/insurance_fund_events.ts b/ethereum-steth-v2/src/utils/events/insurance_fund_events.ts index 6dd750f6..a8b74a40 100644 --- a/ethereum-steth-v2/src/utils/events/insurance_fund_events.ts +++ b/ethereum-steth-v2/src/utils/events/insurance_fund_events.ts @@ -1,9 +1,11 @@ import { EventOfNotice } from '../../entity/events' -import { ERC20, ETH_DECIMALS } from '../constants' +import { Address, ERC20, ETH_DECIMALS } from '../constants' import BigNumber from 'bignumber.js' -import { FindingSeverity, FindingType } from 'forta-agent' +import { FindingSeverity, FindingType, LogDescription } from 'forta-agent' import { Result } from '@ethersproject/abi/lib' import { etherscanAddress } from '../string' +import { faker } from '@faker-js/faker' +import { createLogDescriptionMock } from './helper/event_helper' export function getInsuranceFundEvents( INSURANCE_FUND_ADDRESS: string, @@ -79,3 +81,39 @@ export function getInsuranceFundEvents( }, ] } + +export function getFilteredInsuranceFundEventsMock(): LogDescription[] { + const descriptions = [ + { + ['_amount']: Address.DAI_ADDRESS, + ['_recipient']: faker.finance.ethereumAddress(), + }, + { + ['_token']: Address.DAI_ADDRESS, + ['_recipient']: faker.finance.ethereumAddress(), + }, + { + ['_token']: Address.DAI_ADDRESS, + ['_amount']: faker.number.bigInt(), + ['_recipient']: faker.finance.ethereumAddress(), + }, + { + ['_token']: Address.DAI_ADDRESS, + ['_amount']: faker.number.bigInt(), + ['_recipient']: faker.finance.ethereumAddress(), + }, + { + ['previousOwner']: faker.finance.ethereumAddress(), + ['newOwner']: faker.finance.ethereumAddress(), + }, + ] + + const out: LogDescription[] = [] + for (const desc of descriptions) { + // eslint-disable-next-line + // @ts-expect-error + out.push(createLogDescriptionMock(desc)) + } + + return out +} diff --git a/ethereum-steth-v2/src/utils/events/lido_events.ts b/ethereum-steth-v2/src/utils/events/lido_events.ts index d4db37dc..688576a7 100644 --- a/ethereum-steth-v2/src/utils/events/lido_events.ts +++ b/ethereum-steth-v2/src/utils/events/lido_events.ts @@ -1,7 +1,9 @@ -import { FindingSeverity, FindingType } from 'forta-agent' +import { FindingSeverity, FindingType, LogDescription } from 'forta-agent' import { EventOfNotice } from '../../entity/events' import { Result } from '@ethersproject/abi/lib' import { etherscanAddress } from '../string' +import { faker } from '@faker-js/faker' +import { createLogDescriptionMock } from './helper/event_helper' export function getLidoEvents(LIDO_STETH_ADDRESS: string): EventOfNotice[] { return [ @@ -95,3 +97,36 @@ export function getLidoEvents(LIDO_STETH_ADDRESS: string): EventOfNotice[] { }, ] } + +export function getFilteredLidoEventsMock(): LogDescription[] { + const descriptions = [ + {}, + {}, + {}, + {}, + { + ['maxStakeLimit']: faker.number.int(), + ['stakeLimitIncreasePerBlock']: faker.number.int(), + }, + {}, + { + ['lidoLocator']: faker.finance.ethereumAddress(), + }, + { + ['vault']: faker.finance.ethereumAddress(), + ['token']: faker.finance.ethereumAddress(), + }, + { + ['version']: faker.system.semver(), + }, + ] + + const out: LogDescription[] = [] + for (const desc of descriptions) { + // eslint-disable-next-line + // @ts-expect-error + out.push(createLogDescriptionMock(desc)) + } + + return out +} diff --git a/ethereum-steth-v2/src/utils/events/withdrawals_events.ts b/ethereum-steth-v2/src/utils/events/withdrawals_events.ts index caa9d70c..57b23ff7 100644 --- a/ethereum-steth-v2/src/utils/events/withdrawals_events.ts +++ b/ethereum-steth-v2/src/utils/events/withdrawals_events.ts @@ -1,7 +1,9 @@ -import { FindingSeverity, FindingType } from 'forta-agent' +import { FindingSeverity, FindingType, LogDescription } from 'forta-agent' import BigNumber from 'bignumber.js' import { Result } from '@ethersproject/abi/lib' import { EventOfNotice } from '../../entity/events' +import { faker } from '@faker-js/faker' +import { createLogDescriptionMock } from './helper/event_helper' export const WITHDRAWALS_BUNKER_MODE_ENABLED_EVENT = 'event BunkerModeEnabled(uint256 _sinceTimestamp)' @@ -41,3 +43,21 @@ export function getWithdrawalsEvents(WITHDRAWAL_QUEUE_ADDRESS: string): EventOfN }, ] } + +export function getFilteredWithdrawalsEventsMock(): LogDescription[] { + const descriptions = [ + {}, + { + ['duration']: faker.number.int(), + }, + ] + + const out: LogDescription[] = [] + for (const desc of descriptions) { + // eslint-disable-next-line + // @ts-expect-error + out.push(createLogDescriptionMock(desc)) + } + + return out +} diff --git a/ethereum-steth-v2/tests/.gitignore b/ethereum-steth-v2/tests/.gitignore new file mode 100644 index 00000000..d7d2a035 --- /dev/null +++ b/ethereum-steth-v2/tests/.gitignore @@ -0,0 +1 @@ +custom_test.spec.ts diff --git a/ethereum-steth-v2/tests/e2e/agent-steth-ops.spec.ts b/ethereum-steth-v2/tests/e2e/agent-steth-ops.spec.ts new file mode 100644 index 00000000..fed1da95 --- /dev/null +++ b/ethereum-steth-v2/tests/e2e/agent-steth-ops.spec.ts @@ -0,0 +1,180 @@ +import { ethers, Finding, FindingSeverity, FindingType, getEthersProvider, Network, Transaction } from 'forta-agent' +import { App } from '../../src/app' +import { JsonRpcProvider } from '@ethersproject/providers' +import { createTransactionEvent, etherBlockToFortaBlockEvent } from './utils' +const TEST_TIMEOUT = 60_000 // ms + +describe('agent-steth-ops e2e tests', () => { + let ethProvider: JsonRpcProvider + + beforeAll(async () => { + ethProvider = getEthersProvider() + }) + + test( + 'should process block with low staking limit (10%)', + async () => { + const app = await App.getInstance() + const blockNumber = 16704075 + const block = await ethProvider.getBlock(blockNumber) + + const blockEvent = etherBlockToFortaBlockEvent(block) + + const result = await app.StethOperationSrv.handleBlock(blockEvent) + + const expected = Finding.fromObject({ + alertId: 'LOW-STAKING-LIMIT', + description: `Current staking limit is 7237.74 ETH this is lower than 10% of max staking limit 150000.00 ETH`, + name: '⚠️ Staking limit below 10%', + severity: FindingSeverity.Info, + type: FindingType.Info, + }) + + expect(result.length).toEqual(1) + expect(result[0].alertId).toEqual(expected.alertId) + expect(result[0].description).toEqual(expected.description) + expect(result[0].name).toEqual(expected.name) + expect(result[0].severity).toEqual(expected.severity) + expect(result[0].type).toEqual(expected.type) + }, + TEST_TIMEOUT, + ) + + test( + 'should process block with huge buffered ETH amount and low deposit executor balance', + async () => { + const app = await App.getInstance() + const blockNumber = 17241600 + const block = await ethProvider.getBlock(blockNumber) + + const blockEvent = etherBlockToFortaBlockEvent(block) + + const result = await app.StethOperationSrv.handleDepositExecutorBalance(blockEvent.blockNumber, block.timestamp) + + const expected = Finding.fromObject({ + alertId: 'LOW-DEPOSIT-EXECUTOR-BALANCE', + description: `Balance of deposit executor is 1.9232. This is extremely low! 😱`, + name: '⚠️ Low deposit executor balance', + severity: FindingSeverity.High, + type: FindingType.Suspicious, + }) + + expect(result.length).toEqual(1) + expect(result[0].alertId).toEqual(expected.alertId) + expect(result[0].description).toEqual(expected.description) + expect(result[0].name).toEqual(expected.name) + expect(result[0].severity).toEqual(expected.severity) + expect(result[0].type).toEqual(expected.type) + }, + TEST_TIMEOUT, + ) + + test( + 'should process tx with EL rewards vault set and staking changes', + async () => { + const app = await App.getInstance() + const txHash = '0x11a48020ae69cf08bd063f1fbc8ecf65bd057015aaa991bf507dbc598aadb68e' + + const receipt = await ethProvider.send('eth_getTransactionReceipt', [txHash]) + const block = await ethProvider.send('eth_getBlockByNumber', [ + ethers.utils.hexValue(parseInt(receipt.blockNumber)), + true, + ]) + const transaction = block.transactions.find((tx: Transaction) => tx.hash.toLowerCase() === txHash)! + + const txEvent = createTransactionEvent(transaction, block, Network.MAINNET, [], receipt.logs) + + const results = app.StethOperationSrv.handleTransaction(txEvent) + + const expected = [ + { + name: 'βœ… Lido: Staking resumed', + description: 'Staking was resumed!', + alertId: 'LIDO-STAKING-RESUMED', + protocol: 'ethereum', + severity: 4, + type: 4, + }, + { + name: '⚠️ Lido: Staking limit set', + description: + 'Staking limit was set with:\n' + + 'Max staking limit: 150000000000000000000000\n' + + 'Stake limit increase per block: 23437500000000000000', + alertId: 'LIDO-STAKING-LIMIT-SET', + protocol: 'ethereum', + severity: 4, + type: 4, + }, + ] + + expect(results.length).toEqual(2) + expect(results[0].alertId).toEqual(expected[0].alertId) + expect(results[0].description).toEqual(expected[0].description) + expect(results[0].name).toEqual(expected[0].name) + expect(results[0].severity).toEqual(expected[0].severity) + expect(results[0].type).toEqual(expected[0].type) + + expect(results[1].alertId).toEqual(expected[1].alertId) + expect(results[1].description).toEqual(expected[1].description) + expect(results[1].name).toEqual(expected[1].name) + expect(results[1].severity).toEqual(expected[1].severity) + expect(results[1].type).toEqual(expected[1].type) + }, + TEST_TIMEOUT, + ) + + test( + 'should process tx with transferred ownership of Insurance fund', + async () => { + const app = await App.getInstance() + const txHash = '0x91c7c2f33faf3b5fb097138c1d49c1d4e83f99e1c3b346b3cad35a5928c03b3a' + + const receipt = await ethProvider.send('eth_getTransactionReceipt', [txHash]) + const block = await ethProvider.send('eth_getBlockByNumber', [ + ethers.utils.hexValue(parseInt(receipt.blockNumber)), + true, + ]) + const transaction = block.transactions.find((tx: Transaction) => tx.hash.toLowerCase() === txHash)! + + const txEvent = createTransactionEvent(transaction, block, Network.MAINNET, [], receipt.logs) + + const results = app.StethOperationSrv.handleTransaction(txEvent) + + const expected = [ + { + name: '🚨 Insurance fund: Ownership transferred', + description: + 'Owner of the insurance fund was transferred from [0x0000000000000000000000000000000000000000](https://etherscan.io/address/0x0000000000000000000000000000000000000000) to [0xbD829522d4791b9660f59f5998faE451dACA4E1C](https://etherscan.io/address/0xbD829522d4791b9660f59f5998faE451dACA4E1C)', + alertId: 'INS-FUND-OWNERSHIP-TRANSFERRED', + protocol: 'ethereum', + severity: 5, + type: 4, + }, + { + name: '🚨 Insurance fund: Ownership transferred', + description: + 'Owner of the insurance fund was transferred from [0xbD829522d4791b9660f59f5998faE451dACA4E1C](https://etherscan.io/address/0xbD829522d4791b9660f59f5998faE451dACA4E1C) to [0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c](https://etherscan.io/address/0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c)', + alertId: 'INS-FUND-OWNERSHIP-TRANSFERRED', + protocol: 'ethereum', + severity: 5, + type: 4, + }, + ] + + expect(results.length).toEqual(2) + expect(results[0].alertId).toEqual(expected[0].alertId) + expect(results[0].description).toEqual(expected[0].description) + expect(results[0].name).toEqual(expected[0].name) + expect(results[0].severity).toEqual(expected[0].severity) + expect(results[0].type).toEqual(expected[0].type) + + expect(results[1].alertId).toEqual(expected[1].alertId) + expect(results[1].description).toEqual(expected[1].description) + expect(results[1].name).toEqual(expected[1].name) + expect(results[1].severity).toEqual(expected[1].severity) + expect(results[1].type).toEqual(expected[1].type) + }, + TEST_TIMEOUT, + ) +}) diff --git a/ethereum-steth-v2/tests/e2e/agent-vaults.spec.ts b/ethereum-steth-v2/tests/e2e/agent-vaults.spec.ts new file mode 100644 index 00000000..c9d6e8af --- /dev/null +++ b/ethereum-steth-v2/tests/e2e/agent-vaults.spec.ts @@ -0,0 +1,33 @@ +import { App } from '../../src/app' +import { Finding, FindingSeverity, FindingType, getEthersProvider } from 'forta-agent' +import { etherBlockToFortaBlockEvent } from './utils' + +describe('agent-vaults e2e tests', () => { + const ethProvider = getEthersProvider() + + test('should process block with high EL vault balance difference', async () => { + const app = await App.getInstance() + + const blockNumber = 17007842 + const block = await ethProvider.getBlock(17007842) + + app.VaultSrv.initialize(blockNumber) + const blockEvent = etherBlockToFortaBlockEvent(block) + const result = await app.VaultSrv.handleBlock(blockEvent) + + const expected = Finding.fromObject({ + alertId: 'EL-VAULT-BALANCE-CHANGE', + description: `EL Vault Balance has increased by 689.017 ETH`, + name: 'πŸ’΅ EL Vault Balance significant change', + severity: FindingSeverity.Info, + type: FindingType.Info, + }) + + expect(result.length).toEqual(1) + expect(result[0].alertId).toEqual(expected.alertId) + expect(result[0].description).toEqual(expected.description) + expect(result[0].name).toEqual(expected.name) + expect(result[0].severity).toEqual(expected.severity) + expect(result[0].type).toEqual(expected.type) + }) +}) diff --git a/ethereum-steth-v2/tests/e2e/utils.ts b/ethereum-steth-v2/tests/e2e/utils.ts new file mode 100644 index 00000000..1fccfdb1 --- /dev/null +++ b/ethereum-steth-v2/tests/e2e/utils.ts @@ -0,0 +1,128 @@ +import { Block as EtherBlock } from '@ethersproject/abstract-provider' +import { Block, BlockEvent, EventType, Network, Trace } from 'forta-agent' +import { formatAddress, isZeroAddress } from 'forta-agent/dist/cli/utils' +import { TransactionEvent } from 'forta-agent/dist/sdk/transaction.event' +import { getContractAddress } from 'ethers/lib/utils' +import { JsonRpcBlock, JsonRpcTransaction } from 'forta-agent/dist/cli/utils/get.block.with.transactions' +import { JsonRpcLog } from 'forta-agent/dist/cli/utils/get.transaction.receipt' + +export function etherBlockToFortaBlockEvent(block: EtherBlock): BlockEvent { + const blok: Block = { + difficulty: block.difficulty.toString(), + extraData: block.extraData, + gasLimit: block.gasLimit.toString(), + gasUsed: block.gasUsed.toString(), + hash: block.hash, + logsBloom: '', + miner: formatAddress(block.miner), + mixHash: '', + nonce: block.nonce, + number: block.number, + parentHash: block.parentHash, + receiptsRoot: '', + sha3Uncles: '', + size: '', + stateRoot: '', + timestamp: block.timestamp, + totalDifficulty: block.difficulty.toString(), + transactions: block.transactions, + transactionsRoot: '', + uncles: [], + } + + return new BlockEvent(EventType.BLOCK, Network.MAINNET, blok) +} + +export function createTransactionEvent( + transaction: JsonRpcTransaction, + block: JsonRpcBlock, + networkId: number, + traces: Trace[] = [], + logs: JsonRpcLog[] = [], +): TransactionEvent { + const tx = { + hash: transaction.hash, + from: formatAddress(transaction.from), + to: transaction.to ? formatAddress(transaction.to) : null, + nonce: parseInt(transaction.nonce), + gas: transaction.gas, + gasPrice: transaction.gasPrice, + value: transaction.value, + data: transaction.input, + r: transaction.r, + s: transaction.s, + v: transaction.v, + } + const addresses = { + [tx.from]: true, + } + if (tx.to) { + addresses[tx.to] = true + } + + const blok = { + hash: block.hash, + number: parseInt(block.number), + timestamp: parseInt(block.timestamp), + } + + const trcs: Trace[] = [] + traces.forEach((trace) => { + if (trace.action.address) { + addresses[formatAddress(trace.action.address)] = true + } + if (trace.action.refundAddress) { + addresses[formatAddress(trace.action.refundAddress)] = true + } + addresses[formatAddress(trace.action.to)] = true + addresses[formatAddress(trace.action.from)] = true + + trcs.push({ + action: { + callType: trace.action.callType, + to: formatAddress(trace.action.to), + input: trace.action.input, + from: formatAddress(trace.action.from), + value: trace.action.value, + init: trace.action.init, + address: formatAddress(trace.action.address), + balance: trace.action.balance, + refundAddress: formatAddress(trace.action.refundAddress), + }, + blockHash: trace.blockHash, + blockNumber: trace.blockNumber, + result: { + gasUsed: trace.result?.gasUsed, + address: trace.result?.address, + code: trace.result?.code, + output: trace.result?.output, + }, + subtraces: trace.subtraces, + traceAddress: trace.traceAddress, + transactionHash: trace.transactionHash, + transactionPosition: trace.transactionPosition, + type: trace.type, + error: trace.error, + }) + }) + + const lgs = logs.map((log) => ({ + address: formatAddress(log.address), + topics: log.topics, + data: log.data, + logIndex: parseInt(log.logIndex), + blockNumber: parseInt(log.blockNumber), + blockHash: log.blockHash, + transactionIndex: parseInt(log.transactionIndex), + transactionHash: log.transactionHash, + removed: log.removed, + })) + lgs.forEach((log) => (addresses[log.address] = true)) + + let contractAddress = null + if (isZeroAddress(transaction.to)) { + contractAddress = formatAddress(getContractAddress({ from: transaction.from, nonce: transaction.nonce })) + } + + return new TransactionEvent(EventType.BLOCK, networkId, tx, trcs, addresses, blok, lgs, contractAddress) +} diff --git a/ethereum-steth-v2/tsconfig.json b/ethereum-steth-v2/tsconfig.json index d943778f..32fd2f0a 100644 --- a/ethereum-steth-v2/tsconfig.json +++ b/ethereum-steth-v2/tsconfig.json @@ -6,13 +6,7 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "noImplicitAny": true, - "resolveJsonModule": true + "resolveJsonModule": true, }, - "exclude": [ - "e2e", - "node_modules", - "test", - "dist", - "**/*spec.ts" - ] -} \ No newline at end of file + "exclude": ["node_modules", "tests"], +} diff --git a/ethereum-steth-v2/yarn.lock b/ethereum-steth-v2/yarn.lock index 80a1c15b..823d3468 100644 --- a/ethereum-steth-v2/yarn.lock +++ b/ethereum-steth-v2/yarn.lock @@ -981,9 +981,9 @@ integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.21" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.21.tgz#5dc1df7b3dc4a6209e503a924e1ca56097a2bb15" - integrity sha512-SRfKmRe1KvYnxjEMtxEr+J4HIeMX5YBg/qhRHpxEIGjhX1rshcHlnFUE9K0GazhVKWM7B+nARSkV8LuvJdJ5/g== + version "0.3.22" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz#72a621e5de59f5f1ef792d0793a82ee20f645e4c" + integrity sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw== dependencies: "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" @@ -1073,9 +1073,9 @@ integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== "@sinonjs/commons@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" - integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== dependencies: type-detect "4.0.8" @@ -1189,9 +1189,9 @@ "@types/node" "*" "@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0": - version "20.11.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.5.tgz#be10c622ca7fcaa3cf226cf80166abc31389d86e" - integrity sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w== + version "20.11.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.6.tgz#6adf4241460e28be53836529c033a41985f85b6e" + integrity sha512-+EOokTnksGVgip2PbYbr3xnR7kZigh4LbybAfBAw5BpnQ+FqBYUsvCEjYd70IXKlbohQ64mzEYmMtlWUY8q//Q== dependencies: undici-types "~5.26.4" @@ -1254,15 +1254,15 @@ "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^6.12.0": - version "6.19.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.0.tgz#db03f3313b57a30fbbdad2e6929e88fc7feaf9ba" - integrity sha512-DUCUkQNklCQYnrBSSikjVChdc84/vMPDQSgJTHBZ64G9bA9w0Crc0rd2diujKbTdp6w2J47qkeHQLoi0rpLCdg== + version "6.19.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.1.tgz#bb0676af940bc23bf299ca58dbdc6589c2548c2e" + integrity sha512-roQScUGFruWod9CEyoV5KlCYrubC/fvG8/1zXuT0WTcxX87GnMMmnksMwSg99lo1xiKrBzw2icsJPMAw1OtKxg== dependencies: "@eslint-community/regexpp" "^4.5.1" - "@typescript-eslint/scope-manager" "6.19.0" - "@typescript-eslint/type-utils" "6.19.0" - "@typescript-eslint/utils" "6.19.0" - "@typescript-eslint/visitor-keys" "6.19.0" + "@typescript-eslint/scope-manager" "6.19.1" + "@typescript-eslint/type-utils" "6.19.1" + "@typescript-eslint/utils" "6.19.1" + "@typescript-eslint/visitor-keys" "6.19.1" debug "^4.3.4" graphemer "^1.4.0" ignore "^5.2.4" @@ -1271,14 +1271,14 @@ ts-api-utils "^1.0.1" "@typescript-eslint/parser@^6.12.0": - version "6.19.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.19.0.tgz#80344086f362181890ade7e94fc35fe0480bfdf5" - integrity sha512-1DyBLG5SH7PYCd00QlroiW60YJ4rWMuUGa/JBV0iZuqi4l4IK3twKPq5ZkEebmGqRjXWVgsUzfd3+nZveewgow== - dependencies: - "@typescript-eslint/scope-manager" "6.19.0" - "@typescript-eslint/types" "6.19.0" - "@typescript-eslint/typescript-estree" "6.19.0" - "@typescript-eslint/visitor-keys" "6.19.0" + version "6.19.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.19.1.tgz#68a87bb21afaf0b1689e9cdce0e6e75bc91ada78" + integrity sha512-WEfX22ziAh6pRE9jnbkkLGp/4RhTpffr2ZK5bJ18M8mIfA8A+k97U9ZyaXCEJRlmMHh7R9MJZWXp/r73DzINVQ== + dependencies: + "@typescript-eslint/scope-manager" "6.19.1" + "@typescript-eslint/types" "6.19.1" + "@typescript-eslint/typescript-estree" "6.19.1" + "@typescript-eslint/visitor-keys" "6.19.1" debug "^4.3.4" "@typescript-eslint/scope-manager@5.62.0": @@ -1289,21 +1289,21 @@ "@typescript-eslint/types" "5.62.0" "@typescript-eslint/visitor-keys" "5.62.0" -"@typescript-eslint/scope-manager@6.19.0": - version "6.19.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.19.0.tgz#b6d2abb825b29ab70cb542d220e40c61c1678116" - integrity sha512-dO1XMhV2ehBI6QN8Ufi7I10wmUovmLU0Oru3n5LVlM2JuzB4M+dVphCPLkVpKvGij2j/pHBWuJ9piuXx+BhzxQ== +"@typescript-eslint/scope-manager@6.19.1": + version "6.19.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.19.1.tgz#2f527ee30703a6169a52b31d42a1103d80acd51b" + integrity sha512-4CdXYjKf6/6aKNMSly/BP4iCSOpvMmqtDzRtqFyyAae3z5kkqEjKndR5vDHL8rSuMIIWP8u4Mw4VxLyxZW6D5w== dependencies: - "@typescript-eslint/types" "6.19.0" - "@typescript-eslint/visitor-keys" "6.19.0" + "@typescript-eslint/types" "6.19.1" + "@typescript-eslint/visitor-keys" "6.19.1" -"@typescript-eslint/type-utils@6.19.0": - version "6.19.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.19.0.tgz#522a494ef0d3e9fdc5e23a7c22c9331bbade0101" - integrity sha512-mcvS6WSWbjiSxKCwBcXtOM5pRkPQ6kcDds/juxcy/727IQr3xMEcwr/YLHW2A2+Fp5ql6khjbKBzOyjuPqGi/w== +"@typescript-eslint/type-utils@6.19.1": + version "6.19.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.19.1.tgz#6a130e3afe605a4898e043fa9f72e96309b54935" + integrity sha512-0vdyld3ecfxJuddDjACUvlAeYNrHP/pDeQk2pWBR2ESeEzQhg52DF53AbI9QCBkYE23lgkhLCZNkHn2hEXXYIg== dependencies: - "@typescript-eslint/typescript-estree" "6.19.0" - "@typescript-eslint/utils" "6.19.0" + "@typescript-eslint/typescript-estree" "6.19.1" + "@typescript-eslint/utils" "6.19.1" debug "^4.3.4" ts-api-utils "^1.0.1" @@ -1312,10 +1312,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== -"@typescript-eslint/types@6.19.0": - version "6.19.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.19.0.tgz#689b0498c436272a6a2059b09f44bcbd90de294a" - integrity sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A== +"@typescript-eslint/types@6.19.1": + version "6.19.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.19.1.tgz#2d4c9d492a63ede15e7ba7d129bdf7714b77f771" + integrity sha512-6+bk6FEtBhvfYvpHsDgAL3uo4BfvnTnoge5LrrCj2eJN8g3IJdLTD4B/jK3Q6vo4Ql/Hoip9I8aB6fF+6RfDqg== "@typescript-eslint/typescript-estree@5.62.0": version "5.62.0" @@ -1330,13 +1330,13 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@6.19.0": - version "6.19.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.0.tgz#0813ba364a409afb4d62348aec0202600cb468fa" - integrity sha512-o/zefXIbbLBZ8YJ51NlkSAt2BamrK6XOmuxSR3hynMIzzyMY33KuJ9vuMdFSXW+H0tVvdF9qBPTHA91HDb4BIQ== +"@typescript-eslint/typescript-estree@6.19.1": + version "6.19.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.1.tgz#796d88d88882f12e85bb33d6d82d39e1aea54ed1" + integrity sha512-aFdAxuhzBFRWhy+H20nYu19+Km+gFfwNO4TEqyszkMcgBDYQjmPJ61erHxuT2ESJXhlhrO7I5EFIlZ+qGR8oVA== dependencies: - "@typescript-eslint/types" "6.19.0" - "@typescript-eslint/visitor-keys" "6.19.0" + "@typescript-eslint/types" "6.19.1" + "@typescript-eslint/visitor-keys" "6.19.1" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" @@ -1344,17 +1344,17 @@ semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/utils@6.19.0": - version "6.19.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.19.0.tgz#557b72c3eeb4f73bef8037c85dae57b21beb1a4b" - integrity sha512-QR41YXySiuN++/dC9UArYOg4X86OAYP83OWTewpVx5ct1IZhjjgTLocj7QNxGhWoTqknsgpl7L+hGygCO+sdYw== +"@typescript-eslint/utils@6.19.1": + version "6.19.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.19.1.tgz#df93497f9cfddde2bcc2a591da80536e68acd151" + integrity sha512-JvjfEZuP5WoMqwh9SPAPDSHSg9FBHHGhjPugSRxu5jMfjvBpq5/sGTD+9M9aQ5sh6iJ8AY/Kk/oUYVEMAPwi7w== dependencies: "@eslint-community/eslint-utils" "^4.4.0" "@types/json-schema" "^7.0.12" "@types/semver" "^7.5.0" - "@typescript-eslint/scope-manager" "6.19.0" - "@typescript-eslint/types" "6.19.0" - "@typescript-eslint/typescript-estree" "6.19.0" + "@typescript-eslint/scope-manager" "6.19.1" + "@typescript-eslint/types" "6.19.1" + "@typescript-eslint/typescript-estree" "6.19.1" semver "^7.5.4" "@typescript-eslint/utils@^5.10.0": @@ -1379,12 +1379,12 @@ "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" -"@typescript-eslint/visitor-keys@6.19.0": - version "6.19.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.0.tgz#4565e0ecd63ca1f81b96f1dd76e49f746c6b2b49" - integrity sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ== +"@typescript-eslint/visitor-keys@6.19.1": + version "6.19.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.1.tgz#2164073ed4fc34a5ff3b5e25bb5a442100454c4c" + integrity sha512-gkdtIO+xSO/SmI0W68DBg4u1KElmIUo3vXzgHyGPs6cxgB0sa3TlptRAAE0hUY1hM6FcDKEv7aIwiTGm76cXfQ== dependencies: - "@typescript-eslint/types" "6.19.0" + "@typescript-eslint/types" "6.19.1" eslint-visitor-keys "^3.4.1" "@ungap/structured-clone@^1.2.0": @@ -1499,9 +1499,9 @@ asn1.js@^5.2.0: safer-buffer "^2.1.0" async-mutex@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.4.0.tgz#ae8048cd4d04ace94347507504b3cf15e631c25f" - integrity sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA== + version "0.4.1" + resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.4.1.tgz#bccf55b96f2baf8df90ed798cb5544a1f6ee4c2c" + integrity sha512-WfoBo4E/TbCX1G95XTjbWTE3X2XLG0m1Xbv2cwOtuPdyH9CZvnaA5nCt1ucjaKEgW2A5IF71hxrRhr83Je5xjA== dependencies: tslib "^2.4.0" @@ -1786,9 +1786,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001565: - version "1.0.30001578" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001578.tgz#11741580434ce60aae4b4a9abee9f9f8d7bf5be5" - integrity sha512-J/jkFgsQ3NEl4w2lCoM9ZPxrD+FoBNJ7uJUpGVjIg/j0OwJosWM36EPDv+Yyi0V4twBk9pPmlFS+PLykgEvUmg== + version "1.0.30001579" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz#45c065216110f46d6274311a4b3fcf6278e0852a" + integrity sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA== chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" @@ -2102,9 +2102,9 @@ doctrine@^3.0.0: esutils "^2.0.2" electron-to-chromium@^1.4.601: - version "1.4.635" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.635.tgz#e4e064b8711a98827652ce17cc11b0e0184c40d1" - integrity sha512-iu/2D0zolKU3iDGXXxdOzNf72Jnokn+K1IN6Kk4iV6l1Tr2g/qy+mvmtfAiBwZe5S3aB5r92vp+zSZ69scYRrg== + version "1.4.643" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.643.tgz#081a20c5534db91e66ef094f68624960f674768f" + integrity sha512-QHscvvS7gt155PtoRC0dR2ilhL8E9LHhfTQEq1uD5AL0524rBLAwpAREFH06f87/e45B9XkR6Ki5dbhbCsVEIg== elliptic@6.5.4, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.4: version "6.5.4" @@ -4413,11 +4413,6 @@ ts-jest@^29.1.2: semver "^7.5.3" yargs-parser "^21.0.1" -ts-log@^2.2.5: - version "2.2.5" - resolved "https://registry.yarnpkg.com/ts-log/-/ts-log-2.2.5.tgz#aef3252f1143d11047e2cb6f7cfaac7408d96623" - integrity sha512-PGcnJoTBnVGy6yYNFxWVNkdcAuAMstvutN9MgDJIV6L0oG8fB+ZNNy1T+wJzah8RPGor1mZuPQkVfXNDpy9eHA== - ts-retry@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/ts-retry/-/ts-retry-4.2.4.tgz#151e813adec2e7edffa5493c2f07b47e71fd336e" From 7c5cd86e5f16f881e690416275ffca207ebd3acf Mon Sep 17 00:00:00 2001 From: Sergey White Date: Wed, 24 Jan 2024 20:15:12 +0300 Subject: [PATCH 10/22] feat: ethereum-steth-v2 fix code style --- ethereum-steth-v2/README.md | 36 +++++++++---------- ethereum-steth-v2/package.json | 2 +- ethereum-steth-v2/src/agent.ts | 2 +- .../src/services/gate-seal/GateSeal.srv.ts | 6 ++-- .../steth_operation/StethOperation.spec.ts | 6 ++-- .../steth_operation/StethOperation.srv.ts | 4 +-- .../src/services/vault/Vault.srv.ts | 8 ++--- .../services/withdrawals/Withdrawals.srv.ts | 8 ++--- .../utils/contract_mocks/contract_mocks.ts | 8 ++--- .../utils/events/deposit_security_events.ts | 2 +- .../src/utils/events/lido_events.ts | 8 ++--- .../src/utils/events/withdrawals_events.ts | 2 +- .../tests/e2e/agent-steth-ops.spec.ts | 4 +-- ethereum-steth-v2/tsconfig.json | 9 +++-- .../subagents/gate-seal/agent-gate-seal.ts | 6 ++-- .../src/subagents/vaults/agent-vaults.ts | 8 ++--- .../withdrawals/agent-withdrawals.ts | 2 +- 17 files changed, 63 insertions(+), 58 deletions(-) diff --git a/ethereum-steth-v2/README.md b/ethereum-steth-v2/README.md index a8b1f423..3e455083 100644 --- a/ethereum-steth-v2/README.md +++ b/ethereum-steth-v2/README.md @@ -12,12 +12,12 @@ 2. 🚨 Huge depositable ETH amount (checks every 100 blocks) 3. ⚠️ High depositable ETH amount (checks every 100 blocks) 4. ⚠️ Low deposit executor balance (checks every 100 blocks) - 5. ⚠️ Staking limit below 10% (checks every 25 blocks) - 6. πŸ“‰ Staking limit below 30% (checks every 25 blocks) + 5. ⚠️ Unspent staking limit below 10% (checks every 25 blocks) + 6. πŸ“‰ Unspent staking limit below 30% (checks every 25 blocks) 2. HandleTransaction 1. Deposit Security events 1. 🚨 Deposit Security: Deposits paused - 2. βœ… Deposit Security: Deposits resumed + 2. ⚠️Deposit Security: Deposits resumed 3. ⚠️ Deposit Security: Guardian added 4. ⚠️ Deposit Security: Guardian removed 5. 🚨 Deposit Security: Guardian quorum changed @@ -26,14 +26,14 @@ 8. 🚨 Deposit Security: Owner changed 2. Lido events 1. 🚨🚨🚨 Lido: Stopped 🚨🚨🚨 - 2. βœ… Lido: Resumed + 2. ⚠️ Lido: Resumed 3. 🚨 Lido: Staking paused - 4. βœ… Lido: Staking resumed + 4. ⚠️ Lido: Staking resumed 5. ⚠️ Lido: Staking limit set 6. 🚨 Lido: Staking limit removed 7. 🚨 Lido: Locator set - 8. ℹ️ Lido: Funds recovered to vault - 9. ℹ️ Lido: Contract version set + 8. ⚠️Lido: Funds recovered to vault + 9. ⚠️Lido: Contract version set 3. Insurance fund events 1. ⚠️ Insurance fund: ETH transferred 2. ⚠️ Insurance fund: ERC721 transferred @@ -45,36 +45,36 @@ 2. ℹ️ Lido Burner: ERC721 recovered 2. Withdrawals. 1. HandleBlock runs on each 100-th block or one per 20 minutes - 1. ⚠️ Withdrawals: % of stake limit is drained and unfinalized queue is on par with drained stake + 1. ⚠️ Withdrawals: % of stake limit is spent and unfinalized queue is on par with drained stake limit 2. ⚠️ Withdrawals: unfinalized queue is more than 100_000 stETH 3. ⚠️ Withdrawals: unfinalized queue wait time is too long - 4. πŸ€” Withdrawals: ${unclaimedSizeRate.times(100).toFixed(2)}% of finalized requests are unclaimed - 5. πŸ€” Withdrawals: unclaimed requests size is more than withdrawal queue balance + 4. ⚠️ Withdrawals: ${unclaimedSizeRate.times(100).toFixed(2)}% of finalized requests are unclaimed + 5. ⚠️ Withdrawals: unclaimed requests size is more than withdrawal queue balance 2. HandleTransaction 1. 🚨 Withdrawals: BUNKER MODE ON! 🚨 - 2. βœ… Withdrawals: BUNKER MODE OFF! βœ… + 2. ⚠️Withdrawals: BUNKER MODE OFF! βœ… 3. ℹ️ Huge stETH withdrawal requests batch - 4. ⚠️ Withdrawals: the sum of received withdrawal requests since the last rebase greater than 150_000 stETH + 4. ⚠️ Withdrawals: the sum of received withdrawal requests since the last rebase greater than 150_000 stETH (max staking limit) 5. πŸ€” Withdrawals: claimed amount is more than requested - 6. βœ… Withdrawals: contract was unpaused + 6. ⚠️Withdrawals: contract was unpaused 7. 🚨 Withdrawals: contract was paused 3. GateSeal 1. HandleBlock runs on each next block 1. ⚠️ GateSeal: default GateSeal address in forta agent is expired 2. ⚠️️ GateSeal: default GateSeal address in forta agent doesn't have PAUSE_ROLE for contracts - 3. 🚨GateSeal: actual address doesn't have PAUSE_ROLE for contracts - 4. 🚨🚨🚨 GateSeal: is expired! 🚨🚨🚨 + 3. 🚨 GateSeal: actual address doesn't have PAUSE_ROLE for contracts + 4. 🚨 GateSeal: is expired! 5. ⚠️ GateSeal: is about to be expired 2. HandleTransaction 1. 🚨🚨🚨 GateSeal: is sealed 🚨🚨🚨 - 2. 🚨 GateSeal: new one created + 2. ℹ️ GateSeal: is expired 4. Vaults 1. Handleblock 1. πŸ’΅ Withdrawal Vault Balance significant change (checks every on 100-th block) 2. πŸ’΅ EL Vault Balance significant change - 3. 🚨 Withdrawal Vault balance mismatch - 4. 🚨 EL Vault balance mismatch + 3. 🚨🚨🚨 Withdrawal Vault balance mismatch + 4. 🚨🚨🚨 EL Vault balance mismatch 2. HandleTransaction 1. 🚨 Burner shares transfer diff --git a/ethereum-steth-v2/package.json b/ethereum-steth-v2/package.json index ae8402d5..37030cdd 100644 --- a/ethereum-steth-v2/package.json +++ b/ethereum-steth-v2/package.json @@ -1,6 +1,6 @@ { "name": "lido-steth-forta-bot", - "version": "0.0.1", + "version": "1.0.0", "description": "Forta Bot for Lido stETH", "repository": { "type": "git", diff --git a/ethereum-steth-v2/src/agent.ts b/ethereum-steth-v2/src/agent.ts index 7cc560af..628d0da2 100644 --- a/ethereum-steth-v2/src/agent.ts +++ b/ethereum-steth-v2/src/agent.ts @@ -151,5 +151,5 @@ export const handleTransaction = (): HandleTransaction => { export default { initialize: initialize(), handleBlock: handleBlock(), - // handleTransaction: handleTransaction(), + handleTransaction: handleTransaction(), } diff --git a/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts b/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts index a6ddfb4f..1360ce83 100644 --- a/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts +++ b/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts @@ -169,7 +169,7 @@ export class GateSealSrv { ) { out.push( Finding.fromObject({ - name: "🚨GateSeal: actual address doesn't have PAUSE_ROLE for contracts", + name: "🚨 GateSeal: actual address doesn't have PAUSE_ROLE for contracts", description: `GateSeal address: ${etherscanAddress(this.gateSealAddress)}${additionalDesc}`, alertId: 'GATE-SEAL-WITHOUT-PAUSE-ROLE', severity: FindingSeverity.Critical, @@ -209,7 +209,7 @@ export class GateSealSrv { if (expiryTimestamp.right.eq(0) || Number(expiryTimestamp.right) <= currentBlockTimestamp) { out.push( Finding.fromObject({ - name: '🚨🚨🚨 GateSeal: is expired! 🚨🚨🚨', + name: '🚨 GateSeal: is expired!', description: `GateSeal address: ${etherscanAddress(this.gateSealAddress)}}`, alertId: 'GATE-SEAL-IS-EXPIRED', severity: FindingSeverity.Critical, @@ -295,7 +295,7 @@ export class GateSealSrv { const { gate_seal } = newGateSealEvent.args out.push( Finding.fromObject({ - name: '🚨 GateSeal: new one created', + name: 'ℹ️ GateSeal: is expired', description: `GateSeal address: ${etherscanAddress( gate_seal, )}\ndev: Please, update \`GATE_SEAL_DEFAULT_ADDRESS\` in code`, diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts index 472ce03f..98bb86b2 100644 --- a/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts @@ -764,7 +764,7 @@ describe('StethOperationSrv', () => { expect(result[0].type).toEqual(expected.type) }) - test('⚠️ Staking limit below 10%', async () => { + test('⚠️ Unspent staking limit below 10%', async () => { const getStakingLimitInfo: StakingLimitInfo = { currentStakeLimit: new BigNumber(9), isStakingPaused: false, @@ -795,7 +795,7 @@ describe('StethOperationSrv', () => { const expected = Finding.fromObject({ alertId: 'LOW-STAKING-LIMIT', description: `Current staking limit is 9.00 ETH this is lower than 10% of max staking limit 100.00 ETH`, - name: '⚠️ Staking limit below 10%', + name: '⚠️ Unspent staking limit below 10%', severity: FindingSeverity.Info, type: FindingType.Info, }) @@ -841,7 +841,7 @@ describe('StethOperationSrv', () => { const expected = Finding.fromObject({ alertId: 'LOW-STAKING-LIMIT', description: `Current staking limit is 250.00 ETH this is lower than 30% of max staking limit 1000.00 ETH`, - name: 'πŸ“‰ Staking limit below 30%', + name: 'πŸ“‰ Unspent staking limit below 30%', severity: FindingSeverity.Info, type: FindingType.Info, }) diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts index aefca95e..9c1587f2 100644 --- a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts @@ -434,7 +434,7 @@ export class StethOperationSrv { ) { out.push( Finding.fromObject({ - name: '⚠️ Staking limit below 10%', + name: '⚠️ Unspent staking limit below 10%', description: `Current staking limit is ${currentStakingLimit.toFixed(2)} ETH ` + `this is lower than 10% of max staking limit ` + @@ -451,7 +451,7 @@ export class StethOperationSrv { ) { out.push( Finding.fromObject({ - name: 'πŸ“‰ Staking limit below 30%', + name: 'πŸ“‰ Unspent staking limit below 30%', description: `Current staking limit is ${currentStakingLimit.toFixed(2)} ETH ` + `this is lower than 30% of max staking limit ` + diff --git a/ethereum-steth-v2/src/services/vault/Vault.srv.ts b/ethereum-steth-v2/src/services/vault/Vault.srv.ts index 978b635a..a966432c 100644 --- a/ethereum-steth-v2/src/services/vault/Vault.srv.ts +++ b/ethereum-steth-v2/src/services/vault/Vault.srv.ts @@ -265,7 +265,7 @@ export class VaultSrv { if (currentBalance.right.lt(prevBalance)) { out.push( Finding.fromObject({ - name: '🚨 Withdrawal Vault balance mismatch', + name: '🚨🚨🚨 Withdrawal Vault balance mismatch', description: `Withdrawal Vault Balance has decreased by ${toEthString( prevBalance.minus(currentBalance.right), )} without Oracle report`, @@ -285,7 +285,7 @@ export class VaultSrv { if (currentBalance.right.lt(expectedBalance)) { out.push( Finding.fromObject({ - name: '🚨 Withdrawal Vault balance mismatch', + name: '🚨🚨🚨 Withdrawal Vault balance mismatch', description: `Withdrawal Vault Balance has decreased by ${toEthString( expectedBalance.minus(currentBalance.right), )} but Oracle report shows ${toEthString(withdrawalsWithdrawn)}`, @@ -323,7 +323,7 @@ export class VaultSrv { if (currentBalance.right.lt(prevBalance)) { out.push( Finding.fromObject({ - name: '🚨 EL Vault balance mismatch', + name: '🚨🚨🚨 EL Vault balance mismatch', description: `EL Vault Balance has decreased by ${toEthString( prevBalance.minus(currentBalance.right), )} without Oracle report`, @@ -343,7 +343,7 @@ export class VaultSrv { if (currentBalance.right.lt(expectedBalance)) { out.push( Finding.fromObject({ - name: '🚨 EL Vault balance mismatch', + name: '🚨🚨🚨 EL Vault balance mismatch', description: `EL Vault Balance has decreased by ${toEthString( expectedBalance.minus(currentBalance.right), )} but Oracle report shows ${toEthString(executionLayerRewardsWithdrawn)}`, diff --git a/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts b/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts index a643852f..3d22273c 100644 --- a/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts +++ b/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts @@ -482,7 +482,7 @@ export class WithdrawalsSrv { if (unclaimedSizeRate.gte(UNCLAIMED_REQUESTS_SIZE_RATE_THRESHOLD)) { out.push( Finding.fromObject({ - name: `πŸ€” Withdrawals: ${unclaimedSizeRate.times(100).toFixed(2)}% of finalized requests are unclaimed`, + name: `⚠️ Withdrawals: ${unclaimedSizeRate.times(100).toFixed(2)}% of finalized requests are unclaimed`, description: `Unclaimed (for all time): ${unclaimedStETH .div(ETH_DECIMALS) .toFixed(2)} stETH\nClaimed (for 2 weeks): ${claimedStETH @@ -520,7 +520,7 @@ export class WithdrawalsSrv { if (unclaimedStETH.gt(withdrawalQueueBalance.right)) { out.push( Finding.fromObject({ - name: `πŸ€” Withdrawals: unclaimed requests size is more than withdrawal queue balance`, + name: `⚠️ Withdrawals: unclaimed requests size is more than withdrawal queue balance`, description: `Unclaimed: ${unclaimedStETH .div(ETH_DECIMALS) .toFixed(2)} stETH\nWithdrawal queue balance: ${withdrawalQueueBalance.right @@ -572,7 +572,7 @@ export class WithdrawalsSrv { const delay = formatDelay(txEvent.block.timestamp - Number(this.cache.getBunkerModeEnabledSinceTimestamp())) out.push( Finding.fromObject({ - name: 'βœ… Withdrawals: BUNKER MODE OFF! βœ…', + name: '⚠️ Withdrawals: BUNKER MODE OFF! βœ…', description: `Bunker lasted ${delay}`, alertId: 'WITHDRAWALS-BUNKER-DISABLED', severity: FindingSeverity.High, @@ -635,7 +635,7 @@ export class WithdrawalsSrv { if (this.cache.getLastBigRequestAfterRebaseAlertTimestamp() < this.cache.getLastTokenRebaseTimestamp()) { out.push( Finding.fromObject({ - name: `⚠️ Withdrawals: the sum of received withdrawal requests since the last rebase greater than ${BIG_WITHDRAWAL_REQUEST_AFTER_REBASE_THRESHOLD} stETH`, + name: `⚠️ Withdrawals: the sum of received withdrawal requests since the last rebase greater than ${BIG_WITHDRAWAL_REQUEST_AFTER_REBASE_THRESHOLD} stETH (max staking limit)`, description: `Amount: ${this.cache.getAmountOfRequestedStETHSinceLastTokenRebase().toFixed(2)} stETH`, alertId: 'WITHDRAWALS-BIG-WITHDRAWAL-REQUEST-AFTER-REBASE', severity: FindingSeverity.High, diff --git a/ethereum-steth-v2/src/utils/contract_mocks/contract_mocks.ts b/ethereum-steth-v2/src/utils/contract_mocks/contract_mocks.ts index 3f44c623..c7a9dba0 100644 --- a/ethereum-steth-v2/src/utils/contract_mocks/contract_mocks.ts +++ b/ethereum-steth-v2/src/utils/contract_mocks/contract_mocks.ts @@ -12,8 +12,8 @@ export const LidoContractMock = (): jest.Mocked => ({ filters: { Unbuffered: () => { return { - address: '0xae7ab96520de3a18e5e111b5eaab095312d7fe84', - topics: ['0x76a397bea5768d4fca97ef47792796e35f98dc81b16c1de84e28a818e1f97108'], + address: faker.finance.ethereumAddress(), + topics: [faker.finance.ethereumAddress()], } }, }, @@ -24,8 +24,8 @@ export const WithdrawalQueueContractMock = (): jest.Mocked { return { - address: '0x889edc2edab5f40e902b864ad4d7ade8e412f9b1', - topics: ['0x197874c72af6a06fb0aa4fab45fd39c7cb61ac0992159872dc3295207da7e9eb'], + address: faker.finance.ethereumAddress(), + topics: [faker.finance.ethereumAddress()], } }, }, diff --git a/ethereum-steth-v2/src/utils/events/deposit_security_events.ts b/ethereum-steth-v2/src/utils/events/deposit_security_events.ts index fa17f483..4287dff8 100644 --- a/ethereum-steth-v2/src/utils/events/deposit_security_events.ts +++ b/ethereum-steth-v2/src/utils/events/deposit_security_events.ts @@ -21,7 +21,7 @@ export function getDepositSecurityEvents(DEPOSIT_SECURITY_ADDRESS: string): Even address: DEPOSIT_SECURITY_ADDRESS, event: 'event DepositsUnpaused(uint24 indexed stakingModuleId)', alertId: 'LIDO-DEPOSITS-UNPAUSED', - name: 'βœ… Deposit Security: Deposits resumed', + name: '⚠️ Deposit Security: Deposits resumed', description: (args: Result) => `Deposits were resumed for ${args.stakingModuleId} staking module`, severity: FindingSeverity.High, type: FindingType.Info, diff --git a/ethereum-steth-v2/src/utils/events/lido_events.ts b/ethereum-steth-v2/src/utils/events/lido_events.ts index 688576a7..b8c9d364 100644 --- a/ethereum-steth-v2/src/utils/events/lido_events.ts +++ b/ethereum-steth-v2/src/utils/events/lido_events.ts @@ -20,7 +20,7 @@ export function getLidoEvents(LIDO_STETH_ADDRESS: string): EventOfNotice[] { address: LIDO_STETH_ADDRESS, event: 'event Resumed()', alertId: 'LIDO-RESUMED', - name: 'βœ… Lido: Resumed', + name: '⚠️ Lido: Resumed', description: () => `Lido DAO contract was resumed`, severity: FindingSeverity.High, type: FindingType.Info, @@ -38,7 +38,7 @@ export function getLidoEvents(LIDO_STETH_ADDRESS: string): EventOfNotice[] { address: LIDO_STETH_ADDRESS, event: 'event StakingResumed()', alertId: 'LIDO-STAKING-RESUMED', - name: 'βœ… Lido: Staking resumed', + name: '⚠️ Lido: Staking resumed', description: () => `Staking was resumed!`, severity: FindingSeverity.High, type: FindingType.Info, @@ -77,7 +77,7 @@ export function getLidoEvents(LIDO_STETH_ADDRESS: string): EventOfNotice[] { address: LIDO_STETH_ADDRESS, event: 'event RecoverToVault(address vault, address token, uint256 amount)', alertId: 'LIDO-RECOVER-TO-VAULT', - name: 'ℹ️ Lido: Funds recovered to vault', + name: '⚠️ Lido: Funds recovered to vault', description: (args: Result) => `Funds recovered to vault:\n` + `Vault: ${etherscanAddress(args.vault)}\n` + @@ -90,7 +90,7 @@ export function getLidoEvents(LIDO_STETH_ADDRESS: string): EventOfNotice[] { address: LIDO_STETH_ADDRESS, event: 'event ContractVersionSet(uint256 version)', alertId: 'LIDO-CONTRACT-VERSION-SET', - name: 'ℹ️ Lido: Contract version set', + name: '⚠️ Lido: Contract version set', description: (args: Result) => `Contract version set:\n` + `Version: ${args.version}`, severity: FindingSeverity.Info, type: FindingType.Info, diff --git a/ethereum-steth-v2/src/utils/events/withdrawals_events.ts b/ethereum-steth-v2/src/utils/events/withdrawals_events.ts index 57b23ff7..3c9624d5 100644 --- a/ethereum-steth-v2/src/utils/events/withdrawals_events.ts +++ b/ethereum-steth-v2/src/utils/events/withdrawals_events.ts @@ -27,7 +27,7 @@ export function getWithdrawalsEvents(WITHDRAWAL_QUEUE_ADDRESS: string): EventOfN address: WITHDRAWAL_QUEUE_ADDRESS, event: 'event Resumed()', alertId: 'WITHDRAWALS-UNPAUSED', - name: 'βœ… Withdrawals: contract was unpaused', + name: '⚠️ Withdrawals: contract was unpaused', description: () => 'Contract was resumed', severity: FindingSeverity.High, type: FindingType.Info, diff --git a/ethereum-steth-v2/tests/e2e/agent-steth-ops.spec.ts b/ethereum-steth-v2/tests/e2e/agent-steth-ops.spec.ts index fed1da95..15763458 100644 --- a/ethereum-steth-v2/tests/e2e/agent-steth-ops.spec.ts +++ b/ethereum-steth-v2/tests/e2e/agent-steth-ops.spec.ts @@ -25,7 +25,7 @@ describe('agent-steth-ops e2e tests', () => { const expected = Finding.fromObject({ alertId: 'LOW-STAKING-LIMIT', description: `Current staking limit is 7237.74 ETH this is lower than 10% of max staking limit 150000.00 ETH`, - name: '⚠️ Staking limit below 10%', + name: '⚠️ Unspent staking limit below 10%', severity: FindingSeverity.Info, type: FindingType.Info, }) @@ -88,7 +88,7 @@ describe('agent-steth-ops e2e tests', () => { const expected = [ { - name: 'βœ… Lido: Staking resumed', + name: '⚠️ Lido: Staking resumed', description: 'Staking was resumed!', alertId: 'LIDO-STAKING-RESUMED', protocol: 'ethereum', diff --git a/ethereum-steth-v2/tsconfig.json b/ethereum-steth-v2/tsconfig.json index 32fd2f0a..16872281 100644 --- a/ethereum-steth-v2/tsconfig.json +++ b/ethereum-steth-v2/tsconfig.json @@ -6,7 +6,12 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "noImplicitAny": true, - "resolveJsonModule": true, + "resolveJsonModule": true }, - "exclude": ["node_modules", "tests"], + "exclude": [ + "node_modules", + "tests", + "dist", + "**/*spec.ts" + ] } diff --git a/ethereum-steth/src/subagents/gate-seal/agent-gate-seal.ts b/ethereum-steth/src/subagents/gate-seal/agent-gate-seal.ts index edae8352..1030c4ed 100644 --- a/ethereum-steth/src/subagents/gate-seal/agent-gate-seal.ts +++ b/ethereum-steth/src/subagents/gate-seal/agent-gate-seal.ts @@ -136,7 +136,7 @@ async function handlePauseRole(blockEvent: BlockEvent, findings: Finding[]) { ) { findings.push( Finding.fromObject({ - name: "🚨GateSeal: actual address doesn't have PAUSE_ROLE for contracts", + name: "🚨 GateSeal: actual address doesn't have PAUSE_ROLE for contracts", description: `GateSeal address: ${etherscanAddress( actualGateSeal, )}${additionalDesc}`, @@ -170,7 +170,7 @@ async function handleExpiryGateSeal( if (expiryTimestamp == "0" || Number(expiryTimestamp) <= now) { findings.push( Finding.fromObject({ - name: "🚨🚨🚨 GateSeal: is expired! 🚨🚨🚨", + name: "🚨 GateSeal: is expired!", description: `GateSeal address: ${etherscanAddress(actualGateSeal)}}`, alertId: "GATE-SEAL-IS-EXPIRED", severity: FindingSeverity.Critical, @@ -255,7 +255,7 @@ async function handleNewGateSeal( const { gate_seal } = newGateSealEvent.args; findings.push( Finding.fromObject({ - name: "🚨 GateSeal: new one created", + name: "ℹ️ GateSeal: is expired", description: `GateSeal address: ${etherscanAddress( gate_seal, )}\ndev: Please, update \`GATE_SEAL_DEFAULT_ADDRESS\` in code`, diff --git a/ethereum-steth/src/subagents/vaults/agent-vaults.ts b/ethereum-steth/src/subagents/vaults/agent-vaults.ts index 12c7b2a1..be50bbd7 100644 --- a/ethereum-steth/src/subagents/vaults/agent-vaults.ts +++ b/ethereum-steth/src/subagents/vaults/agent-vaults.ts @@ -138,7 +138,7 @@ async function handleNoWithdrawalVaultDrains( if (currentBalance.lt(prevBalance)) { findings.push( Finding.fromObject({ - name: "🚨 Withdrawal Vault balance mismatch", + name: "🚨🚨🚨 Withdrawal Vault balance mismatch", description: `Withdrawal Vault Balance has decreased by ${toEthString( prevBalance.minus(currentBalance), )} without Oracle report`, @@ -158,7 +158,7 @@ async function handleNoWithdrawalVaultDrains( if (currentBalance.lt(expectedBalance)) { findings.push( Finding.fromObject({ - name: "🚨 Withdrawal Vault balance mismatch", + name: "🚨🚨🚨 Withdrawal Vault balance mismatch", description: `Withdrawal Vault Balance has decreased by ${toEthString( expectedBalance.minus(currentBalance), )} but Oracle report shows ${toEthString(withdrawalsWithdrawn)}`, @@ -212,7 +212,7 @@ async function handleNoELVaultDrains( if (currentBalance.lt(prevBalance)) { findings.push( Finding.fromObject({ - name: "🚨 EL Vault balance mismatch", + name: "🚨🚨🚨 EL Vault balance mismatch", description: `EL Vault Balance has decreased by ${toEthString( prevBalance.minus(currentBalance), )} without Oracle report`, @@ -232,7 +232,7 @@ async function handleNoELVaultDrains( if (currentBalance.lt(expectedBalance)) { findings.push( Finding.fromObject({ - name: "🚨 EL Vault balance mismatch", + name: "🚨🚨🚨 EL Vault balance mismatch", description: `EL Vault Balance has decreased by ${toEthString( expectedBalance.minus(currentBalance), )} but Oracle report shows ${toEthString( diff --git a/ethereum-steth/src/subagents/withdrawals/agent-withdrawals.ts b/ethereum-steth/src/subagents/withdrawals/agent-withdrawals.ts index 239f49fe..7e0dd482 100644 --- a/ethereum-steth/src/subagents/withdrawals/agent-withdrawals.ts +++ b/ethereum-steth/src/subagents/withdrawals/agent-withdrawals.ts @@ -423,7 +423,7 @@ async function handleUnclaimedRequests( if (unclaimedStETH.gt(withdrawalQueueBalance)) { findings.push( Finding.fromObject({ - name: `πŸ€” Withdrawals: unclaimed requests size is more than withdrawal queue balance`, + name: `⚠️ Withdrawals: unclaimed requests size is more than withdrawal queue balance`, description: `Unclaimed: ${unclaimedStETH .div(ETH_DECIMALS) .toFixed( From 904b34a070774d9cd25289bde9304c58627a7035 Mon Sep 17 00:00:00 2001 From: Sergey White Date: Thu, 25 Jan 2024 19:39:43 +0300 Subject: [PATCH 11/22] feat: ethereum-steth-v2 handle tokenRebased event --- ethereum-steth-v2/README.md | 4 +- ethereum-steth-v2/src/agent.ts | 3 +- ethereum-steth-v2/src/app.ts | 16 +- ethereum-steth-v2/src/clients/eth_provider.ts | 57 ++++ .../src/clients/mocks/eth_provider_mock.ts | 3 + ethereum-steth-v2/src/entity/share_rate.ts | 6 + .../steth_operation/StethOperation.cache.ts | 17 +- .../steth_operation/StethOperation.spec.ts | 260 +++++++++++++++++- .../steth_operation/StethOperation.srv.ts | 78 +++++- .../services/withdrawals/Withdrawals.srv.ts | 6 +- .../src/utils/constants.testnet.ts | 47 ---- .../src/utils/events/lido_events.ts | 16 +- .../tests/e2e/agent-steth-ops.spec.ts | 46 +++- ethereum-steth-v2/tests/e2e/utils.ts | 2 +- ethereum-steth-v2/tsconfig.json | 9 +- 15 files changed, 493 insertions(+), 77 deletions(-) create mode 100644 ethereum-steth-v2/src/entity/share_rate.ts delete mode 100644 ethereum-steth-v2/src/utils/constants.testnet.ts diff --git a/ethereum-steth-v2/README.md b/ethereum-steth-v2/README.md index 3e455083..8bdbf287 100644 --- a/ethereum-steth-v2/README.md +++ b/ethereum-steth-v2/README.md @@ -34,6 +34,8 @@ 7. 🚨 Lido: Locator set 8. ⚠️Lido: Funds recovered to vault 9. ⚠️Lido: Contract version set + 10. ⚠️ Lido: Token rebased + 11. 🚨 Share rate unexpected has changed 3. Insurance fund events 1. ⚠️ Insurance fund: ETH transferred 2. ⚠️ Insurance fund: ERC721 transferred @@ -48,7 +50,7 @@ 1. ⚠️ Withdrawals: % of stake limit is spent and unfinalized queue is on par with drained stake limit 2. ⚠️ Withdrawals: unfinalized queue is more than 100_000 stETH - 3. ⚠️ Withdrawals: unfinalized queue wait time is too long + 3. ⚠️ Withdrawals: unfinalized queue wait time is more then 1 day 4. ⚠️ Withdrawals: ${unclaimedSizeRate.times(100).toFixed(2)}% of finalized requests are unclaimed 5. ⚠️ Withdrawals: unclaimed requests size is more than withdrawal queue balance 2. HandleTransaction diff --git a/ethereum-steth-v2/src/agent.ts b/ethereum-steth-v2/src/agent.ts index 628d0da2..e930e8d5 100644 --- a/ethereum-steth-v2/src/agent.ts +++ b/ethereum-steth-v2/src/agent.ts @@ -112,6 +112,7 @@ export const handleBlock = (): HandleBlock => { const [bufferedEthFindings, withdrawalsFindings, gateSealFindings, vaultFindings] = await Promise.all([ app.StethOperationSrv.handleBlock(blockEvent), + app.StethOperationSrv.handleInvariants(blockEvent.blockNumber), app.WithdrawalsSrv.handleBlock(blockEvent), app.GateSealSrv.handleBlock(blockEvent), app.VaultSrv.handleBlock(blockEvent), @@ -135,7 +136,7 @@ export const handleTransaction = (): HandleTransaction => { const app = await App.getInstance() const out: Finding[] = [] - const stethOperationFindings = app.StethOperationSrv.handleTransaction(txEvent) + const stethOperationFindings = await app.StethOperationSrv.handleTransaction(txEvent, txEvent.block.number) const withdrawalsFindings = app.WithdrawalsSrv.handleTransaction(txEvent) const gateSealFindings = app.GateSealSrv.handleTransaction(txEvent) const vaultFindings = app.VaultSrv.handleTransaction(txEvent) diff --git a/ethereum-steth-v2/src/app.ts b/ethereum-steth-v2/src/app.ts index 6f557528..8178a83e 100644 --- a/ethereum-steth-v2/src/app.ts +++ b/ethereum-steth-v2/src/app.ts @@ -20,7 +20,6 @@ import { getWithdrawalsEvents } from './utils/events/withdrawals_events' import { GateSealSrv } from './services/gate-seal/GateSeal.srv' import { DataRW } from './utils/mutex' import { GateSealCache } from './services/gate-seal/GateSeal.cache' -import { TestNetAddress } from './utils/constants.testnet' import * as Winston from 'winston' import { VaultSrv } from './services/vault/Vault.srv' @@ -41,20 +40,13 @@ export class App { public static async getInstance(): Promise { if (!App.instance) { const etherscanKey = Buffer.from('SVZCSjZUSVBXWUpZSllXSVM0SVJBSlcyNjRITkFUUjZHVQ==', 'base64').toString('utf-8') - - const etherscanProvider = new ethers.providers.EtherscanProvider( - process.env.FORTA_AGENT_RUN_TIER == 'testnet' ? 'goerli' : undefined, - etherscanKey, - ) - - let address: Address = Address - if (process.env.FORTA_AGENT_RUN_TIER === 'testnet') { - address = TestNetAddress - } - const ethersProvider = getEthersProvider() ethersProvider.formatter = new FormatterWithEIP1898() + const etherscanProvider = new ethers.providers.EtherscanProvider(ethersProvider.network, etherscanKey) + + const address: Address = Address + const lidoContact = Lido__factory.connect(address.LIDO_STETH_ADDRESS, ethersProvider) const wdQueueContact = WithdrawalQueueERC721__factory.connect(address.WITHDRAWALS_QUEUE_ADDRESS, ethersProvider) diff --git a/ethereum-steth-v2/src/clients/eth_provider.ts b/ethereum-steth-v2/src/clients/eth_provider.ts index e58f5e21..ec7f2968 100644 --- a/ethereum-steth-v2/src/clients/eth_provider.ts +++ b/ethereum-steth-v2/src/clients/eth_provider.ts @@ -54,6 +54,12 @@ export abstract class IETHProvider { fromBlockNumber: number, toBlockNumber: number, ): Promise> + + public abstract getTotalPooledEther(blockNumber: number): Promise> + + public abstract getTotalShares(blockNumber: number): Promise> + + public abstract getShareRate(blockNumber: number): Promise> } export class ETHProvider implements IETHProvider { @@ -481,4 +487,55 @@ export class ETHProvider implements IETHProvider { return E.left(new Error(`Could not call "this.exitBusContract.functions.hasRole". Cause: ${e}`)) } } + + public async getTotalPooledEther(blockNumber: number): Promise> { + try { + const out = await retryAsync( + async (): Promise => { + return await this.lidoContract.getTotalPooledEther({ + blockTag: blockNumber, + }) + }, + { delay: 500, maxTry: 5 }, + ) + + return E.right(new BigNumber(out.toString())) + } catch (e) { + return E.left(new Error(`Could not call "this.getTotalPooledEther". Cause: ${e}`)) + } + } + + public async getTotalShares(blockNumber: number): Promise> { + try { + const out = await retryAsync( + async (): Promise => { + return await this.lidoContract.getTotalShares({ + blockTag: blockNumber, + }) + }, + { delay: 500, maxTry: 5 }, + ) + + return E.right(new BigNumber(out.toString())) + } catch (e) { + return E.left(new Error(`Could not call "this.getTotalShares". Cause: ${e}`)) + } + } + + public async getShareRate(blockNumber: number): Promise> { + const [totalPooledEth, totalShares] = await Promise.all([ + this.getTotalPooledEther(blockNumber), + this.getTotalShares(blockNumber), + ]) + + if (E.isLeft(totalPooledEth)) { + return E.left(totalPooledEth.left) + } + + if (E.isLeft(totalShares)) { + return E.left(totalShares.left) + } + // Formula: shareRate = (totalPooledEth * 10**27) / totalShares + return E.right(totalPooledEth.right.multipliedBy(new BigNumber(10).pow(27)).div(totalShares.right)) + } } diff --git a/ethereum-steth-v2/src/clients/mocks/eth_provider_mock.ts b/ethereum-steth-v2/src/clients/mocks/eth_provider_mock.ts index 4dad35a2..36f462ca 100644 --- a/ethereum-steth-v2/src/clients/mocks/eth_provider_mock.ts +++ b/ethereum-steth-v2/src/clients/mocks/eth_provider_mock.ts @@ -15,4 +15,7 @@ export const ETHProviderMock = (): jest.Mocked => ({ checkGateSeal: jest.fn(), getExpiryTimestamp: jest.fn(), getETHDistributedEvent: jest.fn(), + getTotalPooledEther: jest.fn(), + getTotalShares: jest.fn(), + getShareRate: jest.fn(), }) diff --git a/ethereum-steth-v2/src/entity/share_rate.ts b/ethereum-steth-v2/src/entity/share_rate.ts new file mode 100644 index 00000000..0d2f8008 --- /dev/null +++ b/ethereum-steth-v2/src/entity/share_rate.ts @@ -0,0 +1,6 @@ +import BigNumber from 'bignumber.js' + +export type ShareRate = { + amount: BigNumber + blockNumber: number +} diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.cache.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.cache.ts index a0cab1aa..53b2d7b5 100644 --- a/ethereum-steth-v2/src/services/steth_operation/StethOperation.cache.ts +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.cache.ts @@ -1,4 +1,5 @@ import BigNumber from 'bignumber.js' +import { ShareRate } from '../../entity/share_rate' export class StethOperationCache { private _lastDepositorTxTime = 0 @@ -8,8 +9,14 @@ export class StethOperationCache { private _lastReportedExecutorBalanceTimestamp = 0 private _lastReportedStakingLimit10Timestamp = 0 private _lastReportedStakingLimit30Timestamp = 0 + private _shareRate: ShareRate - constructor() {} + constructor() { + this._shareRate = { + amount: new BigNumber(0), + blockNumber: 0, + } + } public getLastDepositorTxTime(): number { return this._lastDepositorTxTime @@ -66,4 +73,12 @@ export class StethOperationCache { public setLastReportedStakingLimit30Timestamp(blockTimestamp: number) { this._lastReportedStakingLimit30Timestamp = blockTimestamp } + + public getShareRate(): ShareRate { + return this._shareRate + } + + public setShareRate(shareRate: ShareRate) { + this._shareRate = shareRate + } } diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts index 98bb86b2..bdfcdf04 100644 --- a/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts @@ -868,6 +868,9 @@ describe('StethOperationSrv', () => { const events = [...depositEvents, ...lidoEvents, ...insuranceFundEvents, ...burnerEvents] + const shareRateMock = new BigNumber('1.15490045560519776042410219381324898101464198621e+27') + ethProviderMock.getShareRate.mockResolvedValue(E.right(shareRateMock)) + const srv = new StethOperationSrv( logger, cache, @@ -904,7 +907,8 @@ describe('StethOperationSrv', () => { txEventMock.filterLog.mockReturnValueOnce([logDescription]) } - const result = srv.handleTransaction(txEventMock) + const blockNumber = 19061521 + const result = await srv.handleTransaction(txEventMock, blockNumber) for (let i = 0; i < result.length; i++) { const expected: Finding = Finding.fromObject({ @@ -918,6 +922,260 @@ describe('StethOperationSrv', () => { expect(result[i]).toEqual(expected) } + + expect(srv.getStorage().getShareRate().amount).toEqual(shareRateMock) + expect(srv.getStorage().getShareRate().blockNumber).toEqual(19061521) + }) + + test('get shareRate error', async () => { + const cache = new StethOperationCache() + + const depositEvents = getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS) + const lidoEvents = getLidoEvents(address.LIDO_STETH_ADDRESS) + const insuranceFundEvents = getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20) + const burnerEvents = getBurnerEvents(address.BURNER_ADDRESS) + + const events = [...depositEvents, ...lidoEvents, ...insuranceFundEvents, ...burnerEvents] + + ethProviderMock.getShareRate.mockResolvedValue(E.left(new Error('shareRateErr'))) + + const srv = new StethOperationSrv( + logger, + cache, + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + depositEvents, + lidoEvents, + insuranceFundEvents, + burnerEvents, + ) + + const txEventMock = TransactionEventContractMock() + txEventMock.addresses = { + [address.DEPOSIT_SECURITY_ADDRESS]: true, + [address.LIDO_STETH_ADDRESS]: true, + [address.INSURANCE_FUND_ADDRESS]: true, + [address.BURNER_ADDRESS]: true, + } + + txEventMock.to = address.DEPOSIT_SECURITY_ADDRESS + + const filteredEvents: LogDescription[] = [] + for (const logDescription of [ + ...getFilteredDepositSecurityEventsMock(), + ...getFilteredLidoEventsMock(), + ...getFilteredInsuranceFundEventsMock(), + ...getFilteredBurnerEventsMock(), + ]) { + filteredEvents.push(logDescription) + txEventMock.filterLog.mockReturnValueOnce([logDescription]) + } + + const blockNumber = 19061521 + const result = await srv.handleTransaction(txEventMock, blockNumber) + + for (let i = 0; i < result.length - 1; i++) { + const expected: Finding = Finding.fromObject({ + name: events[i].name, + description: events[i].description(filteredEvents[i].args), + alertId: events[i].alertId, + severity: events[i].severity, + type: events[i].type, + metadata: { args: String(filteredEvents[i].args) }, + }) + + expect(result[i]).toEqual(expected) + } + + const expectedShareRateErrFinding = Finding.fromObject({ + alertId: 'LIDO-AGENT-ERROR', + description: `Could not call "ethProvider.getShareRate". Cause shareRateErr`, + name: 'Error in StethOperationSrv.handleTransaction:146', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + }) + + expect(result[result.length - 1].name).toEqual(expectedShareRateErrFinding.name) + expect(result[result.length - 1].description).toEqual(expectedShareRateErrFinding.description) + expect(result[result.length - 1].alertId).toEqual(expectedShareRateErrFinding.alertId) + expect(result[result.length - 1].severity).toEqual(expectedShareRateErrFinding.severity) + expect(result[result.length - 1].type).toEqual(expectedShareRateErrFinding.type) + }) + }) + + describe('handleInvariants', () => { + test(`ethProviderErr`, async () => { + const want = new Error(`getShareRateErr`) + ethProviderMock.getShareRate.mockResolvedValue(E.left(want)) + + const srv = new StethOperationSrv( + logger, + new StethOperationCache(), + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS), + getLidoEvents(address.LIDO_STETH_ADDRESS), + getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20), + getBurnerEvents(address.BURNER_ADDRESS), + ) + + const currentBlock = 19061449 + + srv.getStorage().setShareRate({ + blockNumber: currentBlock + 1, + amount: new BigNumber('1.15490045560519776042410219381324898101464198621e+27'), + }) + + const result = await srv.handleInvariants(currentBlock) + + const expectedShareRateErrFinding = Finding.fromObject({ + alertId: 'LIDO-AGENT-ERROR', + description: `Could not call "ethProvider.getShareRate". Cause getShareRateErr`, + name: 'Error in StethOperationSrv.handleInvariants:136', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + }) + + expect(result[0].name).toEqual(expectedShareRateErrFinding.name) + expect(result[0].description).toEqual(expectedShareRateErrFinding.description) + expect(result[0].alertId).toEqual(expectedShareRateErrFinding.alertId) + expect(result[0].severity).toEqual(expectedShareRateErrFinding.severity) + expect(result[0].type).toEqual(expectedShareRateErrFinding.type) + }) + + test(`should found invariant on +0.15`, async () => { + const cachedShareRate = new BigNumber('1.15490045560519776042410219381324898101464198621e+27') + + ethProviderMock.getShareRate.mockResolvedValue( + E.right(cachedShareRate.plus(new BigNumber('0.15490045560519776042410219381324898101464198621e+27'))), + ) + + const srv = new StethOperationSrv( + logger, + new StethOperationCache(), + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS), + getLidoEvents(address.LIDO_STETH_ADDRESS), + getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20), + getBurnerEvents(address.BURNER_ADDRESS), + ) + + const currentBlock = 19061449 + + srv.getStorage().setShareRate({ + blockNumber: currentBlock - 1, + amount: cachedShareRate, + }) + + const result = await srv.handleInvariants(currentBlock) + + const expectedShareRateErrFinding = Finding.fromObject({ + alertId: 'LIDO-INVARIANT-ERROR', + description: `Prev.shareRate(19061448) = 1.1549004556051977e+27 +Curr.shareRate(19061449) = 1.3098009112103954e+27 +Diff: 1.5490045560519778e+26`, + name: '🚨 Share rate unexpected has changed', + severity: FindingSeverity.Critical, + type: FindingType.Suspicious, + }) + + expect(result[0].name).toEqual(expectedShareRateErrFinding.name) + expect(result[0].description).toEqual(expectedShareRateErrFinding.description) + expect(result[0].alertId).toEqual(expectedShareRateErrFinding.alertId) + expect(result[0].severity).toEqual(expectedShareRateErrFinding.severity) + expect(result[0].type).toEqual(expectedShareRateErrFinding.type) + }) + + test(`should found invariant on -0.15`, async () => { + const cachedShareRate = new BigNumber('1.15490045560519776042410219381324898101464198621e+27') + + ethProviderMock.getShareRate.mockResolvedValue( + E.right(cachedShareRate.minus(new BigNumber('0.15490045560519776042410219381324898101464198621e+27'))), + ) + + const srv = new StethOperationSrv( + logger, + new StethOperationCache(), + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS), + getLidoEvents(address.LIDO_STETH_ADDRESS), + getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20), + getBurnerEvents(address.BURNER_ADDRESS), + ) + + const currentBlock = 19061449 + + srv.getStorage().setShareRate({ + blockNumber: currentBlock - 1, + amount: cachedShareRate, + }) + + const result = await srv.handleInvariants(currentBlock) + + const expectedShareRateErrFinding = Finding.fromObject({ + alertId: 'LIDO-INVARIANT-ERROR', + description: `Prev.shareRate(19061448) = 1.1549004556051977e+27 +Curr.shareRate(19061449) = 1e+27 +Diff: -1.5490045560519778e+26`, + name: '🚨 Share rate unexpected has changed', + severity: FindingSeverity.Critical, + type: FindingType.Suspicious, + }) + + expect(result[0].name).toEqual(expectedShareRateErrFinding.name) + expect(result[0].description).toEqual(expectedShareRateErrFinding.description) + expect(result[0].alertId).toEqual(expectedShareRateErrFinding.alertId) + expect(result[0].severity).toEqual(expectedShareRateErrFinding.severity) + expect(result[0].type).toEqual(expectedShareRateErrFinding.type) + }) + + test(`should not found invariant on 0.00001`, async () => { + const cachedShareRate = new BigNumber('1.15490045560519776042410219381324898101464198621e+27') + + ethProviderMock.getShareRate.mockResolvedValue(E.right(cachedShareRate.plus(new BigNumber('0.00001')))) + + const srv = new StethOperationSrv( + logger, + new StethOperationCache(), + ethProviderMock, + address.DEPOSIT_SECURITY_ADDRESS, + address.LIDO_STETH_ADDRESS, + address.DEPOSIT_EXECUTOR_ADDRESS, + lidoContractMock, + wdQueueContractMock, + getDepositSecurityEvents(address.DEPOSIT_SECURITY_ADDRESS), + getLidoEvents(address.LIDO_STETH_ADDRESS), + getInsuranceFundEvents(address.INSURANCE_FUND_ADDRESS, address.KNOWN_ERC20), + getBurnerEvents(address.BURNER_ADDRESS), + ) + + const currentBlock = 19061449 + + srv.getStorage().setShareRate({ + blockNumber: currentBlock - 1, + amount: cachedShareRate, + }) + + const result = await srv.handleInvariants(currentBlock) + expect(result.length).toEqual(0) }) }) }) diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts index 9c1587f2..a904b4a1 100644 --- a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts @@ -12,6 +12,7 @@ import { elapsedTime } from '../../utils/time' import { LidoContract, TransactionEventContract, WithdrawalQueueContract } from './contracts' import { Logger } from 'winston' import { TypedEvent } from '../../generated/common' +import { alertId_token_rebased } from '../../utils/events/lido_events' // Formula: (60 * 60 * 72) / 13 = 19_938 const HISTORY_BLOCK_OFFSET: number = Math.floor((60 * 60 * 72) / 13) @@ -127,7 +128,56 @@ export class StethOperationSrv { return findings } - public handleTransaction(txEvent: TransactionEventContract): Finding[] { + public async handleInvariants(blockNumber: number): Promise { + const start = new Date().getTime() + const findings: Finding[] = [] + + if (this.cache.getShareRate().blockNumber !== 0) { + const shareRate = await this.ethProvider.getShareRate(blockNumber) + if (E.isLeft(shareRate)) { + const f: Finding = Finding.fromObject({ + name: `Error in ${StethOperationSrv.name}.${this.handleInvariants.name}:136`, + description: `Could not call "ethProvider.getShareRate". Cause ${shareRate.left.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { + stack: `${shareRate.left.stack}`, + }, + }) + + return [f] + } + + const shareRateFromReport = this.cache.getShareRate() + if (!shareRate.right.eq(shareRateFromReport.amount)) { + const percentsFromReporedShareRate = shareRate.right.div(shareRateFromReport.amount).multipliedBy(100) + const deviation = 100.0 - percentsFromReporedShareRate.toNumber() + + // deviation by 0.0001 (1 bp or 0.01%) + if (Math.abs(deviation) >= 0.01) { + findings.push( + Finding.fromObject({ + name: `🚨 Share rate unexpected has changed`, + description: + `Prev.shareRate(${shareRateFromReport.blockNumber}) = ${shareRateFromReport.amount.toNumber()} \n` + + `Curr.shareRate(${blockNumber}) = ${shareRate.right.toNumber()} \n` + + `Diff: ${shareRate.right.minus(shareRateFromReport.amount).toNumber()}`, + alertId: 'LIDO-INVARIANT-ERROR', + severity: FindingSeverity.Critical, + type: FindingType.Suspicious, + }), + ) + } + } + } + + this.logger.info(elapsedTime(StethOperationSrv.name + '.' + this.handleInvariants.name, start)) + + return findings + } + + public async handleTransaction(txEvent: TransactionEventContract, blockNumber: number): Promise { const out: Finding[] = [] if (txEvent.to == this.depositSecurityAddress) { @@ -138,9 +188,33 @@ export class StethOperationSrv { const lidoFindings = this.handleEventsOfNotice(txEvent, this.lidoEvents) const insuranceFundFindings = this.handleEventsOfNotice(txEvent, this.insuranceFundEvents) const burnerFindings = this.handleEventsOfNotice(txEvent, this.burnerEvents) - out.push(...depositSecFindings, ...lidoFindings, ...insuranceFundFindings, ...burnerFindings) + for (const f of lidoFindings) { + if (f.alertId === alertId_token_rebased) { + const shareRate = await this.ethProvider.getShareRate(blockNumber) + if (E.isLeft(shareRate)) { + const f: Finding = Finding.fromObject({ + name: `Error in ${StethOperationSrv.name}.${this.handleTransaction.name}:146`, + description: `Could not call "ethProvider.getShareRate". Cause ${shareRate.left.message}`, + alertId: 'LIDO-AGENT-ERROR', + severity: FindingSeverity.Low, + type: FindingType.Degraded, + metadata: { + stack: `${shareRate.left.stack}`, + }, + }) + + out.push(f) + } else { + this.cache.setShareRate({ + amount: shareRate.right, + blockNumber: blockNumber, + }) + } + } + } + return out } diff --git a/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts b/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts index 3d22273c..6d91d6f7 100644 --- a/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts +++ b/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts @@ -373,11 +373,15 @@ export class WithdrawalsSrv { currentBlockTimestamp - this.cache.getLastLongUnfinalizedQueueAlertTimestamp() > LONG_UNFINALIZED_QUEUE_TRIGGER_EVERY ) { + const waitTime = currentBlockTimestamp - this.cache.getLastLongUnfinalizedQueueAlertTimestamp() + // if we are in turbo mode and unfinalized queue is not finalized for 5 days // and alert hasn't been sent for 1 day out.push( Finding.fromObject({ - name: '⚠️ Withdrawals: unfinalized queue wait time is too long', + name: `⚠️ Withdrawals: unfinalized queue wait time is ${new Date( + waitTime * 1000, + ).getHours()} more then 1 day`, description: `Unfinalized queue wait time is ${formatDelay( currentBlockTimestamp - this.cache.getFirstUnfinalizedRequestTimestamp(), )}`, diff --git a/ethereum-steth-v2/src/utils/constants.testnet.ts b/ethereum-steth-v2/src/utils/constants.testnet.ts deleted file mode 100644 index cdc16c9b..00000000 --- a/ethereum-steth-v2/src/utils/constants.testnet.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Address, ERC20 } from './constants' - -const DEPOSIT_SECURITY_ADDRESS: string = '0xe57025e250275ca56f92d76660decfc490c7e79a' -const BURNER_ADDRESS: string = '0x20c61c07c2e2fab04bf5b4e12ce45a459a18f3b1' -const LDO_ADDRESS: string = '0x56340274fB5a72af1A3C6609061c451De7961Bd4' -const LIDO_STETH_ADDRESS: string = '0x1643E812aE58766192Cf7D2Cf9567dF2C37e9B7F' -const WSTETH_ADDRESS: string = '0x6320cd32aa674d2898a68ec82e869385fc5f7e2f' -const WITHDRAWALS_QUEUE_ADDRESS: string = '0xcf117961421ca9e546cd7f50bc73abcdb3039533' -const DEPOSIT_EXECUTOR_ADDRESS: string = '0x745ad85f7c20ea6f3c85b830208394e0d70a31ea' -const INSURANCE_FUND_ADDRESS: string = '0x2fae4d2d86efb17249f24c9fb70855d4c58585a5' -const DAI_ADDRESS: string = '0x6B175474E89094C44Da98b954EedeAC495271d0F' -const USDT_ADDRESS: string = '0xdAC17F958D2ee523a2206206994597C13D831ec7' -const USDC_ADDRESS: string = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' -const GATE_SEAL_DEFAULT_ADDRESS: string = '0xee656d0c8e9f0646b505a13295c188164a3d8eac' -const EXITBUS_ORACLE_ADDRESS: string = '0xb75a55efab5a8f5224ae93b34b25741edd3da98b' -const GATE_SEAL_FACTORY_ADDRESS: string = '0x248c4a34645921c70a591ccc200cb75d6a4c5091' -const WITHDRAWALS_VAULT_ADDRESS: string = '0xdc62f9e8C34be08501Cdef4EBDE0a280f576D762' -const EL_REWARDS_VAULT_ADDRESS: string = '0x94750381bE1AbA0504C666ee1DB118F68f0780D4' - -const KNOWN_ERC20 = new Map([ - [LIDO_STETH_ADDRESS, { decimals: 18, name: 'stETH' }], - [WSTETH_ADDRESS, { decimals: 18, name: 'wstETH' }], - [LDO_ADDRESS, { decimals: 18, name: 'LDO' }], - [DAI_ADDRESS, { decimals: 18, name: 'DAI' }], - [USDT_ADDRESS, { decimals: 6, name: 'USDT' }], - [USDC_ADDRESS, { decimals: 6, name: 'USDC' }], -]) - -export const TestNetAddress: Address = { - DEPOSIT_SECURITY_ADDRESS: DEPOSIT_SECURITY_ADDRESS, - BURNER_ADDRESS: BURNER_ADDRESS, - LDO_ADDRESS: LDO_ADDRESS, - LIDO_STETH_ADDRESS: LIDO_STETH_ADDRESS, - WSTETH_ADDRESS: WSTETH_ADDRESS, - WITHDRAWALS_QUEUE_ADDRESS: WITHDRAWALS_QUEUE_ADDRESS, - DEPOSIT_EXECUTOR_ADDRESS: DEPOSIT_EXECUTOR_ADDRESS, - INSURANCE_FUND_ADDRESS: INSURANCE_FUND_ADDRESS, - DAI_ADDRESS: DAI_ADDRESS, - USDT_ADDRESS: USDT_ADDRESS, - USDC_ADDRESS: USDC_ADDRESS, - GATE_SEAL_DEFAULT_ADDRESS: GATE_SEAL_DEFAULT_ADDRESS, - EXITBUS_ORACLE_ADDRESS: EXITBUS_ORACLE_ADDRESS, - GATE_SEAL_FACTORY_ADDRESS: GATE_SEAL_FACTORY_ADDRESS, - WITHDRAWALS_VAULT_ADDRESS: WITHDRAWALS_VAULT_ADDRESS, - EL_REWARDS_VAULT_ADDRESS: EL_REWARDS_VAULT_ADDRESS, - KNOWN_ERC20: KNOWN_ERC20, -} diff --git a/ethereum-steth-v2/src/utils/events/lido_events.ts b/ethereum-steth-v2/src/utils/events/lido_events.ts index b8c9d364..e6615814 100644 --- a/ethereum-steth-v2/src/utils/events/lido_events.ts +++ b/ethereum-steth-v2/src/utils/events/lido_events.ts @@ -5,6 +5,8 @@ import { etherscanAddress } from '../string' import { faker } from '@faker-js/faker' import { createLogDescriptionMock } from './helper/event_helper' +export const alertId_token_rebased = 'LIDO-TOKEN-REBASED' + export function getLidoEvents(LIDO_STETH_ADDRESS: string): EventOfNotice[] { return [ { @@ -95,9 +97,18 @@ export function getLidoEvents(LIDO_STETH_ADDRESS: string): EventOfNotice[] { severity: FindingSeverity.Info, type: FindingType.Info, }, + { + address: LIDO_STETH_ADDRESS, + event: + 'event TokenRebased(uint256 indexed reportTimestamp, uint256 timeElapsed, uint256 preTotalShares, uint256 preTotalEther, uint256 postTotalShares, uint256 postTotalEther, uint256 sharesMintedAsFees)', + alertId: alertId_token_rebased, + name: '⚠️ Lido: Token rebased', + description: (args: Result) => `reportTimestamp: ${args.reportTimestamp}`, + severity: FindingSeverity.Info, + type: FindingType.Info, + }, ] } - export function getFilteredLidoEventsMock(): LogDescription[] { const descriptions = [ {}, @@ -119,6 +130,9 @@ export function getFilteredLidoEventsMock(): LogDescription[] { { ['version']: faker.system.semver(), }, + { + ['amount']: faker.number.int(), + }, ] const out: LogDescription[] = [] diff --git a/ethereum-steth-v2/tests/e2e/agent-steth-ops.spec.ts b/ethereum-steth-v2/tests/e2e/agent-steth-ops.spec.ts index 15763458..4ac44dc9 100644 --- a/ethereum-steth-v2/tests/e2e/agent-steth-ops.spec.ts +++ b/ethereum-steth-v2/tests/e2e/agent-steth-ops.spec.ts @@ -2,6 +2,8 @@ import { ethers, Finding, FindingSeverity, FindingType, getEthersProvider, Netwo import { App } from '../../src/app' import { JsonRpcProvider } from '@ethersproject/providers' import { createTransactionEvent, etherBlockToFortaBlockEvent } from './utils' +import BigNumber from 'bignumber.js' + const TEST_TIMEOUT = 60_000 // ms describe('agent-steth-ops e2e tests', () => { @@ -84,7 +86,7 @@ describe('agent-steth-ops e2e tests', () => { const txEvent = createTransactionEvent(transaction, block, Network.MAINNET, [], receipt.logs) - const results = app.StethOperationSrv.handleTransaction(txEvent) + const results = await app.StethOperationSrv.handleTransaction(txEvent, txEvent.blockNumber) const expected = [ { @@ -139,7 +141,7 @@ describe('agent-steth-ops e2e tests', () => { const txEvent = createTransactionEvent(transaction, block, Network.MAINNET, [], receipt.logs) - const results = app.StethOperationSrv.handleTransaction(txEvent) + const results = await app.StethOperationSrv.handleTransaction(txEvent, txEvent.blockNumber) const expected = [ { @@ -177,4 +179,44 @@ describe('agent-steth-ops e2e tests', () => { }, TEST_TIMEOUT, ) + + test( + 'Share rate', + async () => { + const app = await App.getInstance() + const txHash = '0xe71ac8b9f8f7b360f5defd3f6738f8482f8c15f1dd5f6827544bef8b7b4fbd37' + + const receipt = await ethProvider.send('eth_getTransactionReceipt', [txHash]) + const block = await ethProvider.send('eth_getBlockByNumber', [ + ethers.utils.hexValue(parseInt(receipt.blockNumber)), + true, + ]) + const transaction = block.transactions.find((tx: Transaction) => tx.hash.toLowerCase() === txHash)! + const txEvent = createTransactionEvent(transaction, block, Network.MAINNET, [], receipt.logs) + const results = await app.StethOperationSrv.handleTransaction(txEvent, parseInt(receipt.blockNumber)) + + const expected: Finding = Finding.fromObject({ + name: '⚠️ Lido: Token rebased', + description: 'reportTimestamp: 1706011211', + alertId: 'LIDO-TOKEN-REBASED', + severity: 1, + type: 4, + }) + + expect(results[0].alertId).toEqual(expected.alertId) + expect(results[0].description).toEqual(expected.description) + expect(results[0].name).toEqual(expected.name) + expect(results[0].severity).toEqual(expected.severity) + expect(results[0].type).toEqual(expected.type) + + expect(app.StethOperationSrv.getStorage().getShareRate().blockNumber).toEqual(19069339) + expect(app.StethOperationSrv.getStorage().getShareRate().amount).toEqual( + new BigNumber('1.15469003182482499409518734333781126194978625178e+27'), + ) + + const findings = await app.StethOperationSrv.handleInvariants(19069340) + expect(findings.length).toEqual(0) + }, + TEST_TIMEOUT, + ) }) diff --git a/ethereum-steth-v2/tests/e2e/utils.ts b/ethereum-steth-v2/tests/e2e/utils.ts index 1fccfdb1..af8cfc25 100644 --- a/ethereum-steth-v2/tests/e2e/utils.ts +++ b/ethereum-steth-v2/tests/e2e/utils.ts @@ -1,5 +1,5 @@ import { Block as EtherBlock } from '@ethersproject/abstract-provider' -import { Block, BlockEvent, EventType, Network, Trace } from 'forta-agent' +import { Block, BlockEvent, ethers, EventType, Log, LogDescription, Network, Trace } from 'forta-agent' import { formatAddress, isZeroAddress } from 'forta-agent/dist/cli/utils' import { TransactionEvent } from 'forta-agent/dist/sdk/transaction.event' import { getContractAddress } from 'ethers/lib/utils' diff --git a/ethereum-steth-v2/tsconfig.json b/ethereum-steth-v2/tsconfig.json index 16872281..e1ba6f34 100644 --- a/ethereum-steth-v2/tsconfig.json +++ b/ethereum-steth-v2/tsconfig.json @@ -6,12 +6,7 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "noImplicitAny": true, - "resolveJsonModule": true + "resolveJsonModule": true, }, - "exclude": [ - "node_modules", - "tests", - "dist", - "**/*spec.ts" - ] + "exclude": ["node_modules", "tests", "dist", "**/*spec.ts"], } From da09ac6f2aa61c4bf12c901b387f202aa47faeb1 Mon Sep 17 00:00:00 2001 From: Sergey White Date: Thu, 25 Jan 2024 20:16:08 +0300 Subject: [PATCH 12/22] feat: ethereum-steth-v2 added ScannerId --- ethereum-steth-v2/src/agent.ts | 23 ++++++++++++++--- ethereum-steth-v2/src/app.ts | 25 ++++++++++++++++++- ..._provider_mock.ts => eth_provider.mock.ts} | 0 .../steth_operation/StethOperation.spec.ts | 4 +-- .../{contract_mocks.ts => contract.mock.ts} | 0 .../log_description.mock.ts} | 0 .../src/utils/events/burner_events.ts | 2 +- .../utils/events/deposit_security_events.ts | 2 +- .../src/utils/events/insurance_fund_events.ts | 2 +- .../src/utils/events/lido_events.ts | 2 +- .../src/utils/events/withdrawals_events.ts | 2 +- ethereum-steth-v2/tsconfig.json | 2 +- 12 files changed, 52 insertions(+), 12 deletions(-) rename ethereum-steth-v2/src/clients/mocks/{eth_provider_mock.ts => eth_provider.mock.ts} (100%) rename ethereum-steth-v2/src/utils/contract_mocks/{contract_mocks.ts => contract.mock.ts} (100%) rename ethereum-steth-v2/src/utils/{events/helper/event_helper.ts => contract_mocks/log_description.mock.ts} (100%) diff --git a/ethereum-steth-v2/src/agent.ts b/ethereum-steth-v2/src/agent.ts index e930e8d5..f5bf3612 100644 --- a/ethereum-steth-v2/src/agent.ts +++ b/ethereum-steth-v2/src/agent.ts @@ -1,4 +1,12 @@ -import { BlockEvent, Finding, FindingSeverity, FindingType, HandleBlock, HandleTransaction } from 'forta-agent' +import { + BlockEvent, + decodeJwt, + Finding, + FindingSeverity, + FindingType, + HandleBlock, + HandleTransaction, +} from 'forta-agent' import * as process from 'process' import { argv } from 'process' import { InitializeResponse } from 'forta-agent/dist/sdk/initialize.response' @@ -21,6 +29,14 @@ export function initialize(): Initialize { const startTime = new Date().getTime() const app = await App.getInstance() + const token = await App.getJwt() + if (E.isLeft(token)) { + console.log(`Error: ${token.left.message}`) + console.log(`Stack: ${token.left.stack}`) + + process.exit(1) + } + const latestBlockNumber = await app.ethClient.getStartedBlockForApp(argv) if (E.isLeft(latestBlockNumber)) { console.log(`Error: ${latestBlockNumber.left.message}`) @@ -66,9 +82,11 @@ export function initialize(): Initialize { ] metadata.agents = '[' + agents.toString() + ']' + const decodedJwt = decodeJwt(token.right) + await app.findingsRW.write([ Finding.fromObject({ - name: 'Agent launched', + name: `Agent launched, ScannerId: ${decodedJwt.payload.sub}`, description: `Version: ${Version.desc}`, alertId: 'LIDO-AGENT-LAUNCHED', severity: FindingSeverity.Info, @@ -78,7 +96,6 @@ export function initialize(): Initialize { ]) console.log(elapsedTime('Agent.initialize', startTime) + '\n') - console.log( `[${app.StethOperationSrv.getName()}] Last Depositor TxTime: ${new Date( app.StethOperationSrv.getStorage().getLastDepositorTxTime() * 1000, diff --git a/ethereum-steth-v2/src/app.ts b/ethereum-steth-v2/src/app.ts index 8178a83e..abc449d5 100644 --- a/ethereum-steth-v2/src/app.ts +++ b/ethereum-steth-v2/src/app.ts @@ -1,5 +1,5 @@ import { StethOperationSrv } from './services/steth_operation/StethOperation.srv' -import { ethers, Finding, getEthersProvider } from 'forta-agent' +import { ethers, fetchJwt, Finding, getEthersProvider, verifyJwt } from 'forta-agent' import { Address } from './utils/constants' import { StethOperationCache } from './services/steth_operation/StethOperation.cache' import { ETHProvider, IETHProvider } from './clients/eth_provider' @@ -22,6 +22,7 @@ import { DataRW } from './utils/mutex' import { GateSealCache } from './services/gate-seal/GateSeal.cache' import * as Winston from 'winston' import { VaultSrv } from './services/vault/Vault.srv' +import * as E from 'fp-ts/Either' export type Container = { ethClient: IETHProvider @@ -37,6 +38,28 @@ export class App { private constructor() {} + public static async getJwt(): Promise> { + let token: string + try { + token = await fetchJwt({}) + } catch (e) { + return E.left(new Error(`Could not fetch jwt. cause ${e}`)) + } + + if (process.env.NODE_ENV === 'production') { + try { + const isTokenOk = await verifyJwt(token) + if (!isTokenOk) { + return E.left(new Error(`Token verification failed`)) + } + } catch (e) { + return E.left(new Error(`Token verification failed`)) + } + } + + return E.right(token) + } + public static async getInstance(): Promise { if (!App.instance) { const etherscanKey = Buffer.from('SVZCSjZUSVBXWUpZSllXSVM0SVJBSlcyNjRITkFUUjZHVQ==', 'base64').toString('utf-8') diff --git a/ethereum-steth-v2/src/clients/mocks/eth_provider_mock.ts b/ethereum-steth-v2/src/clients/mocks/eth_provider.mock.ts similarity index 100% rename from ethereum-steth-v2/src/clients/mocks/eth_provider_mock.ts rename to ethereum-steth-v2/src/clients/mocks/eth_provider.mock.ts diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts index bdfcdf04..69bb32e2 100644 --- a/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts @@ -17,13 +17,13 @@ import { import { getFilteredLidoEventsMock, getLidoEvents } from '../../utils/events/lido_events' import { getFilteredInsuranceFundEventsMock, getInsuranceFundEvents } from '../../utils/events/insurance_fund_events' import { getBurnerEvents, getFilteredBurnerEventsMock } from '../../utils/events/burner_events' -import { ETHProviderMock } from '../../clients/mocks/eth_provider_mock' +import { ETHProviderMock } from '../../clients/mocks/eth_provider.mock' import { LidoContractMock, TransactionEventContractMock, TypedEventMock, WithdrawalQueueContractMock, -} from '../../utils/contract_mocks/contract_mocks' +} from '../../utils/contract_mocks/contract.mock' import { LidoContract, WithdrawalQueueContract } from './contracts' import { expect } from '@jest/globals' import { TransactionResponse } from '@ethersproject/abstract-provider' diff --git a/ethereum-steth-v2/src/utils/contract_mocks/contract_mocks.ts b/ethereum-steth-v2/src/utils/contract_mocks/contract.mock.ts similarity index 100% rename from ethereum-steth-v2/src/utils/contract_mocks/contract_mocks.ts rename to ethereum-steth-v2/src/utils/contract_mocks/contract.mock.ts diff --git a/ethereum-steth-v2/src/utils/events/helper/event_helper.ts b/ethereum-steth-v2/src/utils/contract_mocks/log_description.mock.ts similarity index 100% rename from ethereum-steth-v2/src/utils/events/helper/event_helper.ts rename to ethereum-steth-v2/src/utils/contract_mocks/log_description.mock.ts diff --git a/ethereum-steth-v2/src/utils/events/burner_events.ts b/ethereum-steth-v2/src/utils/events/burner_events.ts index 189dd779..a5482fbc 100644 --- a/ethereum-steth-v2/src/utils/events/burner_events.ts +++ b/ethereum-steth-v2/src/utils/events/burner_events.ts @@ -3,7 +3,7 @@ import { FindingSeverity, FindingType, LogDescription } from 'forta-agent' import { Result } from '@ethersproject/abi/lib' import { etherscanAddress } from '../string' import { faker } from '@faker-js/faker' -import { createLogDescriptionMock } from './helper/event_helper' +import { createLogDescriptionMock } from '../contract_mocks/log_description.mock' export function getBurnerEvents(BURNER_ADDRESS: string): EventOfNotice[] { return [ diff --git a/ethereum-steth-v2/src/utils/events/deposit_security_events.ts b/ethereum-steth-v2/src/utils/events/deposit_security_events.ts index 4287dff8..f9d25b47 100644 --- a/ethereum-steth-v2/src/utils/events/deposit_security_events.ts +++ b/ethereum-steth-v2/src/utils/events/deposit_security_events.ts @@ -3,7 +3,7 @@ import { FindingSeverity, FindingType, LogDescription } from 'forta-agent' import { Result } from '@ethersproject/abi/lib' import { etherscanAddress } from '../string' import { faker } from '@faker-js/faker' -import { createLogDescriptionMock } from './helper/event_helper' +import { createLogDescriptionMock } from '../contract_mocks/log_description.mock' export function getDepositSecurityEvents(DEPOSIT_SECURITY_ADDRESS: string): EventOfNotice[] { return [ diff --git a/ethereum-steth-v2/src/utils/events/insurance_fund_events.ts b/ethereum-steth-v2/src/utils/events/insurance_fund_events.ts index a8b74a40..6fdc4b9f 100644 --- a/ethereum-steth-v2/src/utils/events/insurance_fund_events.ts +++ b/ethereum-steth-v2/src/utils/events/insurance_fund_events.ts @@ -5,7 +5,7 @@ import { FindingSeverity, FindingType, LogDescription } from 'forta-agent' import { Result } from '@ethersproject/abi/lib' import { etherscanAddress } from '../string' import { faker } from '@faker-js/faker' -import { createLogDescriptionMock } from './helper/event_helper' +import { createLogDescriptionMock } from '../contract_mocks/log_description.mock' export function getInsuranceFundEvents( INSURANCE_FUND_ADDRESS: string, diff --git a/ethereum-steth-v2/src/utils/events/lido_events.ts b/ethereum-steth-v2/src/utils/events/lido_events.ts index e6615814..99e22e2b 100644 --- a/ethereum-steth-v2/src/utils/events/lido_events.ts +++ b/ethereum-steth-v2/src/utils/events/lido_events.ts @@ -3,7 +3,7 @@ import { EventOfNotice } from '../../entity/events' import { Result } from '@ethersproject/abi/lib' import { etherscanAddress } from '../string' import { faker } from '@faker-js/faker' -import { createLogDescriptionMock } from './helper/event_helper' +import { createLogDescriptionMock } from '../contract_mocks/log_description.mock' export const alertId_token_rebased = 'LIDO-TOKEN-REBASED' diff --git a/ethereum-steth-v2/src/utils/events/withdrawals_events.ts b/ethereum-steth-v2/src/utils/events/withdrawals_events.ts index 3c9624d5..8d1512da 100644 --- a/ethereum-steth-v2/src/utils/events/withdrawals_events.ts +++ b/ethereum-steth-v2/src/utils/events/withdrawals_events.ts @@ -3,7 +3,7 @@ import BigNumber from 'bignumber.js' import { Result } from '@ethersproject/abi/lib' import { EventOfNotice } from '../../entity/events' import { faker } from '@faker-js/faker' -import { createLogDescriptionMock } from './helper/event_helper' +import { createLogDescriptionMock } from '../contract_mocks/log_description.mock' export const WITHDRAWALS_BUNKER_MODE_ENABLED_EVENT = 'event BunkerModeEnabled(uint256 _sinceTimestamp)' diff --git a/ethereum-steth-v2/tsconfig.json b/ethereum-steth-v2/tsconfig.json index e1ba6f34..d65bbd37 100644 --- a/ethereum-steth-v2/tsconfig.json +++ b/ethereum-steth-v2/tsconfig.json @@ -8,5 +8,5 @@ "noImplicitAny": true, "resolveJsonModule": true, }, - "exclude": ["node_modules", "tests", "dist", "**/*spec.ts"], + "exclude": ["node_modules", "tests", "dist", "**/*spec.ts", "**/*mock.ts"], } From 42bc03e914e87fc00b876507ad2f8d8991cec716 Mon Sep 17 00:00:00 2001 From: Sergey White Date: Thu, 25 Jan 2024 20:17:43 +0300 Subject: [PATCH 13/22] feat: ethereum-steth-v2 fix cs --- ethereum-steth-v2/README.md | 2 +- ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ethereum-steth-v2/README.md b/ethereum-steth-v2/README.md index 8bdbf287..3f0717da 100644 --- a/ethereum-steth-v2/README.md +++ b/ethereum-steth-v2/README.md @@ -58,7 +58,7 @@ 2. ⚠️Withdrawals: BUNKER MODE OFF! βœ… 3. ℹ️ Huge stETH withdrawal requests batch 4. ⚠️ Withdrawals: the sum of received withdrawal requests since the last rebase greater than 150_000 stETH (max staking limit) - 5. πŸ€” Withdrawals: claimed amount is more than requested + 5. ⚠️Withdrawals: claimed amount is more than requested 6. ⚠️Withdrawals: contract was unpaused 7. 🚨 Withdrawals: contract was paused 3. GateSeal diff --git a/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts b/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts index 6d91d6f7..5c0dffd7 100644 --- a/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts +++ b/ethereum-steth-v2/src/services/withdrawals/Withdrawals.srv.ts @@ -728,7 +728,7 @@ export class WithdrawalsSrv { if (claimedAmount.gt(currAmount)) { out.push( Finding.fromObject({ - name: `πŸ€” Withdrawals: claimed amount is more than requested`, + name: `⚠️Withdrawals: claimed amount is more than requested`, description: `Request ID: ${etherscanNft(this.wdQueueContract.address, reqId)}\nClaimed: ${claimedAmount .div(ETH_DECIMALS) .toFixed(2)} ETH\nRequested: ${(curr.amount as BigNumber) From 8f4311b33dbf4658b55240f849a783dbefb0c4b6 Mon Sep 17 00:00:00 2001 From: Sergey White Date: Fri, 26 Jan 2024 13:03:17 +0300 Subject: [PATCH 14/22] feat: ethereum-steth-v2 fix cs --- ethereum-steth-v2/src/agent.ts | 9 ++-- ethereum-steth-v2/src/app.ts | 4 +- ethereum-steth-v2/src/clients/eth_provider.ts | 52 ++++++++++--------- ethereum-steth-v2/src/entity/gate_seal.ts | 2 +- .../src/services/gate-seal/GateSeal.srv.ts | 4 +- .../steth_operation/StethOperation.spec.ts | 6 ++- .../steth_operation/StethOperation.srv.ts | 4 +- ethereum-steth-v2/src/utils/constants.ts | 4 +- 8 files changed, 45 insertions(+), 40 deletions(-) diff --git a/ethereum-steth-v2/src/agent.ts b/ethereum-steth-v2/src/agent.ts index f5bf3612..d76d22fe 100644 --- a/ethereum-steth-v2/src/agent.ts +++ b/ethereum-steth-v2/src/agent.ts @@ -110,15 +110,15 @@ export function initialize(): Initialize { } } -let isHandleBLockRunning: boolean = false +let isHandleBlockRunning: boolean = false export const handleBlock = (): HandleBlock => { return async function (blockEvent: BlockEvent): Promise { const startTime = new Date().getTime() - if (isHandleBLockRunning) { + if (isHandleBlockRunning) { return [] } - isHandleBLockRunning = true + isHandleBlockRunning = true const app = await App.getInstance() const out: Finding[] = [] @@ -134,10 +134,11 @@ export const handleBlock = (): HandleBlock => { app.GateSealSrv.handleBlock(blockEvent), app.VaultSrv.handleBlock(blockEvent), ]) + out.push(...bufferedEthFindings, ...withdrawalsFindings, ...gateSealFindings, ...vaultFindings) console.log(elapsedTime('handleBlock', startTime) + '\n') - isHandleBLockRunning = false + isHandleBlockRunning = false return out } } diff --git a/ethereum-steth-v2/src/app.ts b/ethereum-steth-v2/src/app.ts index abc449d5..42e69d97 100644 --- a/ethereum-steth-v2/src/app.ts +++ b/ethereum-steth-v2/src/app.ts @@ -43,7 +43,7 @@ export class App { try { token = await fetchJwt({}) } catch (e) { - return E.left(new Error(`Could not fetch jwt. cause ${e}`)) + return E.left(new Error(`Could not fetch jwt. Cause ${e}`)) } if (process.env.NODE_ENV === 'production') { @@ -75,7 +75,7 @@ export class App { const gateSealContact = GateSeal__factory.connect(address.GATE_SEAL_DEFAULT_ADDRESS, ethersProvider) const exitBusOracleContract = ValidatorsExitBusOracle__factory.connect( - address.EXITBUS_ORACLE_ADDRESS, + address.EXIT_BUS_ORACLE_ADDRESS, ethersProvider, ) const ethClient = new ETHProvider( diff --git a/ethereum-steth-v2/src/clients/eth_provider.ts b/ethereum-steth-v2/src/clients/eth_provider.ts index ec7f2968..8fd32253 100644 --- a/ethereum-steth-v2/src/clients/eth_provider.ts +++ b/ethereum-steth-v2/src/clients/eth_provider.ts @@ -18,6 +18,9 @@ import { ETHDistributedEvent } from '../generated/Lido' import { DataRW } from '../utils/mutex' import { IEtherscanProvider } from './contracts' +const DELAY_IN_500MS = 500 +const ATTEMPTS_5 = 5 + export abstract class IETHProvider { public abstract getTransaction(txHash: string): Promise> @@ -109,7 +112,7 @@ export class ETHProvider implements IETHProvider { try { latestBlockNumber = await this.jsonRpcProvider.getBlockNumber() } catch (e) { - return E.left(new Error(`Could not fetch latest block number. cause: ${e}`)) + return E.left(new Error(`Could not fetch latest block number. Cause: ${e}`)) } } @@ -132,12 +135,12 @@ export class ETHProvider implements IETHProvider { return tx }, - { delay: 500, maxTry: 5 }, + { delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 }, ) return E.right(out) } catch (e) { - return E.left(new Error(`Could not fetch transaction. cause: ${e}`)) + return E.left(new Error(`Could not fetch transaction. Cause: ${e}`)) } } @@ -152,7 +155,7 @@ export class ETHProvider implements IETHProvider { async (): Promise => { return await this.etherscanProvider.getHistory(depositSecurityAddress, start, end) }, - { delay: 500, maxTry: 5 }, + { delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 }, ) } catch (e) { throw new Error(`Could not fetch transaction history between ${start} and ${end} blocks. Cause: ${e}`) @@ -161,7 +164,6 @@ export class ETHProvider implements IETHProvider { const batchPromises: Promise[] = [] const out = new DataRW([]) - // const out: TransactionResponse[] = [] const batchSize = 10_000 for (let i = startBlock; i <= endBlock; i += batchSize) { @@ -180,7 +182,7 @@ export class ETHProvider implements IETHProvider { return E.right(await out.read()) } catch (error) { - return E.left(new Error(`Could not fetch transaction history for last 3 days. Cause ${error}`)) + return E.left(new Error(`Could not fetch transaction history. Cause ${error}`)) } } @@ -190,12 +192,12 @@ export class ETHProvider implements IETHProvider { async (): Promise => { return await this.etherscanProvider.getBalance(lidoStethAddress, block) }, - { delay: 500, maxTry: 5 }, + { delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 }, ) return E.right(new BigNumber(String(out))) } catch (e) { - return E.left(new Error(`Could not fetch Steth balance. cause: ${e}`)) + return E.left(new Error(`Could not fetch Steth balance. Cause: ${e}`)) } } @@ -205,12 +207,12 @@ export class ETHProvider implements IETHProvider { async (): Promise => { return await this.jsonRpcProvider.getBalance(address, block) }, - { delay: 500, maxTry: 5 }, + { delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 }, ) return E.right(new BigNumber(String(out))) } catch (e) { - return E.left(new Error(`Could not fetch balance by address ${address}. cause: ${e}`)) + return E.left(new Error(`Could not fetch balance by address ${address}. Cause: ${e}`)) } } @@ -222,12 +224,12 @@ export class ETHProvider implements IETHProvider { blockHash: blockHash, } as never) }, - { delay: 500, maxTry: 5 }, + { delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 }, ) return E.right(new BigNumber(String(out))) } catch (e) { - return E.left(new Error(`Could not fetch balance by address ${address} and blockHash ${blockHash}. cause: ${e}`)) + return E.left(new Error(`Could not fetch balance by address ${address} and blockHash ${blockHash}. Cause: ${e}`)) } } @@ -245,12 +247,12 @@ export class ETHProvider implements IETHProvider { isStakingPaused: resp.isStakingPaused, } }, - { delay: 500, maxTry: 5 }, + { delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 }, ) return E.right(out) } catch (e) { - return E.left(new Error(`Could not call "lidoContract.getStakeLimitFullInfo. Cause ${e}`)) + return E.left(new Error(`Could not call "lidoContract.getStakeLimitFullInfo". Cause ${e}`)) } } @@ -264,7 +266,7 @@ export class ETHProvider implements IETHProvider { return new BigNumber(String(out)) }, - { delay: 500, maxTry: 5 }, + { delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 }, ) return E.right(out) @@ -289,7 +291,7 @@ export class ETHProvider implements IETHProvider { return resp.statuses }, - { delay: 500, maxTry: 5 }, + { delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 }, ) } catch (e) { throw new Error( @@ -330,7 +332,7 @@ export class ETHProvider implements IETHProvider { blockTag: blockNumber, }) }, - { delay: 500, maxTry: 5 }, + { delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 }, ) return E.right(new BigNumber(resp.toString())) @@ -365,7 +367,7 @@ export class ETHProvider implements IETHProvider { const out: GateSeal = { roleForWithdrawalQueue: isGateSealHasPauseRole.right, roleForExitBus: isGateSealHasExitBusPauseRoleMember.right, - exitbusOracleAddress: this.exitBusContract.address, + exitBusOracleAddress: this.exitBusContract.address, withdrawalQueueAddress: this.wdQueueContract.address, } @@ -382,7 +384,7 @@ export class ETHProvider implements IETHProvider { return new BigNumber(String(resp)) }, - { delay: 500, maxTry: 5 }, + { delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 }, ) return E.right(expiryTimestamp) @@ -410,7 +412,7 @@ export class ETHProvider implements IETHProvider { return resp }, - { delay: 500, maxTry: 5 }, + { delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 }, ) return E.right(report) @@ -431,7 +433,7 @@ export class ETHProvider implements IETHProvider { return resp }, - { delay: 500, maxTry: 5 }, + { delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 }, ) return E.right(isExpired) @@ -455,7 +457,7 @@ export class ETHProvider implements IETHProvider { return resp }, - { delay: 500, maxTry: 5 }, + { delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 }, ) return E.right(queuePauseRoleMember) @@ -479,7 +481,7 @@ export class ETHProvider implements IETHProvider { return resp }, - { delay: 500, maxTry: 5 }, + { delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 }, ) return E.right(exitBusPauseRoleMember) @@ -496,7 +498,7 @@ export class ETHProvider implements IETHProvider { blockTag: blockNumber, }) }, - { delay: 500, maxTry: 5 }, + { delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 }, ) return E.right(new BigNumber(out.toString())) @@ -513,7 +515,7 @@ export class ETHProvider implements IETHProvider { blockTag: blockNumber, }) }, - { delay: 500, maxTry: 5 }, + { delay: DELAY_IN_500MS, maxTry: ATTEMPTS_5 }, ) return E.right(new BigNumber(out.toString())) diff --git a/ethereum-steth-v2/src/entity/gate_seal.ts b/ethereum-steth-v2/src/entity/gate_seal.ts index 98270f87..fbb71526 100644 --- a/ethereum-steth-v2/src/entity/gate_seal.ts +++ b/ethereum-steth-v2/src/entity/gate_seal.ts @@ -1,7 +1,7 @@ export type GateSeal = { roleForWithdrawalQueue: boolean roleForExitBus: boolean - exitbusOracleAddress: string + exitBusOracleAddress: string withdrawalQueueAddress: string } diff --git a/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts b/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts index 1360ce83..c18caaf4 100644 --- a/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts +++ b/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts @@ -72,7 +72,7 @@ export class GateSealSrv { if (!status.right.roleForExitBus || !status.right.roleForWithdrawalQueue) { let additionalDesc = '' if (!status.right.roleForExitBus) { - additionalDesc += `\nNo PAUSE_ROLE for ExitBus address: ${etherscanAddress(status.right.exitbusOracleAddress)}` + additionalDesc += `\nNo PAUSE_ROLE for ExitBus address: ${etherscanAddress(status.right.exitBusOracleAddress)}` } if (!status.right.roleForWithdrawalQueue) { additionalDesc += `\nNo PAUSE_ROLE for WithdrawalQueue address: ${etherscanAddress( @@ -156,7 +156,7 @@ export class GateSealSrv { if (!status.right.roleForExitBus || !status.right.roleForWithdrawalQueue) { let additionalDesc = '' if (!status.right.roleForExitBus) { - additionalDesc += `\nNo PAUSE_ROLE for ExitBus address: ${etherscanAddress(status.right.exitbusOracleAddress)}` + additionalDesc += `\nNo PAUSE_ROLE for ExitBus address: ${etherscanAddress(status.right.exitBusOracleAddress)}` } if (!status.right.roleForWithdrawalQueue) { additionalDesc += `\nNo PAUSE_ROLE for WithdrawalQueue address: ${etherscanAddress( diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts index 69bb32e2..3a36b5f4 100644 --- a/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts @@ -74,7 +74,9 @@ describe('StethOperationSrv', () => { const currentBlock = 19061449 const result = await srv.initialize(currentBlock) - expect(result).toStrictEqual(want) + expect(result).toStrictEqual( + new Error('Could not fetch transaction history for last 3 days. Cause getHistory error'), + ) }) test(`ethProvider.getStethBalance error`, async () => { @@ -750,7 +752,7 @@ describe('StethOperationSrv', () => { const expected = Finding.fromObject({ alertId: 'LIDO-AGENT-ERROR', - description: `Could not call "lidoContract.getStakeLimitFullInfo. Cause getStakingLimitInfoErr`, + description: `Could not call "lidoContract.getStakeLimitFullInfo". Cause getStakingLimitInfoErr`, name: 'Error in StethOperationSrv.handleStakingLimit:418', severity: FindingSeverity.Low, type: FindingType.Degraded, diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts index a904b4a1..28eaa8f2 100644 --- a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts @@ -83,7 +83,7 @@ export class StethOperationSrv { ) if (E.isLeft(history)) { this.logger.info(elapsedTime(`[${this.name}.initialize]`, start)) - return history.left + return new Error(`Could not fetch transaction history for last 3 days. Cause ${history.left.message}`) } const depositorTxTimestamps: number[] = [] @@ -489,7 +489,7 @@ export class StethOperationSrv { if (E.isLeft(stakingLimitInfo)) { const f: Finding = Finding.fromObject({ name: `Error in ${StethOperationSrv.name}.${this.handleStakingLimit.name}:418`, - description: `Could not call "lidoContract.getStakeLimitFullInfo. Cause ${stakingLimitInfo.left.message}`, + description: `Could not call "lidoContract.getStakeLimitFullInfo". Cause ${stakingLimitInfo.left.message}`, alertId: 'LIDO-AGENT-ERROR', severity: FindingSeverity.Low, type: FindingType.Degraded, diff --git a/ethereum-steth-v2/src/utils/constants.ts b/ethereum-steth-v2/src/utils/constants.ts index 6982b2a8..f17d6009 100644 --- a/ethereum-steth-v2/src/utils/constants.ts +++ b/ethereum-steth-v2/src/utils/constants.ts @@ -18,7 +18,7 @@ export type Address = { USDT_ADDRESS: string USDC_ADDRESS: string GATE_SEAL_DEFAULT_ADDRESS: string - EXITBUS_ORACLE_ADDRESS: string + EXIT_BUS_ORACLE_ADDRESS: string GATE_SEAL_FACTORY_ADDRESS: string WITHDRAWALS_VAULT_ADDRESS: string EL_REWARDS_VAULT_ADDRESS: string @@ -66,7 +66,7 @@ export const Address: Address = { USDT_ADDRESS: USDT_ADDRESS, USDC_ADDRESS: USDC_ADDRESS, GATE_SEAL_DEFAULT_ADDRESS: GATE_SEAL_DEFAULT_ADDRESS, - EXITBUS_ORACLE_ADDRESS: EXITBUS_ORACLE_ADDRESS, + EXIT_BUS_ORACLE_ADDRESS: EXITBUS_ORACLE_ADDRESS, GATE_SEAL_FACTORY_ADDRESS: GATE_SEAL_FACTORY_ADDRESS, WITHDRAWALS_VAULT_ADDRESS: WITHDRAWALS_VAULT_ADDRESS, EL_REWARDS_VAULT_ADDRESS: EL_REWARDS_VAULT_ADDRESS, From e572e3724b23bef96d703147f2b7192537df3505 Mon Sep 17 00:00:00 2001 From: Sergey White Date: Fri, 26 Jan 2024 13:04:42 +0300 Subject: [PATCH 15/22] feat: ethereum-steth-v2 fix cs --- .../src/services/steth_operation/StethOperation.srv.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts index 28eaa8f2..7f961153 100644 --- a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts @@ -151,8 +151,8 @@ export class StethOperationSrv { const shareRateFromReport = this.cache.getShareRate() if (!shareRate.right.eq(shareRateFromReport.amount)) { - const percentsFromReporedShareRate = shareRate.right.div(shareRateFromReport.amount).multipliedBy(100) - const deviation = 100.0 - percentsFromReporedShareRate.toNumber() + const percentsFromReportedShareRate = shareRate.right.div(shareRateFromReport.amount).multipliedBy(100) + const deviation = 100.0 - percentsFromReportedShareRate.toNumber() // deviation by 0.0001 (1 bp or 0.01%) if (Math.abs(deviation) >= 0.01) { From 61cc10e0bbe96b121daba07a195ffded7abea558 Mon Sep 17 00:00:00 2001 From: Sergey White Date: Fri, 26 Jan 2024 13:29:51 +0300 Subject: [PATCH 16/22] feat: ethereum-steth-v2 moved mocks --- ethereum-steth-v2/package.json | 6 +- .../contract_mocks/log_description.mock.ts | 19 --- .../src/utils/events/burner_events.ts | 28 +-- .../utils/events/deposit_security_events.ts | 29 +--- .../src/utils/events/insurance_fund_events.ts | 42 +---- .../src/utils/events/lido_events.ts | 39 +---- .../src/utils/events/mocks/events.mock.ts | 159 ++++++++++++++++++ .../src/utils/events/withdrawals_events.ts | 22 +-- ethereum-steth-v2/tsconfig.json | 10 +- ethereum-steth-v2/yarn.lock | 104 ++++++------ 10 files changed, 228 insertions(+), 230 deletions(-) delete mode 100644 ethereum-steth-v2/src/utils/contract_mocks/log_description.mock.ts create mode 100644 ethereum-steth-v2/src/utils/events/mocks/events.mock.ts diff --git a/ethereum-steth-v2/package.json b/ethereum-steth-v2/package.json index 37030cdd..8b2746ec 100644 --- a/ethereum-steth-v2/package.json +++ b/ethereum-steth-v2/package.json @@ -55,11 +55,11 @@ "fp-ts": "^2.16.1", "lodash": "^4.17.21", "ts-retry": "^4.2.4", - "winston": "^3.11.0" + "winston": "^3.11.0", + "@ethersproject/abi": "^5.0.0", + "@ethersproject/providers": "^5.0.0" }, "devDependencies": { - "@ethersproject/abi": "^5.0.0", - "@ethersproject/providers": "^5.0.0", "@faker-js/faker": "^8.3.1", "@jest/globals": "^29.7.0", "@tsconfig/node20": "^20.1.2", diff --git a/ethereum-steth-v2/src/utils/contract_mocks/log_description.mock.ts b/ethereum-steth-v2/src/utils/contract_mocks/log_description.mock.ts deleted file mode 100644 index cc4e05db..00000000 --- a/ethereum-steth-v2/src/utils/contract_mocks/log_description.mock.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { LogDescription } from 'forta-agent' -import { faker } from '@faker-js/faker' -import { Result } from '@ethersproject/abi/lib' - -export function createLogDescriptionMock(args?: Result): jest.Mocked { - return { - address: faker.finance.ethereumAddress(), - // eslint-disable-next-line - // @ts-expect-error - args: args ?? {}, - // eslint-disable-next-line - // @ts-expect-error - eventFragment: jest.fn(), - logIndex: faker.number.int(), - name: faker.string.uuid(), - signature: faker.finance.ethereumAddress(), - topic: faker.finance.ethereumAddress(), - } -} diff --git a/ethereum-steth-v2/src/utils/events/burner_events.ts b/ethereum-steth-v2/src/utils/events/burner_events.ts index a5482fbc..7a508224 100644 --- a/ethereum-steth-v2/src/utils/events/burner_events.ts +++ b/ethereum-steth-v2/src/utils/events/burner_events.ts @@ -1,9 +1,7 @@ import { EventOfNotice } from '../../entity/events' -import { FindingSeverity, FindingType, LogDescription } from 'forta-agent' +import { FindingSeverity, FindingType } from 'forta-agent' import { Result } from '@ethersproject/abi/lib' import { etherscanAddress } from '../string' -import { faker } from '@faker-js/faker' -import { createLogDescriptionMock } from '../contract_mocks/log_description.mock' export function getBurnerEvents(BURNER_ADDRESS: string): EventOfNotice[] { return [ @@ -35,27 +33,3 @@ export function getBurnerEvents(BURNER_ADDRESS: string): EventOfNotice[] { }, ] } - -export function getFilteredBurnerEventsMock(): LogDescription[] { - const descriptions = [ - { - ['requestedBy']: faker.finance.ethereumAddress(), - ['token']: faker.finance.ethereumAddress(), - ['amount']: faker.number.int(), - }, - { - ['requestedBy']: faker.finance.ethereumAddress(), - ['token']: faker.finance.ethereumAddress(), - ['tokenId']: faker.number.int(), - }, - ] - - const out: LogDescription[] = [] - for (const desc of descriptions) { - // eslint-disable-next-line - // @ts-expect-error - out.push(createLogDescriptionMock(desc)) - } - - return out -} diff --git a/ethereum-steth-v2/src/utils/events/deposit_security_events.ts b/ethereum-steth-v2/src/utils/events/deposit_security_events.ts index f9d25b47..3bed6c26 100644 --- a/ethereum-steth-v2/src/utils/events/deposit_security_events.ts +++ b/ethereum-steth-v2/src/utils/events/deposit_security_events.ts @@ -1,9 +1,7 @@ import { EventOfNotice } from '../../entity/events' -import { FindingSeverity, FindingType, LogDescription } from 'forta-agent' +import { FindingSeverity, FindingType } from 'forta-agent' import { Result } from '@ethersproject/abi/lib' import { etherscanAddress } from '../string' -import { faker } from '@faker-js/faker' -import { createLogDescriptionMock } from '../contract_mocks/log_description.mock' export function getDepositSecurityEvents(DEPOSIT_SECURITY_ADDRESS: string): EventOfNotice[] { return [ @@ -82,28 +80,3 @@ export function getDepositSecurityEvents(DEPOSIT_SECURITY_ADDRESS: string): Even }, ] } - -export function getFilteredDepositSecurityEventsMock(): LogDescription[] { - const descriptions = [ - { - ['guardian']: faker.finance.ethereumAddress(), - ['stakingModuleId']: 1, - }, - { ['stakingModuleId']: 1 }, - { ['guardian']: faker.finance.ethereumAddress() }, - { ['newValue']: faker.number.int() }, - { ['newValue']: faker.number.int() }, - { ['newValue']: faker.number.int() }, - { ['newValue']: faker.number.int() }, - { ['newValue']: faker.number.int() }, - ] - - const out: LogDescription[] = [] - for (const desc of descriptions) { - // eslint-disable-next-line - // @ts-expect-error - out.push(createLogDescriptionMock(desc)) - } - - return out -} diff --git a/ethereum-steth-v2/src/utils/events/insurance_fund_events.ts b/ethereum-steth-v2/src/utils/events/insurance_fund_events.ts index 6fdc4b9f..6dd750f6 100644 --- a/ethereum-steth-v2/src/utils/events/insurance_fund_events.ts +++ b/ethereum-steth-v2/src/utils/events/insurance_fund_events.ts @@ -1,11 +1,9 @@ import { EventOfNotice } from '../../entity/events' -import { Address, ERC20, ETH_DECIMALS } from '../constants' +import { ERC20, ETH_DECIMALS } from '../constants' import BigNumber from 'bignumber.js' -import { FindingSeverity, FindingType, LogDescription } from 'forta-agent' +import { FindingSeverity, FindingType } from 'forta-agent' import { Result } from '@ethersproject/abi/lib' import { etherscanAddress } from '../string' -import { faker } from '@faker-js/faker' -import { createLogDescriptionMock } from '../contract_mocks/log_description.mock' export function getInsuranceFundEvents( INSURANCE_FUND_ADDRESS: string, @@ -81,39 +79,3 @@ export function getInsuranceFundEvents( }, ] } - -export function getFilteredInsuranceFundEventsMock(): LogDescription[] { - const descriptions = [ - { - ['_amount']: Address.DAI_ADDRESS, - ['_recipient']: faker.finance.ethereumAddress(), - }, - { - ['_token']: Address.DAI_ADDRESS, - ['_recipient']: faker.finance.ethereumAddress(), - }, - { - ['_token']: Address.DAI_ADDRESS, - ['_amount']: faker.number.bigInt(), - ['_recipient']: faker.finance.ethereumAddress(), - }, - { - ['_token']: Address.DAI_ADDRESS, - ['_amount']: faker.number.bigInt(), - ['_recipient']: faker.finance.ethereumAddress(), - }, - { - ['previousOwner']: faker.finance.ethereumAddress(), - ['newOwner']: faker.finance.ethereumAddress(), - }, - ] - - const out: LogDescription[] = [] - for (const desc of descriptions) { - // eslint-disable-next-line - // @ts-expect-error - out.push(createLogDescriptionMock(desc)) - } - - return out -} diff --git a/ethereum-steth-v2/src/utils/events/lido_events.ts b/ethereum-steth-v2/src/utils/events/lido_events.ts index 99e22e2b..f9063261 100644 --- a/ethereum-steth-v2/src/utils/events/lido_events.ts +++ b/ethereum-steth-v2/src/utils/events/lido_events.ts @@ -1,9 +1,7 @@ -import { FindingSeverity, FindingType, LogDescription } from 'forta-agent' +import { FindingSeverity, FindingType } from 'forta-agent' import { EventOfNotice } from '../../entity/events' import { Result } from '@ethersproject/abi/lib' import { etherscanAddress } from '../string' -import { faker } from '@faker-js/faker' -import { createLogDescriptionMock } from '../contract_mocks/log_description.mock' export const alertId_token_rebased = 'LIDO-TOKEN-REBASED' @@ -109,38 +107,3 @@ export function getLidoEvents(LIDO_STETH_ADDRESS: string): EventOfNotice[] { }, ] } -export function getFilteredLidoEventsMock(): LogDescription[] { - const descriptions = [ - {}, - {}, - {}, - {}, - { - ['maxStakeLimit']: faker.number.int(), - ['stakeLimitIncreasePerBlock']: faker.number.int(), - }, - {}, - { - ['lidoLocator']: faker.finance.ethereumAddress(), - }, - { - ['vault']: faker.finance.ethereumAddress(), - ['token']: faker.finance.ethereumAddress(), - }, - { - ['version']: faker.system.semver(), - }, - { - ['amount']: faker.number.int(), - }, - ] - - const out: LogDescription[] = [] - for (const desc of descriptions) { - // eslint-disable-next-line - // @ts-expect-error - out.push(createLogDescriptionMock(desc)) - } - - return out -} diff --git a/ethereum-steth-v2/src/utils/events/mocks/events.mock.ts b/ethereum-steth-v2/src/utils/events/mocks/events.mock.ts new file mode 100644 index 00000000..073c2d2c --- /dev/null +++ b/ethereum-steth-v2/src/utils/events/mocks/events.mock.ts @@ -0,0 +1,159 @@ +import { Result } from '@ethersproject/abi' +import { LogDescription } from 'forta-agent' +import { faker } from '@faker-js/faker' +import { Address } from '../../constants' + +export function createLogDescriptionMock(args?: Result): jest.Mocked { + return { + address: faker.finance.ethereumAddress(), + // eslint-disable-next-line + // @ts-expect-error + args: args ?? {}, + // eslint-disable-next-line + // @ts-expect-error + eventFragment: jest.fn(), + logIndex: faker.number.int(), + name: faker.string.uuid(), + signature: faker.finance.ethereumAddress(), + topic: faker.finance.ethereumAddress(), + } +} + +export function getFilteredBurnerEventsMock(): LogDescription[] { + const descriptions = [ + { + ['requestedBy']: faker.finance.ethereumAddress(), + ['token']: faker.finance.ethereumAddress(), + ['amount']: faker.number.int(), + }, + { + ['requestedBy']: faker.finance.ethereumAddress(), + ['token']: faker.finance.ethereumAddress(), + ['tokenId']: faker.number.int(), + }, + ] + + const out: LogDescription[] = [] + for (const desc of descriptions) { + // eslint-disable-next-line + // @ts-expect-error + out.push(createLogDescriptionMock(desc)) + } + + return out +} + +export function getFilteredDepositSecurityEventsMock(): LogDescription[] { + const descriptions = [ + { + ['guardian']: faker.finance.ethereumAddress(), + ['stakingModuleId']: 1, + }, + { ['stakingModuleId']: 1 }, + { ['guardian']: faker.finance.ethereumAddress() }, + { ['newValue']: faker.number.int() }, + { ['newValue']: faker.number.int() }, + { ['newValue']: faker.number.int() }, + { ['newValue']: faker.number.int() }, + { ['newValue']: faker.number.int() }, + ] + + const out: LogDescription[] = [] + for (const desc of descriptions) { + // eslint-disable-next-line + // @ts-expect-error + out.push(createLogDescriptionMock(desc)) + } + + return out +} + +export function getFilteredInsuranceFundEventsMock(): LogDescription[] { + const descriptions = [ + { + ['_amount']: Address.DAI_ADDRESS, + ['_recipient']: faker.finance.ethereumAddress(), + }, + { + ['_token']: Address.DAI_ADDRESS, + ['_recipient']: faker.finance.ethereumAddress(), + }, + { + ['_token']: Address.DAI_ADDRESS, + ['_amount']: faker.number.bigInt(), + ['_recipient']: faker.finance.ethereumAddress(), + }, + { + ['_token']: Address.DAI_ADDRESS, + ['_amount']: faker.number.bigInt(), + ['_recipient']: faker.finance.ethereumAddress(), + }, + { + ['previousOwner']: faker.finance.ethereumAddress(), + ['newOwner']: faker.finance.ethereumAddress(), + }, + ] + + const out: LogDescription[] = [] + for (const desc of descriptions) { + // eslint-disable-next-line + // @ts-expect-error + out.push(createLogDescriptionMock(desc)) + } + + return out +} + +export function getFilteredLidoEventsMock(): LogDescription[] { + const descriptions = [ + {}, + {}, + {}, + {}, + { + ['maxStakeLimit']: faker.number.int(), + ['stakeLimitIncreasePerBlock']: faker.number.int(), + }, + {}, + { + ['lidoLocator']: faker.finance.ethereumAddress(), + }, + { + ['vault']: faker.finance.ethereumAddress(), + ['token']: faker.finance.ethereumAddress(), + }, + { + ['version']: faker.system.semver(), + }, + { + ['amount']: faker.number.int(), + }, + ] + + const out: LogDescription[] = [] + for (const desc of descriptions) { + // eslint-disable-next-line + // @ts-expect-error + out.push(createLogDescriptionMock(desc)) + } + + return out +} + +export function getFilteredWithdrawalsEventsMock(): LogDescription[] { + const descriptions = [ + {}, + { + ['duration']: faker.number.int(), + }, + ] + + const out: LogDescription[] = [] + for (const desc of descriptions) { + // eslint-disable-next-line + // @ts-expect-error + out.push(createLogDescriptionMock(desc)) + } + + return out +} diff --git a/ethereum-steth-v2/src/utils/events/withdrawals_events.ts b/ethereum-steth-v2/src/utils/events/withdrawals_events.ts index 8d1512da..dcf29bb0 100644 --- a/ethereum-steth-v2/src/utils/events/withdrawals_events.ts +++ b/ethereum-steth-v2/src/utils/events/withdrawals_events.ts @@ -1,9 +1,7 @@ -import { FindingSeverity, FindingType, LogDescription } from 'forta-agent' +import { FindingSeverity, FindingType } from 'forta-agent' import BigNumber from 'bignumber.js' import { Result } from '@ethersproject/abi/lib' import { EventOfNotice } from '../../entity/events' -import { faker } from '@faker-js/faker' -import { createLogDescriptionMock } from '../contract_mocks/log_description.mock' export const WITHDRAWALS_BUNKER_MODE_ENABLED_EVENT = 'event BunkerModeEnabled(uint256 _sinceTimestamp)' @@ -43,21 +41,3 @@ export function getWithdrawalsEvents(WITHDRAWAL_QUEUE_ADDRESS: string): EventOfN }, ] } - -export function getFilteredWithdrawalsEventsMock(): LogDescription[] { - const descriptions = [ - {}, - { - ['duration']: faker.number.int(), - }, - ] - - const out: LogDescription[] = [] - for (const desc of descriptions) { - // eslint-disable-next-line - // @ts-expect-error - out.push(createLogDescriptionMock(desc)) - } - - return out -} diff --git a/ethereum-steth-v2/tsconfig.json b/ethereum-steth-v2/tsconfig.json index d65bbd37..1fd0c82a 100644 --- a/ethereum-steth-v2/tsconfig.json +++ b/ethereum-steth-v2/tsconfig.json @@ -6,7 +6,13 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "noImplicitAny": true, - "resolveJsonModule": true, + "resolveJsonModule": true }, - "exclude": ["node_modules", "tests", "dist", "**/*spec.ts", "**/*mock.ts"], + "exclude": [ + "node_modules", + "tests", + "dist", + "**/*spec.ts", + "**/*mock.ts" + ] } diff --git a/ethereum-steth-v2/yarn.lock b/ethereum-steth-v2/yarn.lock index 823d3468..36f74f95 100644 --- a/ethereum-steth-v2/yarn.lock +++ b/ethereum-steth-v2/yarn.lock @@ -15,7 +15,7 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.23.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== @@ -29,20 +29,20 @@ integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== "@babel/core@^7.11.6", "@babel/core@^7.12.3": - version "7.23.7" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.7.tgz#4d8016e06a14b5f92530a13ed0561730b5c6483f" - integrity sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw== + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.9.tgz#b028820718000f267870822fec434820e9b1e4d1" + integrity sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.23.5" "@babel/generator" "^7.23.6" "@babel/helper-compilation-targets" "^7.23.6" "@babel/helper-module-transforms" "^7.23.3" - "@babel/helpers" "^7.23.7" - "@babel/parser" "^7.23.6" - "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.7" - "@babel/types" "^7.23.6" + "@babel/helpers" "^7.23.9" + "@babel/parser" "^7.23.9" + "@babel/template" "^7.23.9" + "@babel/traverse" "^7.23.9" + "@babel/types" "^7.23.9" convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -142,14 +142,14 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== -"@babel/helpers@^7.23.7": - version "7.23.8" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.8.tgz#fc6b2d65b16847fd50adddbd4232c76378959e34" - integrity sha512-KDqYz4PiOWvDFrdHLPhKtCThtIcKVy6avWD2oG4GEvyQ+XDZwHD4YQd+H2vNMnq2rkdxsDkU82T+Vk8U/WXHRQ== +"@babel/helpers@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.9.tgz#c3e20bbe7f7a7e10cb9b178384b4affdf5995c7d" + integrity sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ== dependencies: - "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.7" - "@babel/types" "^7.23.6" + "@babel/template" "^7.23.9" + "@babel/traverse" "^7.23.9" + "@babel/types" "^7.23.9" "@babel/highlight@^7.23.4": version "7.23.4" @@ -160,10 +160,10 @@ chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.23.6": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b" - integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.9.tgz#7b903b6149b0f8fa7ad564af646c4c38a77fc44b" + integrity sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -263,19 +263,19 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/template@^7.22.15", "@babel/template@^7.3.3": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" - integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== +"@babel/template@^7.22.15", "@babel/template@^7.23.9", "@babel/template@^7.3.3": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.23.9.tgz#f881d0487cba2828d3259dcb9ef5005a9731011a" + integrity sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA== dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/parser" "^7.22.15" - "@babel/types" "^7.22.15" + "@babel/code-frame" "^7.23.5" + "@babel/parser" "^7.23.9" + "@babel/types" "^7.23.9" -"@babel/traverse@^7.23.7": - version "7.23.7" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305" - integrity sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg== +"@babel/traverse@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.9.tgz#2f9d6aead6b564669394c5ce0f9302bb65b9d950" + integrity sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg== dependencies: "@babel/code-frame" "^7.23.5" "@babel/generator" "^7.23.6" @@ -283,15 +283,15 @@ "@babel/helper-function-name" "^7.23.0" "@babel/helper-hoist-variables" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.23.6" - "@babel/types" "^7.23.6" + "@babel/parser" "^7.23.9" + "@babel/types" "^7.23.9" debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.6", "@babel/types@^7.3.3": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.6.tgz#be33fdb151e1f5a56877d704492c240fc71c7ccd" - integrity sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg== +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.6", "@babel/types@^7.23.9", "@babel/types@^7.3.3": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.9.tgz#1dd7b59a9a2b5c87f8b41e52770b5ecbf492e002" + integrity sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q== dependencies: "@babel/helper-string-parser" "^7.23.4" "@babel/helper-validator-identifier" "^7.22.20" @@ -696,9 +696,9 @@ "@ethersproject/strings" "^5.7.0" "@faker-js/faker@^8.3.1": - version "8.3.1" - resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-8.3.1.tgz#7753df0cb88d7649becf984a96dd1bd0a26f43e3" - integrity sha512-FdgpFxY6V6rLZE9mmIBb9hM0xpfvQOSNOLnzolzKwsE1DH+gC7lEKV1p1IbR0lAYyvYd5a4u3qWJzowUkw1bIw== + version "8.4.0" + resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-8.4.0.tgz#dc09924ee5fa6438eaaa49d0a8820c64d2f8518f" + integrity sha512-htW87352wzUCdX1jyUQocUcmAaFqcR/w082EC8iP/gtkF0K+aKcBp0hR5Arb7dzR8tQ1TrhE9DNa5EbJELm84w== "@grpc/grpc-js@^1.3.6": version "1.9.14" @@ -1189,9 +1189,9 @@ "@types/node" "*" "@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0": - version "20.11.6" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.6.tgz#6adf4241460e28be53836529c033a41985f85b6e" - integrity sha512-+EOokTnksGVgip2PbYbr3xnR7kZigh4LbybAfBAw5BpnQ+FqBYUsvCEjYd70IXKlbohQ64mzEYmMtlWUY8q//Q== + version "20.11.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.7.tgz#cb49aedd758c978c30806d0c38b520ed2a3df6e0" + integrity sha512-GPmeN1C3XAyV5uybAf4cMLWT9fDWcmQhZVtMFu7OR32WjrqGG+Wnk2V1d0bmtUyE/Zy1QJ9BxyiTih9z8Oks8A== dependencies: undici-types "~5.26.4" @@ -1531,9 +1531,9 @@ awilix@^4.3.4: glob "^7.1.6" axios@^1.6.2: - version "1.6.5" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.5.tgz#2c090da14aeeab3770ad30c3a1461bc970fb0cd8" - integrity sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg== + version "1.6.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7" + integrity sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA== dependencies: follow-redirects "^1.15.4" form-data "^4.0.0" @@ -1786,9 +1786,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001565: - version "1.0.30001579" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz#45c065216110f46d6274311a4b3fcf6278e0852a" - integrity sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA== + version "1.0.30001580" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001580.tgz#e3c76bc6fe020d9007647044278954ff8cd17d1e" + integrity sha512-mtj5ur2FFPZcCEpXFy8ADXbDACuNFXg6mxVDqp7tqooX6l3zwm+d8EPoeOSIFRDvHs8qu7/SLFOGniULkcH2iA== chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" @@ -2102,9 +2102,9 @@ doctrine@^3.0.0: esutils "^2.0.2" electron-to-chromium@^1.4.601: - version "1.4.643" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.643.tgz#081a20c5534db91e66ef094f68624960f674768f" - integrity sha512-QHscvvS7gt155PtoRC0dR2ilhL8E9LHhfTQEq1uD5AL0524rBLAwpAREFH06f87/e45B9XkR6Ki5dbhbCsVEIg== + version "1.4.647" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.647.tgz#3c8d4815e5ed2fbdd37f4ab7333cd9f8fc56d53a" + integrity sha512-Z/fTNGwc45WrYQhPaEcz5tAJuZZ8G7S/DBnhS6Kgp4BxnS40Z/HqlJ0hHg3Z79IGVzuVartIlTcjw/cQbPLgOw== elliptic@6.5.4, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.4: version "6.5.4" From a39e0a2dfc0bff77e360ba63c91cf8a6ff3ea499 Mon Sep 17 00:00:00 2001 From: Sergey White Date: Fri, 26 Jan 2024 13:33:46 +0300 Subject: [PATCH 17/22] feat: ethereum-steth-v2 fix tests --- .../steth_operation/StethOperation.spec.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts index 3a36b5f4..d5100cb7 100644 --- a/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts @@ -10,13 +10,10 @@ import { IETHProvider } from '../../clients/eth_provider' import { StethOperationCache } from './StethOperation.cache' import * as E from 'fp-ts/Either' import { Address, ETH_DECIMALS } from '../../utils/constants' -import { - getDepositSecurityEvents, - getFilteredDepositSecurityEventsMock, -} from '../../utils/events/deposit_security_events' -import { getFilteredLidoEventsMock, getLidoEvents } from '../../utils/events/lido_events' -import { getFilteredInsuranceFundEventsMock, getInsuranceFundEvents } from '../../utils/events/insurance_fund_events' -import { getBurnerEvents, getFilteredBurnerEventsMock } from '../../utils/events/burner_events' +import { getDepositSecurityEvents } from '../../utils/events/deposit_security_events' +import { getLidoEvents } from '../../utils/events/lido_events' +import { getInsuranceFundEvents } from '../../utils/events/insurance_fund_events' +import { getBurnerEvents } from '../../utils/events/burner_events' import { ETHProviderMock } from '../../clients/mocks/eth_provider.mock' import { LidoContractMock, @@ -34,6 +31,12 @@ import { Finding, FindingSeverity, FindingType, LogDescription } from 'forta-age import * as Winston from 'winston' import { TypedEvent } from '../../generated/common' import { StakingLimitInfo } from '../../entity/stakingLimitInfo' +import { + getFilteredBurnerEventsMock, + getFilteredDepositSecurityEventsMock, + getFilteredInsuranceFundEventsMock, + getFilteredLidoEventsMock, +} from '../../utils/events/mocks/events.mock' describe('StethOperationSrv', () => { let ethProviderMock: jest.Mocked From 28db48773d2ac386608e911b55b4fbb52241c67c Mon Sep 17 00:00:00 2001 From: Sergey White Date: Fri, 26 Jan 2024 13:37:06 +0300 Subject: [PATCH 18/22] feat: ethereum-steth-v2 remove unnecessary ts compiler configs --- ethereum-steth-v2/tsconfig.json | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ethereum-steth-v2/tsconfig.json b/ethereum-steth-v2/tsconfig.json index 1fd0c82a..9f1e16f5 100644 --- a/ethereum-steth-v2/tsconfig.json +++ b/ethereum-steth-v2/tsconfig.json @@ -1,12 +1,7 @@ { "extends": "@tsconfig/node20/tsconfig.json", "compilerOptions": { - "outDir": "dist", - "baseUrl": ".", - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "noImplicitAny": true, - "resolveJsonModule": true + "outDir": "dist" }, "exclude": [ "node_modules", From cfac870d441564f87679ff72063e1f2d49c223ef Mon Sep 17 00:00:00 2001 From: Sergey White Date: Fri, 26 Jan 2024 16:09:20 +0300 Subject: [PATCH 19/22] feat: ethereum-steth-v2 cs fixes --- ethereum-steth-v2/package.json | 8 ++++---- ethereum-steth-v2/src/agent.ts | 3 --- ethereum-steth-v2/src/clients/eth_provider.ts | 2 +- .../{stakingLimitInfo.ts => staking_limit_info.ts} | 0 .../steth_operation/StethOperation.spec.ts | 14 +++++++------- .../services/steth_operation/StethOperation.srv.ts | 7 ++++--- .../tests/e2e/agent-steth-ops.spec.ts | 2 +- ethereum-steth-v2/tests/e2e/utils.ts | 2 +- 8 files changed, 18 insertions(+), 20 deletions(-) rename ethereum-steth-v2/src/entity/{stakingLimitInfo.ts => staking_limit_info.ts} (100%) diff --git a/ethereum-steth-v2/package.json b/ethereum-steth-v2/package.json index 8b2746ec..3ea77c7a 100644 --- a/ethereum-steth-v2/package.json +++ b/ethereum-steth-v2/package.json @@ -38,10 +38,10 @@ "stake": "forta-agent stake", "test": "jest", "generate-types": "typechain --target=ethers-v5 --out-dir=./src/generated ./src/abi/*", - "eslint:lint": "eslint ./src", - "eslint:format": "eslint ./src --fix", - "prettier:check": "prettier --check .", - "prettier:format": "prettier --write .", + "eslint:lint": "eslint ./src ./tests", + "eslint:format": "eslint ./src ./tests --fix", + "prettier:check": "prettier --check ./src ./tests", + "prettier:format": "prettier --write ./src ./tests", "lint": "yarn run prettier:check && yarn run eslint:lint", "format": "yarn run eslint:format && yarn run prettier:format", "postinstall": "yarn generate-types" diff --git a/ethereum-steth-v2/src/agent.ts b/ethereum-steth-v2/src/agent.ts index d76d22fe..94c79095 100644 --- a/ethereum-steth-v2/src/agent.ts +++ b/ethereum-steth-v2/src/agent.ts @@ -129,7 +129,6 @@ export const handleBlock = (): HandleBlock => { const [bufferedEthFindings, withdrawalsFindings, gateSealFindings, vaultFindings] = await Promise.all([ app.StethOperationSrv.handleBlock(blockEvent), - app.StethOperationSrv.handleInvariants(blockEvent.blockNumber), app.WithdrawalsSrv.handleBlock(blockEvent), app.GateSealSrv.handleBlock(blockEvent), app.VaultSrv.handleBlock(blockEvent), @@ -146,7 +145,6 @@ export const handleBlock = (): HandleBlock => { let isHandleTransactionRunning: boolean = false export const handleTransaction = (): HandleTransaction => { return async function (txEvent: TransactionEvent): Promise { - const startTime = new Date().getTime() if (isHandleTransactionRunning) { return [] } @@ -161,7 +159,6 @@ export const handleTransaction = (): HandleTransaction => { out.push(...stethOperationFindings, ...withdrawalsFindings, ...gateSealFindings, ...vaultFindings) - console.log(elapsedTime('handleTransaction', startTime) + '\n') isHandleTransactionRunning = false return out } diff --git a/ethereum-steth-v2/src/clients/eth_provider.ts b/ethereum-steth-v2/src/clients/eth_provider.ts index 8fd32253..036c3f82 100644 --- a/ethereum-steth-v2/src/clients/eth_provider.ts +++ b/ethereum-steth-v2/src/clients/eth_provider.ts @@ -5,7 +5,7 @@ import { retryAsync } from 'ts-retry' import { BigNumber as EtherBigNumber } from '@ethersproject/bignumber/lib/bignumber' import BigNumber from 'bignumber.js' import { ETH_DECIMALS } from '../utils/constants' -import { StakingLimitInfo } from '../entity/stakingLimitInfo' +import { StakingLimitInfo } from '../entity/staking_limit_info' import { GateSeal as GateSealContract, Lido as LidoContract, diff --git a/ethereum-steth-v2/src/entity/stakingLimitInfo.ts b/ethereum-steth-v2/src/entity/staking_limit_info.ts similarity index 100% rename from ethereum-steth-v2/src/entity/stakingLimitInfo.ts rename to ethereum-steth-v2/src/entity/staking_limit_info.ts diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts index d5100cb7..98d98eda 100644 --- a/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts @@ -30,7 +30,7 @@ import BigNumber from 'bignumber.js' import { Finding, FindingSeverity, FindingType, LogDescription } from 'forta-agent' import * as Winston from 'winston' import { TypedEvent } from '../../generated/common' -import { StakingLimitInfo } from '../../entity/stakingLimitInfo' +import { StakingLimitInfo } from '../../entity/staking_limit_info' import { getFilteredBurnerEventsMock, getFilteredDepositSecurityEventsMock, @@ -1012,7 +1012,7 @@ describe('StethOperationSrv', () => { }) }) - describe('handleInvariants', () => { + describe('handleShareRateChange', () => { test(`ethProviderErr`, async () => { const want = new Error(`getShareRateErr`) ethProviderMock.getShareRate.mockResolvedValue(E.left(want)) @@ -1039,12 +1039,12 @@ describe('StethOperationSrv', () => { amount: new BigNumber('1.15490045560519776042410219381324898101464198621e+27'), }) - const result = await srv.handleInvariants(currentBlock) + const result = await srv.handleShareRateChange(currentBlock) const expectedShareRateErrFinding = Finding.fromObject({ alertId: 'LIDO-AGENT-ERROR', description: `Could not call "ethProvider.getShareRate". Cause getShareRateErr`, - name: 'Error in StethOperationSrv.handleInvariants:136', + name: 'Error in StethOperationSrv.handleShareRateChange:136', severity: FindingSeverity.Low, type: FindingType.Degraded, }) @@ -1085,7 +1085,7 @@ describe('StethOperationSrv', () => { amount: cachedShareRate, }) - const result = await srv.handleInvariants(currentBlock) + const result = await srv.handleShareRateChange(currentBlock) const expectedShareRateErrFinding = Finding.fromObject({ alertId: 'LIDO-INVARIANT-ERROR', @@ -1133,7 +1133,7 @@ Diff: 1.5490045560519778e+26`, amount: cachedShareRate, }) - const result = await srv.handleInvariants(currentBlock) + const result = await srv.handleShareRateChange(currentBlock) const expectedShareRateErrFinding = Finding.fromObject({ alertId: 'LIDO-INVARIANT-ERROR', @@ -1179,7 +1179,7 @@ Diff: -1.5490045560519778e+26`, amount: cachedShareRate, }) - const result = await srv.handleInvariants(currentBlock) + const result = await srv.handleShareRateChange(currentBlock) expect(result.length).toEqual(0) }) }) diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts index 7f961153..fb874fd1 100644 --- a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts @@ -120,6 +120,7 @@ export class StethOperationSrv { this.handleBufferedEth(blockEvent.block.number, blockEvent.block.timestamp), this.handleDepositExecutorBalance(blockEvent.block.number, blockEvent.block.timestamp), this.handleStakingLimit(blockEvent.block.number, blockEvent.block.timestamp), + this.handleShareRateChange(blockEvent.block.number), ]) findings.push(...bufferedEthFindings, ...depositorBalanceFindings, ...stakingLimitFindings) @@ -128,7 +129,7 @@ export class StethOperationSrv { return findings } - public async handleInvariants(blockNumber: number): Promise { + public async handleShareRateChange(blockNumber: number): Promise { const start = new Date().getTime() const findings: Finding[] = [] @@ -136,7 +137,7 @@ export class StethOperationSrv { const shareRate = await this.ethProvider.getShareRate(blockNumber) if (E.isLeft(shareRate)) { const f: Finding = Finding.fromObject({ - name: `Error in ${StethOperationSrv.name}.${this.handleInvariants.name}:136`, + name: `Error in ${StethOperationSrv.name}.${this.handleShareRateChange.name}:136`, description: `Could not call "ethProvider.getShareRate". Cause ${shareRate.left.message}`, alertId: 'LIDO-AGENT-ERROR', severity: FindingSeverity.Low, @@ -172,7 +173,7 @@ export class StethOperationSrv { } } - this.logger.info(elapsedTime(StethOperationSrv.name + '.' + this.handleInvariants.name, start)) + this.logger.info(elapsedTime(StethOperationSrv.name + '.' + this.handleShareRateChange.name, start)) return findings } diff --git a/ethereum-steth-v2/tests/e2e/agent-steth-ops.spec.ts b/ethereum-steth-v2/tests/e2e/agent-steth-ops.spec.ts index 4ac44dc9..a9e558d1 100644 --- a/ethereum-steth-v2/tests/e2e/agent-steth-ops.spec.ts +++ b/ethereum-steth-v2/tests/e2e/agent-steth-ops.spec.ts @@ -214,7 +214,7 @@ describe('agent-steth-ops e2e tests', () => { new BigNumber('1.15469003182482499409518734333781126194978625178e+27'), ) - const findings = await app.StethOperationSrv.handleInvariants(19069340) + const findings = await app.StethOperationSrv.handleShareRateChange(19069340) expect(findings.length).toEqual(0) }, TEST_TIMEOUT, diff --git a/ethereum-steth-v2/tests/e2e/utils.ts b/ethereum-steth-v2/tests/e2e/utils.ts index af8cfc25..1fccfdb1 100644 --- a/ethereum-steth-v2/tests/e2e/utils.ts +++ b/ethereum-steth-v2/tests/e2e/utils.ts @@ -1,5 +1,5 @@ import { Block as EtherBlock } from '@ethersproject/abstract-provider' -import { Block, BlockEvent, ethers, EventType, Log, LogDescription, Network, Trace } from 'forta-agent' +import { Block, BlockEvent, EventType, Network, Trace } from 'forta-agent' import { formatAddress, isZeroAddress } from 'forta-agent/dist/cli/utils' import { TransactionEvent } from 'forta-agent/dist/sdk/transaction.event' import { getContractAddress } from 'ethers/lib/utils' From 7d65e170e6651223666d2aba2ff7d6d4aef54642 Mon Sep 17 00:00:00 2001 From: Sergey White Date: Mon, 29 Jan 2024 12:47:43 +0300 Subject: [PATCH 20/22] fix: eth-steth fix cs --- ethereum-steth-v2/README.md | 147 +++++++++--------- .../src/services/gate-seal/GateSeal.srv.ts | 2 +- .../steth_operation/StethOperation.spec.ts | 8 +- .../steth_operation/StethOperation.srv.ts | 7 +- .../src/services/vault/Vault.srv.ts | 8 +- .../src/utils/events/lido_events.ts | 2 +- .../tests/e2e/agent-steth-ops.spec.ts | 4 +- .../subagents/gate-seal/agent-gate-seal.ts | 2 +- 8 files changed, 91 insertions(+), 89 deletions(-) diff --git a/ethereum-steth-v2/README.md b/ethereum-steth-v2/README.md index 3f0717da..e02e1c0e 100644 --- a/ethereum-steth-v2/README.md +++ b/ethereum-steth-v2/README.md @@ -1,4 +1,4 @@ -# Lido ethereum Steth bot +# Lido Ethereum StETH bot ## Supported chains @@ -7,78 +7,83 @@ ## Alerts 1. StETH operations - 1. HandleBlock - 1. 🚨🚨🚨 Buffered ETH drain (checks each block) - 2. 🚨 Huge depositable ETH amount (checks every 100 blocks) - 3. ⚠️ High depositable ETH amount (checks every 100 blocks) - 4. ⚠️ Low deposit executor balance (checks every 100 blocks) - 5. ⚠️ Unspent staking limit below 10% (checks every 25 blocks) - 6. πŸ“‰ Unspent staking limit below 30% (checks every 25 blocks) - 2. HandleTransaction - 1. Deposit Security events - 1. 🚨 Deposit Security: Deposits paused - 2. ⚠️Deposit Security: Deposits resumed - 3. ⚠️ Deposit Security: Guardian added - 4. ⚠️ Deposit Security: Guardian removed - 5. 🚨 Deposit Security: Guardian quorum changed - 6. ⚠️ Deposit Security: Max deposits changed - 7. ⚠️ Deposit Security: Min deposit block distance changed - 8. 🚨 Deposit Security: Owner changed - 2. Lido events - 1. 🚨🚨🚨 Lido: Stopped 🚨🚨🚨 - 2. ⚠️ Lido: Resumed - 3. 🚨 Lido: Staking paused - 4. ⚠️ Lido: Staking resumed - 5. ⚠️ Lido: Staking limit set - 6. 🚨 Lido: Staking limit removed - 7. 🚨 Lido: Locator set - 8. ⚠️Lido: Funds recovered to vault - 9. ⚠️Lido: Contract version set - 10. ⚠️ Lido: Token rebased - 11. 🚨 Share rate unexpected has changed - 3. Insurance fund events - 1. ⚠️ Insurance fund: ETH transferred - 2. ⚠️ Insurance fund: ERC721 transferred - 3. 🚨 Insurance fund: ERC20 transferred - 4. ⚠️ Insurance fund: ERC1155 transferred - 5. 🚨 Insurance fund: Ownership transferred - 4. Burner events - 1. ℹ️ Lido Burner: ERC20 recovered - 2. ℹ️ Lido Burner: ERC721 recovered -2. Withdrawals. - 1. HandleBlock runs on each 100-th block or one per 20 minutes - 1. ⚠️ Withdrawals: % of stake limit is spent and unfinalized queue is on par with drained stake - limit - 2. ⚠️ Withdrawals: unfinalized queue is more than 100_000 stETH - 3. ⚠️ Withdrawals: unfinalized queue wait time is more then 1 day - 4. ⚠️ Withdrawals: ${unclaimedSizeRate.times(100).toFixed(2)}% of finalized requests are unclaimed - 5. ⚠️ Withdrawals: unclaimed requests size is more than withdrawal queue balance - 2. HandleTransaction - 1. 🚨 Withdrawals: BUNKER MODE ON! 🚨 - 2. ⚠️Withdrawals: BUNKER MODE OFF! βœ… - 3. ℹ️ Huge stETH withdrawal requests batch - 4. ⚠️ Withdrawals: the sum of received withdrawal requests since the last rebase greater than 150_000 stETH (max staking limit) - 5. ⚠️Withdrawals: claimed amount is more than requested - 6. ⚠️Withdrawals: contract was unpaused - 7. 🚨 Withdrawals: contract was paused + 1. HandleBlock + 1. 🚨🚨🚨 Buffered ETH drain (checks each block) + 2. 🚨 Huge depositable ETH amount (checks every 100 blocks) + 3. ⚠️ High depositable ETH amount (checks every 100 blocks) + 4. ⚠️ Low deposit executor balance (checks every 100 blocks) + 5. ⚠️ Unspent staking limit below 10% (checks every 25 blocks) + 6. πŸ“‰ Unspent staking limit below 30% (checks every 25 blocks) + 2. HandleTransaction + 1. Deposit Security events + 1. 🚨 Deposit Security: Deposits paused + 2. 🚨 Deposit Security: Guardian quorum changed + 3. 🚨 Deposit Security: Owner changed + 4. ⚠️ Deposit Security: Deposits resumed + 5. ⚠️ Deposit Security: Guardian added + 6. ⚠️ Deposit Security: Guardian removed + 7. ⚠️ Deposit Security: Max deposits changed + 8. ⚠️ Deposit Security: Min deposit block distance changed + 2. Lido events + 1. 🚨🚨🚨 Lido: Stopped 🚨🚨🚨 + 2. 🚨🚨🚨 Share rate unexpected has changed + 3. 🚨 Lido: Staking limit removed + 4. 🚨 Lido: Locator set + 5. ⚠️ Lido: Resumed + 6. 🚨 Lido: Staking paused + 7. ⚠️ Lido: Staking resumed + 8. ⚠️ Lido: Staking limit set + 9. ⚠️ Lido: Funds recovered to vault + 10. ⚠️ Lido: Contract version set + 11. ℹ️ Lido: Token rebased + + 3. Insurance fund events + 1. 🚨 Insurance fund: ERC20 transferred + 2. 🚨 Insurance fund: Ownership transferred + 3. ⚠️ Insurance fund: ETH transferred + 4. ⚠️ Insurance fund: ERC1155 transferred + 5. ⚠️ Insurance fund: ERC721 transferred + 4. Burner events + 1. ℹ️ Lido Burner: ERC20 recovered + 2. ℹ️ Lido Burner: ERC721 recovered +2. Withdrawals + 1. HandleBlock runs on each 100-th block or one per 20 minutes + 1. ⚠️ Withdrawals: % of stake limit is spent and unfinalized queue is on par with drained stake + limit + 2. ⚠️ Withdrawals: unfinalized queue is more than 100_000 stETH + 3. ⚠️ Withdrawals: unfinalized queue wait time is more than 1 day + 4. ⚠️ Withdrawals: ${unclaimedSizeRate.times(100).toFixed(2)}% of finalized requests are unclaimed + 5. ⚠️ Withdrawals: unclaimed requests size is more than withdrawal queue balance + 2. HandleTransaction + 1. 🚨 Withdrawals: BUNKER MODE ON! 🚨 + 2. 🚨 Withdrawals: contract was paused + 3. ⚠️ Withdrawals: BUNKER MODE OFF! βœ… + 4. ℹ️ Huge stETH withdrawal requests batch + 5. ⚠️ Withdrawals: the sum of received withdrawal requests since the last rebase greater than 150_000 stETH (max + staking limit) + 6. ⚠️ Withdrawals: claimed amount is more than requested + 7. ⚠️ Withdrawals: contract was unpaused 3. GateSeal - 1. HandleBlock runs on each next block - 1. ⚠️ GateSeal: default GateSeal address in forta agent is expired - 2. ⚠️️ GateSeal: default GateSeal address in forta agent doesn't have PAUSE_ROLE for contracts - 3. 🚨 GateSeal: actual address doesn't have PAUSE_ROLE for contracts - 4. 🚨 GateSeal: is expired! - 5. ⚠️ GateSeal: is about to be expired - 2. HandleTransaction - 1. 🚨🚨🚨 GateSeal: is sealed 🚨🚨🚨 - 2. ℹ️ GateSeal: is expired + 1. HandleBlock runs on each next block + 1. 🚨 GateSeal: actual address doesn't have PAUSE_ROLE for contracts + 2. 🚨 GateSeal: is expired! + 3. 🚨️ GateSeal: is expired. Update code! + 4. ⚠️ GateSeal: default GateSeal address in forta agent is expired + 5. ⚠️️ GateSeal: default GateSeal address in forta agent doesn't have PAUSE_ROLE for contracts + 6. ⚠️ GateSeal: is about to be expired + 2. HandleTransaction + 1. 🚨🚨🚨 GateSeal: is sealed 🚨🚨🚨 + 2. 🚨 GateSeal: is expired 4. Vaults - 1. Handleblock - 1. πŸ’΅ Withdrawal Vault Balance significant change (checks every on 100-th block) - 2. πŸ’΅ EL Vault Balance significant change - 3. 🚨🚨🚨 Withdrawal Vault balance mismatch - 4. 🚨🚨🚨 EL Vault balance mismatch - 2. HandleTransaction - 1. 🚨 Burner shares transfer + 1. Handleblock + 1. 🚨🚨🚨 Withdrawal Vault balance mismatch. [without oracle report] + 2. 🚨🚨🚨 Withdrawal Vault balance mismatch. [within oracle report] + 3. 🚨🚨🚨 EL Vault balance mismatch. [without oracle report] + 4. 🚨🚨🚨 EL Vault balance mismatch. [within oracle report] + 5. πŸ’΅ Withdrawal Vault Balance significant change (checks every on 100-th block) + 6. πŸ’΅ EL Vault Balance significant change + 2. HandleTransaction + 1. 🚨 Burner shares transfer ## Development (Forta specific) diff --git a/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts b/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts index c18caaf4..2aab990f 100644 --- a/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts +++ b/ethereum-steth-v2/src/services/gate-seal/GateSeal.srv.ts @@ -295,7 +295,7 @@ export class GateSealSrv { const { gate_seal } = newGateSealEvent.args out.push( Finding.fromObject({ - name: 'ℹ️ GateSeal: is expired', + name: '🚨️ GateSeal: is expired. Update code!', description: `GateSeal address: ${etherscanAddress( gate_seal, )}\ndev: Please, update \`GATE_SEAL_DEFAULT_ADDRESS\` in code`, diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts index 98d98eda..aef72f30 100644 --- a/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.spec.ts @@ -799,7 +799,7 @@ describe('StethOperationSrv', () => { const expected = Finding.fromObject({ alertId: 'LOW-STAKING-LIMIT', - description: `Current staking limit is 9.00 ETH this is lower than 10% of max staking limit 100.00 ETH`, + description: `Current staking limit is lower than 10% of max staking limit`, name: '⚠️ Unspent staking limit below 10%', severity: FindingSeverity.Info, type: FindingType.Info, @@ -815,7 +815,7 @@ describe('StethOperationSrv', () => { expect(cache.getLastReportedStakingLimit10Timestamp()).toEqual(currentBlockDate.getTime()) }) - test('⚠️ Staking limit below 30%', async () => { + test('πŸ“‰ Staking limit below 30%', async () => { const getStakingLimitInfo: StakingLimitInfo = { currentStakeLimit: new BigNumber(250), isStakingPaused: false, @@ -1092,7 +1092,7 @@ describe('StethOperationSrv', () => { description: `Prev.shareRate(19061448) = 1.1549004556051977e+27 Curr.shareRate(19061449) = 1.3098009112103954e+27 Diff: 1.5490045560519778e+26`, - name: '🚨 Share rate unexpected has changed', + name: '🚨🚨🚨 Share rate unexpected has changed', severity: FindingSeverity.Critical, type: FindingType.Suspicious, }) @@ -1140,7 +1140,7 @@ Diff: 1.5490045560519778e+26`, description: `Prev.shareRate(19061448) = 1.1549004556051977e+27 Curr.shareRate(19061449) = 1e+27 Diff: -1.5490045560519778e+26`, - name: '🚨 Share rate unexpected has changed', + name: '🚨🚨🚨 Share rate unexpected has changed', severity: FindingSeverity.Critical, type: FindingType.Suspicious, }) diff --git a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts index fb874fd1..5befa882 100644 --- a/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts +++ b/ethereum-steth-v2/src/services/steth_operation/StethOperation.srv.ts @@ -159,7 +159,7 @@ export class StethOperationSrv { if (Math.abs(deviation) >= 0.01) { findings.push( Finding.fromObject({ - name: `🚨 Share rate unexpected has changed`, + name: `🚨🚨🚨 Share rate unexpected has changed`, description: `Prev.shareRate(${shareRateFromReport.blockNumber}) = ${shareRateFromReport.amount.toNumber()} \n` + `Curr.shareRate(${blockNumber}) = ${shareRate.right.toNumber()} \n` + @@ -510,10 +510,7 @@ export class StethOperationSrv { out.push( Finding.fromObject({ name: '⚠️ Unspent staking limit below 10%', - description: - `Current staking limit is ${currentStakingLimit.toFixed(2)} ETH ` + - `this is lower than 10% of max staking limit ` + - `${maxStakingLimit.toFixed(2)} ETH`, + description: `Current staking limit is lower than 10% of max staking limit`, alertId: 'LOW-STAKING-LIMIT', severity: FindingSeverity.Info, type: FindingType.Info, diff --git a/ethereum-steth-v2/src/services/vault/Vault.srv.ts b/ethereum-steth-v2/src/services/vault/Vault.srv.ts index a966432c..8de6662b 100644 --- a/ethereum-steth-v2/src/services/vault/Vault.srv.ts +++ b/ethereum-steth-v2/src/services/vault/Vault.srv.ts @@ -265,7 +265,7 @@ export class VaultSrv { if (currentBalance.right.lt(prevBalance)) { out.push( Finding.fromObject({ - name: '🚨🚨🚨 Withdrawal Vault balance mismatch', + name: '🚨🚨🚨 Withdrawal Vault balance mismatch. [without oracle report]', description: `Withdrawal Vault Balance has decreased by ${toEthString( prevBalance.minus(currentBalance.right), )} without Oracle report`, @@ -285,7 +285,7 @@ export class VaultSrv { if (currentBalance.right.lt(expectedBalance)) { out.push( Finding.fromObject({ - name: '🚨🚨🚨 Withdrawal Vault balance mismatch', + name: '🚨🚨🚨 Withdrawal Vault balance mismatch. [within oracle report]', description: `Withdrawal Vault Balance has decreased by ${toEthString( expectedBalance.minus(currentBalance.right), )} but Oracle report shows ${toEthString(withdrawalsWithdrawn)}`, @@ -323,7 +323,7 @@ export class VaultSrv { if (currentBalance.right.lt(prevBalance)) { out.push( Finding.fromObject({ - name: '🚨🚨🚨 EL Vault balance mismatch', + name: '🚨🚨🚨 EL Vault balance mismatch. [without oracle report]', description: `EL Vault Balance has decreased by ${toEthString( prevBalance.minus(currentBalance.right), )} without Oracle report`, @@ -343,7 +343,7 @@ export class VaultSrv { if (currentBalance.right.lt(expectedBalance)) { out.push( Finding.fromObject({ - name: '🚨🚨🚨 EL Vault balance mismatch', + name: '🚨🚨🚨 EL Vault balance mismatch. [within oracle report]', description: `EL Vault Balance has decreased by ${toEthString( expectedBalance.minus(currentBalance.right), )} but Oracle report shows ${toEthString(executionLayerRewardsWithdrawn)}`, diff --git a/ethereum-steth-v2/src/utils/events/lido_events.ts b/ethereum-steth-v2/src/utils/events/lido_events.ts index f9063261..025aedd1 100644 --- a/ethereum-steth-v2/src/utils/events/lido_events.ts +++ b/ethereum-steth-v2/src/utils/events/lido_events.ts @@ -100,7 +100,7 @@ export function getLidoEvents(LIDO_STETH_ADDRESS: string): EventOfNotice[] { event: 'event TokenRebased(uint256 indexed reportTimestamp, uint256 timeElapsed, uint256 preTotalShares, uint256 preTotalEther, uint256 postTotalShares, uint256 postTotalEther, uint256 sharesMintedAsFees)', alertId: alertId_token_rebased, - name: '⚠️ Lido: Token rebased', + name: 'ℹ️ Lido: Token rebased', description: (args: Result) => `reportTimestamp: ${args.reportTimestamp}`, severity: FindingSeverity.Info, type: FindingType.Info, diff --git a/ethereum-steth-v2/tests/e2e/agent-steth-ops.spec.ts b/ethereum-steth-v2/tests/e2e/agent-steth-ops.spec.ts index a9e558d1..f247aec0 100644 --- a/ethereum-steth-v2/tests/e2e/agent-steth-ops.spec.ts +++ b/ethereum-steth-v2/tests/e2e/agent-steth-ops.spec.ts @@ -26,7 +26,7 @@ describe('agent-steth-ops e2e tests', () => { const expected = Finding.fromObject({ alertId: 'LOW-STAKING-LIMIT', - description: `Current staking limit is 7237.74 ETH this is lower than 10% of max staking limit 150000.00 ETH`, + description: `Current staking limit is lower than 10% of max staking limit`, name: '⚠️ Unspent staking limit below 10%', severity: FindingSeverity.Info, type: FindingType.Info, @@ -196,7 +196,7 @@ describe('agent-steth-ops e2e tests', () => { const results = await app.StethOperationSrv.handleTransaction(txEvent, parseInt(receipt.blockNumber)) const expected: Finding = Finding.fromObject({ - name: '⚠️ Lido: Token rebased', + name: 'ℹ️ Lido: Token rebased', description: 'reportTimestamp: 1706011211', alertId: 'LIDO-TOKEN-REBASED', severity: 1, diff --git a/ethereum-steth/src/subagents/gate-seal/agent-gate-seal.ts b/ethereum-steth/src/subagents/gate-seal/agent-gate-seal.ts index 1030c4ed..b6759cde 100644 --- a/ethereum-steth/src/subagents/gate-seal/agent-gate-seal.ts +++ b/ethereum-steth/src/subagents/gate-seal/agent-gate-seal.ts @@ -255,7 +255,7 @@ async function handleNewGateSeal( const { gate_seal } = newGateSealEvent.args; findings.push( Finding.fromObject({ - name: "ℹ️ GateSeal: is expired", + name: "🚨 GateSeal: is expired", description: `GateSeal address: ${etherscanAddress( gate_seal, )}\ndev: Please, update \`GATE_SEAL_DEFAULT_ADDRESS\` in code`, From a9f30ab7c0770a8a9ff4f7497697161ba4584652 Mon Sep 17 00:00:00 2001 From: Sergey White Date: Mon, 29 Jan 2024 12:55:50 +0300 Subject: [PATCH 21/22] fix: eth-steth fix cs --- ethereum-steth-v2/README.md | 147 ++++++++++++++++----------------- ethereum-steth-v2/package.json | 2 +- 2 files changed, 74 insertions(+), 75 deletions(-) diff --git a/ethereum-steth-v2/README.md b/ethereum-steth-v2/README.md index e02e1c0e..af2cd684 100644 --- a/ethereum-steth-v2/README.md +++ b/ethereum-steth-v2/README.md @@ -7,83 +7,82 @@ ## Alerts 1. StETH operations - 1. HandleBlock - 1. 🚨🚨🚨 Buffered ETH drain (checks each block) - 2. 🚨 Huge depositable ETH amount (checks every 100 blocks) - 3. ⚠️ High depositable ETH amount (checks every 100 blocks) - 4. ⚠️ Low deposit executor balance (checks every 100 blocks) - 5. ⚠️ Unspent staking limit below 10% (checks every 25 blocks) - 6. πŸ“‰ Unspent staking limit below 30% (checks every 25 blocks) - 2. HandleTransaction - 1. Deposit Security events - 1. 🚨 Deposit Security: Deposits paused - 2. 🚨 Deposit Security: Guardian quorum changed - 3. 🚨 Deposit Security: Owner changed - 4. ⚠️ Deposit Security: Deposits resumed - 5. ⚠️ Deposit Security: Guardian added - 6. ⚠️ Deposit Security: Guardian removed - 7. ⚠️ Deposit Security: Max deposits changed - 8. ⚠️ Deposit Security: Min deposit block distance changed - 2. Lido events - 1. 🚨🚨🚨 Lido: Stopped 🚨🚨🚨 - 2. 🚨🚨🚨 Share rate unexpected has changed - 3. 🚨 Lido: Staking limit removed - 4. 🚨 Lido: Locator set - 5. ⚠️ Lido: Resumed - 6. 🚨 Lido: Staking paused - 7. ⚠️ Lido: Staking resumed - 8. ⚠️ Lido: Staking limit set - 9. ⚠️ Lido: Funds recovered to vault - 10. ⚠️ Lido: Contract version set - 11. ℹ️ Lido: Token rebased - - 3. Insurance fund events - 1. 🚨 Insurance fund: ERC20 transferred - 2. 🚨 Insurance fund: Ownership transferred - 3. ⚠️ Insurance fund: ETH transferred - 4. ⚠️ Insurance fund: ERC1155 transferred - 5. ⚠️ Insurance fund: ERC721 transferred - 4. Burner events - 1. ℹ️ Lido Burner: ERC20 recovered - 2. ℹ️ Lido Burner: ERC721 recovered + 1. HandleBlock + 1. 🚨🚨🚨 Buffered ETH drain (checks each block) + 2. 🚨 Huge depositable ETH amount (checks every 100 blocks) + 3. ⚠️ High depositable ETH amount (checks every 100 blocks) + 4. ⚠️ Low deposit executor balance (checks every 100 blocks) + 5. ⚠️ Unspent staking limit below 10% (checks every 25 blocks) + 6. πŸ“‰ Unspent staking limit below 30% (checks every 25 blocks) + 2. HandleTransaction + 1. Deposit Security events + 2. 🚨 Deposit Security: Deposits paused + 3. 🚨 Deposit Security: Guardian quorum changed + 4. 🚨 Deposit Security: Owner changed + 5. ⚠️ Deposit Security: Deposits resumed + 6. ⚠️ Deposit Security: Guardian added + 7. ⚠️ Deposit Security: Guardian removed + 8. ⚠️ Deposit Security: Max deposits changed + 9. ⚠️ Deposit Security: Min deposit block distance changed + 3. Lido events + 1. 🚨🚨🚨 Lido: Stopped 🚨🚨🚨 + 2. 🚨🚨🚨 Share rate unexpected has changed + 3. 🚨 Lido: Staking limit removed + 4. 🚨 Lido: Locator set + 5. ⚠️ Lido: Resumed + 6. 🚨 Lido: Staking paused + 7. ⚠️ Lido: Staking resumed + 8. ⚠️ Lido: Staking limit set + 9. ⚠️ Lido: Funds recovered to vault + 10. ⚠️ Lido: Contract version set + 11. ℹ️ Lido: Token rebased + 4. Insurance fund events + 1. 🚨 Insurance fund: ERC20 transferred + 2. 🚨 Insurance fund: Ownership transferred + 3. ⚠️ Insurance fund: ETH transferred + 4. ⚠️ Insurance fund: ERC1155 transferred + 5. ⚠️ Insurance fund: ERC721 transferred + 5. Burner events + 1. ℹ️ Lido Burner: ERC20 recovered + 2. ℹ️ Lido Burner: ERC721 recovered 2. Withdrawals - 1. HandleBlock runs on each 100-th block or one per 20 minutes - 1. ⚠️ Withdrawals: % of stake limit is spent and unfinalized queue is on par with drained stake - limit - 2. ⚠️ Withdrawals: unfinalized queue is more than 100_000 stETH - 3. ⚠️ Withdrawals: unfinalized queue wait time is more than 1 day - 4. ⚠️ Withdrawals: ${unclaimedSizeRate.times(100).toFixed(2)}% of finalized requests are unclaimed - 5. ⚠️ Withdrawals: unclaimed requests size is more than withdrawal queue balance - 2. HandleTransaction - 1. 🚨 Withdrawals: BUNKER MODE ON! 🚨 - 2. 🚨 Withdrawals: contract was paused - 3. ⚠️ Withdrawals: BUNKER MODE OFF! βœ… - 4. ℹ️ Huge stETH withdrawal requests batch - 5. ⚠️ Withdrawals: the sum of received withdrawal requests since the last rebase greater than 150_000 stETH (max - staking limit) - 6. ⚠️ Withdrawals: claimed amount is more than requested - 7. ⚠️ Withdrawals: contract was unpaused + 1. HandleBlock runs on each 100-th block or one per 20 minutes + 1. ⚠️ Withdrawals: % of stake limit is spent and unfinalized queue is on par with drained stake + limit + 2. ⚠️ Withdrawals: unfinalized queue is more than 100_000 stETH + 3. ⚠️ Withdrawals: unfinalized queue wait time is more than 1 day + 4. ⚠️ Withdrawals: ${unclaimedSizeRate.times(100).toFixed(2)}% of finalized requests are unclaimed + 5. ⚠️ Withdrawals: unclaimed requests size is more than withdrawal queue balance + 2. HandleTransaction + 1. 🚨 Withdrawals: BUNKER MODE ON! 🚨 + 2. 🚨 Withdrawals: contract was paused + 3. ⚠️ Withdrawals: BUNKER MODE OFF! βœ… + 4. ℹ️ Huge stETH withdrawal requests batch + 5. ⚠️ Withdrawals: the sum of received withdrawal requests since the last rebase greater than 150_000 stETH (max + staking limit) + 6. ⚠️ Withdrawals: claimed amount is more than requested + 7. ⚠️ Withdrawals: contract was unpaused 3. GateSeal - 1. HandleBlock runs on each next block - 1. 🚨 GateSeal: actual address doesn't have PAUSE_ROLE for contracts - 2. 🚨 GateSeal: is expired! - 3. 🚨️ GateSeal: is expired. Update code! - 4. ⚠️ GateSeal: default GateSeal address in forta agent is expired - 5. ⚠️️ GateSeal: default GateSeal address in forta agent doesn't have PAUSE_ROLE for contracts - 6. ⚠️ GateSeal: is about to be expired - 2. HandleTransaction - 1. 🚨🚨🚨 GateSeal: is sealed 🚨🚨🚨 - 2. 🚨 GateSeal: is expired + 1. HandleBlock runs on each next block + 1. 🚨 GateSeal: actual address doesn't have PAUSE_ROLE for contracts + 2. 🚨 GateSeal: is expired! + 3. 🚨️ GateSeal: is expired. Update code! + 4. ⚠️ GateSeal: default GateSeal address in forta agent is expired + 5. ⚠️️ GateSeal: default GateSeal address in forta agent doesn't have PAUSE_ROLE for contracts + 6. ⚠️ GateSeal: is about to be expired + 2. HandleTransaction + 1. 🚨🚨🚨 GateSeal: is sealed 🚨🚨🚨 + 2. 🚨 GateSeal: is expired 4. Vaults - 1. Handleblock - 1. 🚨🚨🚨 Withdrawal Vault balance mismatch. [without oracle report] - 2. 🚨🚨🚨 Withdrawal Vault balance mismatch. [within oracle report] - 3. 🚨🚨🚨 EL Vault balance mismatch. [without oracle report] - 4. 🚨🚨🚨 EL Vault balance mismatch. [within oracle report] - 5. πŸ’΅ Withdrawal Vault Balance significant change (checks every on 100-th block) - 6. πŸ’΅ EL Vault Balance significant change - 2. HandleTransaction - 1. 🚨 Burner shares transfer + 1. Handleblock + 1. 🚨🚨🚨 Withdrawal Vault balance mismatch. [without oracle report] + 2. 🚨🚨🚨 Withdrawal Vault balance mismatch. [within oracle report] + 3. 🚨🚨🚨 EL Vault balance mismatch. [without oracle report] + 4. 🚨🚨🚨 EL Vault balance mismatch. [within oracle report] + 5. πŸ’΅ Withdrawal Vault Balance significant change (checks every on 100-th block) + 6. πŸ’΅ EL Vault Balance significant change + 2. HandleTransaction + 1. 🚨 Burner shares transfer ## Development (Forta specific) diff --git a/ethereum-steth-v2/package.json b/ethereum-steth-v2/package.json index 3ea77c7a..4c66e424 100644 --- a/ethereum-steth-v2/package.json +++ b/ethereum-steth-v2/package.json @@ -41,7 +41,7 @@ "eslint:lint": "eslint ./src ./tests", "eslint:format": "eslint ./src ./tests --fix", "prettier:check": "prettier --check ./src ./tests", - "prettier:format": "prettier --write ./src ./tests", + "prettier:format": "prettier --write ./src ./tests README.md", "lint": "yarn run prettier:check && yarn run eslint:lint", "format": "yarn run eslint:format && yarn run prettier:format", "postinstall": "yarn generate-types" From 7eeb1208f268a1d70c8fe9f5946476c7968f4446 Mon Sep 17 00:00:00 2001 From: Sergey White Date: Mon, 29 Jan 2024 16:19:39 +0300 Subject: [PATCH 22/22] fix: eth-steth pasted touched files from main branch --- ethereum-governance/src/common/utils.ts | 8 +++----- ethereum-huge-tx/src/common/utils.ts | 22 ++++++++++----------- ethereum-validators-set/src/common/utils.ts | 8 +++----- lido-on-polygon/src/helpers.ts | 6 ++---- polygon/src/helpers.ts | 6 ++---- storage-watcher/src/common/utils.ts | 8 +++----- voting-watcher/src/helpers.ts | 6 ++---- 7 files changed, 25 insertions(+), 39 deletions(-) diff --git a/ethereum-governance/src/common/utils.ts b/ethereum-governance/src/common/utils.ts index 3dadfd54..0812a4de 100644 --- a/ethereum-governance/src/common/utils.ts +++ b/ethereum-governance/src/common/utils.ts @@ -44,8 +44,8 @@ export function etherscanNft(address: string, id: number | string): string { /** * Special wrapper under `require` function that allows to - * redefine variables from a file with the same name and `.` suffix. - * `` is a string that is passed by `FORTA_AGENT_RUN_TIER` environment variable. + * redefine variables from a file with the same name and `.` suffix. + * `` is a string that is passed by `FORTA_AGENT_RUN_TIER` environment variable. * @param module module object to get the path from. * @param path relative to module path to the main file to import. * @param mode `strict` or `merge`. Default: `strict`. @@ -56,9 +56,7 @@ export function requireWithTier( mode: RedefineMode = RedefineMode.Strict, ): T { const defaultContent = require(`${module.path}/${path}`); - if (!RUN_TIER) { - return defaultContent; - } + if (!RUN_TIER) return defaultContent; let tieredContent: any; try { tieredContent = require(`${module.path}/${path}.${RUN_TIER}`); diff --git a/ethereum-huge-tx/src/common/utils.ts b/ethereum-huge-tx/src/common/utils.ts index 11e6c23c..a47c9705 100644 --- a/ethereum-huge-tx/src/common/utils.ts +++ b/ethereum-huge-tx/src/common/utils.ts @@ -2,21 +2,21 @@ import { TransactionEvent } from "forta-agent"; import BigNumber from "bignumber.js"; import { + TransferEventInfo, ComplexTransferPattern, - CURVE_EXCHANGE_EVENT, - CURVE_POOL_ADDRESS, + TransferPattern, + TransferText, + TransferEventMetadata, ETH_DECIMALS, + CURVE_EXCHANGE_EVENT, EXCHANGE_ETH_TO_STETH_CURVE_PATTERN, EXCHANGE_STETH_TO_ETH_CURVE_PATTERN, - LDO_TOKEN_ADDRESS, + TX_AMOUNT_THRESHOLD, PARTIALLY_MONITORED_TOKENS, SIMPLE_TRANSFERS, - TransferEventInfo, - TransferEventMetadata, - TransferPattern, - TransferText, - TX_AMOUNT_THRESHOLD, TX_AMOUNT_THRESHOLD_LDO, + LDO_TOKEN_ADDRESS, + CURVE_POOL_ADDRESS, } from "../subagents/huge-tx/constants"; const SI_SYMBOL = ["", "k", "M", "G", "T", "P", "E"]; @@ -235,13 +235,11 @@ export function applicableAmount(transferInfo: TransferEventInfo) { } export function abbreviateNumber(number: number): string { - // what tier.ts? (determines SI symbol) + // what tier? (determines SI symbol) const tier = (Math.log10(Math.abs(number)) / 3) | 0; // if zero, we don't need a suffix - if (tier == 0) { - return Math.round(number).toString(); - } + if (tier == 0) return Math.round(number).toString(); // get suffix and determine scale const suffix = SI_SYMBOL[tier]; diff --git a/ethereum-validators-set/src/common/utils.ts b/ethereum-validators-set/src/common/utils.ts index 3dadfd54..0812a4de 100644 --- a/ethereum-validators-set/src/common/utils.ts +++ b/ethereum-validators-set/src/common/utils.ts @@ -44,8 +44,8 @@ export function etherscanNft(address: string, id: number | string): string { /** * Special wrapper under `require` function that allows to - * redefine variables from a file with the same name and `.` suffix. - * `` is a string that is passed by `FORTA_AGENT_RUN_TIER` environment variable. + * redefine variables from a file with the same name and `.` suffix. + * `` is a string that is passed by `FORTA_AGENT_RUN_TIER` environment variable. * @param module module object to get the path from. * @param path relative to module path to the main file to import. * @param mode `strict` or `merge`. Default: `strict`. @@ -56,9 +56,7 @@ export function requireWithTier( mode: RedefineMode = RedefineMode.Strict, ): T { const defaultContent = require(`${module.path}/${path}`); - if (!RUN_TIER) { - return defaultContent; - } + if (!RUN_TIER) return defaultContent; let tieredContent: any; try { tieredContent = require(`${module.path}/${path}.${RUN_TIER}`); diff --git a/lido-on-polygon/src/helpers.ts b/lido-on-polygon/src/helpers.ts index 8e5fd1c0..22832e75 100644 --- a/lido-on-polygon/src/helpers.ts +++ b/lido-on-polygon/src/helpers.ts @@ -5,13 +5,11 @@ import { ethersProvider } from "./ethers"; const SI_SYMBOL = ["", "k", "M", "G", "T", "P", "E"]; export function abbreviateNumber(number: number): string { - // what tier.ts? (determines SI symbol) + // what tier? (determines SI symbol) const tier = (Math.log10(Math.abs(number)) / 3) | 0; // if zero, we don't need a suffix - if (tier == 0) { - return Math.round(number).toString(); - } + if (tier == 0) return Math.round(number).toString(); // get suffix and determine scale const suffix = SI_SYMBOL[tier]; diff --git a/polygon/src/helpers.ts b/polygon/src/helpers.ts index bdb8c045..1c57ca18 100644 --- a/polygon/src/helpers.ts +++ b/polygon/src/helpers.ts @@ -1,13 +1,11 @@ const SI_SYMBOL = ["", "k", "M", "G", "T", "P", "E"]; export function abbreviateNumber(number: number): string { - // what tier.ts? (determines SI symbol) + // what tier? (determines SI symbol) const tier = (Math.log10(Math.abs(number)) / 3) | 0; // if zero, we don't need a suffix - if (tier == 0) { - return Math.round(number).toString(); - } + if (tier == 0) return Math.round(number).toString(); // get suffix and determine scale const suffix = SI_SYMBOL[tier]; diff --git a/storage-watcher/src/common/utils.ts b/storage-watcher/src/common/utils.ts index f0a8b7cd..945449ed 100644 --- a/storage-watcher/src/common/utils.ts +++ b/storage-watcher/src/common/utils.ts @@ -47,8 +47,8 @@ export enum RedefineMode { /** * Special wrapper under `require` function that allows to - * redefine variables from a file with the same name and `.` suffix. - * `` is a string that is passed by `FORTA_AGENT_RUN_TIER` environment variable. + * redefine variables from a file with the same name and `.` suffix. + * `` is a string that is passed by `FORTA_AGENT_RUN_TIER` environment variable. * @param module module object to get the path from. * @param path relative to module path to the main file to import. * @param mode `strict` or `merge`. Default: `strict`. @@ -59,9 +59,7 @@ export function requireWithTier( mode: RedefineMode = RedefineMode.Strict, ): T { const defaultContent = require(`${module.path}/${path}`); - if (!RUN_TIER) { - return defaultContent; - } + if (!RUN_TIER) return defaultContent; let tieredContent: any; try { tieredContent = require(`${module.path}/${path}.${RUN_TIER}`); diff --git a/voting-watcher/src/helpers.ts b/voting-watcher/src/helpers.ts index bddac998..c75e787a 100644 --- a/voting-watcher/src/helpers.ts +++ b/voting-watcher/src/helpers.ts @@ -15,13 +15,11 @@ export function getResultStr(quorumDistance: number, passed: boolean) { const SI_SYMBOL = ["", "k", "M", "G", "T", "P", "E"]; export function abbreviateNumber(number: number): string { - // what tier.ts? (determines SI symbol) + // what tier? (determines SI symbol) const tier = (Math.log10(Math.abs(number)) / 3) | 0; // if zero, we don't need a suffix - if (tier == 0) { - return Math.round(number).toString(); - } + if (tier == 0) return Math.round(number).toString(); // get suffix and determine scale const suffix = SI_SYMBOL[tier];