Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSM alerts #621

Open
wants to merge 43 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
5d9a9f7
feat: csm-alerts initial commit
larmork Sep 9, 2024
bcdf918
feat: Add `CSModule.srv` alerts (#614)
larmork Sep 26, 2024
f445d6d
feat: move to SDKv2
madlabman Oct 2, 2024
77c3b02
chore: reformat
madlabman Oct 10, 2024
271a87d
chore: improve some HashConsensus findings
madlabman Oct 11, 2024
27a78d6
fix: update .env.sample
madlabman Oct 11, 2024
0e03b19
feat: add IPFS links
madlabman Oct 11, 2024
19a0e84
chore: fix linter config
madlabman Oct 11, 2024
21308f0
chore: some alerts touch ups
madlabman Oct 15, 2024
38b9c63
feat: extract block identifier in range mode
madlabman Oct 15, 2024
70783ad
chore: fill in mainnet addresses
madlabman Oct 15, 2024
dbfbc15
chore: use common alert id for launch event
madlabman Oct 15, 2024
ff607c6
fix: formatDelay function
madlabman Oct 15, 2024
a2fc787
feat: add SDKv1 shim
madlabman Oct 15, 2024
ac7d0d7
fix: cleanup findings
madlabman Oct 16, 2024
702a402
chore: fix typing
madlabman Oct 16, 2024
24acf97
chore: remove unused env variables
madlabman Oct 16, 2024
082fd03
chore: update .dockerignore
madlabman Oct 16, 2024
ce8d6b0
feat: add .envrc
madlabman Oct 16, 2024
9d951f2
fix: trailing comma in tsconfig.json
madlabman Oct 16, 2024
16a43b0
fix: cleanup package.json scripts
madlabman Oct 16, 2024
25b62e8
docs: update README
madlabman Oct 16, 2024
09e3db5
fix: support transactions separated by comma
madlabman Oct 16, 2024
657d66a
fix: update Dockerfile command
madlabman Oct 16, 2024
3ac81bb
feat: using JWT-based provider
madlabman Oct 16, 2024
de8fa83
chore: log block handler run
madlabman Oct 17, 2024
27d3852
feat: add GateSeal alerts
madlabman Oct 17, 2024
03d4928
fix: check CSFeeDistributor invariants when it makes sense
madlabman Oct 17, 2024
edecd2e
chore: add logging to v1 shim
madlabman Oct 17, 2024
83252fe
feat: formatShares in updated distribution alert
madlabman Oct 19, 2024
45b2a63
chore: remove unused findings helpers
madlabman Oct 19, 2024
1039f49
chore: push code errors findings
madlabman Oct 19, 2024
912f9fa
build: switch to v1 bot in Dockerfile
madlabman Oct 19, 2024
52f1608
chore: drop tests
madlabman Oct 21, 2024
64ff388
docs: update README
madlabman Oct 21, 2024
551d0d5
chore: update icon
madlabman Oct 21, 2024
d9955a7
feat: separate HashConsensus events with fixes
madlabman Oct 21, 2024
eb328dc
fix: update mainnet ORACLE_MEMBERS
madlabman Oct 21, 2024
e06305c
chore: apply suggestions from code review
madlabman Oct 23, 2024
d2fa8e1
chore: upd finding icons
madlabman Oct 23, 2024
3655b8d
docs: update README
madlabman Oct 23, 2024
3193111
fix: by-severity icons
madlabman Nov 1, 2024
e51abfa
chore: missing C
madlabman Nov 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions csm-alerts/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
dist/
forta.config.json
generated
.env
11 changes: 11 additions & 0 deletions csm-alerts/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
APP_NAME=csm-alerts

LOG_FORMAT=simple
LOG_LEVEL=debug

FORTA_AGENT_RUN_TIER=holesky

## FORTA compatible env names
FORTA_CHAIN_ID=17000

# vim: ft=sh
3 changes: 3 additions & 0 deletions csm-alerts/.envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
layout node
use node
dotenv_if_exists
1 change: 1 addition & 0 deletions csm-alerts/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
generated
69 changes: 69 additions & 0 deletions csm-alerts/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
settings: {
'import/resolver': {
typescript: {
project: './tsconfig.json',
},
},
},
plugins: ['@typescript-eslint', 'prettier', 'import'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
'plugin:import/recommended',
'plugin:import/typescript',
],
root: true,
env: {
node: true,
jest: true,
},
// FIXME: Remove *.spec.ts after implementing tests.
ignorePatterns: ['.eslintrc.js', '*.spec.ts'],
rules: {
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-empty-interface': 'off',
'@typescript-eslint/no-require-imports': 'off',
'sort-imports': [
'error',
{
ignoreCase: false,
ignoreDeclarationSort: true, // don't want to sort import lines, use eslint-plugin-import instead
ignoreMemberSort: false,
memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'],
allowSeparatedGroups: true,
},
],
'import/no-unresolved': 'error',
'import/order': [
'error',
{
groups: [
'builtin', // Built-in imports (come from NodeJS native) go first
'external', // <- External imports
'internal', // <- Absolute imports
['sibling', 'parent'], // <- Relative imports, the sibling and parent types they can be mingled together
'index', // <- index imports
'unknown', // <- unknown
],
'newlines-between': 'always',
alphabetize: {
/* sort in ascending order. Options: ["ignore", "asc", "desc"] */
order: 'asc',
/* ignore case. Options: [true, false] */
caseInsensitive: true,
},
},
],
},
}
12 changes: 12 additions & 0 deletions csm-alerts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
node_modules

