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