diff --git a/.env b/.env new file mode 100644 index 0000000..792911d --- /dev/null +++ b/.env @@ -0,0 +1,7 @@ +DB_NAME=squid +DB_PASS=squid +DB_PORT=5432 +DB_USERNAME=postgres +DB_HOST=localhost + +SHARED_CONFIG_URL="https://ipfs.io/ipfs/bafkreiasnla2ya55of6nwm3swjstip4q2ixfa3t6tvixyibclfovxnerte" \ No newline at end of file diff --git a/.github/workflows/e2e_testing.yml b/.github/workflows/e2e_testing.yml new file mode 100644 index 0000000..a4330ff --- /dev/null +++ b/.github/workflows/e2e_testing.yml @@ -0,0 +1,34 @@ +name: E2E Tests + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + test: + name: Test + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18.x] + steps: + - name: Checkout code + uses: actions/checkout@v2.4.0 + - name: Install Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v2.5.1 + with: + node-version: ${{ matrix.node-version }} + - name: Run Docker + run: docker compose up --build -d + - name: Wait + run: sleep 80s + - name: Install dependencies + run: yarn install + - name: Run tests + run: yarn run test:e2e + - name: Setup tmate session + if: success() || failure() + uses: mxschmitt/action-tmate@v3 + - name: Clean up Docker containers + if: always() + run: docker compose down \ No newline at end of file diff --git a/commands.json b/commands.json index ba72db9..86a8652 100644 --- a/commands.json +++ b/commands.json @@ -1,98 +1,73 @@ { - "$schema": "https://cdn.subsquid.io/schemas/commands.json", - "commands": { - "clean": { - "description": "delete all build artifacts", - "cmd": ["npx", "--yes", "rimraf", "lib"] - }, - "build": { - "description": "Build the squid project", - "deps": ["clean"], - "cmd": ["tsc"] - }, - "up": { - "description": "Start indexer", - "cmd": [ - "docker", - "compose", - "up", - "-d" - ] - }, - "down": { - "description": "Drop indexer", - "cmd": [ - "docker", - "compose", - "down" - ] - }, - "migration:apply": { - "description": "Apply the DB migrations", - "cmd": ["squid-typeorm-migration", "apply"] - }, - "migration:generate": { - "description": "Generate a DB migration matching the TypeORM entities", - "deps": ["migration:clean"], - "cmd": ["squid-typeorm-migration", "generate"] - }, - "migration:clean": { - "description": "Clean the migrations folder", - "cmd": ["npx", "--yes", "rimraf", "./db/migrations/*.js"] - }, - "migration": { - "cmd": ["squid-typeorm-migration", "generate"], - "hidden": true - }, - "codegen": { - "description": "Generate TypeORM entities from the schema file", - "cmd": ["squid-typeorm-codegen"] - }, - "evm-typegen": { - "description": "Generate data access classes for an ABI file(s) in the ./abi folder", - "cmd": ["squid-evm-typegen", "./src/abi", {"glob": "./src/abi/*.json"}, "--multicall"] - }, - "substrate-typegen": { - "description": "Generate data access classes for an substrate metadata", - "cmd": ["squid-substrate-typegen", "./typegen.json"] - }, - "start-sepolia":{ - "description": "Start Sepolia indexer", - "deps": ["build", "migration:apply"], - "cmd": ["node", "--require=dotenv/config", "lib/main.js", "dotenv_config_path=envs/.env.sepolia"] - }, - "start-holesky":{ - "description": "Start Holesky indexer", - "deps": ["build", "migration:apply"], - "cmd": ["node", "--require=dotenv/config", "lib/main.js", "dotenv_config_path=envs/.env.holesky"] - }, - "process": { - "description": "Load .env and start the squid processor", - "deps": ["build", "migration:apply"], - "cmd": ["node", "--require=dotenv/config", "lib/main.js"] - }, - "start-init": { - "description": "Initialize the database and load config data", - "deps": ["build", "migration:apply"], - "cmd": ["node", "--require=dotenv/config", "lib/main_init.js"] - }, - "process:prod": { - "description": "Start the squid processor", - "cmd": ["node", "lib/main.js"], - "hidden": true - }, - "check-updates": { - "cmd": ["npx", "--yes", "npm-check-updates", "--filter=/subsquid/", "--upgrade"], - "hidden": true - }, - "bump": { - "description": "Bump @subsquid packages to the latest versions", - "deps": ["check-updates"], - "cmd": ["npm", "i", "-f"] - }, - "open": { - "description": "Open a local browser window", - "cmd": ["npx", "--yes", "opener"] - } + "$schema": "https://cdn.subsquid.io/schemas/commands.json", + "commands": { + "clean": { + "description": "delete all build artifacts", + "cmd": ["npx", "--yes", "rimraf", "lib"] + }, + "build": { + "description": "Build the squid project", + "deps": ["clean"], + "cmd": ["tsc"] + }, + "up": { + "description": "Start indexer", + "cmd": [ + "docker", + "compose", + "up", + "-d" + ] + }, + "down": { + "description": "Drop indexer", + "cmd": [ + "docker", + "compose", + "down" + ] + }, + "migration:apply": { + "description": "Apply the DB migrations", + "cmd": ["squid-typeorm-migration", "apply"] + }, + "migration:generate": { + "description": "Generate a DB migration matching the TypeORM entities", + "deps": ["migration:clean"], + "cmd": ["squid-typeorm-migration", "generate"] + }, + "migration:clean": { + "description": "Clean the migrations folder", + "cmd": ["npx", "--yes", "rimraf", "./db/migrations/*.js"] + }, + "migration": { + "cmd": ["squid-typeorm-migration", "generate"], + "hidden": true + }, + "codegen": { + "description": "Generate TypeORM entities from the schema file", + "cmd": ["squid-typeorm-codegen"] + }, + "evm-typegen": { + "description": "Generate data access classes for an ABI file(s) in the ./abi folder", + "cmd": ["squid-evm-typegen", "./src/abi", {"glob": "./src/abi/*.json"}, "--multicall"] + }, + "substrate-typegen": { + "description": "Generate data access classes for an substrate metadata", + "cmd": ["squid-substrate-typegen", "./typegen.json"] + }, + "check-updates": { + "cmd": ["npx", "--yes", "npm-check-updates", "--filter=/subsquid/", "--upgrade"], + "hidden": true + }, + "bump": { + "description": "Bump @subsquid packages to the latest versions", + "deps": ["check-updates"], + "cmd": ["npm", "i", "-f"] + }, + "open": { + "description": "Open a local browser window", + "cmd": ["npx", "--yes", "opener"] } } +} \ No newline at end of file diff --git a/db/migrations/1734354978596-Data.js b/db/migrations/1734354978596-Data.js index 987b337..9925392 100644 --- a/db/migrations/1734354978596-Data.js +++ b/db/migrations/1734354978596-Data.js @@ -1,5 +1,5 @@ -module.exports = class Data1734354978596 { - name = 'Data1734354978596' +module.exports = class Data1734427728342 { + name = 'Data1734427728342' async up(db) { await db.query(`CREATE TABLE "account" ("id" character varying NOT NULL, "address_status" text, CONSTRAINT "PK_54115ee388cdb6d86bb4bf5b2ea" PRIMARY KEY ("id"))`) @@ -7,9 +7,10 @@ module.exports = class Data1734354978596 { await db.query(`CREATE INDEX "IDX_9ced91570695137ec1d60c1a61" ON "deposit" ("account_id") `) await db.query(`CREATE TABLE "execution" ("id" character varying NOT NULL, "tx_hash" text NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE, "block_number" text NOT NULL, "message" text, CONSTRAINT "PK_cc6684fedf29ec4c86db8448a2b" PRIMARY KEY ("id"))`) await db.query(`CREATE TABLE "resource" ("id" character varying NOT NULL, "type" text NOT NULL, CONSTRAINT "PK_e2894a5867e06ae2e8889f1173f" PRIMARY KEY ("id"))`) - await db.query(`CREATE TABLE "route" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "from_domain_id" character varying, "to_domain_id" character varying, "resource_id" text, CONSTRAINT "PK_08affcd076e46415e5821acf52d" PRIMARY KEY ("id"))`) + await db.query(`CREATE TABLE "route" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "from_domain_id" character varying, "to_domain_id" character varying, "resource_id" character varying, CONSTRAINT "PK_08affcd076e46415e5821acf52d" PRIMARY KEY ("id"))`) await db.query(`CREATE INDEX "IDX_cd316229471fecd312159ee307" ON "route" ("from_domain_id") `) await db.query(`CREATE INDEX "IDX_863162a8edb416799e89f386f8" ON "route" ("to_domain_id") `) + await db.query(`CREATE INDEX "IDX_4536bd7b96f363dedfe95dd26f" ON "route" ("resource_id") `) await db.query(`CREATE UNIQUE INDEX "IDX_7dc7af2a7a9c846759377d1450" ON "route" ("from_domain_id", "to_domain_id", "resource_id") `) await db.query(`CREATE TABLE "domain" ("id" character varying NOT NULL, "type" text NOT NULL, "name" text NOT NULL, CONSTRAINT "PK_27e3ec3ea0ae02c8c5bceab3ba9" PRIMARY KEY ("id"))`) await db.query(`CREATE TABLE "token" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "decimals" integer NOT NULL, "token_address" text NOT NULL, "token_symbol" text NOT NULL, "resource_id" character varying, "domain_id" character varying, CONSTRAINT "PK_82fae97f905930df5d62a702fc9" PRIMARY KEY ("id"))`) @@ -28,6 +29,7 @@ module.exports = class Data1734354978596 { await db.query(`ALTER TABLE "deposit" ADD CONSTRAINT "FK_9ced91570695137ec1d60c1a61b" FOREIGN KEY ("account_id") REFERENCES "account"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) await db.query(`ALTER TABLE "route" ADD CONSTRAINT "FK_cd316229471fecd312159ee307e" FOREIGN KEY ("from_domain_id") REFERENCES "domain"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) await db.query(`ALTER TABLE "route" ADD CONSTRAINT "FK_863162a8edb416799e89f386f82" FOREIGN KEY ("to_domain_id") REFERENCES "domain"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "route" ADD CONSTRAINT "FK_4536bd7b96f363dedfe95dd26f1" FOREIGN KEY ("resource_id") REFERENCES "resource"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) await db.query(`ALTER TABLE "token" ADD CONSTRAINT "FK_435ef0917a04e91698042dff2bc" FOREIGN KEY ("resource_id") REFERENCES "resource"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) await db.query(`ALTER TABLE "token" ADD CONSTRAINT "FK_3d565341b16e9c03d63e05eac83" FOREIGN KEY ("domain_id") REFERENCES "domain"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) await db.query(`ALTER TABLE "fee" ADD CONSTRAINT "FK_119250e5caf62c90134b8e2f2d3" FOREIGN KEY ("transfer_id") REFERENCES "transfer"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) @@ -47,6 +49,7 @@ module.exports = class Data1734354978596 { await db.query(`DROP TABLE "route"`) await db.query(`DROP INDEX "public"."IDX_cd316229471fecd312159ee307"`) await db.query(`DROP INDEX "public"."IDX_863162a8edb416799e89f386f8"`) + await db.query(`DROP INDEX "public"."IDX_4536bd7b96f363dedfe95dd26f"`) await db.query(`DROP INDEX "public"."IDX_7dc7af2a7a9c846759377d1450"`) await db.query(`DROP TABLE "domain"`) await db.query(`DROP TABLE "token"`) @@ -65,6 +68,7 @@ module.exports = class Data1734354978596 { await db.query(`ALTER TABLE "deposit" DROP CONSTRAINT "FK_9ced91570695137ec1d60c1a61b"`) await db.query(`ALTER TABLE "route" DROP CONSTRAINT "FK_cd316229471fecd312159ee307e"`) await db.query(`ALTER TABLE "route" DROP CONSTRAINT "FK_863162a8edb416799e89f386f82"`) + await db.query(`ALTER TABLE "route" DROP CONSTRAINT "FK_4536bd7b96f363dedfe95dd26f1"`) await db.query(`ALTER TABLE "token" DROP CONSTRAINT "FK_435ef0917a04e91698042dff2bc"`) await db.query(`ALTER TABLE "token" DROP CONSTRAINT "FK_3d565341b16e9c03d63e05eac83"`) await db.query(`ALTER TABLE "fee" DROP CONSTRAINT "FK_119250e5caf62c90134b8e2f2d3"`) diff --git a/docker-compose.yml b/docker-compose.yml index 34a14b2..61ad5e4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,35 @@ version: "3" services: + + evm1: + image: ghcr.io/sygmaprotocol/indexer-evm1:latest + container_name: evm1 + command: ganache-cli --chainId 1337 -d --db data/ --blockTime 2 > /dev/null + logging: + driver: none + ports: + - "8545:8545" + + evm2: + image: ghcr.io/sygmaprotocol/indexer-evm2:latest + command: ganache-cli --chainId 1338 -d --db data/ --blockTime 2 > /dev/null + container_name: evm2 + logging: + driver: none + ports: + - "8547:8545" + + substrate-pallet: + container_name: substrate-pallet + command: ["--pruning=25000"] + image: ghcr.io/sygmaprotocol/indexer-substrate:latest + ports: + - "9944:9944" + - "9933:9933" + logging: + driver: none + db: image: postgres:15 environment: @@ -9,30 +38,45 @@ services: ports: - "23798:${DB_PORT}" env_file: ./envs/.env.init + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME} -d ${DB_NAME} && psql -U ${DB_USERNAME} -d ${DB_NAME} -c 'SELECT 1'"] + interval: 10s + retries: 5 + start_period: 30s + timeout: 10s indexer-init: build: context: . dockerfile: ./Dockerfile restart: on-failure depends_on: - - db + db: + condition: service_healthy env_file: ./envs/.env.init - indexer-sepolia: + indexer_evm1: build: context: . dockerfile: ./Dockerfile restart: on-failure depends_on: - indexer-init - env_file: ./envs/.env.sepolia - indexer-holesky: + env_file: ./envs/.env.evm-1 + indexer_evm2: build: context: . dockerfile: ./Dockerfile restart: on-failure depends_on: - indexer-init - env_file: ./envs/.env.holesky + env_file: ./envs/.env.evm-2 + indexer_substrate: + build: + context: . + dockerfile: ./Dockerfile + restart: on-failure + depends_on: + - indexer-init + env_file: ./envs/.env.substrate api: build: context: . @@ -41,5 +85,4 @@ services: - indexer-init env_file: ./envs/.env.api ports: - - "8000:8000" - + - "8000:8000" \ No newline at end of file diff --git a/envs/.env.api b/envs/.env.api new file mode 100644 index 0000000..a487c9b --- /dev/null +++ b/envs/.env.api @@ -0,0 +1,5 @@ +DB_NAME=squid +DB_PASS=squid +DB_PORT=5432 +DB_USERNAME=postgres +DB_HOST=db \ No newline at end of file diff --git a/envs/.env.evm-1 b/envs/.env.evm-1 new file mode 100644 index 0000000..0692246 --- /dev/null +++ b/envs/.env.evm-1 @@ -0,0 +1,13 @@ +DB_NAME=squid +DB_PASS=squid +DB_PORT=5432 +DB_USERNAME=postgres +DB_HOST=db +START_SCRIPT_ENV=index + +PROCESSOR_PROMETHEUS_PORT=3000 + +SHARED_CONFIG_URL="https://ipfs.io/ipfs/bafkreiasnla2ya55of6nwm3swjstip4q2ixfa3t6tvixyibclfovxnerte" + +DOMAIN_ID=1 +1_METADATA='{"rpcUrl": "http://evm1:8545"}' diff --git a/envs/.env.evm-2 b/envs/.env.evm-2 new file mode 100644 index 0000000..1889c40 --- /dev/null +++ b/envs/.env.evm-2 @@ -0,0 +1,13 @@ +DB_NAME=squid +DB_PASS=squid +DB_PORT=5432 +DB_USERNAME=postgres +DB_HOST=db +START_SCRIPT_ENV=index + +PROCESSOR_PROMETHEUS_PORT=3002 + +SHARED_CONFIG_URL="https://ipfs.io/ipfs/bafkreiasnla2ya55of6nwm3swjstip4q2ixfa3t6tvixyibclfovxnerte" + +DOMAIN_ID=2 +2_METADATA='{"rpcUrl": "http://evm2:8545"}' diff --git a/envs/.env.init.example b/envs/.env.init similarity index 55% rename from envs/.env.init.example rename to envs/.env.init index b74b4fc..35391d3 100644 --- a/envs/.env.init.example +++ b/envs/.env.init @@ -7,4 +7,7 @@ DB_USERNAME=postgres DB_HOST=db START_SCRIPT_ENV=init -SHARED_CONFIG_URL= +SHARED_CONFIG_URL="https://ipfs.io/ipfs/bafkreiasnla2ya55of6nwm3swjstip4q2ixfa3t6tvixyibclfovxnerte" + +DOMAIN_ID=0 +0_METADATA='{"rpcUrl": ""}' \ No newline at end of file diff --git a/envs/.env.sepolia.example b/envs/.env.sepolia.example deleted file mode 100644 index 98a16a8..0000000 --- a/envs/.env.sepolia.example +++ /dev/null @@ -1,21 +0,0 @@ -# Environment variables for the indexer-sepolia service - -DB_NAME=squid -DB_PASS=squid -DB_PORT=5432 -DB_USERNAME=postgres -DB_HOST=db -START_SCRIPT_ENV=index - -PROCESSOR_PROMETHEUS_PORT=3000 -GQL_PORT=4350 - -SHARED_CONFIG_URL= - -DOMAIN_TYPE=evm -DOMAIN_ID=2 -DOMAIN_GATEWAY= -DOMAIN_BRIDGE_ADDRESS= -START_BLOCK= -RPC_URL= -SUPPORTED_SUBSTRATE_RPCS=[{"id": 3, "endpoint":"wss://"}, ...] diff --git a/envs/.env.substrate b/envs/.env.substrate new file mode 100644 index 0000000..f6c3612 --- /dev/null +++ b/envs/.env.substrate @@ -0,0 +1,13 @@ +DB_NAME=squid +DB_PASS=squid +DB_PORT=5432 +DB_USERNAME=postgres +DB_HOST=db +START_SCRIPT_ENV=index + +PROCESSOR_PROMETHEUS_PORT=3004 + +SHARED_CONFIG_URL="https://ipfs.io/ipfs/bafkreiasnla2ya55of6nwm3swjstip4q2ixfa3t6tvixyibclfovxnerte" + +DOMAIN_ID=3 +3_METADATA='{"rpcUrl": "ws://substrate-pallet:9944"}' \ No newline at end of file diff --git a/package.json b/package.json index 1b62b63..4e19b64 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "start": "docker-compose up --build", "stop": "docker-compose down", "test:unit": "NODE_ENV=test nyc --exclude **/data/** --all --include \"src/**/*.ts\" mocha -r ts-node/register \"tests/unit/**/*.spec.ts\"", + "test:e2e": "NODE_ENV=test SERVER_PORT=0 mocha --timeout 40000 -r ts-node/register -r dotenv/config \"tests/e2e/**/*.spec.ts\"", "update": "npx npm-check-updates --filter /subsquid/ --upgrade && npm i -f" }, "dependencies": { diff --git a/schema.graphql b/schema.graphql index 7bc79ee..8b8b562 100644 --- a/schema.graphql +++ b/schema.graphql @@ -7,7 +7,7 @@ type Transfer @entity @index(fields: ["routeID", "depositNonce"], unique: true){ feeID: String fee: Fee @unique routeID: String - route: Route + route: Route! amount: String } @@ -91,5 +91,6 @@ type Route @entity @index(fields: ["fromDomainID", "toDomainID", "resourceID"], toDomain: Domain resourceID: String + resource: Resource transfers: [Transfer!] @derivedFrom(field: "route") } \ No newline at end of file diff --git a/src/api/schemas/index.ts b/src/api/schemas/index.ts index b93c05b..88d6049 100644 --- a/src/api/schemas/index.ts +++ b/src/api/schemas/index.ts @@ -152,27 +152,43 @@ export const executionSchema = { }, }; -export const transferSchema = { - type: "object", +export const routeSchema = { properties: { id: { type: "string", format: "ObjectId", - example: "2-1-2", + example: "1ffe10e3-c16c-4fdb-a357-380accf1eb66", }, - amount: { type: "string", example: "0.0001" }, fromDomainID: { type: "string", example: "1" }, fromDomain: { ...domainSchema }, toDomainID: { type: "string", nullable: true, example: "2" }, toDomain: { ...domainSchema }, - depositNonce: { type: "string", example: "2" }, - resource: { ...resourceSchema }, resourceID: { type: "string", format: "ObjectId", example: "0x0000000000000000000000000000000000000000000000000000000000000300", }, + resource: { ...resourceSchema }, + }, +}; + +export const transferSchema = { + type: "object", + properties: { + id: { + type: "string", + format: "ObjectId", + example: "2-1-2", + }, + amount: { type: "string", example: "0.0001" }, + depositNonce: { type: "string", example: "2" }, + routeID: { + type: "string", + format: "ObjectId", + example: "1ffe10e3-c16c-4fdb-a357-380accf1eb66", + }, + route: { ...routeSchema }, status: { ...transferStatusSchema }, deposit: { ...depositSchema }, execution: { ...executionSchema }, diff --git a/src/api/services/dataAccess/transfers.service.ts b/src/api/services/dataAccess/transfers.service.ts index 35f5c26..615f42b 100644 --- a/src/api/services/dataAccess/transfers.service.ts +++ b/src/api/services/dataAccess/transfers.service.ts @@ -50,7 +50,7 @@ export class TransfersService { relations: { deposit: true, execution: true, - route: true, + route: { resource: true, fromDomain: true, toDomain: true }, fee: { token: true }, }, }); diff --git a/src/model/generated/route.model.ts b/src/model/generated/route.model.ts index 4d7ca46..6bd0a0f 100644 --- a/src/model/generated/route.model.ts +++ b/src/model/generated/route.model.ts @@ -2,10 +2,11 @@ The Licensed Work is (c) 2024 Sygma SPDX-License-Identifier: LGPL-3.0-only */ -import {Entity as Entity_, PrimaryColumn as PrimaryColumn_, Index as Index_, StringColumn as StringColumn_, ManyToOne as ManyToOne_} from "@subsquid/typeorm-store" +import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, Index as Index_, StringColumn as StringColumn_, ManyToOne as ManyToOne_, OneToMany as OneToMany_} from "@subsquid/typeorm-store" import {Domain} from "./domain.model" -import { OneToMany, PrimaryGeneratedColumn } from "typeorm" -import { Transfer } from "./transfer.model" +import {Resource} from "./resource.model" +import {Transfer} from "./transfer.model" +import { PrimaryGeneratedColumn } from "typeorm" @Index_(["fromDomainID", "toDomainID", "resourceID"], {unique: true}) @Entity_() @@ -34,6 +35,10 @@ export class Route { @StringColumn_({nullable: true}) resourceID!: string | undefined | null - @OneToMany(() => Transfer, e => e.route) + @Index_() + @ManyToOne_(() => Resource, {nullable: true}) + resource!: Resource | undefined | null + + @OneToMany_(() => Transfer, e => e.route) transfers!: Transfer[] } diff --git a/src/model/generated/transfer.model.ts b/src/model/generated/transfer.model.ts index bb49127..d504349 100644 --- a/src/model/generated/transfer.model.ts +++ b/src/model/generated/transfer.model.ts @@ -2,7 +2,7 @@ The Licensed Work is (c) 2024 Sygma SPDX-License-Identifier: LGPL-3.0-only */ -import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, StringColumn as StringColumn_, OneToOne as OneToOne_, Index as Index_, JoinColumn as JoinColumn_, ManyToOne as ManyToOne_} from "@subsquid/typeorm-store" +import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, Index as Index_, StringColumn as StringColumn_, OneToOne as OneToOne_, JoinColumn as JoinColumn_, ManyToOne as ManyToOne_} from "@subsquid/typeorm-store" import {TransferStatus} from "./_transferStatus" import {Deposit} from "./deposit.model" import {Execution} from "./execution.model" @@ -48,7 +48,7 @@ export class Transfer { @Index_() @ManyToOne_(() => Route, {nullable: true}) - route!: Route | undefined | null + route!: Route @StringColumn_({nullable: true}) amount!: string | undefined | null diff --git a/tests/e2e/indexing.spec.ts b/tests/e2e/indexing.spec.ts new file mode 100644 index 0000000..46ccefb --- /dev/null +++ b/tests/e2e/indexing.spec.ts @@ -0,0 +1,510 @@ +/* +The Licensed Work is (c) 2024 Sygma +SPDX-License-Identifier: LGPL-3.0-only +*/ + +import { expect } from "chai"; +import { Transfer, Deposit, Execution, TransferStatus } from "../../src/model"; +import { Network, ResourceType } from "@buildwithsygma/core"; + +const NUMBER_OF_TRANSFERS = 31; +const NUMBER_OF_SUBSTRATE_DEPOSITS = 1; +const NUMBER_OF_FUNGIBLE_DEPOSITS = 29; +const NUMBER_OF_PERMISSIONLESS_DEPOSITS = 1; +const NUMBER_OF_NFT_DEPOSITS = 1; + +const FUNGIBLE_EVM_DEPOSIT_TXHASH = + "0x0a4fb75c91ca774d1b2faeed14a0d9c8f261dba64fc8adfe070c0b9f78a07492"; +const FUNGIBLE_EVM_EXECUTION_TXHASH = + "0x10f878f78f6e5fe7a916c40b981d62ffe6e04bff7c783802a9ecda36dae059fb"; +const NONFUNGIBLE_EVM_DEPOSIT_TXHASH = + "0x12327002087fe09d30a4bd45e97e55549d92dbf05d254788591dc2b6bca4ef0f"; +const FUNGIBLE_SUBSTRATE_TO_EVM_DEPOSIT_TXHASH = "0000000279-f6443-000001"; +const FUNGIBLE_EVM_TO_SUBSTRATE_DEPOSIT_TXHASH = + "0x967b320daebffcba435b6bf9ba493963471f6ca9d12c84c9f156bda6862934e0"; +const PERMISSIONLESS_GENERIC_EVM_DEPOSIT_TXHASH = + "0x2b355542a454d8faedccc75c8741ef0d2f531ea4cd8ed53544734ff681377699"; + +const SENDER_ADDRESS = "0x5c1f5961696bad2e73f73417f07ef55c62a2dc5b"; + +type TransferResponse = Transfer & { + deposit: Deposit; + execution: Execution; +}; + +describe("Indexer e2e tests", function () { + let substrateDeposits = 0; + let fungibleDeposits = 0; + let permissionlessDeposits = 0; + let nftDeposits = 0; + + before(async () => { + let numberOfTransfers = 0; + let isProcessing = false; + while (numberOfTransfers !== NUMBER_OF_TRANSFERS || isProcessing) { + const response = await fetch( + "http://localhost:8000/api/transfers?page=1&limit=100" + ); + console.log("RESPONSE:::::::::::", response) + const transfers: Array = await response.json(); + + numberOfTransfers = transfers.length; + + isProcessing = false; + for (const transfer of transfers) { + if (!transfer.deposit || !transfer.execution) { + isProcessing = true; + } + } + } + }); + + it("should succesfully fetch all transfers", async () => { + const response = await fetch( + "http://localhost:8000/api/transfers?page=1&limit=100" + ); + const transfers: Array = await response.json(); + + for (const transfer of transfers) { + if (transfer.route.fromDomain?.name.toLowerCase() == Network.SUBSTRATE) { + substrateDeposits++; + } + switch (transfer.route.resource?.type) { + case ResourceType.FUNGIBLE: { + fungibleDeposits++; + break; + } + case ResourceType.NON_FUNGIBLE: { + nftDeposits++; + break; + } + case ResourceType.PERMISSIONLESS_GENERIC: { + permissionlessDeposits++; + break; + } + } + } + + expect(transfers.length).to.be.deep.equal(NUMBER_OF_TRANSFERS); + expect(substrateDeposits).to.be.eq(NUMBER_OF_SUBSTRATE_DEPOSITS); + expect(fungibleDeposits).to.be.eq(NUMBER_OF_FUNGIBLE_DEPOSITS); + expect(permissionlessDeposits).to.be.eq(NUMBER_OF_PERMISSIONLESS_DEPOSITS); + expect(nftDeposits).to.be.eq(NUMBER_OF_NFT_DEPOSITS); + + transfers.map((transfer) => { + expect(transfer.id).to.be.not.null; + expect(transfer.status).to.be.not.null; + expect(transfer.depositNonce).to.be.not.null; + expect(transfer.route).to.be.not.null; + expect(transfer.route.fromDomainID).to.be.not.null; + expect(transfer.route.toDomainID).to.be.not.null; + expect(transfer.route.resourceID).to.be.not.null; + expect(transfer.feeID).to.be.not.null; + expect(transfer.fee).to.be.not.null; + expect(transfer.amount).to.be.not.null; + expect(transfer.deposit.txHash).to.be.not.null; + expect(transfer.deposit.blockNumber).to.be.not.null; + expect(transfer.deposit.depositData).to.be.not.null; + expect(transfer.deposit.timestamp).to.be.not.null; + expect(transfer.deposit.handlerResponse).to.be.not.null; + expect(transfer.deposit.accountID).to.be.not.null; + expect(transfer.deposit.account).to.be.not.null; + expect(transfer.deposit.destination).to.be.not.null; + expect(transfer.deposit.timestamp).to.be.not.null; + expect(transfer.execution).to.be.not.null; + expect(transfer.execution.timestamp).to.be.not.null; + expect(transfer.execution.txHash).to.be.not.null; + expect(transfer.execution.blockNumber).to.be.not.null; + expect(transfer.execution.message).to.be.not.null; + }); + }); + + it("should succesfully fetch evm fungible transfer", async () => { + const res = await fetch( + `http://localhost:8000/api/transfers?txHash=${FUNGIBLE_EVM_DEPOSIT_TXHASH}` + ); + const transfers: TransferResponse[] = await res.json(); + + expect(res.status).to.be.deep.equal(200); + expect(transfers.length).to.be.deep.equal(1); + expect(transfers[0]).to.be.deep.equal({ + id: transfers[0].id, + status: TransferStatus.executed, + depositNonce: "1", + routeID: transfers[0].routeID, + route: { + id: transfers[0].route.id, + fromDomainID: "1", + fromDomain: { name: "Ethereum 1", id: "1" }, + toDomainID: "2", + toDomain: { name: "evm2", id: "2" }, + resourceID: + "0x0000000000000000000000000000000000000000000000000000000000000000", + resource: { + id: "0x0000000000000000000000000000000000000000000000000000000000000000", + type: ResourceType.FUNGIBLE, + }, + }, + fee: { + id: transfers[0].fee?.id, + amount: "100000000000000", + tokenID: transfers[0].fee?.tokenID, + token: { + id: transfers[0].fee?.tokenID, + resourceID: "", + decimals: 18, + tokenAddress: "0x0000000000000000000000000000000000000000", + tokenSymbol: "eth", + }, + }, + amount: "0.0000000000000001", + deposit: { + id: transfers[0].deposit.id, + txHash: + "0x0a4fb75c91ca774d1b2faeed14a0d9c8f261dba64fc8adfe070c0b9f78a07492", + blockNumber: "115", + depositData: + "0x000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000145c1f5961696bad2e73f73417f07ef55c62a2dc5b", + handlerResponse: + "0x0000000000000000000000000000000000000000000000000000000000000064", + timestamp: "2024-11-14T08:16:41.000Z", + + accountID: "0x5c1f5961696bad2e73f73417f07ef55c62a2dc5b", + destination: "0x5c1f5961696bad2e73f73417f07ef55c62a2dc5b", + }, + execution: { + id: transfers[0].execution.id, + txHash: + "0x8be14ce560b614606e2fad63c6bd58f80a7bc2ae344114eed094ec5296178888", + blockNumber: "123", + timestamp: "2024-11-14T08:17:00.000Z", + message: "", + }, + }); + }); + + it("should succesfully fetch evm fungible transfer by execution hash", async () => { + const res = await fetch( + `http://localhost:8000/api/transfers?txHash=${FUNGIBLE_EVM_EXECUTION_TXHASH}&component=execution` + ); + const transfers: TransferResponse[] = await res.json(); + + expect(res.status).to.be.deep.equal(200); + expect(transfers.length).to.be.deep.equal(1); + expect(transfers[0]).to.be.deep.equal({ + id: transfers[0].id, + status: TransferStatus.executed, + depositNonce: "29", + routeID: transfers[0].routeID, + route: { + id: transfers[0].route.id, + fromDomainID: "1", + fromDomain: { name: "Ethereum 1", id: "1" }, + toDomainID: "2", + toDomain: { name: "evm2", id: "2" }, + resourceID: + "0x0000000000000000000000000000000000000000000000000000000000000300", + resource: { + id: "0x0000000000000000000000000000000000000000000000000000000000000300", + type: ResourceType.FUNGIBLE, + }, + }, + fee: { + id: transfers[0].fee?.id, + amount: "100000000000000", + tokenID: transfers[0].fee?.tokenID, + token: { + id: transfers[0].fee?.tokenID, + resourceID: "", + decimals: 18, + tokenAddress: "0x0000000000000000000000000000000000000000", + tokenSymbol: "eth", + }, + }, + amount: "0.0000000000000001", + deposit: { + id: transfers[0].deposit.id, + txHash: + "0xcf0ed5a25739be16c610e7fd17c8140f21b82128e28cccc5ccef5db16dca052f", + blockNumber: "164", + depositData: + "0x000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000148e0a907331554af72563bd8d43051c2e64be5d35", + handlerResponse: + "0x0000000000000000000000000000000000000000000000000000000000000064", + timestamp: "2024-11-14T08:18:21.000Z", + + accountID: "0x5c1f5961696bad2e73f73417f07ef55c62a2dc5b", + destination: "0x8e0a907331554af72563bd8d43051c2e64be5d35", + }, + execution: { + id: transfers[0].execution.id, + txHash: + "0x10f878f78f6e5fe7a916c40b981d62ffe6e04bff7c783802a9ecda36dae059fb", + blockNumber: "167", + timestamp: "2024-11-14T08:18:30.000Z", + message: "", + }, + }); + }); + + it("should succesfully fetch evm non-fungible transfer", async () => { + const res = await fetch( + `http://localhost:8000/api/transfers?txHash=${NONFUNGIBLE_EVM_DEPOSIT_TXHASH}` + ); + const transfers: TransferResponse[] = await res.json(); + + expect(res.status).to.be.deep.equal(200); + expect(transfers.length).to.be.deep.equal(1); + expect(transfers[0]).to.be.deep.equal({ + id: transfers[0].id, + status: TransferStatus.executed, + depositNonce: "2", + routeID: transfers[0].routeID, + route: { + id: transfers[0].route.id, + fromDomainID: "1", + fromDomain: { name: "Ethereum 1", id: "1" }, + toDomainID: "2", + toDomain: { name: "evm2", id: "2" }, + resourceID: + "0x0000000000000000000000000000000000000000000000000000000000000200", + resource: { + id: "0x0000000000000000000000000000000000000000000000000000000000000200", + type: ResourceType.NON_FUNGIBLE, + }, + }, + fee: { + id: transfers[0].fee?.id, + amount: "100000000000000", + tokenID: transfers[0].fee?.tokenID, + token: { + id: transfers[0].fee?.tokenID, + resourceID: "", + decimals: 18, + tokenAddress: "0x0000000000000000000000000000000000000000", + tokenSymbol: "eth", + }, + }, + amount: "2935717020161974584", + deposit: { + id: transfers[0].deposit.id, + txHash: + "0x12327002087fe09d30a4bd45e97e55549d92dbf05d254788591dc2b6bca4ef0f", + blockNumber: "130", + depositData: + "0x00000000000000000000000000000000000000000000000028bdc31363d2a13800000000000000000000000000000000000000000000000000000000000000148e0a907331554af72563bd8d43051c2e64be5d35000000000000000000000000000000000000000000000000000000000000000c6d657461646174612e75726c", + handlerResponse: "0x6d657461646174612e746573746d657461646174612e75726c", + timestamp: "2024-11-14T08:17:11.000Z", + + accountID: "0x5c1f5961696bad2e73f73417f07ef55c62a2dc5b", + destination: "0x8e0a907331554af72563bd8d43051c2e64be5d35", + }, + execution: { + id: transfers[0].execution.id, + txHash: + "0x43c02a7cee493621c550e059489db14500b5a388185d61deeb7d9a7f52959e8d", + blockNumber: "138", + timestamp: "2024-11-14T08:17:31.000Z", + message: "", + }, + }); + }); + + it("should succesfully fetch substrate to evm fungible transfer", async () => { + const res = await fetch( + `http://localhost:8000/api/transfers?txHash=${FUNGIBLE_SUBSTRATE_TO_EVM_DEPOSIT_TXHASH}` + ); + const transfers: TransferResponse[] = await res.json(); + + expect(res.status).to.be.deep.equal(200); + expect(transfers.length).to.be.deep.equal(1); + expect(transfers[0]).to.be.deep.equal({ + id: transfers[0].id, + status: TransferStatus.executed, + depositNonce: "0", + routeID: transfers[0].routeID, + route: { + id: transfers[0].route.id, + fromDomainID: "3", + fromDomain: { name: "Substrate", id: "3" }, + toDomainID: "1", + toDomain: { name: "Ethereum 1", id: "1" }, + resourceID: + "0x0000000000000000000000000000000000000000000000000000000000000300", + resource: { + id: "0x0000000000000000000000000000000000000000000000000000000000000300", + type: ResourceType.FUNGIBLE, + }, + }, + fee: { + id: transfers[0].fee?.id, + amount: "0", + tokenID: transfers[0].fee?.tokenID, + token: { + id: transfers[0].fee?.tokenID, + resourceID: + "0x0000000000000000000000000000000000000000000000000000000000000300", + decimals: 6, + tokenAddress: + '{"concrete":{"parents":1,"interior":{"x3":[{"parachain":2004},{"generalKey":[5,"0x7379676d61000000000000000000000000000000000000000000000000000000"]},{"generalKey":[4,"0x7573646300000000000000000000000000000000000000000000000000000000"]}]}}}', + tokenSymbol: "USDC", + }, + }, + amount: "1.0", + deposit: { + id: transfers[0].deposit.id, + txHash: "0000000279-f6443-000001", + blockNumber: "279", + depositData: + "0x00000000000000000000000000000000000000000000000000000000000f424000000000000000000000000000000000000000000000000000000000000000145c1f5961696bad2e73f73417f07ef55c62a2dc5b", + handlerResponse: "0x", + timestamp: "2024-11-14T08:19:48.001Z", + accountID: + "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + destination: "0x5c1f5961696bad2e73f73417f07ef55c62a2dc5b", + }, + execution: { + id: transfers[0].execution.id, + txHash: + "0x9b10747083d576b05caa28edbecd5937080b77ae27da3485b29376e168e4076d", + blockNumber: "218", + timestamp: "2024-11-14T08:20:10.000Z", + message: "", + }, + }); + }); + + it("should succesfully fetch evm to substrate fungible transfer", async () => { + const res = await fetch( + `http://localhost:8000/api/transfers?txHash=${FUNGIBLE_EVM_TO_SUBSTRATE_DEPOSIT_TXHASH}` + ); + const transfers: TransferResponse[] = await res.json(); + + expect(res.status).to.be.deep.equal(200); + expect(transfers.length).to.be.deep.equal(1); + expect(transfers[0]).to.be.deep.equal({ + id: transfers[0].id, + status: TransferStatus.executed, + depositNonce: "1", + routeID: transfers[0].routeID, + route: { + id: transfers[0].route.id, + fromDomainID: "1", + fromDomain: { name: "Ethereum 1", id: "1" }, + toDomainID: "3", + toDomain: { name: "Substrate", id: "3" }, + resourceID: + "0x0000000000000000000000000000000000000000000000000000000000000300", + resource: { + id: "0x0000000000000000000000000000000000000000000000000000000000000300", + type: ResourceType.FUNGIBLE, + }, + }, + fee: { + id: transfers[0].fee?.id, + amount: "100000000000000", + tokenID: transfers[0].fee?.tokenID, + token: { + id: transfers[0].fee?.tokenID, + resourceID: "", + decimals: 18, + tokenAddress: "0x0000000000000000000000000000000000000000", + tokenSymbol: "eth", + }, + }, + amount: "0.0001", + deposit: { + id: transfers[0].deposit.id, + txHash: + "0x967b320daebffcba435b6bf9ba493963471f6ca9d12c84c9f156bda6862934e0", + blockNumber: "195", + depositData: + "0x00000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000002400010100d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + handlerResponse: + "0x00000000000000000000000000000000000000000000000000005af3107a4000", + timestamp: "2024-11-14T08:19:23.000Z", + accountID: "0x5c1f5961696bad2e73f73417f07ef55c62a2dc5b", + destination: "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + }, + execution: { + id: transfers[0].execution.id, + txHash: "0000000278-91805-000001", + blockNumber: "278", + timestamp: "2024-11-14T08:19:42.000Z", + message: "", + }, + }); + }); + + it("should succesfully fetch evm permissionless generic transfer", async () => { + const res = await fetch( + `http://localhost:8000/api/transfers?txHash=${PERMISSIONLESS_GENERIC_EVM_DEPOSIT_TXHASH}` + ); + const transfers: TransferResponse[] = await res.json(); + + expect(res.status).to.be.deep.equal(200); + expect(transfers.length).to.be.deep.equal(1); + expect(transfers[0]).to.be.deep.equal({ + id: transfers[0].id, + status: TransferStatus.executed, + depositNonce: "28", + routeID: transfers[0].routeID, + route: { + id: transfers[0].route.id, + fromDomainID: "1", + fromDomain: { name: "Ethereum 1", id: "1" }, + toDomainID: "2", + toDomain: { name: "evm2", id: "2" }, + resourceID: + "0x0000000000000000000000000000000000000000000000000000000000000500", + resource: { + id: "0x0000000000000000000000000000000000000000000000000000000000000500", + type: ResourceType.PERMISSIONLESS_GENERIC, + }, + }, + fee: { + id: transfers[0].fee?.id, + amount: "100000000000000", + tokenID: transfers[0].fee?.tokenID, + token: { + id: transfers[0].fee?.tokenID, + decimals: 18, + resourceID: "", + tokenAddress: "0x0000000000000000000000000000000000000000", + tokenSymbol: "eth", + }, + }, + amount: "", + deposit: { + id: transfers[0].deposit.id, + txHash: + "0x2b355542a454d8faedccc75c8741ef0d2f531ea4cd8ed53544734ff681377699", + blockNumber: "155", + depositData: + "0x00000000000000000000000000000000000000000000000000000000000927c00004ea287d1514a2451c8553371e754f5e93a440adcca1c0dcf395145c1f5961696bad2e73f73417f07ef55c62a2dc5b35353436383833363939383137363233353732000000000000000000000000000000000000000000000000005c1f5961696bad2e73f73417f07ef55c62a2dc5b", + handlerResponse: "0x", + timestamp: "2024-11-14T08:18:03.000Z", + accountID: "0x5c1f5961696bad2e73f73417f07ef55c62a2dc5b", + destination: "0xa2451c8553371e754f5e93a440adcca1c0dcf395", + }, + execution: { + id: transfers[0].execution.id, + txHash: + "0x508195d23128b60c20a577eca7ace567e6ec68f636bad42ddb554b7d96644dd3", + blockNumber: "162", + timestamp: "2024-11-14T08:18:20.000Z", + message: "", + }, + }); + }); + + it("should succesfully fetch all transfers by one sender", async () => { + const res = await fetch( + `http://localhost:8000/api/transfers?sender=${SENDER_ADDRESS}&page=1&limit=100` + ); + const transfers: TransferResponse[] = await res.json(); + + expect(res.status).to.be.deep.equal(200); + expect(transfers.length).to.be.deep.equal(30); + }); +});