dist
generated

forta.config.json
version.json

.direnv
.env

.DS_Store
1 change: 1 addition & 0 deletions csm-alerts/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v20.10.0
2 changes: 2 additions & 0 deletions csm-alerts/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
generated
abis
7 changes: 7 additions & 0 deletions csm-alerts/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"semi": false,
"trailingComma": "all",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 4
}
31 changes: 31 additions & 0 deletions csm-alerts/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Build stage: compile Typescript to Javascript
FROM node:20.10.0-alpine3.18 AS base

RUN apk add --no-cache tini==0.19.0-r1

FROM base AS builder

WORKDIR /app

COPY . .
RUN yarn install --immutable && 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 APP_NAME=csm-alerts
ENV NODE_ENV=production

ENV LOG_FORMAT=simple
ENV LOG_LEVEL=info

WORKDIR /app

COPY package*.json yarn.lock ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./src
COPY version.json ./

ENTRYPOINT ["/sbin/tini", "--"]
CMD ["yarn", "run", "start:prod:v1"]
183 changes: 183 additions & 0 deletions csm-alerts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# Lido CSM bot

## Supported chains

- Mainnet
- Holesky

## Alerts

1. **CSModule**
1. General
1. 🟠 MEDIUM: targetLimitMode was set for an operator.
2. 🫧 LOW: Module's share is close to the targetShare.
3. 🫧 LOW: More than N "empty" batches in the queue. (N = 30)
4. 🫧 LOW: More than N validators in the queue. (N = 200)
5. 🔵 INFO: EL rewards stealing penalty reported/settled/cancelled for an operator.
6. 🔵 INFO: Operator X was unvetted.
7. 🔵 INFO: Public release is activated.
8. 🔵 INFO: Every 100 new operators created (69th as well).
2. Roles monitoring
1. 🚨 CRITICAL: role change: DEFAULT_ADMIN_ROLE
2. 🚨 CRITICAL: role change: PAUSE_ROLE
3. 🚨 CRITICAL: role change: RESUME_ROLE
4. 🚨 CRITICAL: role change: MODULE_MANAGER_ROLE
5. 🚨 CRITICAL: role change: STAKING_ROUTER_ROLE
6. 🚨 CRITICAL: role change: REPORT_EL_REWARDS_STEALING_PENALTY_ROLE
7. 🚨 CRITICAL: role change: SETTLE_EL_REWARDS_STEALING_PENALTY_ROLE
8. 🚨 CRITICAL: role change: VERIFIER_ROLE
9. 🚨 CRITICAL: role change: RECOVERER_ROLE
2. **CSAccounting**
1. General
1. 🚨 CRITICAL: sharesOf(CSAccounting.address) < CSBondCoreStorage.totalBondShares
2. 🫧 LOW: sharesOf(CSAccounting.address) - CSBondCoreStorage.totalBondShares > 0.1 ether
2. Events monitoring
1. 🚨 CRITICAL: ChargePenaltyRecipientSet(address chargeRecipient)
2. 🚨 CRITICAL: BondCurveUpdated(uint256 indexed curveId, uint256[] bondCurve)
3. 🚨 CRITICAL: Approval(address owner, address spender, uint256 value) of stETH from CSAccounting, unless to the Burner
4. 🔴 HIGH: BondCurveAdded(uint256[] bondCurve)
5. 🔴 HIGH: BondCurveSet(uint256 indexed nodeOperatorId, uint256 curveId)
3. Roles monitoring
1. 🚨 CRITICAL: DEFAULT_ADMIN_ROLE
2. 🚨 CRITICAL: PAUSE_ROLE
3. 🚨 CRITICAL: RESUME_ROLE
4. 🚨 CRITICAL: ACCOUNTING_MANAGER_ROLE
5. 🚨 CRITICAL: MANAGE_BOND_CURVES_ROLE
6. 🚨 CRITICAL: SET_BOND_CURVE_ROLE
7. 🚨 CRITICAL: RESET_BOND_CURVE_ROLE
8. 🚨 CRITICAL: RECOVERER_ROLE
3. **CSFeeOracle**
1. General
1. 🚨 CRITICAL: FeeDistributorContractSet(address feeDistributorContract)
2. 🚨 CRITICAL: ConsensusHashContractSet(address indexed addr, address indexed prevAddr)
3. 🔴 HIGH: PerfLeewaySet(uint256 valueBP)
4. 🔴 HIGH: ConsensusVersionSet(uint256 indexed version, uint256 indexed prevVersion)
5. 🫧 INFO: WarnProcessingMissed(uint256 indexed refSlot)
6. 🫧 INFO: ReportSubmitted(uint256 indexed refSlot, bytes32 hash, uint256 processingDeadlineTime)
2. Roles monitoring
1. 🚨 CRITICAL: DEFAULT_ADMIN_ROLE
2. 🚨 CRITICAL: CONTRACT_MANAGER_ROLE
3. 🚨 CRITICAL: SUBMIT_DATA_ROLE
4. 🚨 CRITICAL: PAUSE_ROLE
5. 🚨 CRITICAL: RESUME_ROLE
6. 🚨 CRITICAL: RECOVERER_ROLE
3. HashConsensus (for CSFeeOracle)
1. Events monitoring
1. 🔴 HIGH: MemberAdded(address indexed addr, uint256 newTotalMembers, uint256 newQuorum)
2. 🔴 HIGH: MemberRemoved(address indexed addr, uint256 newTotalMembers, uint256 newQuorum)
3. 🔴 HIGH: QuorumSet(uint256 newQuorum, uint256 totalMembers, uint256 prevQuorum)
4. 🔴 HIGH: FastLaneConfigSet(uint256 fastLaneLengthSlots)
5. 🔴 HIGH: FrameConfigSet(uint256 newInitialEpoch, uint256 newEpochsPerFrame)
6. 🔴 HIGH: ReportProcessorSet(address indexed processor, address indexed prevProcessor)
7. 🔴 HIGH: another report variant appeared (alternative hash) event ReportReceived(uint256 indexed refSlot, address indexed member, bytes32 report)
8. 🔴 HIGH: ConsensusLost(uint256 indexed refSlot)
9. 🟡 MEDIUM: Sloppy oracle fast lane member
10. 🔵 INFO: ConsensusReached(uint256 indexed refSlot, bytes32 report, uint256 support)
2. Roles monitoring
1. 🚨 CRITICAL: DEFAULT_ADMIN_ROLE
2. 🚨 CRITICAL: DISABLE_CONSENSUS_ROLE
3. 🚨 CRITICAL: MANAGE_MEMBERS_AND_QUORUM_ROLE
4. 🚨 CRITICAL: MANAGE_FRAME_CONFIG_ROLE
5. 🚨 CRITICAL: MANAGE_FAST_LANE_CONFIG_ROLE
6. 🚨 CRITICAL: MANAGE_REPORT_PROCESSOR_ROLE
4. **CSFeeDistributor**

