added agent

This commit is contained in:
bhavik 2021-11-30 04:06:33 +05:30
parent d715ee7896
commit 0192090d09
53 changed files with 65925 additions and 1 deletions

View File

@ -0,0 +1,3 @@
node_modules
dist
forta.config.json

3
Connector_agent/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules
dist
forta.config.json

View 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
View 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

View File

@ -0,0 +1,5 @@
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
testPathIgnorePatterns: ["dist"],
};

11792
Connector_agent/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View 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"
}
}

View 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,
}),
])
})
})
})

View 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,
}

View 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',
}

View 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. */
}
}

View File

@ -0,0 +1,3 @@
node_modules
dist
forta.config.json

3
Governance_agent/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules
dist
forta.config.json

View 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" ]

View 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

View File

@ -0,0 +1,5 @@
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
testPathIgnorePatterns: ["dist"],
};

11792
Governance_agent/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View 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"
}
}

View 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,
}),
])
})
})
})

View 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,
}

View 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,
}

View 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. */
}
}

View File

@ -0,0 +1,3 @@
node_modules
dist
forta.config.json

3
INST_Tracking_agent/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules
dist
forta.config.json

View 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" ]

View 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

File diff suppressed because it is too large Load Diff

View 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"
}
}

View 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),
};

View 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
);
});
});

View 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,
};

View File

@ -0,0 +1,3 @@
node_modules
dist
forta.config.json

3
Implementation _agent/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules
dist
forta.config.json

View 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" ]

View 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

View File

@ -0,0 +1,5 @@
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
testPathIgnorePatterns: ["dist"],
};

12341
Implementation _agent/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View 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"
}
}

View 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,
}),
])
})
})
})

View 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,
}

View 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,
}

View 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,
}

View 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. */
}
}

View File

@ -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>

View File

@ -0,0 +1,3 @@
node_modules
dist
forta.config.json

3
Token_Tracking_agent/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules
dist
forta.config.json

View 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" ]

View 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

File diff suppressed because it is too large Load Diff

View 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"
}
}

View 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"
}
]

View 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),
};

View 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
};