diff --git a/README.md b/README.md index 7f73eef..70f5be6 100644 --- a/README.md +++ b/README.md @@ -405,6 +405,137 @@ There are multiple methods you can use for creating the exact `BlockEvent` you w - `addTransactions(txns)` This method adds the hashes of a spread list of transaction events at the end of `block.transactions` field in the event. - `addTransactionsHashes(hashes)` This method adds a hashes spread list to the end of `block.transactions` field in the event. +### TestAlertEvent + +The concept of a `TestAlertEvent` class does not actually exist. It was not implemented because the `forta-agent` library provides built-in static methods that serve the purpose that `TestAlertEvent` would have fulfilled. Below we are providing some instructions on how to use `forta-agent` to create an `AlertEvent` you could use for testing. + +#### Creating Alert + +In the `forta-agent` SDK, you'll find a class called `Alert`, which essentially represents an `Alert` within the context of the `AlertEvent` in Forta. + +- You cannot directly instantiate an `Alert` object through its constructor because it has a private constructor. Instead, there's a static method named `fromObject`. +- To use it, call `Alert.fromObject(alertInput: AlertInput)`, where `AlertInput` is an interface containing all the properties an `Alert` can have. Each property in `AlertInput` is optional, allowing you to create an `Alert` with only the properties you need for your use case or testing. +- For more details about `Alert` and its properties, refer to the official [forta-docs](https://docs.forta.network/en/latest/sdk/#alert), and for more details about `AlertInput`'s properties, refer to our implementation in [alert.type.ts](https://github.com/Macbeth98/general-agents-module/blob/test/alertEvent/src/utils/alert.type.ts#L56-L76). + +#### Creating AlertEvent + +`AlertEvent` is a class with a constructor that takes a single argument of type `Alert`. + +- You can use the `Alert` object created using the above method to obtain an `AlertEvent` object. +- All other properties in `AlertEvent` are simply getters, serving as aliases to the property methods of the `Alert` class. +- To learn more about `AlertEvent` and its properties, consult the official [forta-docs](https://docs.forta.network/en/latest/sdk/#alertevent). + +Refer to the code snippet below for an example. + +#### Basic Usage: + +```ts +import { Alert, AlertEvent, EntityType, Label } from "forta-agent"; +import { AlertInput } from "../utils/alert.type"; +import { createAddress, createTransactionHash } from "../utils"; + +let alert: Alert; +let alertEvent: AlertEvent; +let alertInput: AlertInput; + +const createAlert = (alertInput: AlertInput): Alert => { + return Alert.fromObject(alertInput); +}; + +const getLabel = (name: string, value: string): Label => { + return Label.fromObject({ + entityType: EntityType.Transaction, + entity: createTransactionHash({ to: createAddress("0x1234") }), + label: name, + confidence: 1, + metadata: { value: value }, + }); +}; + +const getAlertInput = (): AlertInput => { + let alertInput: AlertInput = { + addresses: [createAddress("0x1234"), createAddress("0x5678"), createAddress("0x9abc")], + alertId: createTransactionHash({ to: createAddress("0x1234") }), + hash: createTransactionHash({ to: createAddress("0x45678987654") }), + contracts: [ + { address: createAddress("0x1234"), name: "Contract1" }, + { address: createAddress("0x5678"), name: "Contract2" }, + { address: createAddress("0x9abc"), name: "Contract3" }, + ], + createdAt: "2021-01-01T00:00:00.000Z", + description: "Test Alert", + findingType: "Info", + name: "Test Alert", + protocol: "Test", + scanNodeCount: 1, + severity: "Info", + alertDocumentType: "Alert", + relatedAlerts: [createTransactionHash({ to: createAddress("0x1234") })], + chainId: 1, + labels: [getLabel("label1", "value1"), getLabel("label2", "value2")], + source: { + transactionHash: createTransactionHash({ to: createAddress("0x1234") }), + block: { + timestamp: "2021-01-01T00:00:00.000Z", + chainId: 1, + hash: createTransactionHash({ to: createAddress("0x1234") }), + number: 1, + }, + bot: { + id: "botId", + reference: "botReference", + image: "botImage", + }, + sourceAlert: { + hash: createTransactionHash({ to: createAddress("0x1234") }), + botId: "botId", + timestamp: "2021-01-01T00:00:00.000Z", + chainId: 1, + }, + }, + metadata: { + metadata1: "value1", + metadata2: "value2", + }, + projects: [ + { + id: "projectId", + name: "projectName", + contacts: { + securityEmailAddress: "securityEmailAddress", + generalEmailAddress: "generalEmailAddress", + }, + website: "website", + token: { + symbol: "symbol", + name: "name", + decimals: 1, + chainId: 1, + address: createAddress("0x1234"), + }, + social: { + twitter: "twitter", + github: "github", + everest: "everest", + coingecko: "coingecko", + }, + }, + ], + addressBloomFilter: { + bitset: "bitset", + k: "k", + m: "m", + }, + }; + + return alertInput; +}; + +alertInput = getAlertInput(); +alert = createAlert(alertInput); +alertEvent = new AlertEvent(alert); +``` + ### runBlock This is a helper function to simulate the execution of `run block` cli command when the bot has implemented a `handleTransaction` and a `handleBlock`. diff --git a/package-lock.json b/package-lock.json index 0b74b16..c8291fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "forta-agent-tools", - "version": "3.2.9", + "version": "3.2.10", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "forta-agent-tools", - "version": "3.2.9", + "version": "3.2.10", "license": "GPL-3.0", "dependencies": { "@ethersproject/properties": "^5.6.0", @@ -16,7 +16,7 @@ "ethers": "^5.7.2", "ethers-multicall": "^0.2.3", "evm": "^0.2.0", - "forta-agent": "0.1.17", + "forta-agent": "^0.1.40", "jest-when": "^3.5.1", "lru-cache": "^7.12.0", "node-fetch": "^2.6.7" @@ -2130,6 +2130,14 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -3045,9 +3053,9 @@ } }, "node_modules/forta-agent": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/forta-agent/-/forta-agent-0.1.17.tgz", - "integrity": "sha512-6iyccwTdL5EDGm3GOi3KwWU31/YIQ8DaEftE7E0g+mF0Z0NWL+sHzOtg3q1RmuvE+EsMXPAz9i/OdyyMvTfHKg==", + "version": "0.1.40", + "resolved": "https://registry.npmjs.org/forta-agent/-/forta-agent-0.1.40.tgz", + "integrity": "sha512-2M0+J4+p48iqTlkJbC75Nb6hnK5dLHh02Z6aaOAMBgVUFHuP7LA+uPKhTIbmZ6h5mKHpXYqE0lnDnPAl0yw18A==", "dependencies": { "@grpc/grpc-js": "^1.3.6", "@grpc/proto-loader": "^0.6.4", @@ -3055,12 +3063,14 @@ "async-retry": "^1.3.3", "awilix": "^4.3.4", "axios": "^0.21.1", + "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", @@ -4515,6 +4525,14 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/murmurhash3js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/murmurhash3js/-/murmurhash3js-3.0.1.tgz", + "integrity": "sha512-KL8QYUaxq7kUbcl0Yto51rMcYt7E/4N4BG3/c96Iqw1PQrTRspu8Cpx4TZ4Nunib1d4bEkIH3gjCYlP2RLBdow==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/n-readlines": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/n-readlines/-/n-readlines-1.0.1.tgz", @@ -7515,6 +7533,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==" + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -8214,9 +8237,9 @@ } }, "forta-agent": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/forta-agent/-/forta-agent-0.1.17.tgz", - "integrity": "sha512-6iyccwTdL5EDGm3GOi3KwWU31/YIQ8DaEftE7E0g+mF0Z0NWL+sHzOtg3q1RmuvE+EsMXPAz9i/OdyyMvTfHKg==", + "version": "0.1.40", + "resolved": "https://registry.npmjs.org/forta-agent/-/forta-agent-0.1.40.tgz", + "integrity": "sha512-2M0+J4+p48iqTlkJbC75Nb6hnK5dLHh02Z6aaOAMBgVUFHuP7LA+uPKhTIbmZ6h5mKHpXYqE0lnDnPAl0yw18A==", "requires": { "@grpc/grpc-js": "^1.3.6", "@grpc/proto-loader": "^0.6.4", @@ -8224,12 +8247,14 @@ "async-retry": "^1.3.3", "awilix": "^4.3.4", "axios": "^0.21.1", + "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", @@ -9340,6 +9365,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "murmurhash3js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/murmurhash3js/-/murmurhash3js-3.0.1.tgz", + "integrity": "sha512-KL8QYUaxq7kUbcl0Yto51rMcYt7E/4N4BG3/c96Iqw1PQrTRspu8Cpx4TZ4Nunib1d4bEkIH3gjCYlP2RLBdow==" + }, "n-readlines": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/n-readlines/-/n-readlines-1.0.1.tgz", diff --git a/package.json b/package.json index 6d76bc0..72577b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "forta-agent-tools", - "version": "3.2.9", + "version": "3.2.10", "description": "Forta Agents for common approaches", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -33,13 +33,13 @@ "@ethersproject/transactions": "^5.6.2", "@types/node-fetch": "^2.6.2", "async-mutex": "^0.3.2", + "ethers": "^5.7.2", "ethers-multicall": "^0.2.3", "evm": "^0.2.0", - "forta-agent": "0.1.17", + "forta-agent": "^0.1.40", "jest-when": "^3.5.1", "lru-cache": "^7.12.0", - "node-fetch": "^2.6.7", - "ethers": "^5.7.2" + "node-fetch": "^2.6.7" }, "directories": { "lib": "lib" diff --git a/src/test/test_alert_event.spec.ts b/src/test/test_alert_event.spec.ts new file mode 100644 index 0000000..e62aaeb --- /dev/null +++ b/src/test/test_alert_event.spec.ts @@ -0,0 +1,112 @@ +import { Alert, AlertEvent, EntityType, Label } from "forta-agent"; +import { AlertInput } from "../utils/alert.type"; +import { createAddress, createTransactionHash } from "../utils"; + +describe("Test AlertEvent", () => { + let alert: Alert; + let alertEvent: AlertEvent; + let alertInput: AlertInput; + + beforeEach(() => { + alertInput = getAlertInput(); + alert = createAlert(alertInput); + alertEvent = new AlertEvent(alert); + }); + + const createAlert = (alertInput: AlertInput): Alert => { + return Alert.fromObject(alertInput); + }; + + const getLabel = (name: string, value: string): Label => { + return Label.fromObject({ + entityType: EntityType.Transaction, + entity: createTransactionHash({ to: createAddress("0x1234") }), + label: name, + confidence: 1, + metadata: { value: value }, + }); + }; + + const getAlertInput = (): AlertInput => { + let alertInput: AlertInput = { + addresses: [createAddress("0x1234"), createAddress("0x5678"), createAddress("0x9abc")], + alertId: createTransactionHash({ to: createAddress("0x1234") }), + hash: createTransactionHash({ to: createAddress("0x45678987654") }), + contracts: [ + { address: createAddress("0x1234"), name: "Contract1" }, + { address: createAddress("0x5678"), name: "Contract2" }, + { address: createAddress("0x9abc"), name: "Contract3" }, + ], + createdAt: "2021-01-01T00:00:00.000Z", + description: "Test Alert", + findingType: "Info", + name: "Test Alert", + protocol: "Test", + scanNodeCount: 1, + severity: "Info", + alertDocumentType: "Alert", + relatedAlerts: [createTransactionHash({ to: createAddress("0x1234") })], + chainId: 1, + labels: [getLabel("label1", "value1"), getLabel("label2", "value2")], + source: { + transactionHash: createTransactionHash({ to: createAddress("0x1234") }), + block: { + timestamp: "2021-01-01T00:00:00.000Z", + chainId: 1, + hash: createTransactionHash({ to: createAddress("0x1234") }), + number: 1, + }, + bot: { + id: "botId", + reference: "botReference", + image: "botImage", + }, + sourceAlert: { + hash: createTransactionHash({ to: createAddress("0x1234") }), + botId: "botId", + timestamp: "2021-01-01T00:00:00.000Z", + chainId: 1, + }, + }, + metadata: { + metadata1: "value1", + metadata2: "value2", + }, + projects: [ + { + id: "projectId", + name: "projectName", + contacts: { + securityEmailAddress: "securityEmailAddress", + generalEmailAddress: "generalEmailAddress", + }, + website: "website", + token: { + symbol: "symbol", + name: "name", + decimals: 1, + chainId: 1, + address: createAddress("0x1234"), + }, + social: { + twitter: "twitter", + github: "github", + everest: "everest", + coingecko: "coingecko", + }, + }, + ], + addressBloomFilter: { + bitset: "bitset", + k: "k", + m: "m", + }, + }; + + return alertInput; + }; + + it("returns the alert with the set AlertInput values", () => { + expect(alertEvent.alert).toEqual(alert); + }); +}); diff --git a/src/utils/alert.type.ts b/src/utils/alert.type.ts new file mode 100644 index 0000000..acfbddb --- /dev/null +++ b/src/utils/alert.type.ts @@ -0,0 +1,76 @@ +import { Label } from "forta-agent"; + +export declare type AlertContract = { + address: string; + name: string; + projectId?: string; +}; +export declare type AlertSource = { + transactionHash?: string; + block?: { + timestamp: string; + chainId: number; + hash: string; + number: number; + }; + bot?: { + id?: string; + reference?: string; + image?: string; + }; + sourceAlert?: { + hash?: string; + botId?: string; + timestamp?: string; + chainId?: number; + }; +}; +export declare type AlertProject = { + id: string; + name: string; + contacts?: { + securityEmailAddress?: string; + generalEmailAddress?: string; + }; + website?: string; + token?: { + symbol?: string; + name?: string; + decimals?: number; + chainId: number; + address: string; + }; + social?: { + twitter?: string; + github?: string; + everest?: string; + coingecko?: string; + }; +}; +export declare type AlertAddressBloomFilter = { + bitset: string; + k: string; + m: string; +}; + +export declare type AlertInput = { + addresses?: string[]; + alertId?: string; + hash?: string; + contracts?: AlertContract[]; + createdAt?: string; + description?: string; + findingType?: string; + name?: string; + protocol?: string; + scanNodeCount?: number; + severity?: string; + alertDocumentType?: string; + relatedAlerts?: string[]; + chainId?: number; + labels?: Label[]; + source?: AlertSource; + metadata?: any; + projects?: AlertProject[]; + addressBloomFilter?: AlertAddressBloomFilter; +}; diff --git a/src/utils/index.ts b/src/utils/index.ts index afcd2fb..a112247 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -4,6 +4,7 @@ import { ProviderCache, ProviderCacheOptions } from "./provider.cache"; import CachedContract from "./cached.contract"; import { MulticallProvider, MulticallContract } from "./multicall.provider"; import VictimIdentifier from "./victim-identification/victim.identifier"; +import { AlertInput, AlertSource, AlertContract, AlertProject, AlertAddressBloomFilter } from "./alert.type"; export const padAddress = (address: string) => ethers.utils.hexZeroPad(address, 20); export const createAddress = (address: string) => padAddress(address).toLowerCase(); @@ -22,4 +23,9 @@ export { MulticallProvider, MulticallContract, VictimIdentifier, + AlertInput, + AlertSource, + AlertContract, + AlertProject, + AlertAddressBloomFilter, }; diff --git a/src/utils/victim-identification/victim.identifier.spec.ts b/src/utils/victim-identification/victim.identifier.spec.ts index 4345998..a9f3124 100644 --- a/src/utils/victim-identification/victim.identifier.spec.ts +++ b/src/utils/victim-identification/victim.identifier.spec.ts @@ -2,7 +2,7 @@ import VictimIdentifier from "./victim.identifier"; import fetch from "node-fetch"; import { Interface } from "ethers/lib/utils"; import { when } from "jest-when"; -import { AlertsResponse } from "forta-agent/dist/sdk/graphql/forta"; +import { AlertsResponse } from "forta-agent"; import { ERC20_TRANSFER_EVENT, TOKEN_ABI } from "./helpers/constants"; import { ethers, Trace } from "forta-agent"; import { createAddress } from ".."; @@ -149,6 +149,10 @@ const fetchProtocols = () => { ); }; +const mockHasAddress = (address: string): boolean => { + return address !== ethers.constants.AddressZero; +}; + const WETH_ADDRESS = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"; describe("Victim Identifier tests suite", () => { @@ -174,6 +178,9 @@ describe("Victim Identifier tests suite", () => { address2: createAddress("0x5678"), address2Again: createAddress("0x5678"), }, + hasAddress: (address: string): boolean => { + return false; + }, }, ], pageInfo: { @@ -202,6 +209,7 @@ describe("Victim Identifier tests suite", () => { address2: createAddress("0x5678"), address2Again: createAddress("0x5678"), }, + hasAddress: mockHasAddress, }, ], pageInfo: { @@ -242,6 +250,7 @@ describe("Victim Identifier tests suite", () => { ), address10: createAddress("0x5678"), }, + hasAddress: mockHasAddress, }, ], pageInfo: { @@ -359,6 +368,7 @@ describe("Victim Identifier tests suite", () => { address1again: createAddress("0x1234"), address2: createAddress("0x5678"), }, + hasAddress: mockHasAddress, }, ], pageInfo: { @@ -450,6 +460,7 @@ describe("Victim Identifier tests suite", () => { address1again: createAddress("0x1234"), address2: createAddress("0x5678"), }, + hasAddress: mockHasAddress, }, ], pageInfo: { @@ -532,6 +543,7 @@ describe("Victim Identifier tests suite", () => { address1again: createAddress("0x1234"), address2: createAddress("0x5678"), }, + hasAddress: mockHasAddress, }, ], pageInfo: { @@ -633,6 +645,7 @@ describe("Victim Identifier tests suite", () => { address1again: createAddress("0x1234"), address2: createAddress("0x5678"), }, + hasAddress: mockHasAddress, }, ], pageInfo: { @@ -976,6 +989,7 @@ describe("Victim Identifier tests suite", () => { address1again: createAddress("0x1234"), address2: createAddress("0x5678"), }, + hasAddress: mockHasAddress, }, ], pageInfo: { diff --git a/src/utils/victim-identification/victim.identifier.ts b/src/utils/victim-identification/victim.identifier.ts index 1dff0b4..3be8934 100644 --- a/src/utils/victim-identification/victim.identifier.ts +++ b/src/utils/victim-identification/victim.identifier.ts @@ -1,5 +1,5 @@ import { ethers, TransactionEvent, getAlerts } from "forta-agent"; -import { AlertsResponse } from "forta-agent/dist/sdk/graphql/forta"; +import { AlertsResponse } from "forta-agent"; import LRU from "lru-cache"; import fetch from "node-fetch"; import AddressesExtractor from "./helpers/addresses.extractor";