1. Events monitoring
1. 🚨 CRITICAL: Receiver of TransferShares is NOT CSAccounting, if from is CSFeeDistributor
2. 🔴 HIGH: No fees distributed for X days (repeat every 1 day).
3. 🔵 INFO: DistributionDataUpdated -> Oracle settled a new report.
4. 🔵 INFO: DistributionLogUpdated.
2. Roles monitoring
1. 🚨 CRITICAL: DEFAULT_ADMIN_ROLE
2. 🚨 CRITICAL: RECOVERER_ROLE

5. **OssifiableProxy**
For the following contracts:

- CSModule
- CSAccounting
- CSFeeOracle
- CSFeeDistributor

1. 🚨 CRITICAL: event ProxyOssified()
2. 🚨 CRITICAL: event Upgraded(address indexed implementation)
3. 🚨 CRITICAL: event AdminChanged(address previousAdmin, address newAdmin)

6. **PausableUntil**
For the following contracts:

- CSModule
- CSAccounting
- CSFeeOracle

1. 🚨 CRITICAL: Paused(uint256 duration);
2. 🚨 CRITICAL: Resumed();

7. **AssetRecoverer**
For the following contracts:

- CSModule
- CSAccounting
- CSFeeOracle
- CSFeeDistributor

1. 🔴 HIGH: EtherRecovered()
2. 🔴 HIGH: ERC20Recovered()
3. 🔴 HIGH: StETHSharesRecovered()
4. 🔴 HIGH: ERC721Recovered()
5. 🔴 HIGH: ERC1155Recovered()

