diff --git a/package.json b/package.json index b91d9f1..dbf480a 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "dev": "ts-node-dev src/main.ts", "build": " tsc --project tsconfig.json", "postinstall": "$npm_execpath run typechainGif", - "typechainGif": "D=./node_modules/@etherisc/gif-next/artifacts/contracts/ && typechain --target ethers-v6 --out-dir src/generated/contracts/gif $D/product/IApplicationService.sol/IApplicationService.json $D/product/IPolicyService.sol/IPolicyService.json $D/pool/IPoolService.sol/IPoolService.json $D/pool/IBundleService.sol/IBundleService.json $D/oracle/IOracleService.sol/IOracleService.json $D/registry/ChainNft.sol/ChainNft.json $D/registry/IRegistry.sol/IRegistry.json $D/instance/IInstance.sol/IInstance.json $D/instance/IInstanceService.sol/IInstanceService.json $D/instance/InstanceReader.sol/InstanceReader.json $D/instance/module/IPolicy.sol/IPolicy.json $D/instance/module/IRisk.sol/IRisk.json $D/shared/IComponentService.sol/IComponentService.json" + "typechainGif": "D=./node_modules/@etherisc/gif-next/artifacts/contracts/ && typechain --target ethers-v6 --out-dir src/generated/contracts/gif $D/product/IRiskService.sol/IRiskService.json $D/product/IApplicationService.sol/IApplicationService.json $D/product/IPolicyService.sol/IPolicyService.json $D/pool/IPoolService.sol/IPoolService.json $D/pool/IBundleService.sol/IBundleService.json $D/oracle/IOracleService.sol/IOracleService.json $D/registry/ChainNft.sol/ChainNft.json $D/registry/IRegistry.sol/IRegistry.json $D/instance/IInstance.sol/IInstance.json $D/instance/IInstanceService.sol/IInstanceService.json $D/instance/InstanceReader.sol/InstanceReader.json $D/instance/module/IPolicy.sol/IPolicy.json $D/instance/module/IRisk.sol/IRisk.json $D/shared/IComponentService.sol/IComponentService.json" }, "author": "", "license": "Apache-2.0", diff --git a/prisma/migrations/20241119154054_init/migration.sql b/prisma/migrations/20241119154054_init/migration.sql new file mode 100644 index 0000000..af92654 --- /dev/null +++ b/prisma/migrations/20241119154054_init/migration.sql @@ -0,0 +1,17 @@ +-- CreateTable +CREATE TABLE "Risk" ( + "productNftId" BIGINT NOT NULL, + "riskId" TEXT NOT NULL, + "locked" BOOLEAN NOT NULL DEFAULT false, + "closed" BOOLEAN NOT NULL DEFAULT false, + "created_blockNumber" INTEGER NOT NULL, + "created_timestamp" BIGINT NOT NULL, + "created_txHash" TEXT NOT NULL, + "created_from" TEXT NOT NULL, + "modified_blockNumber" INTEGER NOT NULL, + "modified_timestamp" BIGINT NOT NULL, + "modified_txHash" TEXT NOT NULL, + "modified_from" TEXT NOT NULL, + + CONSTRAINT "Risk_pkey" PRIMARY KEY ("productNftId","riskId") +); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2f2727a..428a69e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -69,3 +69,21 @@ model Component { modified_txHash String modified_from String } + +model Risk { + productNftId BigInt + riskId String + locked Boolean @default(false) + closed Boolean @default(false) + created_blockNumber Int + created_timestamp BigInt + created_txHash String + created_from String + modified_blockNumber Int + modified_timestamp BigInt + modified_txHash String + modified_from String + + // compound primary key + @@id([productNftId, riskId]) +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 2da5954..d347abb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -14,6 +14,8 @@ import PolicyProcessor from './policy_processor'; import { Policy } from './types/policy'; import ComponentProcessor from './component_processor'; import { Component } from './types/component'; +import RiskProcessor from './risk_processor'; +import { Risk } from './types/risk'; dotenv.config(); @@ -29,6 +31,7 @@ class Main { private instanceProcessor: InstanceProcessor; private policyProcessor: PolicyProcessor; private componentProcessor: ComponentProcessor; + private riskProcessor: RiskProcessor; constructor(prisma: PrismaClient) { this.dune = new DuneApi(); @@ -36,16 +39,18 @@ class Main { this.instanceProcessor = new InstanceProcessor(prisma); this.componentProcessor = new ComponentProcessor(prisma); this.policyProcessor = new PolicyProcessor(prisma); + this.riskProcessor = new RiskProcessor(prisma); } public async main(): Promise { const gifEvents = await this.dune.getLatestResult(DUNE_QUERY_ID_GIF_EVENTS, 0); - const { nfts, instances, policies, components } = await this.parseGifEvents(gifEvents); + const { nfts, instances, policies, components, risks } = await this.parseGifEvents(gifEvents); await this.nftProcessor.persistNfts(Array.from(nfts.values())); await this.instanceProcessor.persistInstances(Array.from(instances.values())); await this.policyProcessor.persistPolicies(Array.from(policies.values())); await this.componentProcessor.persistComponents(Array.from(components.values())); + await this.riskProcessor.persistRisks(Array.from(risks.values())); for (const nft of nfts.values()) { logger.info(`NFT: ${nft.nftId} - ${ObjectType[nft.objectType]} - ${nft.objectAddress} - ${nft.owner}`); @@ -65,12 +70,14 @@ class Main { nfts: Map, instances: Map, policies: Map, - components: Map + components: Map, + risks: Map, }> { const nfts = new Map(); const instances = new Map(); const components = new Map(); + const risks = new Map(); const policies = new Map(); for (const event of gifEvents) { @@ -88,7 +95,14 @@ class Main { break; case 'LogComponentServiceRegistered': await this.componentProcessor.processComponentRegisteredEvent(event, components); - break; + break; + case 'LogRiskServiceRiskCreated': + await this.riskProcessor.processRiskCreatedEvent(event, risks); + break; + // TODO: LogRiskServiceRiskUpdated + // TODO: LogRiskServiceRiskLocked + // TODO: LogRiskServiceRiskUnlocked + // TODO: LogRiskServiceRiskClosed case 'LogApplicationServiceApplicationCreated': await this.policyProcessor.processApplicationCreatedEvent(event, policies); break; @@ -110,7 +124,7 @@ class Main { } } - return { nfts, instances, policies, components }; + return { nfts, instances, policies, components, risks }; } } diff --git a/src/risk_processor.ts b/src/risk_processor.ts new file mode 100644 index 0000000..91eb190 --- /dev/null +++ b/src/risk_processor.ts @@ -0,0 +1,101 @@ +import { PrismaClient } from "@prisma/client"; +import { IRiskService__factory } from "./generated/contracts/gif"; +import { logger } from "./logger"; +import { DecodedLogEntry } from "./types/logdata"; +import { Risk } from "./types/risk"; + +export default class RiskProcessor { + private prisma: PrismaClient; + + constructor(prisma: PrismaClient) { + this.prisma = prisma; + } + + async persistRisks(risks: Risk[]): Promise { + for (const risk of risks) { + await this.prisma.risk.upsert({ + where: { productNftId_riskId: { productNftId: risk.productNftId as bigint, riskId: risk.riskId } }, + update: { + locked: risk.locked, + closed: risk.closed, + modified_blockNumber: risk.modified.blockNumber, + modified_timestamp: risk.modified.timestamp as bigint, + modified_txHash: risk.modified.txHash, + modified_from: risk.modified.from + }, + create: { + productNftId: risk.productNftId as bigint, + riskId: risk.riskId, + locked: risk.locked, + closed: risk.closed, + created_blockNumber: risk.created.blockNumber, + created_timestamp: risk.created.timestamp as bigint, + created_txHash: risk.created.txHash, + created_from: risk.created.from, + modified_blockNumber: risk.modified.blockNumber, + modified_timestamp: risk.modified.timestamp as bigint, + modified_txHash: risk.modified.txHash, + modified_from: risk.modified.from + } + }); + } + } + + async processRiskCreatedEvent(event: DecodedLogEntry, risks: Map): Promise> { + if (event.event_name !== 'LogRiskServiceRiskCreated') { + throw new Error(`Invalid event type ${event.event_name}`); + } + + logger.info(`Processing risk creation event ${event.tx_hash} - ${event.event_name} - ${event.data}`); + const data = this.decodeRiskServiceEvent(event); + if (data === null || data === undefined) { + logger.error(`Failed to decode event ${event.tx_hash} - ${event.event_name} - ${event.data}`); + return risks; + } + if (data.name !== 'LogRiskServiceRiskCreated') { + throw new Error(`Invalid event name ${data.name}`); + } + + const productNftId = data.args[0] as BigInt; + const riskId = data.args[1] as string; + const key = `${productNftId}_${riskId}`; + + const risk = { + productNftId, + riskId, + locked: false, + closed: false, + created: { + blockNumber: event.block_number, + timestamp:BigInt(new Date(event.block_time).getTime()), + txHash: event.tx_hash, + from: event.tx_from + }, + modified: { + blockNumber: event.block_number, + timestamp:BigInt(new Date(event.block_time).getTime()), + txHash: event.tx_hash, + from: event.tx_from + } + } as Risk; + risks.set(key, risk); + return risks; + } + + decodeRiskServiceEvent(event: DecodedLogEntry) { + const topic0 = event.topic0; + let topic1 = event.topic1; + if (topic1 === null || topic1 === undefined || topic1 === '') { + topic1 = '0x'; + } + let topic2 = event.topic2; + if (topic2 === null || topic2 === undefined || topic2 === '') { + topic2 = '0x'; + } + let topic3 = event.topic3; + if (topic3 === null || topic3 === undefined || topic3 === '') { + topic3 = '0x'; + } + return IRiskService__factory.createInterface().parseLog({ topics: [topic0, topic1, topic2, topic3], data: event.data }); + } +} \ No newline at end of file diff --git a/src/types/risk.ts b/src/types/risk.ts new file mode 100644 index 0000000..ea3509e --- /dev/null +++ b/src/types/risk.ts @@ -0,0 +1,20 @@ +import { ObjectType } from "./objecttype"; + +export interface Risk { + productNftId: BigInt; + riskId: string; + locked: boolean; + closed: boolean; + created: { + blockNumber: number; + timestamp: BigInt; + txHash: string; + from: string; + } + modified: { + blockNumber: number; + timestamp: BigInt; + txHash: string; + from: string; + } +}