import { afterAll, beforeAll, describe, expect, jest, test, } from "@jest/globals"; import Web3, { ETH_DATA_FORMAT } from "web3"; import axios from "axios"; import { AxiosResponse } from "axios"; import base58 from "bs58"; import { ChainQueryType, SolanaAccountQueryRequest, SolanaAccountQueryResponse, SolanaAccountResult, SolanaPdaEntry, SolanaPdaQueryRequest, SolanaPdaQueryResponse, PerChainQueryRequest, QueryRequest, sign, QueryResponse, } from ".."; jest.setTimeout(125000); const CI = process.env.CI; const ENV = "DEVNET"; const SERVER_URL = CI ? "http://query-server:" : "http://localhost:"; const CCQ_SERVER_URL = SERVER_URL + "6069/v1"; const QUERY_URL = CCQ_SERVER_URL + "/query"; const SOLANA_NODE_URL = CI ? "http://solana-devnet:8899" : "http://localhost:8899"; const PRIVATE_KEY = "cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0"; const ACCOUNTS = [ "2WDq7wSs9zYrpx2kbHDA4RUTRch2CCTP6ZWaH4GNfnQQ", // Example token in devnet "BVxyYhm498L79r4HMQ9sxZ5bi41DmJmeWZ7SCS7Cyvna", // Example NFT in devnet ]; const PDAS: SolanaPdaEntry[] = [ { programAddress: Uint8Array.from( base58.decode("Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o") ), // Core Bridge address seeds: [ new Uint8Array(Buffer.from("GuardianSet")), new Uint8Array(Buffer.alloc(4)), ], // Use index zero in tilt. }, ]; async function getSolanaSlot(comm: string): Promise { const response = await axios.post(SOLANA_NODE_URL, { jsonrpc: "2.0", id: 1, method: "getSlot", params: [{ commitment: comm, transactionDetails: "none" }], }); return response.data.result; } describe("solana", () => { test("serialize and deserialize sol_account request with defaults", () => { const solAccountReq = new SolanaAccountQueryRequest("finalized", ACCOUNTS); expect(solAccountReq.minContextSlot).toEqual(BigInt(0)); expect(solAccountReq.dataSliceOffset).toEqual(BigInt(0)); expect(solAccountReq.dataSliceLength).toEqual(BigInt(0)); const serialized = solAccountReq.serialize(); expect(Buffer.from(serialized).toString("hex")).toEqual( "0000000966696e616c697a656400000000000000000000000000000000000000000000000002165809739240a0ac03b98440fe8985548e3aa683cd0d4d9df5b5659669faa3019c006c48c8cbf33849cb07a3f936159cc523f9591cb1999abd45890ec5fee9b7" ); const solAccountReq2 = SolanaAccountQueryRequest.from(serialized); expect(solAccountReq2).toEqual(solAccountReq); }); test("serialize and deserialize sol_account request no defaults", () => { const minContextSlot = BigInt(123456); const dataSliceOffset = BigInt(12); const dataSliceLength = BigInt(100); const solAccountReq = new SolanaAccountQueryRequest( "finalized", ACCOUNTS, minContextSlot, dataSliceOffset, dataSliceLength ); expect(solAccountReq.minContextSlot).toEqual(minContextSlot); expect(solAccountReq.dataSliceOffset).toEqual(dataSliceOffset); expect(solAccountReq.dataSliceLength).toEqual(dataSliceLength); const serialized = solAccountReq.serialize(); expect(Buffer.from(serialized).toString("hex")).toEqual( "0000000966696e616c697a6564000000000001e240000000000000000c000000000000006402165809739240a0ac03b98440fe8985548e3aa683cd0d4d9df5b5659669faa3019c006c48c8cbf33849cb07a3f936159cc523f9591cb1999abd45890ec5fee9b7" ); const solAccountReq2 = SolanaAccountQueryRequest.from(serialized); expect(solAccountReq2).toEqual(solAccountReq); }); test("serialize and deserialize sol_account response", () => { const slotNumber = BigInt(240866260); const blockTime = BigInt(1704770509); const blockHash = Uint8Array.from( Buffer.from( "9999bac44d09a7f69ee7941819b0a19c59ccb1969640cc513be09ef95ed2d8e3", "hex" ) ); const results: SolanaAccountResult[] = [ { lamports: BigInt(1141440), rentEpoch: BigInt(123456), executable: false, owner: Uint8Array.from( Buffer.from( "02a8f6914e88a16e395ae128948ffa695693376818dd47435221f3c600000000", "hex" ) ), data: Uint8Array.from( Buffer.from( "0200000062d14b7d0e121f8575cce871896548fe26d2899b0578ec92117440cda609b010", "hex" ) ), }, { lamports: BigInt(1141441), rentEpoch: BigInt(123457), executable: true, owner: Uint8Array.from( Buffer.from( "02a8f6914e88a16e395ae128948ffa695693376818dd47435221f3c600000000", "hex" ) ), data: Uint8Array.from( Buffer.from( "0200000083f7752f3b75f905f040f0087c67c47a52272fcfa90e691ea6e8d4362039ecd5", "hex" ) ), }, ]; const solAccountResp = new SolanaAccountQueryResponse( slotNumber, blockTime, blockHash, results ); expect(solAccountResp.slotNumber).toEqual(slotNumber); expect(solAccountResp.blockTime).toEqual(blockTime); expect(solAccountResp.results).toEqual(results); const serialized = solAccountResp.serialize(); expect(Buffer.from(serialized).toString("hex")).toEqual( "000000000e5b53d400000000659cbbcd9999bac44d09a7f69ee7941819b0a19c59ccb1969640cc513be09ef95ed2d8e3020000000000116ac0000000000001e2400002a8f6914e88a16e395ae128948ffa695693376818dd47435221f3c600000000000000240200000062d14b7d0e121f8575cce871896548fe26d2899b0578ec92117440cda609b0100000000000116ac1000000000001e2410102a8f6914e88a16e395ae128948ffa695693376818dd47435221f3c600000000000000240200000083f7752f3b75f905f040f0087c67c47a52272fcfa90e691ea6e8d4362039ecd5" ); const solAccountResp2 = SolanaAccountQueryResponse.from(serialized); expect(solAccountResp2).toEqual(solAccountResp); }); test("successful sol_account query", async () => { const solAccountReq = new SolanaAccountQueryRequest("finalized", ACCOUNTS); const nonce = 42; const query = new PerChainQueryRequest(1, solAccountReq); const request = new QueryRequest(nonce, [query]); const serialized = request.serialize(); const digest = QueryRequest.digest(ENV, serialized); const signature = sign(PRIVATE_KEY, digest); const response = await axios.put( QUERY_URL, { signature, bytes: Buffer.from(serialized).toString("hex"), }, { headers: { "X-API-Key": "my_secret_key" } } ); expect(response.status).toBe(200); const queryResponse = QueryResponse.from(response.data.bytes); expect(queryResponse.version).toEqual(1); expect(queryResponse.requestChainId).toEqual(0); expect(queryResponse.request.version).toEqual(1); expect(queryResponse.request.requests.length).toEqual(1); expect(queryResponse.request.requests[0].chainId).toEqual(1); expect(queryResponse.request.requests[0].query.type()).toEqual( ChainQueryType.SolanaAccount ); const sar = queryResponse.responses[0] .response as SolanaAccountQueryResponse; expect(Number(sar.slotNumber)).not.toEqual(0); expect(Number(sar.blockTime)).not.toEqual(0); expect(sar.results.length).toEqual(2); expect(Number(sar.results[0].lamports)).toEqual(1461600); expect(Number(sar.results[0].rentEpoch)).toEqual(0); expect(sar.results[0].executable).toEqual(false); expect(base58.encode(Buffer.from(sar.results[0].owner))).toEqual( "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" ); expect(Buffer.from(sar.results[0].data).toString("hex")).toEqual( "01000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d0000e8890423c78a0901000000000000000000000000000000000000000000000000000000000000000000000000" ); expect(Number(sar.results[1].lamports)).toEqual(1461600); expect(Number(sar.results[1].rentEpoch)).toEqual(0); expect(sar.results[1].executable).toEqual(false); expect(base58.encode(Buffer.from(sar.results[1].owner))).toEqual( "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" ); expect(Buffer.from(sar.results[1].data).toString("hex")).toEqual( "01000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d01000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000" ); }); test("sol_account query with future min context slot", async () => { const currSlot = await getSolanaSlot("finalized"); const minContextSlot = BigInt(currSlot) + BigInt(10); const solAccountReq = new SolanaAccountQueryRequest( "finalized", ACCOUNTS, minContextSlot ); const nonce = 42; const query = new PerChainQueryRequest(1, solAccountReq); const request = new QueryRequest(nonce, [query]); const serialized = request.serialize(); const digest = QueryRequest.digest(ENV, serialized); const signature = sign(PRIVATE_KEY, digest); const response = await axios.put( QUERY_URL, { signature, bytes: Buffer.from(serialized).toString("hex"), }, { headers: { "X-API-Key": "my_secret_key" } } ); expect(response.status).toBe(200); const queryResponse = QueryResponse.from(response.data.bytes); expect(queryResponse.version).toEqual(1); expect(queryResponse.requestChainId).toEqual(0); expect(queryResponse.request.version).toEqual(1); expect(queryResponse.request.requests.length).toEqual(1); expect(queryResponse.request.requests[0].chainId).toEqual(1); expect(queryResponse.request.requests[0].query.type()).toEqual( ChainQueryType.SolanaAccount ); const sar = queryResponse.responses[0] .response as SolanaAccountQueryResponse; expect(sar.slotNumber).toEqual(minContextSlot); expect(Number(sar.blockTime)).not.toEqual(0); expect(sar.results.length).toEqual(2); expect(Number(sar.results[0].lamports)).toEqual(1461600); expect(Number(sar.results[0].rentEpoch)).toEqual(0); expect(sar.results[0].executable).toEqual(false); expect(base58.encode(Buffer.from(sar.results[0].owner))).toEqual( "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" ); expect(Buffer.from(sar.results[0].data).toString("hex")).toEqual( "01000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d0000e8890423c78a0901000000000000000000000000000000000000000000000000000000000000000000000000" ); expect(Number(sar.results[1].lamports)).toEqual(1461600); expect(Number(sar.results[1].rentEpoch)).toEqual(0); expect(sar.results[1].executable).toEqual(false); expect(base58.encode(Buffer.from(sar.results[1].owner))).toEqual( "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" ); expect(Buffer.from(sar.results[1].data).toString("hex")).toEqual( "01000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d01000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000" ); }); test("serialize and deserialize sol_pda request with defaults", () => { const solPdaReq = new SolanaPdaQueryRequest( "finalized", PDAS, BigInt(123456), BigInt(12), BigInt(20) ); expect(Number(solPdaReq.minContextSlot)).toEqual(123456); expect(Number(solPdaReq.dataSliceOffset)).toEqual(12); expect(Number(solPdaReq.dataSliceLength)).toEqual(20); const serialized = solPdaReq.serialize(); expect(Buffer.from(serialized).toString("hex")).toEqual( "0000000966696e616c697a6564000000000001e240000000000000000c00000000000000140102c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa020000000b477561726469616e5365740000000400000000" ); const solPdaReq2 = SolanaPdaQueryRequest.from(serialized); expect(solPdaReq2).toEqual(solPdaReq); }); test("deserialize sol_pda response", () => { const respBytes = Buffer.from( "0100000c8418d81c00aad6283ba3eb30e141ccdd9296e013ca44e5cc713418921253004b93107ba0d858a548ce989e2bca4132e4c2f9a57a9892e3a87a8304cdb36d8f000000006b010000002b010001050000005e0000000966696e616c697a656400000000000008ff000000000000000c00000000000000140102c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa020000000b477561726469616e5365740000000400000000010001050000009b00000000000008ff0006115e3f6d7540e05035785e15056a8559815e71343ce31db2abf23f65b19c982b68aee7bf207b014fa9188b339cfd573a0778c5deaeeee94d4bcfb12b345bf8e417e5119dae773efd0000000000116ac000000000000000000002c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa0000001457cd18b7f8a4d91a2da9ab4af05d0fbece2dcd65", "hex" ); const queryResponse = QueryResponse.from(respBytes); expect(queryResponse.version).toEqual(1); expect(queryResponse.requestChainId).toEqual(0); expect(queryResponse.request.version).toEqual(1); expect(queryResponse.request.requests.length).toEqual(1); expect(queryResponse.request.requests[0].chainId).toEqual(1); expect(queryResponse.request.requests[0].query.type()).toEqual( ChainQueryType.SolanaPda ); const sar = queryResponse.responses[0].response as SolanaPdaQueryResponse; expect(Number(sar.slotNumber)).toEqual(2303); expect(Number(sar.blockTime)).toEqual(0x0006115e3f6d7540); expect(sar.results.length).toEqual(1); expect(Buffer.from(sar.results[0].account).toString("hex")).toEqual( "4fa9188b339cfd573a0778c5deaeeee94d4bcfb12b345bf8e417e5119dae773e" ); expect(sar.results[0].bump).toEqual(253); expect(Number(sar.results[0].lamports)).not.toEqual(0); expect(Number(sar.results[0].rentEpoch)).toEqual(0); expect(sar.results[0].executable).toEqual(false); expect(Buffer.from(sar.results[0].owner).toString("hex")).toEqual( "02c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa" ); expect(Buffer.from(sar.results[0].data).toString("hex")).toEqual( "57cd18b7f8a4d91a2da9ab4af05d0fbece2dcd65" ); }); test("successful sol_pda query", async () => { const solPdaReq = new SolanaPdaQueryRequest( "finalized", PDAS, BigInt(0), BigInt(12), BigInt(16) // After this, things can change. ); const nonce = 43; const query = new PerChainQueryRequest(1, solPdaReq); const request = new QueryRequest(nonce, [query]); const serialized = request.serialize(); const digest = QueryRequest.digest(ENV, serialized); const signature = sign(PRIVATE_KEY, digest); const response = await axios.put( QUERY_URL, { signature, bytes: Buffer.from(serialized).toString("hex"), }, { headers: { "X-API-Key": "my_secret_key" } } ); expect(response.status).toBe(200); const queryResponse = QueryResponse.from(response.data.bytes); expect(queryResponse.version).toEqual(1); expect(queryResponse.requestChainId).toEqual(0); expect(queryResponse.request.version).toEqual(1); expect(queryResponse.request.requests.length).toEqual(1); expect(queryResponse.request.requests[0].chainId).toEqual(1); expect(queryResponse.request.requests[0].query.type()).toEqual( ChainQueryType.SolanaPda ); const sar = queryResponse.responses[0].response as SolanaPdaQueryResponse; expect(Number(sar.slotNumber)).not.toEqual(0); expect(Number(sar.blockTime)).not.toEqual(0); expect(sar.results.length).toEqual(1); expect(Buffer.from(sar.results[0].account).toString("hex")).toEqual( "4fa9188b339cfd573a0778c5deaeeee94d4bcfb12b345bf8e417e5119dae773e" ); expect(sar.results[0].bump).toEqual(253); expect(Number(sar.results[0].lamports)).not.toEqual(0); expect(Number(sar.results[0].rentEpoch)).toEqual(0); expect(sar.results[0].executable).toEqual(false); expect(Buffer.from(sar.results[0].owner).toString("hex")).toEqual( "02c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa" ); expect(Buffer.from(sar.results[0].data).toString("hex")).toEqual( "57cd18b7f8a4d91a2da9ab4af05d0fbe" ); }); test("successful sol_pda query with future min context slot", async () => { const currSlot = await getSolanaSlot("finalized"); const minContextSlot = BigInt(currSlot) + BigInt(10); const solPdaReq = new SolanaPdaQueryRequest( "finalized", PDAS, minContextSlot, BigInt(12), BigInt(16) // After this, things can change. ); const nonce = 43; const query = new PerChainQueryRequest(1, solPdaReq); const request = new QueryRequest(nonce, [query]); const serialized = request.serialize(); const digest = QueryRequest.digest(ENV, serialized); const signature = sign(PRIVATE_KEY, digest); const response = await axios.put( QUERY_URL, { signature, bytes: Buffer.from(serialized).toString("hex"), }, { headers: { "X-API-Key": "my_secret_key" } } ); expect(response.status).toBe(200); const queryResponse = QueryResponse.from(response.data.bytes); expect(queryResponse.version).toEqual(1); expect(queryResponse.requestChainId).toEqual(0); expect(queryResponse.request.version).toEqual(1); expect(queryResponse.request.requests.length).toEqual(1); expect(queryResponse.request.requests[0].chainId).toEqual(1); expect(queryResponse.request.requests[0].query.type()).toEqual( ChainQueryType.SolanaPda ); const sar = queryResponse.responses[0].response as SolanaPdaQueryResponse; expect(sar.slotNumber).toEqual(minContextSlot); expect(Number(sar.blockTime)).not.toEqual(0); expect(sar.results.length).toEqual(1); expect(Buffer.from(sar.results[0].account).toString("hex")).toEqual( "4fa9188b339cfd573a0778c5deaeeee94d4bcfb12b345bf8e417e5119dae773e" ); expect(sar.results[0].bump).toEqual(253); expect(Number(sar.results[0].lamports)).not.toEqual(0); expect(Number(sar.results[0].rentEpoch)).toEqual(0); expect(sar.results[0].executable).toEqual(false); expect(Buffer.from(sar.results[0].owner).toString("hex")).toEqual( "02c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa" ); expect(Buffer.from(sar.results[0].data).toString("hex")).toEqual( "57cd18b7f8a4d91a2da9ab4af05d0fbe" ); }); test("concurrent queries", async () => { const solAccountReq = new SolanaAccountQueryRequest("finalized", ACCOUNTS); const query = new PerChainQueryRequest(1, solAccountReq); let nonce = 42; let promises: Promise>[] = []; for (let count = 0; count < 20; count++) { nonce += 1; const request = new QueryRequest(nonce, [query]); const serialized = request.serialize(); const digest = QueryRequest.digest(ENV, serialized); const signature = sign(PRIVATE_KEY, digest); const response = axios.put( QUERY_URL, { signature, bytes: Buffer.from(serialized).toString("hex"), }, { headers: { "X-API-Key": "my_secret_key" } } ); promises.push(response); } const responses = await Promise.all(promises); expect(responses.length).toEqual(promises.length); for (let idx = 0; idx < responses.length; idx++) { const response = responses[idx]; expect(response.status).toBe(200); const queryResponse = QueryResponse.from(response.data.bytes); expect(queryResponse.version).toEqual(1); expect(queryResponse.requestChainId).toEqual(0); expect(queryResponse.request.version).toEqual(1); expect(queryResponse.request.requests.length).toEqual(1); expect(queryResponse.request.requests[0].chainId).toEqual(1); expect(queryResponse.request.requests[0].query.type()).toEqual( ChainQueryType.SolanaAccount ); const sar = queryResponse.responses[0] .response as SolanaAccountQueryResponse; expect(Number(sar.slotNumber)).not.toEqual(0); expect(Number(sar.blockTime)).not.toEqual(0); expect(sar.results.length).toEqual(2); expect(Number(sar.results[0].lamports)).toEqual(1461600); expect(Number(sar.results[0].rentEpoch)).toEqual(0); expect(sar.results[0].executable).toEqual(false); expect(base58.encode(Buffer.from(sar.results[0].owner))).toEqual( "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" ); expect(Buffer.from(sar.results[0].data).toString("hex")).toEqual( "01000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d0000e8890423c78a0901000000000000000000000000000000000000000000000000000000000000000000000000" ); expect(Number(sar.results[1].lamports)).toEqual(1461600); expect(Number(sar.results[1].rentEpoch)).toEqual(0); expect(sar.results[1].executable).toEqual(false); expect(base58.encode(Buffer.from(sar.results[1].owner))).toEqual( "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" ); expect(Buffer.from(sar.results[1].data).toString("hex")).toEqual( "01000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d01000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000" ); } }); });