Skip to content

Commit

Permalink
Merge branch 'main' into feat/subgraph-handling
Browse files Browse the repository at this point in the history
  • Loading branch information
marcomariscal committed Nov 11, 2024
2 parents 9539322 + b55a867 commit 2872a72
Show file tree
Hide file tree
Showing 3 changed files with 243 additions and 69 deletions.
28 changes: 24 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
bun run build
test:
runs-on: ubuntu-latest
environment: Testing
environment: Testing All Networks
steps:
- uses: actions/checkout@v4
- name: Install Foundry
Expand All @@ -29,13 +29,23 @@ jobs:
run: anvil &
- name: Run tests
env:
SUBGRAPH_URL: ${{ secrets.SUBGRAPH_URL }}
SUBGRAPH_URL_PREFIX: ${{ secrets.SUBGRAPH_URL_PREFIX }}
SUBGRAPH_NAME_ARBITRUM_ONE: ${{ secrets.SUBGRAPH_NAME_ARBITRUM_ONE }}
SUBGRAPH_NAME_ARBITRUM_SEPOLIA: ${{ secrets.SUBGRAPH_NAME_ARBITRUM_SEPOLIA }}
SUBGRAPH_NAME_BASE: ${{ secrets.SUBGRAPH_NAME_BASE }}
SUBGRAPH_NAME_BASE_SEPOLIA: ${{ secrets.SUBGRAPH_NAME_BASE_SEPOLIA }}
SUBGRAPH_NAME_HOLESKY: ${{ secrets.SUBGRAPH_NAME_HOLESKY }}
SUBGRAPH_NAME_MAINNET: ${{ secrets.SUBGRAPH_NAME_MAINNET }}
SUBGRAPH_NAME_MATIC: ${{ secrets.SUBGRAPH_NAME_MATIC }}
SUBGRAPH_NAME_OPTIMISM: ${{ secrets.SUBGRAPH_NAME_OPTIMISM }}
SUBGRAPH_NAME_OPTIMISM_SEPOLIA: ${{ secrets.SUBGRAPH_NAME_OPTIMISM_SEPOLIA }}
SUBGRAPH_NAME_SEPOLIA: ${{ secrets.SUBGRAPH_NAME_SEPOLIA }}
run: |
bun install
bun test
coverage:
runs-on: ubuntu-latest
environment: Testing
environment: Testing All Networks
steps:
- uses: actions/checkout@v4
- name: Install Foundry
Expand All @@ -46,7 +56,17 @@ jobs:
run: anvil &
- name: Run test coverage
env:
SUBGRAPH_URL: ${{ secrets.SUBGRAPH_URL }}
SUBGRAPH_URL_PREFIX: ${{ secrets.SUBGRAPH_URL_PREFIX }}
SUBGRAPH_NAME_ARBITRUM_ONE: ${{ secrets.SUBGRAPH_NAME_ARBITRUM_ONE }}
SUBGRAPH_NAME_ARBITRUM_SEPOLIA: ${{ secrets.SUBGRAPH_NAME_ARBITRUM_SEPOLIA }}
SUBGRAPH_NAME_BASE: ${{ secrets.SUBGRAPH_NAME_BASE }}
SUBGRAPH_NAME_BASE_SEPOLIA: ${{ secrets.SUBGRAPH_NAME_BASE_SEPOLIA }}
SUBGRAPH_NAME_HOLESKY: ${{ secrets.SUBGRAPH_NAME_HOLESKY }}
SUBGRAPH_NAME_MAINNET: ${{ secrets.SUBGRAPH_NAME_MAINNET }}
SUBGRAPH_NAME_MATIC: ${{ secrets.SUBGRAPH_NAME_MATIC }}
SUBGRAPH_NAME_OPTIMISM: ${{ secrets.SUBGRAPH_NAME_OPTIMISM }}
SUBGRAPH_NAME_OPTIMISM_SEPOLIA: ${{ secrets.SUBGRAPH_NAME_OPTIMISM_SEPOLIA }}
SUBGRAPH_NAME_SEPOLIA: ${{ secrets.SUBGRAPH_NAME_SEPOLIA }}
run: |
bun install
bun test --coverage
Expand Down
18 changes: 18 additions & 0 deletions src/config/startBlocks.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
export enum ERC5564_StartBlocks {
ARBITRUM_ONE = 219468264,
ARBITRUM_SEPOLIA = 24849998,
BASE = 15502414,
BASE_SEPOLIA = 7552655,
HOLESKY = 1222405,
MAINNET = 20042207,
MATIC = 57888814,
OPTIMISM = 121097390,
OPTIMISM_SEPOLIA = 9534458,
SEPOLIA = 5486597
}

