From 59affc166fe7cda25eb890c85020bce3681a83fe Mon Sep 17 00:00:00 2001 From: Bruno Menezes Date: Fri, 6 Sep 2024 13:08:46 +1200 Subject: [PATCH] Feature: add arbitrum network (#44) --- README.md | 28 +++--- src/config.ts | 85 ++++++++++++++--- src/gateways.ts | 2 + src/utils.ts | 4 + tests/gateways.test.ts | 10 ++ tests/processor.test.ts | 196 +++++++++++++++++++++++++++++++++++++++- tests/utils.test.ts | 41 ++++++++- 7 files changed, 334 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 9fb0427..1ea0bfa 100644 --- a/README.md +++ b/README.md @@ -8,19 +8,21 @@ That project requires [docker](https://docker.com) to be installed so the backen ### Configuration (Environment Variables) -> Supported Chains: 1, 11155111, 10, 11155420, 8453, 84532, 31337 - -| Variables | Default | Description | -| :-------------------------: | :--------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| `CHAIN_IDS` | `11155111` | Comma separated supported chain ids to be indexed | -| `RPC_URL_1` | `https://rpc.ankr.com/eth` | Endpoint for Mainnet RPC node | -| `RPC_URL_11155111` | `https://rpc.ankr.com/eth_sepolia` | Endpoint for Sepolia RPC node | -| `RPC_URL_31337` | `http://127.0.0.1:8545` | Endpoint for local node | -| `RPC_URL_10` | `https://mainnet.optimism.io` | Endpoint for Optimism Mainnet RPC node | -| `RPC_URL_11155420` | `https://sepolia.optimism.io` | Endpoint for Optimism Sepolia RPC node | -| `RPC_URL_8453` | `https://mainnet.base.org` | Endpoint for Base Mainnet RPC node | -| `RPC_URL_84532` | `https://sepolia.base.org` | Endpoint for Base Sepolia RPC node | -| `RPC_RATE_LIMIT_{CHAIN_ID}` | `undefined` | Option to fine tune concurrent requests by rate limiting the requests to RPC node providers. Replace {CHAIN_ID} with a supported chain id and set a Number e.g. 15. That is good to avoid [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) status code when using free tiers. The default is Full speed | +> Supported Chains: 1, 11155111, 10, 11155420, 8453, 84532, 42161, 421614, 31337 + +| Variables | Default | Description | +| :-------------------------: | :--------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| `CHAIN_IDS` | `31337` | Comma separated supported chain ids to be indexed | +| `RPC_URL_1` | `https://rpc.ankr.com/eth` | Endpoint for Mainnet RPC node | +| `RPC_URL_11155111` | `https://rpc.ankr.com/eth_sepolia` | Endpoint for Sepolia RPC node | +| `RPC_URL_31337` | `http://127.0.0.1:8545` | Endpoint for local node | +| `RPC_URL_10` | `https://mainnet.optimism.io` | Endpoint for Optimism Mainnet RPC node | +| `RPC_URL_11155420` | `https://sepolia.optimism.io` | Endpoint for Optimism Sepolia RPC node | +| `RPC_URL_8453` | `https://mainnet.base.org` | Endpoint for Base Mainnet RPC node | +| `RPC_URL_84532` | `https://sepolia.base.org` | Endpoint for Base Sepolia RPC node | +| `RPC_URL_42161` | `https://arb1.arbitrum.io/rpc` | Endpoint for Arbitrum RPC node | +| `RPC_URL_421614` | `https://sepolia-rollup.arbitrum.io/rpc` | Endpoint for Arbitrum Sepolia RPC node | +| `RPC_RATE_LIMIT_{CHAIN_ID}` | `undefined` | Option to fine tune concurrent requests by rate limiting the requests to RPC node providers. Replace {CHAIN_ID} with a supported chain id and set a Number e.g. 15. That is good to avoid [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) status code when using free tiers. The default is Full speed | For use with local devnet for fine tunning and deal with any unexpected changes e.g. Foundry nightly versions. diff --git a/src/config.ts b/src/config.ts index 6835e30..6960bb9 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,3 +1,7 @@ +import CartesiDAppFactoryArbitrum from '@cartesi/rollups/deployments/arbitrum/CartesiDAppFactory.json'; +import InputBoxArbitrum from '@cartesi/rollups/deployments/arbitrum/InputBox.json'; +import CartesiDAppFactoryArbitrumSepolia from '@cartesi/rollups/deployments/arbitrum_sepolia/CartesiDAppFactory.json'; +import InputBoxArbitrumSepolia from '@cartesi/rollups/deployments/arbitrum_sepolia/InputBox.json'; import CartesiDAppFactoryBase from '@cartesi/rollups/deployments/base/CartesiDAppFactory.json'; import inputBoxBase from '@cartesi/rollups/deployments/base/InputBox.json'; import CartesiDAppFactoryBaseSepolia from '@cartesi/rollups/deployments/base_sepolia/CartesiDAppFactory.json'; @@ -10,24 +14,35 @@ import CartesiDAppFactoryOptimismSepolia from '@cartesi/rollups/deployments/opti import InputBoxOptimismSepolia from '@cartesi/rollups/deployments/optimism_sepolia/InputBox.json'; import CartesiDAppFactorySepolia from '@cartesi/rollups/deployments/sepolia/CartesiDAppFactory.json'; import InputBoxSepolia from '@cartesi/rollups/deployments/sepolia/InputBox.json'; -import mainnet from '@cartesi/rollups/export/abi/mainnet.json'; +import rollupsMainnet from '@cartesi/rollups/export/abi/mainnet.json'; import { GatewaySettings, RpcEndpointSettings } from '@subsquid/evm-processor'; -import { base, baseSepolia, optimism, optimismSepolia } from 'viem/chains'; +import { + arbitrum, + arbitrumSepolia, + base, + baseSepolia, + foundry, + mainnet, + optimism, + optimismSepolia, + sepolia, +} from 'viem/chains'; import { archiveNodes } from './gateways'; import { parseIntOr } from './utils'; // addresses are the same on all chains export const CartesiDAppFactoryAddress = - mainnet.contracts.CartesiDAppFactory.address.toLowerCase(); + rollupsMainnet.contracts.CartesiDAppFactory.address.toLowerCase(); export const ERC20PortalAddress = - mainnet.contracts.ERC20Portal.address.toLowerCase(); -export const InputBoxAddress = mainnet.contracts.InputBox.address.toLowerCase(); + rollupsMainnet.contracts.ERC20Portal.address.toLowerCase(); +export const InputBoxAddress = + rollupsMainnet.contracts.InputBox.address.toLowerCase(); export const ERC721PortalAddress = - mainnet.contracts.ERC721Portal.address.toLowerCase(); + rollupsMainnet.contracts.ERC721Portal.address.toLowerCase(); export const ERC1155SinglePortalAddress = - mainnet.contracts.ERC1155SinglePortal.address.toLowerCase(); + rollupsMainnet.contracts.ERC1155SinglePortal.address.toLowerCase(); export const ERC1155BatchPortalAddress = - mainnet.contracts.ERC1155BatchPortal.address.toLowerCase(); + rollupsMainnet.contracts.ERC1155BatchPortal.address.toLowerCase(); interface ArchiveDataSource { archive: string | GatewaySettings; rpcEndpoint?: string | RpcEndpointSettings; @@ -59,7 +74,7 @@ export const getConfig = (chainId: number): ProcessorConfig => { const rateLimit = parsedRateLimit <= 0 ? undefined : parsedRateLimit; switch (chainId) { - case 1: // mainnet + case mainnet.id: return { dataSource: { archive: archiveNodes.mainnet, @@ -77,7 +92,7 @@ export const getConfig = (chainId: number): ProcessorConfig => { value: process.env[BLOCK_CONFIRMATIONS], }), }; - case 11155111: // sepolia + case sepolia.id: return { dataSource: { archive: archiveNodes.sepolia, @@ -97,7 +112,7 @@ export const getConfig = (chainId: number): ProcessorConfig => { value: process.env[BLOCK_CONFIRMATIONS], }), }; - case 10: //Optimism-Mainnet + case optimism.id: return { dataSource: { archive: archiveNodes.optimism, @@ -117,7 +132,7 @@ export const getConfig = (chainId: number): ProcessorConfig => { value: process.env[BLOCK_CONFIRMATIONS], }), }; - case 11155420: //Optimism-Sepolia + case optimismSepolia.id: //Optimism-Sepolia return { dataSource: { archive: archiveNodes.optimismSepolia, @@ -137,7 +152,7 @@ export const getConfig = (chainId: number): ProcessorConfig => { value: process.env[BLOCK_CONFIRMATIONS], }), }; - case 8453: //Base-Mainnet + case base.id: return { dataSource: { archive: archiveNodes.base, @@ -157,7 +172,7 @@ export const getConfig = (chainId: number): ProcessorConfig => { value: process.env[BLOCK_CONFIRMATIONS], }), }; - case 84532: //Base-Sepolia + case baseSepolia.id: return { dataSource: { archive: archiveNodes.baseSepolia, @@ -177,7 +192,47 @@ export const getConfig = (chainId: number): ProcessorConfig => { value: process.env[BLOCK_CONFIRMATIONS], }), }; - case 31337: // anvil + case arbitrum.id: + return { + dataSource: { + archive: archiveNodes.arbitrum, + rpcEndpoint: { + url: + process.env[RPC_URL] ?? + arbitrum.rpcUrls.default.http[0], + rateLimit: rateLimit, + }, + }, + from: Math.min( + CartesiDAppFactoryArbitrum.receipt.blockNumber, + InputBoxArbitrum.receipt.blockNumber, + ), + finalityConfirmation: parseIntOr({ + defaultVal: FINALITY_CONFIRMATION, + value: process.env[BLOCK_CONFIRMATIONS], + }), + }; + case arbitrumSepolia.id: + return { + dataSource: { + archive: archiveNodes.arbitrumSepolia, + rpcEndpoint: { + url: + process.env[RPC_URL] ?? + arbitrumSepolia.rpcUrls.default.http[0], + rateLimit: rateLimit, + }, + }, + from: Math.min( + CartesiDAppFactoryArbitrumSepolia.receipt.blockNumber, + InputBoxArbitrumSepolia.receipt.blockNumber, + ), + finalityConfirmation: parseIntOr({ + defaultVal: FINALITY_CONFIRMATION, + value: process.env[BLOCK_CONFIRMATIONS], + }), + }; + case foundry.id: return { dataSource: { rpcEndpoint: diff --git a/src/gateways.ts b/src/gateways.ts index d6328dc..3c96c56 100644 --- a/src/gateways.ts +++ b/src/gateways.ts @@ -12,4 +12,6 @@ export const archiveNodes = { optimismSepolia: 'https://v2.archive.subsquid.io/network/optimism-sepolia', mainnet: 'https://v2.archive.subsquid.io/network/ethereum-mainnet', sepolia: 'https://v2.archive.subsquid.io/network/ethereum-sepolia', + arbitrum: 'https://v2.archive.subsquid.io/network/arbitrum-one', + arbitrumSepolia: 'https://v2.archive.subsquid.io/network/arbitrum-sepolia', } as const; diff --git a/src/utils.ts b/src/utils.ts index ff9c7ac..a37d1f8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,7 @@ import { existsSync, readFileSync } from 'node:fs'; import { + arbitrum, + arbitrumSepolia, base, baseSepolia, foundry, @@ -37,6 +39,8 @@ export const supportedChains = new Set([ baseSepolia.id, optimism.id, optimismSepolia.id, + arbitrum.id, + arbitrumSepolia.id, ]); /** diff --git a/tests/gateways.test.ts b/tests/gateways.test.ts index bca976a..8095d7d 100644 --- a/tests/gateways.test.ts +++ b/tests/gateways.test.ts @@ -10,6 +10,8 @@ describe('Gateways (Squid Archive Nodes)', () => { 'optimismSepolia', 'mainnet', 'sepolia', + 'arbitrum', + 'arbitrumSepolia', ]); }); @@ -33,5 +35,13 @@ describe('Gateways (Squid Archive Nodes)', () => { expect(archiveNodes.sepolia).toStrictEqual( 'https://v2.archive.subsquid.io/network/ethereum-sepolia', ); + + expect(archiveNodes.arbitrum).toStrictEqual( + 'https://v2.archive.subsquid.io/network/arbitrum-one', + ); + + expect(archiveNodes.arbitrumSepolia).toStrictEqual( + 'https://v2.archive.subsquid.io/network/arbitrum-sepolia', + ); }); }); diff --git a/tests/processor.test.ts b/tests/processor.test.ts index c671bc0..1be33a6 100644 --- a/tests/processor.test.ts +++ b/tests/processor.test.ts @@ -31,6 +31,8 @@ const optimism = 10; const optimismSepolia = 11155420; const base = 8453; const baseSepolia = 84532; +const arbitrum = 42161; +const arbitrumSepolia = 421614; describe('Processor creation', () => { beforeEach(() => { @@ -358,6 +360,104 @@ describe('Processor creation', () => { }); }); + test('Required configs for arbitrum', () => { + const processor = createProcessor(arbitrum); + + expect(processor.setGateway).toHaveBeenCalledWith( + 'https://v2.archive.subsquid.io/network/arbitrum-one', + ); + + expect(processor.setRpcEndpoint).toHaveBeenCalledWith({ + url: 'https://arb1.arbitrum.io/rpc', + }); + + expect(processor.setFinalityConfirmation).toHaveBeenCalledWith(10); + expect(processor.setFields).toHaveBeenCalledWith({ + transaction: { + chainId: true, + from: true, + hash: true, + value: true, + }, + }); + expect(processor.setBlockRange).toHaveBeenCalledWith({ + from: 115470622, + }); + + const addLog = processor.addLog as unknown as MockInstance; + + expect(addLog).toHaveBeenCalledTimes(3); + expect(addLog.mock.calls[0][0]).toEqual({ + address: ['0x7122cd1221c20892234186facfe8615e6743ab02'], + topic0: [ + '0xe73165c2d277daf8713fd08b40845cb6bb7a20b2b543f3d35324a475660fcebd', + ], + }); + expect(addLog.mock.calls[1][0]).toEqual({ + address: ['0x59b22d57d4f067708ab0c00552767405926dc768'], + topic0: [ + '0x6aaa400068bf4ca337265e2a1e1e841f66b8597fd5b452fdc52a44bed28a0784', + ], + transaction: true, + }); + + expect(addLog.mock.calls[2][0]).toEqual({ + topic0: [ + '0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0', + ], + transaction: true, + }); + }); + + test('Required configs for arbitrum sepolia', () => { + const processor = createProcessor(arbitrumSepolia); + + expect(processor.setGateway).toHaveBeenCalledWith( + 'https://v2.archive.subsquid.io/network/arbitrum-sepolia', + ); + + expect(processor.setRpcEndpoint).toHaveBeenCalledWith({ + url: 'https://sepolia-rollup.arbitrum.io/rpc', + }); + + expect(processor.setFinalityConfirmation).toHaveBeenCalledWith(10); + expect(processor.setFields).toHaveBeenCalledWith({ + transaction: { + chainId: true, + from: true, + hash: true, + value: true, + }, + }); + expect(processor.setBlockRange).toHaveBeenCalledWith({ + from: 2838409, + }); + + const addLog = processor.addLog as unknown as MockInstance; + + expect(addLog).toHaveBeenCalledTimes(3); + expect(addLog.mock.calls[0][0]).toEqual({ + address: ['0x7122cd1221c20892234186facfe8615e6743ab02'], + topic0: [ + '0xe73165c2d277daf8713fd08b40845cb6bb7a20b2b543f3d35324a475660fcebd', + ], + }); + expect(addLog.mock.calls[1][0]).toEqual({ + address: ['0x59b22d57d4f067708ab0c00552767405926dc768'], + topic0: [ + '0x6aaa400068bf4ca337265e2a1e1e841f66b8597fd5b452fdc52a44bed28a0784', + ], + transaction: true, + }); + + expect(addLog.mock.calls[2][0]).toEqual({ + topic0: [ + '0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0', + ], + transaction: true, + }); + }); + test('Required configs for mainnet', () => { const processor = createProcessor(mainnet); const applicationMetadata = loadApplications(mainnet); @@ -423,7 +523,7 @@ describe('Processor creation', () => { }); }); - test('Set correct chain for sepolia based on environment var', () => { + test('Set correct chain for sepolia set on environment var', () => { const myRPCNodeURL = 'https://my-custom-sepolia-node/v3/api'; vi.stubEnv('RPC_URL_11155111', myRPCNodeURL); @@ -438,7 +538,7 @@ describe('Processor creation', () => { }); }); - test('Set correct chain for mainnet based on environment var', () => { + test('Set correct chain for mainnet set on environment var', () => { const myRPCNodeURL = 'https://my-custom-mainnet-node/v3/api'; vi.stubEnv('RPC_URL_1', myRPCNodeURL); @@ -453,7 +553,7 @@ describe('Processor creation', () => { }); }); - test('Set correct chain for local/anvil based on environment var', () => { + test('Set correct chain for local/anvil set on environment var', () => { const myRPCNodeURL = 'https://my-custom-local-node:9000'; vi.stubEnv('RPC_URL_31337', myRPCNodeURL); @@ -463,4 +563,94 @@ describe('Processor creation', () => { 'https://my-custom-local-node:9000', ); }); + + test('Set correct chain for Base set on environment var', () => { + const myRPCNodeURL = 'https://my-custom-node/v3/api'; + vi.stubEnv('RPC_URL_8453', myRPCNodeURL); + + const processor = createProcessor(base); + + expect(processor.setGateway).toHaveBeenCalledWith( + 'https://v2.archive.subsquid.io/network/base-mainnet', + ); + + expect(processor.setRpcEndpoint).toHaveBeenCalledWith({ + url: 'https://my-custom-node/v3/api', + }); + }); + + test('Set correct chain for Base Sepolia set on environment var', () => { + const myRPCNodeURL = 'https://my-custom-node/v3/api'; + vi.stubEnv('RPC_URL_84532', myRPCNodeURL); + + const processor = createProcessor(baseSepolia); + + expect(processor.setGateway).toHaveBeenCalledWith( + 'https://v2.archive.subsquid.io/network/base-sepolia', + ); + + expect(processor.setRpcEndpoint).toHaveBeenCalledWith({ + url: 'https://my-custom-node/v3/api', + }); + }); + + test('Set correct chain for Optimism set on environment var', () => { + const myRPCNodeURL = 'https://my-custom-node/v3/api'; + vi.stubEnv('RPC_URL_10', myRPCNodeURL); + + const processor = createProcessor(optimism); + + expect(processor.setGateway).toHaveBeenCalledWith( + 'https://v2.archive.subsquid.io/network/optimism-mainnet', + ); + + expect(processor.setRpcEndpoint).toHaveBeenCalledWith({ + url: 'https://my-custom-node/v3/api', + }); + }); + + test('Set correct chain for Optimism Sepolia set on environment var', () => { + const myRPCNodeURL = 'https://my-custom-node/v3/api'; + vi.stubEnv('RPC_URL_11155420', myRPCNodeURL); + + const processor = createProcessor(optimismSepolia); + + expect(processor.setGateway).toHaveBeenCalledWith( + 'https://v2.archive.subsquid.io/network/optimism-sepolia', + ); + + expect(processor.setRpcEndpoint).toHaveBeenCalledWith({ + url: 'https://my-custom-node/v3/api', + }); + }); + + test('Set correct chain for Arbitrum set on environment var', () => { + const myRPCNodeURL = 'https://my-custom-node/v3/api'; + vi.stubEnv('RPC_URL_42161', myRPCNodeURL); + + const processor = createProcessor(arbitrum); + + expect(processor.setGateway).toHaveBeenCalledWith( + 'https://v2.archive.subsquid.io/network/arbitrum-one', + ); + + expect(processor.setRpcEndpoint).toHaveBeenCalledWith({ + url: 'https://my-custom-node/v3/api', + }); + }); + + test('Set correct chain for Arbitrum Sepolia set on environment var', () => { + const myRPCNodeURL = 'https://my-custom-node/v3/api'; + vi.stubEnv('RPC_URL_421614', myRPCNodeURL); + + const processor = createProcessor(arbitrumSepolia); + + expect(processor.setGateway).toHaveBeenCalledWith( + 'https://v2.archive.subsquid.io/network/arbitrum-sepolia', + ); + + expect(processor.setRpcEndpoint).toHaveBeenCalledWith({ + url: 'https://my-custom-node/v3/api', + }); + }); }); diff --git a/tests/utils.test.ts b/tests/utils.test.ts index 676ec97..04f9044 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -1,7 +1,34 @@ +import { + anvil, + arbitrum, + arbitrumSepolia, + base, + baseSepolia, + mainnet, + optimism, + optimismSepolia, + sepolia, +} from 'viem/chains'; import { describe, expect, it, vi } from 'vitest'; -import { loadChainsToIndexFromEnvironment } from '../src/utils'; +import { + loadChainsToIndexFromEnvironment, + supportedChains, +} from '../src/utils'; describe('Utility functions', () => { + it('should list the supported chains', () => { + expect(Array.from(supportedChains.values())).toStrictEqual([ + mainnet.id, + sepolia.id, + anvil.id, + base.id, + baseSepolia.id, + optimism.id, + optimismSepolia.id, + arbitrum.id, + arbitrumSepolia.id, + ]); + }); describe('Environment Loader for Chains', () => { it('should return foundry as default chain when no chains are defined from environment to be indexed', () => { vi.stubEnv('CHAIN_IDS', ''); @@ -27,5 +54,17 @@ describe('Utility functions', () => { usingDefault: false, }); }); + + it('should returned all supported chains when set on environment variable', () => { + vi.stubEnv( + 'CHAIN_IDS', + '11155111, 1, 10, 8453, 84532, 11155420, 42161, 421614', + ); + + expect(loadChainsToIndexFromEnvironment()).toStrictEqual({ + chains: [11155111, 1, 10, 8453, 84532, 11155420, 42161, 421614], + usingDefault: false, + }); + }); }); });