8. **GateSeal**
1. 🔴 HIGH: CSM GateSeal expires soon (less than 3 months).

## Deployment

- Make sure you have uncommitted changes
- Run `yarn push` command
- Copy the resulting docker image reference
- Deploy a new version via https://app.forta.network with the image reference from the previous step

## Development

Install dependencies.

```shell
yarn install
```

Create a `forta.config.json` file with the following content and replace Ethereum RPC URL with the
one you're gonna use for development.

```json
{
"localRpcUrls": {
"ethereum": "http://127.0.0.1:8545"
}
}
```

To start the bot in local mode just run:

```shell
yarn run start
```

Use one of the following commands to check specific range/block/transaction(s):

```shell
yarn run range $BLOCK_NUMBER..$BLOCK_NUMBER
yarn run block $SOME_BLOCK_NUMBER_OR_HASH
yarn run tx $SOME_TX_HASH[,$ANOTHER_HASH]
```

## Forta bot v1 or v2?

This bot is developed using SDKv2, so it's the v2 bot. But the project provides a shim to be used as
a v1 bot, though not well tested. By default, the `yarn push` command uses the Dockerfile in the
repository to build an image of the v2 bot. If you want to push an image to be used as a v1 bot,
replace the command in the Dockerfile with `yarn run start:prod:v1`.

```diff
- CMD ["yarn", "run", "start:prod:v2"]
+ CMD ["yarn", "run", "start:prod:v1"]
```
6 changes: 6 additions & 0 deletions csm-alerts/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** @type {import("ts-jest").JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ['dist'],
}
Loading
Loading