diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2209d32..096bba8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,9 +5,9 @@ name: Run tests on: push: - branches: [ "main" ] + branches: [ "*" ] pull_request: - branches: [ "main" ] + branches: [ "*" ] jobs: build: diff --git a/jest.config.js b/jest.config.js index b0ca35a..228cf2b 100644 --- a/jest.config.js +++ b/jest.config.js @@ -18,7 +18,8 @@ const config = { "json-summary", "text", "lcov" - ] + ], + testTimeout: 60000, }; export default config; \ No newline at end of file diff --git a/src/anchorage/anchorage.service.ts b/src/anchorage/anchorage.service.ts index 5016c67..2449e13 100644 --- a/src/anchorage/anchorage.service.ts +++ b/src/anchorage/anchorage.service.ts @@ -12,7 +12,7 @@ import {GraphQLService} from "../graphql.service"; import {gql} from "@apollo/client/core"; import { JsonRpcProvider } from '@ethersproject/providers' import {ethers} from "ethers"; -import {retainOldStructure} from "../utils"; +import {retainOldStructure, FDGABI } from "../utils"; export class AnchorageGraphQLService extends GraphQLService { async findWithdrawalsProven( @@ -340,6 +340,7 @@ export class AnchorageGraphQLService extends GraphQLService { token: event.l2Token, originChainId: networkConfig.L2.chainId, timeStamp: event.timestamp_initiated, + timeStamp_proven: event.timestamp_ }, } } @@ -418,6 +419,86 @@ export class AnchorageGraphQLService extends GraphQLService { return withdrawalTransactions } + + // Fraud proofing specific functions + // If the block number is not found, return 0 + async getLatestFDGSubmittedBlock( + chainId: string | number + ) { + try { + const qry = gql` + query GetDisputeGameCreateds { + disputeGameCreateds( + orderBy: index, + orderDirection: desc, + first: 1, + where: { resolvedStatus: 2 } + ) { + id + index + rootClaim + l2BlockNumber + } + } + ` + const result = await this.conductQuery( + qry, + undefined, + chainId, + EGraphQLService.DisputeGameFactory + ) + if (result?.data?.disputeGameCreateds?.length) { + return Number(result.data.disputeGameCreateds[0].l2BlockNumber) + } + return 0 + } catch (e) { + console.log('graph-utils: getLatestFDGSubmittedBlock: Error', e) + return 0 + } + } + + async getRootClaimOfFDGSubmission( + chainId: string | number, + blockNumber: number + ) { + try { + const qry = gql` + query GetDisputeGameCreateds { + disputeGameCreateds( + orderBy: index, + orderDirection: asc, + first: 1, + where: { resolvedStatus: 2, l2BlockNumber_gte: ${blockNumber} } + ) { + id + index + rootClaim + l2BlockNumber + } + } + ` + const result = await this.conductQuery( + qry, + undefined, + chainId, + EGraphQLService.DisputeGameFactory + ) + if (result?.data?.disputeGameCreateds?.length) { + return { + status: 'success', + id: result.data.disputeGameCreateds[0].id, + rootClaim: result.data.disputeGameCreateds[0].rootClaim, + l2BlockNumber: Number(result.data.disputeGameCreateds[0].l2BlockNumber), + index: Number(result.data.disputeGameCreateds[0].index), + } + } else { + return { status: 'error', error: 'No submission found for the given block number' } + } + } catch (e) { + console.log('graph-utils: getStateRootOfFDGSubmission: Error', e) + return { status: 'error', error: 'Unknown reason' } + } + } } export const anchorageGraphQLService = new AnchorageGraphQLService() @@ -690,4 +771,4 @@ export const checkBridgeWithdrawalReenter = }) } - //#endregion + //#endregion \ No newline at end of file diff --git a/src/anchorage/types.ts b/src/anchorage/types.ts index 71f640f..a958241 100644 --- a/src/anchorage/types.ts +++ b/src/anchorage/types.ts @@ -27,6 +27,7 @@ export interface IHandleProveWithdrawalConfig { withdrawalHash?: string blockHash?: string timeStamp?: number + timeStamp_proven?: number } //#endregion diff --git a/src/graphql.service.ts b/src/graphql.service.ts index d6d980f..639bbaa 100644 --- a/src/graphql.service.ts +++ b/src/graphql.service.ts @@ -125,7 +125,7 @@ export class GraphQLService { local: '', }, [EGraphQLService.DisputeGameFactory]: { - gql: this.withSubgraphId('3ypGLssvTVBZzegJaK4kFmr8exE9ugG35Duzc1p3S1E4') + gql: this.withSubgraphId('366aAux7wdCaJDZCqFRYQCAu9EcsvKn1KEMFMmAcVGb4') } }, // Boba Sepolia @@ -152,6 +152,20 @@ export class GraphQLService { }, } + RPC_ENDPOINTS = { + // Sepolia + 11155111: { + url: 'https://ethereum-sepolia.publicnode.com/', + }, + } + + getRpcEndpoint(chainId: number) { + if (!this.RPC_ENDPOINTS[chainId]) { + throw new Error("No RPC endpoint for network: " + chainId) + } + return this.RPC_ENDPOINTS[chainId] + } + getBridgeEndpoint = (chainId, service: EGraphQLService, useLocal = false) => { const networkEndpoint = this.GRAPHQL_ENDPOINTS[chainId]; if (!networkEndpoint) { diff --git a/src/utils.ts b/src/utils.ts index 6c17556..d1ea7d6 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -30,4 +30,9 @@ export const retainOldStructure = (data: any[]) => { transactionHash_: transactionHash, }; }); -} \ No newline at end of file +} + +export const FDGABI = [ + {"type":"function","name":"l2BlockNumber","inputs":[],"outputs":[{"name":"l2BlockNumber_","type":"uint256","internalType":"uint256"}],"stateMutability":"pure"}, + {"type":"function","name":"rootClaim","inputs":[],"outputs":[{"name":"rootClaim_","type":"bytes32","internalType":"Claim"}],"stateMutability":"pure"} +] \ No newline at end of file diff --git a/tests/integration/dispute-game-factory.spec.ts b/tests/integration/dispute-game-factory.spec.ts index cb54791..950e105 100644 --- a/tests/integration/dispute-game-factory.spec.ts +++ b/tests/integration/dispute-game-factory.spec.ts @@ -1,4 +1,6 @@ -import {EGraphQLService, graphQLService} from "../../src"; +import { ethers } from "ethers"; +import { EGraphQLService, graphQLService, anchorageGraphQLService } from "../../src"; +import { FDGABI } from "../../src/utils"; describe('Anchorage: Integration Test', function () { const service = graphQLService; @@ -7,4 +9,41 @@ describe('Anchorage: Integration Test', function () { const bridgeEndpoint = service.getBridgeEndpoint(chainId, EGraphQLService.DisputeGameFactory); expect(bridgeEndpoint).toEqual(graphQLService.GRAPHQL_ENDPOINTS[chainId][EGraphQLService.DisputeGameFactory].gql) }); + + it('Latest submitted Block reachable on: Sepolia', async () => { + const chainId = 11155111; + const res = await anchorageGraphQLService.getLatestFDGSubmittedBlock(chainId); + expect(res).toBeGreaterThan(0) + }); + + it('Submitted Block reachable on: Sepolia', async () => { + const chainId = 11155111; + const latestL2BlockNumber = await anchorageGraphQLService.getLatestFDGSubmittedBlock(chainId); + expect(latestL2BlockNumber).toBeGreaterThan(0) + }); + + it('Get root claim from submissions', async () => { + const chainId = 11155111; + const testingL2BlockNumber = 8790790; + + const case1 = await anchorageGraphQLService.getRootClaimOfFDGSubmission(chainId, testingL2BlockNumber); + expect(case1.status).toEqual("success"); + expect(case1.id.toLowerCase()).toEqual(("0x83Ef33E9Ada93ef0040c8C550195fB1018128c9d").toLowerCase()); + expect(case1.l2BlockNumber).toEqual(8790794); + expect(case1.rootClaim.toLowerCase()).toEqual("0xce9110ddcada4c1df37bcdf398d048f0af92da59eee205c506726264e5aeecd3"); + expect(case1.index).toEqual(596); + + const testingL2BlockNumber2 = 8790794; + const case2 = await anchorageGraphQLService.getRootClaimOfFDGSubmission(chainId, testingL2BlockNumber2); + expect(case2.status).toEqual("success"); + expect(case2.id.toLowerCase()).toEqual(("0x83Ef33E9Ada93ef0040c8C550195fB1018128c9d").toLowerCase()); + expect(case2.l2BlockNumber).toEqual(8790794); + expect(case2.rootClaim.toLowerCase()).toEqual("0xce9110ddcada4c1df37bcdf398d048f0af92da59eee205c506726264e5aeecd3"); + expect(case2.index).toEqual(596); + + const latestL2BlockNumber = await anchorageGraphQLService.getLatestFDGSubmittedBlock(chainId); + const case3 = await anchorageGraphQLService.getRootClaimOfFDGSubmission(chainId, latestL2BlockNumber + 10); + expect(case3.status).toEqual("error"); + expect(case3.error).toEqual("No submission found for the given block number"); + }); }); \ No newline at end of file