export enum ERC6538_StartBlocks {
ARBITRUM_ONE = 219468267,
ARBITRUM_SEPOLIA = 25796636,
BASE = 15502414,
BASE_SEPOLIA = 7675097,
HOLESKY = 1222405,
MAINNET = 20042207,
MATIC = 57888814,
OPTIMISM = 121097390,
OPTIMISM_SEPOLIA = 9658043,
SEPOLIA = 5538412
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,96 +5,232 @@ import type { AnnouncementLog } from '../getAnnouncements/types';
import getAnnouncementsUsingSubgraph from './getAnnouncementsUsingSubgraph';
import { GetAnnouncementsUsingSubgraphError } from './types';

describe('getAnnouncementsUsingSubgraph with real subgraph', () => {
const subgraphUrl = process.env.SUBGRAPH_URL;
if (!subgraphUrl) throw new Error('SUBGRAPH_URL not set in env');
enum Network {
ARBITRUM_ONE = 'ARBITRUM_ONE',
ARBITRUM_SEPOLIA = 'ARBITRUM_SEPOLIA',
BASE = 'BASE',
BASE_SEPOLIA = 'BASE_SEPOLIA',
HOLESKY = 'HOLESKY',
MAINNET = 'MAINNET',
MATIC = 'MATIC',
OPTIMISM = 'OPTIMISM',
OPTIMISM_SEPOLIA = 'OPTIMISM_SEPOLIA',
SEPOLIA = 'SEPOLIA'
}

type NetworkInfo = {
name: Network;
url: string;
startBlock: number;
};

type TestResult = {
network: NetworkInfo;
announcements: AnnouncementLog[];
error?: Error;
};

const checkEnvVars = () => {
if (!process.env.SUBGRAPH_NAME_ARBITRUM_ONE)
throw new Error('SUBGRAPH_NAME_ARBITRUM_ONE not set in env');
if (!process.env.SUBGRAPH_NAME_ARBITRUM_SEPOLIA)
throw new Error('SUBGRAPH_NAME_ARBITRUM_SEPOLIA not set in env');
if (!process.env.SUBGRAPH_NAME_BASE)
throw new Error('SUBGRAPH_NAME_BASE not set in env');
if (!process.env.SUBGRAPH_NAME_BASE_SEPOLIA)
throw new Error('SUBGRAPH_NAME_BASE_SEPOLIA not set in env');
if (!process.env.SUBGRAPH_NAME_HOLESKY)
throw new Error('SUBGRAPH_NAME_HOLESKY not set in env');
if (!process.env.SUBGRAPH_NAME_MAINNET)
throw new Error('SUBGRAPH_NAME_MAINNET not set in env');
if (!process.env.SUBGRAPH_NAME_MATIC)
throw new Error('SUBGRAPH_NAME_MATIC not set in env');
if (!process.env.SUBGRAPH_NAME_OPTIMISM)
throw new Error('SUBGRAPH_NAME_OPTIMISM not set in env');
if (!process.env.SUBGRAPH_NAME_OPTIMISM_SEPOLIA)
throw new Error('SUBGRAPH_NAME_OPTIMISM_SEPOLIA not set in env');
if (!process.env.SUBGRAPH_NAME_SEPOLIA)
throw new Error('SUBGRAPH_NAME_SEPOLIA not set in env');
};

const getNetworksInfo = () => {
checkEnvVars();

if (!process.env.SUBGRAPH_URL_PREFIX)
throw new Error('SUBGRAPH_URL_PREFIX not set in env');

const networks: NetworkInfo[] = Object.values(Network)
.map(network => {
const subgraphName = process.env[`SUBGRAPH_NAME_${network}`];
const url = `${process.env.SUBGRAPH_URL_PREFIX}/${subgraphName}/api`;
const startBlock = ERC5564_StartBlocks[network];

return {
name: network,
url,
startBlock
};
})
.filter((network): network is NetworkInfo => network !== null);

const fromBlock = ERC5564_StartBlocks.SEPOLIA;
let result: AnnouncementLog[];
if (networks.length === 0) {
throw new Error('No SUBGRAPH_NAME_* env vars set');
}

return networks;
};

const networks = getNetworksInfo();

describe('getAnnouncementsUsingSubgraph with real subgraph', () => {
let testResults: TestResult[] = [];

beforeAll(async () => {
try {
result = await getAnnouncementsUsingSubgraph({
subgraphUrl,
filter: `
blockNumber_gte: ${fromBlock}
`
});
} catch (error) {
console.error(`Failed to fetch announcements: ${error}`);
throw error;
testResults = await Promise.all(
networks.map(async network => {
try {
const announcements = await getAnnouncementsUsingSubgraph({
subgraphUrl: network.url,
filter: `blockNumber_gte: ${network.startBlock}`
});
return { network, announcements };
} catch (error) {
return { network, announcements: [], error: error as Error };
}
})
);

// Log results after all fetches are complete
for (const result of testResults) {
if (result.error) {
console.error(
`❌ Failed to fetch from ${result.network.name}: ${result.network.url}`
);
console.error(` Error: ${result.error.message}`);
} else {
console.log(
`✅ Successfully fetched from ${result.network.name}: ${result.network.url}`
);
console.log(
` Number of announcements: ${result.announcements.length}`
);
}
}
});

test('fetches announcements successfully', () => {
expect(result).toBeDefined();
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBeGreaterThan(0);
test('should successfully fetch from all subgraphs', () => {
for (const result of testResults) {
expect(result.error).toBeUndefined();
}
});

test('announcement structure is correct', () => {
const announcement = result[0];
expect(announcement).toHaveProperty('blockNumber');
expect(announcement).toHaveProperty('blockHash');
expect(announcement).toHaveProperty('transactionIndex');
expect(announcement).toHaveProperty('removed');
expect(announcement).toHaveProperty('address');
expect(announcement).toHaveProperty('data');
expect(announcement).toHaveProperty('topics');
expect(announcement).toHaveProperty('transactionHash');
expect(announcement).toHaveProperty('logIndex');
expect(announcement).toHaveProperty('schemeId');
expect(announcement).toHaveProperty('stealthAddress');
expect(announcement).toHaveProperty('caller');
expect(announcement).toHaveProperty('ephemeralPubKey');
expect(announcement).toHaveProperty('metadata');
});
test('announcement structure is correct for all subgraphs', () => {
const expectedProperties = [
'blockNumber',
'blockHash',
'transactionIndex',
'removed',
'address',
'data',
'topics',
'transactionHash',
'logIndex',
'schemeId',
'stealthAddress',
'caller',
'ephemeralPubKey',
'metadata'
];

test('applies caller filter correctly', async () => {
if (result.length === 0) {
console.warn('No announcements found to test caller filter');
return;
for (const result of testResults) {
if (result.announcements.length > 0) {
const announcement = result.announcements[0];
for (const prop of expectedProperties) {
expect(announcement).toHaveProperty(prop);
}
}
}
});

const caller = result[0].caller;
const filteredResult = await getAnnouncementsUsingSubgraph({
subgraphUrl,
filter: `
caller: "${caller}"
`,
pageSize: 100
});
test('applies caller filter correctly for all subgraphs', async () => {
for (const result of testResults) {
if (result.announcements.length === 0) {
console.warn(
`No announcements found to test caller filter for ${result.network.name}`
);
continue;
}

expect(filteredResult.length).toBeGreaterThan(0);
expect(
filteredResult.every(a => getAddress(a.caller) === getAddress(caller))
).toBe(true);
const caller = result.announcements[0].caller;
const filteredResult = await getAnnouncementsUsingSubgraph({
subgraphUrl: result.network.url,
filter: `caller: "${caller}"`
});

expect(filteredResult.length).toBeGreaterThan(0);
expect(
filteredResult.every(a => getAddress(a.caller) === getAddress(caller))
).toBe(true);
}
});

test('handles pagination correctly', async () => {
const smallPageSize = 10;
const pagedResult = await getAnnouncementsUsingSubgraph({
subgraphUrl,
filter: `
blockNumber_gte: ${fromBlock}
`,
pageSize: smallPageSize
});
test('handles pagination correctly for all subgraphs', async () => {
const largePageSize = 10000;
const paginationResults = await Promise.all(
networks.map(async network => {
try {
const announcements = await getAnnouncementsUsingSubgraph({
subgraphUrl: network.url,
filter: `blockNumber_gte: ${network.startBlock}`,
pageSize: largePageSize
});
return { network, announcements };
} catch (error) {
return { network, announcements: [], error: error as Error };
}
})
);

for (let i = 0; i < testResults.length; i++) {
const initialResult = testResults[i];
const paginatedResult = paginationResults[i];

// Skip if there was an error in either fetch
if (initialResult.error || paginatedResult.error) {
console.warn(
`Skipping pagination test for ${initialResult.network.name} due to fetch error`
);
continue;
}

expect(pagedResult.length).toBe(result.length);
expect(pagedResult).toEqual(result);
expect(paginatedResult.announcements.length).toBeGreaterThanOrEqual(
initialResult.announcements.length
);

// Check that the paginated results contain at least all the announcements from the initial fetch
const initialAnnouncementSet = new Set(
initialResult.announcements.map(a => a.transactionHash)
);
const paginatedAnnouncementSet = new Set(
paginatedResult.announcements.map(a => a.transactionHash)
);

for (const hash of initialAnnouncementSet) {
expect(paginatedAnnouncementSet.has(hash)).toBe(true);
}
}
});

test('should throw GetAnnouncementsUsingSubgraphError on fetch failure', async () => {
expect(
getAnnouncementsUsingSubgraph({
subgraphUrl: 'http://example.com/subgraph'
subgraphUrl: 'http://example.com/invalid-subgraph'
})
).rejects.toThrow(GetAnnouncementsUsingSubgraphError);

expect(
getAnnouncementsUsingSubgraph({
subgraphUrl: 'http://example.com/subgraph'
subgraphUrl: 'http://example.com/invalid-subgraph'
})
).rejects.toMatchObject({
message: 'Failed to fetch announcements from the subgraph'
Expand Down

0 comments on commit 2872a72

Please sign in to comment.