mirror of
https://github.com/Instadapp/Forta-Agents.git
synced 2024-07-29 21:47:22 +00:00
added agent
This commit is contained in:
parent
d715ee7896
commit
0192090d09
3
Connector_agent/.dockerignore
Normal file
3
Connector_agent/.dockerignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
dist
|
||||
forta.config.json
|
3
Connector_agent/.gitignore
vendored
Normal file
3
Connector_agent/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
dist
|
||||
forta.config.json
|
21
Connector_agent/Dockerfile
Normal file
21
Connector_agent/Dockerfile
Normal file
|
@ -0,0 +1,21 @@
|
|||
# Build stage: compile Typescript to Javascript
|
||||
FROM node:12-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN npm ci
|
||||
RUN npm run build
|
||||
# obfuscate compiled Javascript (optional)
|
||||
# RUN npm install -g javascript-obfuscator
|
||||
# RUN javascript-obfuscator ./dist --output ./obfuscated --split-strings true --split-strings-chunk-length 3
|
||||
|
||||
# Final stage: copy compiled Javascript from previous stage and install production dependencies
|
||||
FROM node:12-alpine
|
||||
ENV NODE_ENV=production
|
||||
WORKDIR /app
|
||||
# if using obfuscated code:
|
||||
# COPY --from=builder /app/obfuscated ./src
|
||||
# else if using unobfuscated code:
|
||||
COPY --from=builder /app/dist ./src
|
||||
COPY package*.json ./
|
||||
RUN npm ci --production
|
||||
CMD [ "npm", "run", "start:prod" ]
|
11
Connector_agent/README.md
Normal file
11
Connector_agent/README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Connector Contract Event Agent
|
||||
|
||||
## Description
|
||||
|
||||
Track these 3 events : added, update & removed.
|
||||
|
||||
Agent reports two type of findings;
|
||||
|
||||
1. Successed Transactions: Successful transactions
|
||||
2. Failed Transactions: Failed transactions
|
||||
|
5
Connector_agent/jest.config.js
Normal file
5
Connector_agent/jest.config.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "node",
|
||||
testPathIgnorePatterns: ["dist"],
|
||||
};
|
11792
Connector_agent/package-lock.json
generated
Normal file
11792
Connector_agent/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
34
Connector_agent/package.json
Normal file
34
Connector_agent/package.json
Normal file
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "forta-agent-starter",
|
||||
"version": "0.0.1",
|
||||
"description": "Forta Agent Typescript starter project",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "npm run start:dev",
|
||||
"start:dev": "nodemon --watch src --watch forta.config.json -e js,ts,json --exec \"npm run build && forta-agent run\"",
|
||||
"start:prod": "forta-agent run --prod",
|
||||
"tx": "npm run build && forta-agent run --tx",
|
||||
"block": "npm run build && forta-agent run --block",
|
||||
"range": "npm run build && forta-agent run --range",
|
||||
"file": "npm run build && forta-agent run --file",
|
||||
"publish": "forta-agent publish",
|
||||
"push": "forta-agent push",
|
||||
"disable": "forta-agent disable",
|
||||
"enable": "forta-agent enable",
|
||||
"keyfile": "forta-agent keyfile",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"bignumber.js": "^9.0.1",
|
||||
"forta-agent": "^0.0.32",
|
||||
"keccak256": "^1.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/nodemon": "^1.19.0",
|
||||
"jest": "^27.0.6",
|
||||
"nodemon": "^2.0.8",
|
||||
"ts-jest": "^27.0.3",
|
||||
"typescript": "^4.3.4"
|
||||
}
|
||||
}
|
251
Connector_agent/src/agent.spec.ts
Normal file
251
Connector_agent/src/agent.spec.ts
Normal file
|
@ -0,0 +1,251 @@
|
|||
import {
|
||||
TransactionEvent,
|
||||
FindingType,
|
||||
FindingSeverity,
|
||||
Finding,
|
||||
EventType,
|
||||
Network,
|
||||
HandleTransaction,
|
||||
} from 'forta-agent'
|
||||
|
||||
import agent from './agent'
|
||||
import {
|
||||
INSTADAPP_CONNECTOR_ADDRESS,
|
||||
UPDATED_CONNECTOR,
|
||||
ADDED_CONNECTOR,
|
||||
REMOVED_CONNECTOR,
|
||||
generateHash,
|
||||
} from './utils'
|
||||
|
||||
describe('Detect Instadapp Connector Contract Event', () => {
|
||||
let handleTransaction: HandleTransaction
|
||||
|
||||
const createTxEvent = ({
|
||||
logs,
|
||||
addresses,
|
||||
status = true,
|
||||
}: any): TransactionEvent => {
|
||||
const tx: any = {}
|
||||
const receipt: any = { logs, status }
|
||||
const block: any = {}
|
||||
const address: any = { ...addresses }
|
||||
|
||||
return new TransactionEvent(
|
||||
EventType.BLOCK,
|
||||
Network.MAINNET,
|
||||
tx,
|
||||
receipt,
|
||||
[],
|
||||
address,
|
||||
block
|
||||
)
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
handleTransaction = agent.handleTransaction
|
||||
})
|
||||
|
||||
describe('Handle Transaction', () => {
|
||||
it('should return empty finding', async () => {
|
||||
const GovEvent = {
|
||||
topics: [],
|
||||
address: INSTADAPP_CONNECTOR_ADDRESS,
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
})
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([])
|
||||
})
|
||||
|
||||
it('should return empty finding - wrong address', async () => {
|
||||
const topicHash: string = generateHash(ADDED_CONNECTOR)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: '0x01',
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
})
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([])
|
||||
})
|
||||
|
||||
it('should return empty finding - empty address', async () => {
|
||||
const topicHash: string = generateHash(ADDED_CONNECTOR)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: '',
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
})
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([])
|
||||
})
|
||||
|
||||
|
||||
})
|
||||
|
||||
describe('Successed Connector Transactions', () => {
|
||||
it('should return Added Connector Event finding', async () => {
|
||||
const topicHash: string = generateHash(ADDED_CONNECTOR)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: INSTADAPP_CONNECTOR_ADDRESS,
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
})
|
||||
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([
|
||||
Finding.fromObject({
|
||||
name: 'INSTADAPP CONNECTOR EVENT',
|
||||
description: `Instadapp ADDED Connector Event is detected.`,
|
||||
alertId: 'INSTADAPP-16',
|
||||
protocol: 'INSTADAPP',
|
||||
type: FindingType.Unknown,
|
||||
severity: FindingSeverity.Info,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('should return Updated Connector Event finding', async () => {
|
||||
const topicHash: string = generateHash(UPDATED_CONNECTOR)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: INSTADAPP_CONNECTOR_ADDRESS,
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
})
|
||||
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([
|
||||
Finding.fromObject({
|
||||
name: 'INSTADAPP CONNECTOR EVENT',
|
||||
description: `Instadapp UPDATED Connector Event is detected.`,
|
||||
alertId: 'INSTADAPP-16',
|
||||
protocol: 'INSTADAPP',
|
||||
type: FindingType.Unknown,
|
||||
severity: FindingSeverity.Info,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('should return Removed Connector Event finding', async () => {
|
||||
const topicHash: string = generateHash(REMOVED_CONNECTOR)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: INSTADAPP_CONNECTOR_ADDRESS,
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
})
|
||||
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([
|
||||
Finding.fromObject({
|
||||
name: 'INSTADAPP CONNECTOR EVENT',
|
||||
description: `Instadapp REMOVED Connector Event is detected.`,
|
||||
alertId: 'INSTADAPP-16',
|
||||
protocol: 'INSTADAPP',
|
||||
type: FindingType.Unknown,
|
||||
severity: FindingSeverity.Info,
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Failed connector Transactions', () => {
|
||||
it('should return Failed Added Connector Event finding', async () => {
|
||||
const topicHash: string = generateHash(ADDED_CONNECTOR)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: INSTADAPP_CONNECTOR_ADDRESS,
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
status: false,
|
||||
})
|
||||
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([
|
||||
Finding.fromObject({
|
||||
name: 'INSTADAPP CONNECTOR EVENT',
|
||||
description: `Instadapp Failed ADDED Connector event is detected.`,
|
||||
alertId: 'INSTADAPP-15',
|
||||
protocol: 'INSTADAPP',
|
||||
type: FindingType.Suspicious,
|
||||
severity: FindingSeverity.High,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('should return Failed Updated Connector Event finding', async () => {
|
||||
const topicHash: string = generateHash(UPDATED_CONNECTOR)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: INSTADAPP_CONNECTOR_ADDRESS,
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
status: false,
|
||||
})
|
||||
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([
|
||||
Finding.fromObject({
|
||||
name: 'INSTADAPP CONNECTOR EVENT',
|
||||
description: `Instadapp Failed UPDATED Connector event is detected.`,
|
||||
alertId: 'INSTADAPP-15',
|
||||
protocol: 'INSTADAPP',
|
||||
type: FindingType.Suspicious,
|
||||
severity: FindingSeverity.High,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('should return Failed Removed Connector Event finding', async () => {
|
||||
const topicHash: string = generateHash(REMOVED_CONNECTOR)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: INSTADAPP_CONNECTOR_ADDRESS,
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
status: false,
|
||||
})
|
||||
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([
|
||||
Finding.fromObject({
|
||||
name: 'INSTADAPP CONNECTOR EVENT',
|
||||
description: `Instadapp Failed REMOVED Connector event is detected.`,
|
||||
alertId: 'INSTADAPP-15',
|
||||
protocol: 'INSTADAPP',
|
||||
type: FindingType.Suspicious,
|
||||
severity: FindingSeverity.High,
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
59
Connector_agent/src/agent.ts
Normal file
59
Connector_agent/src/agent.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
import BigNumber from 'bignumber.js'
|
||||
import {
|
||||
Finding,
|
||||
HandleTransaction,
|
||||
TransactionEvent,
|
||||
FindingSeverity,
|
||||
FindingType,
|
||||
} from 'forta-agent'
|
||||
|
||||
import { INSTADAPP_CONNECTOR_ADDRESS, Sigs } from './utils'
|
||||
|
||||
const handleTransaction: HandleTransaction = async (
|
||||
txEvent: TransactionEvent
|
||||
) => {
|
||||
const findings: Finding[] = []
|
||||
|
||||
// iterating through the event signatures
|
||||
for (let sig in Sigs) {
|
||||
|
||||
// filtering the transaction events
|
||||
const logs = txEvent.filterEvent(Sigs[sig], INSTADAPP_CONNECTOR_ADDRESS)
|
||||
|
||||
// if no events found then continue
|
||||
if (!logs.length) continue;
|
||||
|
||||
// failed transaction
|
||||
if (!txEvent.status) {
|
||||
findings.push(
|
||||
Finding.fromObject({
|
||||
name: 'INSTADAPP CONNECTOR EVENT',
|
||||
description: `Instadapp Failed ${sig} Connector event is detected.`,
|
||||
alertId: 'INSTADAPP-15',
|
||||
protocol: 'INSTADAPP',
|
||||
type: FindingType.Suspicious,
|
||||
severity: FindingSeverity.High,
|
||||
})
|
||||
)
|
||||
}
|
||||
// successful transaction
|
||||
else {
|
||||
findings.push(
|
||||
Finding.fromObject({
|
||||
name: 'INSTADAPP CONNECTOR EVENT',
|
||||
description: `Instadapp ${sig} Connector Event is detected.`,
|
||||
alertId: 'INSTADAPP-16',
|
||||
protocol: 'INSTADAPP',
|
||||
type: FindingType.Unknown,
|
||||
severity: FindingSeverity.Info,
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return findings
|
||||
}
|
||||
|
||||
export default {
|
||||
handleTransaction,
|
||||
}
|
31
Connector_agent/src/utils.ts
Normal file
31
Connector_agent/src/utils.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import keccak256 from 'keccak256'
|
||||
|
||||
export const INSTADAPP_CONNECTOR_ADDRESS =
|
||||
'0x97b0B3A8bDeFE8cB9563a3c610019Ad10DB8aD11'
|
||||
|
||||
export const UPDATED_CONNECTOR =
|
||||
'LogConnectorUpdated(bytes32,string,address,address)'
|
||||
|
||||
export const ADDED_CONNECTOR =
|
||||
'LogConnectorAdded(bytes32,string,address)'
|
||||
|
||||
export const REMOVED_CONNECTOR =
|
||||
'LogConnectorRemoved(bytes32,string,address)'
|
||||
|
||||
|
||||
export const generateHash = (signature: string): string => {
|
||||
const hash = keccak256(signature).toString('hex')
|
||||
return '0x' + hash
|
||||
}
|
||||
|
||||
export const Sigs: any = {
|
||||
UPDATED: UPDATED_CONNECTOR,
|
||||
ADDED: ADDED_CONNECTOR,
|
||||
REMOVED: REMOVED_CONNECTOR,
|
||||
}
|
||||
|
||||
export enum TOPICS {
|
||||
UPDATED = 'UPDATED',
|
||||
ADDED = 'ADDED',
|
||||
REMOVED = 'REMOVED',
|
||||
}
|
72
Connector_agent/tsconfig.json
Normal file
72
Connector_agent/tsconfig.json
Normal file
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "dist", /* Redirect output structure to the directory. */
|
||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
"baseUrl": ".", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
|
||||
/* Advanced Options */
|
||||
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
}
|
||||
}
|
3
Governance_agent/.dockerignore
Normal file
3
Governance_agent/.dockerignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
dist
|
||||
forta.config.json
|
3
Governance_agent/.gitignore
vendored
Normal file
3
Governance_agent/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
dist
|
||||
forta.config.json
|
21
Governance_agent/Dockerfile
Normal file
21
Governance_agent/Dockerfile
Normal file
|
@ -0,0 +1,21 @@
|
|||
# Build stage: compile Typescript to Javascript
|
||||
FROM node:12-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN npm ci
|
||||
RUN npm run build
|
||||
# obfuscate compiled Javascript (optional)
|
||||
# RUN npm install -g javascript-obfuscator
|
||||
# RUN javascript-obfuscator ./dist --output ./obfuscated --split-strings true --split-strings-chunk-length 3
|
||||
|
||||
# Final stage: copy compiled Javascript from previous stage and install production dependencies
|
||||
FROM node:12-alpine
|
||||
ENV NODE_ENV=production
|
||||
WORKDIR /app
|
||||
# if using obfuscated code:
|
||||
# COPY --from=builder /app/obfuscated ./src
|
||||
# else if using unobfuscated code:
|
||||
COPY --from=builder /app/dist ./src
|
||||
COPY package*.json ./
|
||||
RUN npm ci --production
|
||||
CMD [ "npm", "run", "start:prod" ]
|
12
Governance_agent/README.md
Normal file
12
Governance_agent/README.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Governance Contract Event Agent
|
||||
|
||||
## Description
|
||||
|
||||
Track these 3 events : executed, queued & cancelled.
|
||||
|
||||
Agent reports two type of findings;
|
||||
|
||||
1. Successed Transactions: Successful transactions
|
||||
2. Failed Transactions: Failed transactions
|
||||
|
||||
|
5
Governance_agent/jest.config.js
Normal file
5
Governance_agent/jest.config.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "node",
|
||||
testPathIgnorePatterns: ["dist"],
|
||||
};
|
11792
Governance_agent/package-lock.json
generated
Normal file
11792
Governance_agent/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
34
Governance_agent/package.json
Normal file
34
Governance_agent/package.json
Normal file
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "forta-agent-starter",
|
||||
"version": "0.0.1",
|
||||
"description": "Forta Agent Typescript starter project",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "npm run start:dev",
|
||||
"start:dev": "nodemon --watch src --watch forta.config.json -e js,ts,json --exec \"npm run build && forta-agent run\"",
|
||||
"start:prod": "forta-agent run --prod",
|
||||
"tx": "npm run build && forta-agent run --tx",
|
||||
"block": "npm run build && forta-agent run --block",
|
||||
"range": "npm run build && forta-agent run --range",
|
||||
"file": "npm run build && forta-agent run --file",
|
||||
"publish": "forta-agent publish",
|
||||
"push": "forta-agent push",
|
||||
"disable": "forta-agent disable",
|
||||
"enable": "forta-agent enable",
|
||||
"keyfile": "forta-agent keyfile",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"bignumber.js": "^9.0.1",
|
||||
"forta-agent": "^0.0.32",
|
||||
"keccak256": "^1.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/nodemon": "^1.19.0",
|
||||
"jest": "^27.0.6",
|
||||
"nodemon": "^2.0.8",
|
||||
"ts-jest": "^27.0.3",
|
||||
"typescript": "^4.3.4"
|
||||
}
|
||||
}
|
250
Governance_agent/src/agent.spec.ts
Normal file
250
Governance_agent/src/agent.spec.ts
Normal file
|
@ -0,0 +1,250 @@
|
|||
import {
|
||||
TransactionEvent,
|
||||
FindingType,
|
||||
FindingSeverity,
|
||||
Finding,
|
||||
EventType,
|
||||
Network,
|
||||
HandleTransaction,
|
||||
} from 'forta-agent'
|
||||
|
||||
import agent from './agent'
|
||||
import {
|
||||
INSTADAPP_GOVERNANCE_ADDRESS,
|
||||
PROPOSAL_QUEUED_SIGNATURE,
|
||||
PROPOSAL_EXECUTED_SIGNATURE,
|
||||
PROPOSAL_CANCEL_SIGNATURE,
|
||||
// TOPICS,
|
||||
generateHash,
|
||||
} from './utils'
|
||||
|
||||
describe('Detect Instadapp Governance Event', () => {
|
||||
let handleTransaction: HandleTransaction
|
||||
const createTxEvent = ({
|
||||
logs,
|
||||
addresses,
|
||||
status = true,
|
||||
}: any): TransactionEvent => {
|
||||
const tx: any = {}
|
||||
const receipt: any = { logs, status }
|
||||
const block: any = {}
|
||||
const address: any = { ...addresses }
|
||||
|
||||
return new TransactionEvent(
|
||||
EventType.BLOCK,
|
||||
Network.MAINNET,
|
||||
tx,
|
||||
receipt,
|
||||
[],
|
||||
address,
|
||||
block
|
||||
)
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
handleTransaction = agent.handleTransaction
|
||||
})
|
||||
|
||||
describe('Handle Transaction', () => {
|
||||
it('should return empty finding', async () => {
|
||||
const GovEvent = {
|
||||
topics: [],
|
||||
address: INSTADAPP_GOVERNANCE_ADDRESS,
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
})
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([])
|
||||
})
|
||||
|
||||
it('should return empty finding - wrong address', async () => {
|
||||
const topicHash: string = generateHash(PROPOSAL_EXECUTED_SIGNATURE)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: '0x01',
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
})
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([])
|
||||
})
|
||||
|
||||
it('should return empty finding - empty address', async () => {
|
||||
const topicHash: string = generateHash(PROPOSAL_EXECUTED_SIGNATURE)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: '',
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
})
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Successed Gov Transactions', () => {
|
||||
it('should return QUEUE Proposal Event finding', async () => {
|
||||
const topicHash: string = generateHash(PROPOSAL_QUEUED_SIGNATURE)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: INSTADAPP_GOVERNANCE_ADDRESS,
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
})
|
||||
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([
|
||||
Finding.fromObject({
|
||||
name: 'Instadapp Governance Event',
|
||||
description: `Instadapp QUEUE Proposal Event is detected.`,
|
||||
alertId: 'Instadapp-12',
|
||||
protocol: 'Instadapp',
|
||||
type: FindingType.Unknown,
|
||||
severity: FindingSeverity.Info,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('should return EXECUTE Proposal Event finding', async () => {
|
||||
const topicHash: string = generateHash(PROPOSAL_EXECUTED_SIGNATURE)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: INSTADAPP_GOVERNANCE_ADDRESS,
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
})
|
||||
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([
|
||||
Finding.fromObject({
|
||||
name: 'Instadapp Governance Event',
|
||||
description: `Instadapp EXECUTE Proposal Event is detected.`,
|
||||
alertId: 'Instadapp-12',
|
||||
protocol: 'Instadapp',
|
||||
type: FindingType.Unknown,
|
||||
severity: FindingSeverity.Info,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('should return CANCEL Proposal Event finding', async () => {
|
||||
const topicHash: string = generateHash(PROPOSAL_CANCEL_SIGNATURE)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: INSTADAPP_GOVERNANCE_ADDRESS,
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
})
|
||||
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([
|
||||
Finding.fromObject({
|
||||
name: 'Instadapp Governance Event',
|
||||
description: `Instadapp CANCEL Proposal Event is detected.`,
|
||||
alertId: 'Instadapp-12',
|
||||
protocol: 'Instadapp',
|
||||
type: FindingType.Unknown,
|
||||
severity: FindingSeverity.Info,
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Failed Gov Transactions', () => {
|
||||
|
||||
it('should return Failed QUEUE Proposal Event finding', async () => {
|
||||
const topicHash: string = generateHash(PROPOSAL_QUEUED_SIGNATURE)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: INSTADAPP_GOVERNANCE_ADDRESS,
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
status: false,
|
||||
})
|
||||
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([
|
||||
Finding.fromObject({
|
||||
name: 'Instadapp Governance Event',
|
||||
description: `Instadapp Failed QUEUE Proposal event is detected.`,
|
||||
alertId: 'Instadapp-11',
|
||||
protocol: 'Instadapp',
|
||||
type: FindingType.Suspicious,
|
||||
severity: FindingSeverity.High,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('should return Failed EXECUTE Proposal Event finding', async () => {
|
||||
const topicHash: string = generateHash(PROPOSAL_EXECUTED_SIGNATURE)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: INSTADAPP_GOVERNANCE_ADDRESS,
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
status: false,
|
||||
})
|
||||
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([
|
||||
Finding.fromObject({
|
||||
name: 'Instadapp Governance Event',
|
||||
description: `Instadapp Failed EXECUTE Proposal event is detected.`,
|
||||
alertId: 'Instadapp-11',
|
||||
protocol: 'Instadapp',
|
||||
type: FindingType.Suspicious,
|
||||
severity: FindingSeverity.High,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('should return Failed CANCEL Proposal Event finding', async () => {
|
||||
const topicHash: string = generateHash(PROPOSAL_CANCEL_SIGNATURE)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: INSTADAPP_GOVERNANCE_ADDRESS,
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
status: false,
|
||||
})
|
||||
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([
|
||||
Finding.fromObject({
|
||||
name: 'Instadapp Governance Event',
|
||||
description: `Instadapp Failed CANCEL Proposal event is detected.`,
|
||||
alertId: 'Instadapp-11',
|
||||
protocol: 'Instadapp',
|
||||
type: FindingType.Suspicious,
|
||||
severity: FindingSeverity.High,
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
52
Governance_agent/src/agent.ts
Normal file
52
Governance_agent/src/agent.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import BigNumber from 'bignumber.js'
|
||||
import {
|
||||
Finding,
|
||||
HandleTransaction,
|
||||
TransactionEvent,
|
||||
FindingSeverity,
|
||||
FindingType,
|
||||
} from 'forta-agent'
|
||||
import { INSTADAPP_GOVERNANCE_ADDRESS, Sigs } from './utils'
|
||||
|
||||
const handleTransaction: HandleTransaction = async (
|
||||
txEvent: TransactionEvent
|
||||
) => {
|
||||
const findings: Finding[] = []
|
||||
|
||||
for (let sig in Sigs) {
|
||||
const logs = txEvent.filterEvent(Sigs[sig], INSTADAPP_GOVERNANCE_ADDRESS)
|
||||
|
||||
if (!logs.length) continue
|
||||
|
||||
// console.log(Sigs[sig]);
|
||||
if (!txEvent.status) {
|
||||
findings.push(
|
||||
Finding.fromObject({
|
||||
name: 'Instadapp Governance Event',
|
||||
description: `Instadapp Failed ${sig} Proposal event is detected.`,
|
||||
alertId: 'Instadapp-11',
|
||||
protocol: 'Instadapp',
|
||||
type: FindingType.Suspicious,
|
||||
severity: FindingSeverity.High,
|
||||
})
|
||||
)
|
||||
} else {
|
||||
findings.push(
|
||||
Finding.fromObject({
|
||||
name: 'Instadapp Governance Event',
|
||||
description: `Instadapp ${sig} Proposal Event is detected.`,
|
||||
alertId: 'Instadapp-12',
|
||||
protocol: 'Instadapp',
|
||||
type: FindingType.Unknown,
|
||||
severity: FindingSeverity.Info,
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return findings
|
||||
}
|
||||
|
||||
export default {
|
||||
handleTransaction,
|
||||
}
|
27
Governance_agent/src/utils.ts
Normal file
27
Governance_agent/src/utils.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import keccak256 from 'keccak256'
|
||||
|
||||
export const INSTADAPP_GOVERNANCE_ADDRESS =
|
||||
'0x0204Cd037B2ec03605CFdFe482D8e257C765fA1B'
|
||||
|
||||
|
||||
// An event emitted when a proposal has been canceled
|
||||
export const PROPOSAL_CANCEL_SIGNATURE = 'ProposalCanceled(uint256)'
|
||||
|
||||
// An event emitted when a proposal has been queued
|
||||
export const PROPOSAL_QUEUED_SIGNATURE = 'ProposalQueued(uint256,uint256)'
|
||||
|
||||
// An event emitted when a proposal has been executed
|
||||
export const PROPOSAL_EXECUTED_SIGNATURE = 'ProposalExecuted(uint256)'
|
||||
|
||||
|
||||
export const generateHash = (signature: string): string => {
|
||||
const hash = keccak256(signature).toString('hex')
|
||||
return '0x' + hash
|
||||
}
|
||||
|
||||
export const Sigs: any = {
|
||||
QUEUE: PROPOSAL_QUEUED_SIGNATURE,
|
||||
EXECUTE: PROPOSAL_EXECUTED_SIGNATURE,
|
||||
CANCEL: PROPOSAL_CANCEL_SIGNATURE,
|
||||
}
|
||||
|
72
Governance_agent/tsconfig.json
Normal file
72
Governance_agent/tsconfig.json
Normal file
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "dist", /* Redirect output structure to the directory. */
|
||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
"baseUrl": ".", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
|
||||
/* Advanced Options */
|
||||
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
}
|
||||
}
|
3
INST_Tracking_agent/.dockerignore
Normal file
3
INST_Tracking_agent/.dockerignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
dist
|
||||
forta.config.json
|
3
INST_Tracking_agent/.gitignore
vendored
Normal file
3
INST_Tracking_agent/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
dist
|
||||
forta.config.json
|
18
INST_Tracking_agent/Dockerfile
Normal file
18
INST_Tracking_agent/Dockerfile
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Build stage: obfuscate Javascript (optional)
|
||||
# FROM node:14.15.5-alpine as builder
|
||||
# WORKDIR /app
|
||||
# COPY . .
|
||||
# RUN npm install -g javascript-obfuscator
|
||||
# RUN javascript-obfuscator ./src --output ./dist --split-strings true --split-strings-chunk-length 3
|
||||
|
||||
# Final stage: install production dependencies
|
||||
FROM node:12-alpine
|
||||
ENV NODE_ENV=production
|
||||
WORKDIR /app
|
||||
# if using obfuscated code from build stage:
|
||||
# COPY --from=builder /app/dist ./src
|
||||
# else if using unobfuscated code:
|
||||
COPY ./src ./src
|
||||
COPY package*.json ./
|
||||
RUN npm ci --production
|
||||
CMD [ "npm", "run", "start:prod" ]
|
21
INST_Tracking_agent/README.md
Normal file
21
INST_Tracking_agent/README.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
# Large INST transfer Tracking Agent
|
||||
|
||||
## Description
|
||||
|
||||
This agent Detects Transactions with large INST Transfers (100k)
|
||||
|
||||
## Supported Chains
|
||||
|
||||
- Ethereum
|
||||
|
||||
## Alerts
|
||||
|
||||
- INST-31
|
||||
- Fired when a transaction contains a Transfer event of over 100000 INST
|
||||
- Severity is set to "info"
|
||||
- Type is set to "info"
|
||||
- Metadata
|
||||
- from: sender of token
|
||||
- to: receiver of token
|
||||
- amount: how many token were sent
|
||||
|
11413
INST_Tracking_agent/package-lock.json
generated
Normal file
11413
INST_Tracking_agent/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
INST_Tracking_agent/package.json
Normal file
28
INST_Tracking_agent/package.json
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "inst-agent-example",
|
||||
"version": "0.0.1",
|
||||
"description": "Forta Agent that detects inst transfers",
|
||||
"scripts": {
|
||||
"start": "npm run start:dev",
|
||||
"start:dev": "nodemon --watch src --watch forta.config.json -e js,json --exec \"forta-agent run\"",
|
||||
"start:prod": "forta-agent run --prod",
|
||||
"tx": "forta-agent run --tx",
|
||||
"block": "forta-agent run --block",
|
||||
"range": "forta-agent run --range",
|
||||
"file": "forta-agent run --file",
|
||||
"publish": "forta-agent publish",
|
||||
"push": "forta-agent push",
|
||||
"disable": "forta-agent disable",
|
||||
"enable": "forta-agent enable",
|
||||
"keyfile": "forta-agent keyfile",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"bignumber.js": "^9.0.1",
|
||||
"forta-agent": "^0.0.27"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^27.0.6",
|
||||
"nodemon": "^2.0.8"
|
||||
}
|
||||
}
|
55
INST_Tracking_agent/src/agent.js
Normal file
55
INST_Tracking_agent/src/agent.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
const BigNumber = require("bignumber.js");
|
||||
const { Finding, FindingSeverity, FindingType } = require("forta-agent");
|
||||
const {
|
||||
INST_ADDRESS,
|
||||
INST_DECIMALS,
|
||||
TRANSFER_EVENT,
|
||||
} = require("./constants");
|
||||
|
||||
const AMOUNT_THRESHOLD = "100000"; // 1 million
|
||||
|
||||
function provideHandleTransaction(amountThreshold) {
|
||||
return async function handleTransaction(txEvent) {
|
||||
const findings = [];
|
||||
|
||||
// filter the transaction logs for INST Transfer events
|
||||
const InstTransferEvents = txEvent.filterLog(
|
||||
TRANSFER_EVENT,
|
||||
INST_ADDRESS
|
||||
);
|
||||
|
||||
|
||||
InstTransferEvents.forEach((InstTransfer) => {
|
||||
|
||||
// shift decimal places of transfer amount
|
||||
const amount = new BigNumber(
|
||||
InstTransfer.args.value.toString()
|
||||
).dividedBy(10 ** INST_DECIMALS)
|
||||
|
||||
if (amount.isLessThan(amountThreshold)) return;
|
||||
|
||||
const formattedAmount = amount.toFixed(2);
|
||||
findings.push(
|
||||
Finding.fromObject({
|
||||
name: "Large INST Transfer",
|
||||
description: `${formattedAmount} INST transferred`,
|
||||
alertId: "INST-31",
|
||||
severity: FindingSeverity.Info,
|
||||
type: FindingType.Info,
|
||||
metadata: {
|
||||
from: InstTransfer.args.from,
|
||||
to: InstTransfer.args.to,
|
||||
amount: formattedAmount,
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return findings;
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
provideHandleTransaction,
|
||||
handleTransaction: provideHandleTransaction(AMOUNT_THRESHOLD),
|
||||
};
|
98
INST_Tracking_agent/src/agent.spec.js
Normal file
98
INST_Tracking_agent/src/agent.spec.js
Normal file
|
@ -0,0 +1,98 @@
|
|||
const BigNumber = require("bignumber.js");
|
||||
const { Finding, FindingSeverity, FindingType } = require("forta-agent");
|
||||
const { provideHandleTransaction } = require("./agent");
|
||||
const {
|
||||
INST_ADDRESS,
|
||||
TRANSFER_EVENT,
|
||||
INST_DECIMALS,
|
||||
} = require("./constants");
|
||||
|
||||
describe("large transfer event agent", () => {
|
||||
let handleTransaction;
|
||||
const mockAmountThreshold = "1000";
|
||||
const mockTxEvent = {
|
||||
filterLog: jest.fn(),
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
handleTransaction = provideHandleTransaction(mockAmountThreshold);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
mockTxEvent.filterLog.mockReset();
|
||||
});
|
||||
|
||||
it("returns empty findings if there are no transfer events", async () => {
|
||||
mockTxEvent.filterLog.mockReturnValueOnce([]);
|
||||
|
||||
const findings = await handleTransaction(mockTxEvent);
|
||||
|
||||
expect(findings).toStrictEqual([]);
|
||||
expect(mockTxEvent.filterLog).toHaveBeenCalledTimes(1);
|
||||
expect(mockTxEvent.filterLog).toHaveBeenCalledWith(
|
||||
TRANSFER_EVENT,
|
||||
INST_ADDRESS
|
||||
);
|
||||
});
|
||||
|
||||
it("returns findings if there are large transfer events", async () => {
|
||||
const amount = new BigNumber("1001");
|
||||
// console.log(amount);
|
||||
const formattedAmount = amount.toFixed(2);
|
||||
|
||||
const mockInstTransferEvent = {
|
||||
args: {
|
||||
from: "0x123",
|
||||
to: "0xabc",
|
||||
value: amount.multipliedBy(10 ** INST_DECIMALS),
|
||||
|
||||
},
|
||||
};
|
||||
mockTxEvent.filterLog.mockReturnValueOnce([mockInstTransferEvent]);
|
||||
|
||||
const findings = await handleTransaction(mockTxEvent);
|
||||
|
||||
expect(findings).toStrictEqual([
|
||||
Finding.fromObject({
|
||||
name: "Large INST Transfer",
|
||||
description: `${formattedAmount} INST transferred`,
|
||||
alertId: "INST-31",
|
||||
severity: FindingSeverity.Info,
|
||||
type: FindingType.Info,
|
||||
metadata: {
|
||||
from: mockInstTransferEvent.args.from,
|
||||
to: mockInstTransferEvent.args.to,
|
||||
amount: formattedAmount,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
expect(mockTxEvent.filterLog).toHaveBeenCalledTimes(1);
|
||||
expect(mockTxEvent.filterLog).toHaveBeenCalledWith(
|
||||
TRANSFER_EVENT,
|
||||
INST_ADDRESS
|
||||
);
|
||||
});
|
||||
it("returns empty findings if there are transfer events less than threshold", async () => {
|
||||
const amount = new BigNumber("999");
|
||||
// console.log(amount);
|
||||
const formattedAmount = amount.toFixed(2);
|
||||
|
||||
const mockInstTransferEvent = {
|
||||
args: {
|
||||
from: "0x123",
|
||||
to: "0xabc",
|
||||
value: amount.multipliedBy(10 ** INST_DECIMALS),
|
||||
},
|
||||
};
|
||||
mockTxEvent.filterLog.mockReturnValueOnce([mockInstTransferEvent]);
|
||||
|
||||
const findings = await handleTransaction(mockTxEvent);
|
||||
|
||||
expect(findings).toStrictEqual([]);
|
||||
expect(mockTxEvent.filterLog).toHaveBeenCalledTimes(1);
|
||||
expect(mockTxEvent.filterLog).toHaveBeenCalledWith(
|
||||
TRANSFER_EVENT,
|
||||
INST_ADDRESS
|
||||
);
|
||||
});
|
||||
});
|
11
INST_Tracking_agent/src/constants.js
Normal file
11
INST_Tracking_agent/src/constants.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
const INST_ADDRESS = "0x6f40d4A6237C257fff2dB00FA0510DeEECd303eb";
|
||||
const INST_DECIMALS = 18;
|
||||
const TRANSFER_EVENT =
|
||||
"event Transfer(address indexed from, address indexed to, uint value)";
|
||||
|
||||
|
||||
module.exports = {
|
||||
INST_ADDRESS,
|
||||
INST_DECIMALS,
|
||||
TRANSFER_EVENT,
|
||||
};
|
3
Implementation _agent/.dockerignore
Normal file
3
Implementation _agent/.dockerignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
dist
|
||||
forta.config.json
|
3
Implementation _agent/.gitignore
vendored
Normal file
3
Implementation _agent/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
dist
|
||||
forta.config.json
|
21
Implementation _agent/Dockerfile
Normal file
21
Implementation _agent/Dockerfile
Normal file
|
@ -0,0 +1,21 @@
|
|||
# Build stage: compile Typescript to Javascript
|
||||
FROM node:12-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN npm ci
|
||||
RUN npm run build
|
||||
# obfuscate compiled Javascript (optional)
|
||||
# RUN npm install -g javascript-obfuscator
|
||||
# RUN javascript-obfuscator ./dist --output ./obfuscated --split-strings true --split-strings-chunk-length 3
|
||||
|
||||
# Final stage: copy compiled Javascript from previous stage and install production dependencies
|
||||
FROM node:12-alpine
|
||||
ENV NODE_ENV=production
|
||||
WORKDIR /app
|
||||
# if using obfuscated code:
|
||||
# COPY --from=builder /app/obfuscated ./src
|
||||
# else if using unobfuscated code:
|
||||
COPY --from=builder /app/dist ./src
|
||||
COPY package*.json ./
|
||||
RUN npm ci --production
|
||||
CMD [ "npm", "run", "start:prod" ]
|
11
Implementation _agent/README.md
Normal file
11
Implementation _agent/README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Implementation Contract Event Agent
|
||||
|
||||
## Description
|
||||
|
||||
Track these 3 proposal events : add, remove & setDefault.
|
||||
|
||||
Agent reports two type of findings;
|
||||
|
||||
1. Successed Transactions: Successful transactions
|
||||
2. Failed Transactions: Failed transactions
|
||||
|
5
Implementation _agent/jest.config.js
Normal file
5
Implementation _agent/jest.config.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "node",
|
||||
testPathIgnorePatterns: ["dist"],
|
||||
};
|
12341
Implementation _agent/package-lock.json
generated
Normal file
12341
Implementation _agent/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
Implementation _agent/package.json
Normal file
33
Implementation _agent/package.json
Normal file
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "forta-agent-starter",
|
||||
"version": "0.0.1",
|
||||
"description": "Forta Agent Typescript starter project",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "npm run start:dev",
|
||||
"start:dev": "nodemon --watch src --watch forta.config.json -e js,ts,json --exec \"npm run build && forta-agent run\"",
|
||||
"start:prod": "forta-agent run --prod",
|
||||
"tx": "npm run build && forta-agent run --tx",
|
||||
"block": "npm run build && forta-agent run --block",
|
||||
"range": "npm run build && forta-agent run --range",
|
||||
"file": "npm run build && forta-agent run --file",
|
||||
"publish": "forta-agent publish",
|
||||
"push": "forta-agent push",
|
||||
"disable": "forta-agent disable",
|
||||
"enable": "forta-agent enable",
|
||||
"keyfile": "forta-agent keyfile",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"bignumber.js": "^9.0.1",
|
||||
"forta-agent": "^0.0.32"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/nodemon": "^1.19.0",
|
||||
"jest": "^27.0.6",
|
||||
"nodemon": "^2.0.8",
|
||||
"ts-jest": "^27.0.3",
|
||||
"typescript": "^4.3.4"
|
||||
}
|
||||
}
|
250
Implementation _agent/src/agent.spec.ts
Normal file
250
Implementation _agent/src/agent.spec.ts
Normal file
|
@ -0,0 +1,250 @@
|
|||
import {
|
||||
TransactionEvent,
|
||||
FindingType,
|
||||
FindingSeverity,
|
||||
Finding,
|
||||
EventType,
|
||||
Network,
|
||||
HandleTransaction,
|
||||
} from 'forta-agent'
|
||||
|
||||
import agent from './agent'
|
||||
import {
|
||||
INSTADAPP_IMPLEMENTATION_ADDRESS,
|
||||
SETDEFAULT_IMPLEMENTATION,
|
||||
ADD_IMPLEMENTATION,
|
||||
REMOVE_IMPLEMENTATION,
|
||||
generateHash,
|
||||
} from './utils'
|
||||
|
||||
describe('Detect Instadapp Implementation Contract Event', () => {
|
||||
let handleTransaction: HandleTransaction
|
||||
|
||||
const createTxEvent = ({
|
||||
logs,
|
||||
addresses,
|
||||
status = true,
|
||||
}: any): TransactionEvent => {
|
||||
const tx: any = {}
|
||||
const receipt: any = { logs, status }
|
||||
const block: any = {}
|
||||
const address: any = { ...addresses }
|
||||
|
||||
return new TransactionEvent(
|
||||
EventType.BLOCK,
|
||||
Network.MAINNET,
|
||||
tx,
|
||||
receipt,
|
||||
[],
|
||||
address,
|
||||
block
|
||||
)
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
handleTransaction = agent.handleTransaction
|
||||
})
|
||||
|
||||
describe('Handle Transaction', () => {
|
||||
it('should return empty finding', async () => {
|
||||
const GovEvent = {
|
||||
topics: [],
|
||||
address: INSTADAPP_IMPLEMENTATION_ADDRESS,
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
})
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([])
|
||||
})
|
||||
|
||||
it('should return empty finding - wrong address', async () => {
|
||||
const topicHash: string = generateHash(ADD_IMPLEMENTATION)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: '0x01',
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
})
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([])
|
||||
})
|
||||
|
||||
it('should return empty finding - empty address', async () => {
|
||||
const topicHash: string = generateHash(ADD_IMPLEMENTATION)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: '',
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
})
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Successed Implementation Transactions', () => {
|
||||
it('should return SETDEFAULT Event finding', async () => {
|
||||
const topicHash: string = generateHash(SETDEFAULT_IMPLEMENTATION)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: INSTADAPP_IMPLEMENTATION_ADDRESS,
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
})
|
||||
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([
|
||||
Finding.fromObject({
|
||||
name: 'Instadapp Implementation EVENT',
|
||||
description: `Instadapp SETDEFAULT Implementation Event is detected.`,
|
||||
alertId: 'Instadapp-14',
|
||||
protocol: 'Instadapp',
|
||||
type: FindingType.Unknown,
|
||||
severity: FindingSeverity.Info,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('should return ADD Event finding', async () => {
|
||||
const topicHash: string = generateHash(ADD_IMPLEMENTATION)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: INSTADAPP_IMPLEMENTATION_ADDRESS,
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
})
|
||||
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([
|
||||
Finding.fromObject({
|
||||
name: 'Instadapp Implementation EVENT',
|
||||
description: `Instadapp ADD Implementation Event is detected.`,
|
||||
alertId: 'Instadapp-14',
|
||||
protocol: 'Instadapp',
|
||||
type: FindingType.Unknown,
|
||||
severity: FindingSeverity.Info,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('should return REMOVE Event finding', async () => {
|
||||
const topicHash: string = generateHash(REMOVE_IMPLEMENTATION)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: INSTADAPP_IMPLEMENTATION_ADDRESS,
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
})
|
||||
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([
|
||||
Finding.fromObject({
|
||||
name: 'Instadapp Implementation EVENT',
|
||||
description: `Instadapp REMOVE Implementation Event is detected.`,
|
||||
alertId: 'Instadapp-14',
|
||||
protocol: 'Instadapp',
|
||||
type: FindingType.Unknown,
|
||||
severity: FindingSeverity.Info,
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Failed implementation Transactions', () => {
|
||||
it('should return Failed SETDEFAULT Implementation Event finding', async () => {
|
||||
const topicHash: string = generateHash(SETDEFAULT_IMPLEMENTATION)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: INSTADAPP_IMPLEMENTATION_ADDRESS,
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
status: false,
|
||||
})
|
||||
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([
|
||||
Finding.fromObject({
|
||||
name: 'Instadapp Implementation EVENT',
|
||||
description: `Instadapp Failed SETDEFAULT Implementation event is detected.`,
|
||||
alertId: 'Instadapp-13',
|
||||
protocol: 'Instadapp',
|
||||
type: FindingType.Suspicious,
|
||||
severity: FindingSeverity.High,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('should return Failed ADD Implementation Event finding', async () => {
|
||||
const topicHash: string = generateHash(ADD_IMPLEMENTATION)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: INSTADAPP_IMPLEMENTATION_ADDRESS,
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
status: false,
|
||||
})
|
||||
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([
|
||||
Finding.fromObject({
|
||||
name: 'Instadapp Implementation EVENT',
|
||||
description: `Instadapp Failed ADD Implementation event is detected.`,
|
||||
alertId: 'Instadapp-13',
|
||||
protocol: 'Instadapp',
|
||||
type: FindingType.Suspicious,
|
||||
severity: FindingSeverity.High,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('should return Failed REMOVE Event finding', async () => {
|
||||
const topicHash: string = generateHash(REMOVE_IMPLEMENTATION)
|
||||
|
||||
const GovEvent = {
|
||||
topics: [topicHash],
|
||||
address: INSTADAPP_IMPLEMENTATION_ADDRESS,
|
||||
}
|
||||
const txEvent = createTxEvent({
|
||||
logs: [GovEvent],
|
||||
status: false,
|
||||
})
|
||||
|
||||
const findings = await handleTransaction(txEvent)
|
||||
|
||||
expect(findings).toStrictEqual([
|
||||
Finding.fromObject({
|
||||
name: 'Instadapp Implementation EVENT',
|
||||
description: `Instadapp Failed REMOVE Implementation event is detected.`,
|
||||
alertId: 'Instadapp-13',
|
||||
protocol: 'Instadapp',
|
||||
type: FindingType.Suspicious,
|
||||
severity: FindingSeverity.High,
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
52
Implementation _agent/src/agent.ts
Normal file
52
Implementation _agent/src/agent.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import BigNumber from 'bignumber.js'
|
||||
import {
|
||||
Finding,
|
||||
HandleTransaction,
|
||||
TransactionEvent,
|
||||
FindingSeverity,
|
||||
FindingType,
|
||||
} from 'forta-agent'
|
||||
import { INSTADAPP_IMPLEMENTATION_ADDRESS, Sigs } from './utils'
|
||||
|
||||
const handleTransaction: HandleTransaction = async (
|
||||
txEvent: TransactionEvent
|
||||
) => {
|
||||
const findings: Finding[] = []
|
||||
|
||||
for (let sig in Sigs) {
|
||||
// console.log(Sigs[sig]);
|
||||
const logs = txEvent.filterEvent(Sigs[sig], INSTADAPP_IMPLEMENTATION_ADDRESS)
|
||||
// console.log(txEvent);
|
||||
if (!logs.length) continue
|
||||
|
||||
if (!txEvent.status) {
|
||||
findings.push(
|
||||
Finding.fromObject({
|
||||
name: 'Instadapp Implementation EVENT',
|
||||
description: `Instadapp Failed ${sig} Implementation event is detected.`,
|
||||
alertId: 'Instadapp-13',
|
||||
protocol: 'Instadapp',
|
||||
type: FindingType.Suspicious,
|
||||
severity: FindingSeverity.High,
|
||||
})
|
||||
)
|
||||
} else {
|
||||
findings.push(
|
||||
Finding.fromObject({
|
||||
name: 'Instadapp Implementation EVENT',
|
||||
description: `Instadapp ${sig} Implementation Event is detected.`,
|
||||
alertId: 'Instadapp-14',
|
||||
protocol: 'Instadapp',
|
||||
type: FindingType.Unknown,
|
||||
severity: FindingSeverity.Info,
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return findings
|
||||
}
|
||||
|
||||
export default {
|
||||
handleTransaction,
|
||||
}
|
51
Implementation _agent/src/index.ts
Normal file
51
Implementation _agent/src/index.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import BigNumber from 'bignumber.js'
|
||||
import {
|
||||
Finding,
|
||||
HandleTransaction,
|
||||
TransactionEvent,
|
||||
FindingSeverity,
|
||||
FindingType,
|
||||
} from 'forta-agent'
|
||||
import { INSTADAPP_IMPLEMENTATION_ADDRESS, Sigs } from './utils'
|
||||
|
||||
const handleTransaction: HandleTransaction = async (
|
||||
txEvent: TransactionEvent
|
||||
) => {
|
||||
const findings: Finding[] = []
|
||||
|
||||
for (let sig in Sigs) {
|
||||
const logs = txEvent.filterEvent(Sigs[sig], INSTADAPP_IMPLEMENTATION_ADDRESS)
|
||||
|
||||
if (!logs.length) continue
|
||||
|
||||
if (!txEvent.status) {
|
||||
findings.push(
|
||||
Finding.fromObject({
|
||||
name: 'INSTADAPP IMPLEMENTATION EVENT',
|
||||
description: `INSTADAPP Failed ${sig} Implementation event is detected.`,
|
||||
alertId: 'INSTADAPP-13',
|
||||
protocol: 'INSTADAPP',
|
||||
type: FindingType.Suspicious,
|
||||
severity: FindingSeverity.High,
|
||||
})
|
||||
)
|
||||
} else {
|
||||
findings.push(
|
||||
Finding.fromObject({
|
||||
name: 'INSTADAPP IMPLEMENTATION EVENT',
|
||||
description: `INSTADAPP ${sig} Implementation Event is detected.`,
|
||||
alertId: 'INSTADAPP-14',
|
||||
protocol: 'INSTADAPP',
|
||||
type: FindingType.Unknown,
|
||||
severity: FindingSeverity.Info,
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return findings
|
||||
}
|
||||
|
||||
export default {
|
||||
handleTransaction,
|
||||
}
|
26
Implementation _agent/src/utils.ts
Normal file
26
Implementation _agent/src/utils.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import keccak256 from 'keccak256'
|
||||
|
||||
export const INSTADAPP_IMPLEMENTATION_ADDRESS =
|
||||
'0xCBA828153d3a85b30B5b912e1f2daCac5816aE9D'
|
||||
|
||||
export const SETDEFAULT_IMPLEMENTATION =
|
||||
'LogSetDefaultImplementation(address,address)'
|
||||
|
||||
export const ADD_IMPLEMENTATION =
|
||||
'LogAddImplementation(address,bytes4[])'
|
||||
|
||||
export const REMOVE_IMPLEMENTATION =
|
||||
'LogRemoveImplementation(address,bytes4[])'
|
||||
|
||||
|
||||
export const generateHash = (signature: string): string => {
|
||||
const hash = keccak256(signature).toString('hex')
|
||||
return '0x' + hash
|
||||
}
|
||||
|
||||
export const Sigs: any = {
|
||||
SETDEFAULT: SETDEFAULT_IMPLEMENTATION,
|
||||
ADD: ADD_IMPLEMENTATION,
|
||||
REMOVE: REMOVE_IMPLEMENTATION,
|
||||
}
|
||||
|
72
Implementation _agent/tsconfig.json
Normal file
72
Implementation _agent/tsconfig.json
Normal file
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "dist", /* Redirect output structure to the directory. */
|
||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
"baseUrl": ".", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
|
||||
/* Advanced Options */
|
||||
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
}
|
||||
}
|
28
README.md
28
README.md
|
@ -1 +1,27 @@
|
|||
# Forta-Agents
|
||||
# Instadapp-forta-agents
|
||||
|
||||
## Agent List
|
||||
|
||||
Connector Contract Event Agent : Track these 3 events : add, remove & update.
|
||||
|
||||
Implementation Contract Event Agent : Track these 3 events : add, remove & setDefault.
|
||||
|
||||
Governance Event Agent : Track these 3 proposal events : execute, propose, queue.
|
||||
|
||||
High INST Movements : Detects Transactions with large INST Transfers (100k)
|
||||
|
||||
Token_tracking Agent : High volume of DAI and USDC tokens moving in and out of per DSA ($1M USD, but flexible)
|
||||
|
||||
|
||||
## Run
|
||||
|
||||
Before run the agent to see how it works with real data, specify the JSON-RPC provider in the forta.config.json file. Uncomment the jsonRpcUrl property and set it to a websocket provider (e.g. wss://mainnet.infura.io/ws/v3/) if deploying in production, els use HTTP provider if testing with jest. Then ready to run the agent.
|
||||
|
||||
```
|
||||
npm start
|
||||
```
|
||||
|
||||
## Test Data
|
||||
|
||||
For verifying agent behaviour with transactions -
|
||||
npm run tx <Transaction_Hash>
|
||||
|
|
3
Token_Tracking_agent/.dockerignore
Normal file
3
Token_Tracking_agent/.dockerignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
dist
|
||||
forta.config.json
|
3
Token_Tracking_agent/.gitignore
vendored
Normal file
3
Token_Tracking_agent/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
dist
|
||||
forta.config.json
|
18
Token_Tracking_agent/Dockerfile
Normal file
18
Token_Tracking_agent/Dockerfile
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Build stage: obfuscate Javascript (optional)
|
||||
# FROM node:14.15.5-alpine as builder
|
||||
# WORKDIR /app
|
||||
# COPY . .
|
||||
# RUN npm install -g javascript-obfuscator
|
||||
# RUN javascript-obfuscator ./src --output ./dist --split-strings true --split-strings-chunk-length 3
|
||||
|
||||
# Final stage: install production dependencies
|
||||
FROM node:12-alpine
|
||||
ENV NODE_ENV=production
|
||||
WORKDIR /app
|
||||
# if using obfuscated code from build stage:
|
||||
# COPY --from=builder /app/dist ./src
|
||||
# else if using unobfuscated code:
|
||||
COPY ./src ./src
|
||||
COPY package*.json ./
|
||||
RUN npm ci --production
|
||||
CMD [ "npm", "run", "start:prod" ]
|
26
Token_Tracking_agent/README.md
Normal file
26
Token_Tracking_agent/README.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Token transfer tracking Agent
|
||||
|
||||
## Description
|
||||
|
||||
This agent detects High volume of DAI and USDC tokens moving in and out of per DSA ($1M USD)
|
||||
|
||||
## Supported Chains
|
||||
|
||||
- Ethereum
|
||||
|
||||
## Alerts
|
||||
|
||||
- INST-41
|
||||
- Fired when a transaction contains a Transfer event of over 1,000,000 dollar moving in and out of per dsa
|
||||
- Severity is set to "info"
|
||||
- Type is set to "info"
|
||||
- Metadata
|
||||
- from: sender of token
|
||||
- to: receiver of token
|
||||
- amount: how many token were sent
|
||||
|
||||
## Test Data
|
||||
|
||||
The agent behaviour can be verified with the following transactions (npm run tx <Transaction_Hash>):
|
||||
|
||||
- 0x307504dd7a9193987531f4acc171e4c98651640384b3223a9e24acfb1b8e10fb(large Dai transfer from dsa address)
|
16405
Token_Tracking_agent/package-lock.json
generated
Normal file
16405
Token_Tracking_agent/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
Token_Tracking_agent/package.json
Normal file
30
Token_Tracking_agent/package.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "token-agent-example",
|
||||
"version": "0.0.1",
|
||||
"description": "Forta Agent that detects token transfers",
|
||||
"scripts": {
|
||||
"start": "npm run start:dev",
|
||||
"start:dev": "nodemon --watch src --watch forta.config.json -e js,json --exec \"forta-agent run\"",
|
||||
"start:prod": "forta-agent run --prod",
|
||||
"tx": "forta-agent run --tx",
|
||||
"block": "forta-agent run --block",
|
||||
"range": "forta-agent run --range",
|
||||
"file": "forta-agent run --file",
|
||||
"publish": "forta-agent publish",
|
||||
"push": "forta-agent push",
|
||||
"disable": "forta-agent disable",
|
||||
"enable": "forta-agent enable",
|
||||
"keyfile": "forta-agent keyfile",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"bignumber.js": "^9.0.1",
|
||||
"forta-agent": "^0.0.27",
|
||||
"forta-agent-tools": "^1.0.31",
|
||||
"web3": "^1.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^27.0.6",
|
||||
"nodemon": "^2.0.8"
|
||||
}
|
||||
}
|
221
Token_Tracking_agent/src/abi.json
Normal file
221
Token_Tracking_agent/src/abi.json
Normal file
|
@ -0,0 +1,221 @@
|
|||
[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "",
|
||||
"type": "uint64"
|
||||
}
|
||||
],
|
||||
"name": "accountAddr",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "accountID",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "",
|
||||
"type": "uint64"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "",
|
||||
"type": "uint64"
|
||||
}
|
||||
],
|
||||
"name": "accountLink",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "first",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "last",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "count",
|
||||
"type": "uint64"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "",
|
||||
"type": "uint64"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "accountList",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "prev",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "next",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "accounts",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "",
|
||||
"type": "uint64"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "addAuth",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_account",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "init",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "instaIndex",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "removeAuth",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "userLink",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "first",
|
||||
"type": "uint64"
|
||||
},
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "last",
|
||||
"type": "uint64"
|
||||
},
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "count",
|
||||
"type": "uint64"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "",
|
||||
"type": "uint64"
|
||||
}
|
||||
],
|
||||
"name": "userList",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "prev",
|
||||
"type": "uint64"
|
||||
},
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "next",
|
||||
"type": "uint64"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
87
Token_Tracking_agent/src/agent.js
Normal file
87
Token_Tracking_agent/src/agent.js
Normal file
|
@ -0,0 +1,87 @@
|
|||
|
||||
const BigNumber = require("bignumber.js");
|
||||
const { ethers } = require("ethers");
|
||||
const abi = require("./abi.json");
|
||||
const { Finding, FindingSeverity, FindingType, getEthersProvider } = require("forta-agent");
|
||||
const {
|
||||
tokens,
|
||||
TRANSFER_EVENT,
|
||||
} = require("./constants");
|
||||
|
||||
const ethersProvider = getEthersProvider();
|
||||
|
||||
|
||||
// making instance of contract
|
||||
const instaList = new ethers.Contract("0x4c8a1BEb8a87765788946D6B19C6C6355194AbEb", abi, ethersProvider);
|
||||
|
||||
|
||||
const AMOUNT_THRESHOLD = "1000000"; //100k
|
||||
|
||||
function provideHandleTransaction(amountThreshold) {
|
||||
return async function handleTransaction(txEvent) {
|
||||
|
||||
|
||||
const findings = [];
|
||||
|
||||
for (let token in tokens) {
|
||||
|
||||
const tokenTransferEvents = txEvent.filterLog(
|
||||
TRANSFER_EVENT,
|
||||
tokens[token].address
|
||||
);
|
||||
|
||||
// No transfer event found
|
||||
if (tokenTransferEvents.length == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
for (const tokenTransfer of tokenTransferEvents) {
|
||||
|
||||
const to = tokenTransfer.args.to;
|
||||
const from = tokenTransfer.args.from;
|
||||
|
||||
const from_account = await instaList.accountID(from)
|
||||
const to_account = await instaList.accountID(to)
|
||||
|
||||
const from_account_id = from_account.toNumber();
|
||||
const to_account_id = to_account.toNumber();
|
||||
|
||||
// Not a dsa address if account_id is 0
|
||||
if (from_account_id == 0 && to_account_id == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
const amount = new BigNumber(
|
||||
tokenTransfer.args.value.toString()
|
||||
).dividedBy(10 ** (tokens[token].decimals))
|
||||
|
||||
if (amount.isLessThan(amountThreshold)) return findings;
|
||||
|
||||
const formattedAmount = amount.toFixed(2);
|
||||
|
||||
findings.push(
|
||||
Finding.fromObject({
|
||||
name: `Large ${tokens[token].name} Transfer`,
|
||||
description: `${formattedAmount} ${tokens[token].name} Transferred`,
|
||||
alertId: "INST-41",
|
||||
severity: FindingSeverity.Info,
|
||||
type: FindingType.Info,
|
||||
metadata: {
|
||||
from: tokenTransfer.args.from,
|
||||
to: tokenTransfer.args.to,
|
||||
amount: formattedAmount,
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
}
|
||||
return findings;
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
provideHandleTransaction,
|
||||
handleTransaction: provideHandleTransaction(AMOUNT_THRESHOLD),
|
||||
};
|
34
Token_Tracking_agent/src/constants.js
Normal file
34
Token_Tracking_agent/src/constants.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
|
||||
// const Web3 = require("web3");
|
||||
// import keccak256 from 'keccak256'
|
||||
|
||||
|
||||
const DAI_ADDRESS = "0x6b175474e89094c44da98b954eedeac495271d0f";
|
||||
const USDC_ADDRESS = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
|
||||
const DAI_DECIMALS = 18;
|
||||
const USDC_DECIMALS = 6;
|
||||
const TRANSFER_EVENT =
|
||||
"event Transfer(address indexed from, address indexed to, uint value)";
|
||||
// const Provider = ethers.providers.JsonRpcProvider;
|
||||
|
||||
|
||||
function token(name, address, decimals) {
|
||||
this.address = address;
|
||||
this.decimals = decimals;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
var tokens = [
|
||||
new token("USDC", USDC_ADDRESS, USDC_DECIMALS),
|
||||
new token("DAI", DAI_ADDRESS, DAI_DECIMALS)
|
||||
]
|
||||
|
||||
|
||||
module.exports = {
|
||||
tokens,
|
||||
TRANSFER_EVENT,
|
||||
DAI_ADDRESS,
|
||||
DAI_DECIMALS,
|
||||
USDC_ADDRESS,
|
||||
USDC_DECIMALS
|
||||
};
|
Loading…
Reference in New Issue
Block a user