import assert from "assert"; import { checkProvider, getProvider, setupProviders, providerNames } from "./create-provider.js"; import { retryIt } from "./utils.js"; import { JsonRpcProvider, Network } from "../index.js"; import type { Provider } from "../index.js"; import { networkFeatureAtBlock, networkNames, testAddress, testBlock, testReceipt, testTransaction } from "./blockchain-data.js"; import type { TestBlockchainNetwork } from "./blockchain-data.js"; setupProviders(); function forEach(prefix: string, tests: Record>, func: (providerName: string, test: T) => (null | ((p: Provider) => Promise))): void { for (const networkName of networkNames) { const networkTests: Array = tests[networkName]; if (networkTests == null) { continue; } for (const test of networkTests) { for (const providerName of providerNames) { if (!checkProvider(providerName, networkName)) { continue; } // Let the testcase skip this by returning null const testFunc = func(providerName, test); if (testFunc == null) { continue; } // Prepare the testcase retryIt(`${ prefix }: ${ providerName }:${ networkName }.${ test.test }`, async function() { // Create a provider const provider = getProvider(providerName, networkName); try { assert.ok(provider != null, "missing provider"); await testFunc(provider); } catch (error) { // Shutdown socket-based provider, otherwise its socket will prevent // this process from exiting if ((provider).destroy) { (provider).destroy(); } throw error; } // Shutdown socket-based provider, otherwise its socket will prevent // this process from exiting if ((provider).destroy) { (provider).destroy(); } }); } } } } describe("Test Provider Address operations", function() { forEach("test getBalance(address)", testAddress, (providerName, test) => { if (test.balance == null) { return null; } return async (provider) => { assert.equal(await provider.getBalance(test.address), test.balance); }; }); forEach("test getCode(address)", testAddress, (providerName, test) => { if (test.code == null) { return null; } return async (provider) => { assert.equal(await provider.getCode(test.address), test.code); }; }); /* forEach("test lookupAddress(address)", testAddress, (provider, test) => { if (test.name == null) { return null; } return async () => { assert.equal(await provider.lookupAddress(test.address), test.name); }; }); forEach("test resolveName(name)", testAddress, (provider, test) => { if (test.name == null) { return null; } return async () => { assert.equal(await provider.lookupAddress((test.name)), test.address); }; }); */ forEach("test getStorage(address)", testAddress, (providerName, test) => { if (test.storage == null) { return null; } return async (provider) => { for (const key in test.storage) { assert.equal(await provider.getStorage(test.address, key), test.storage[key]); } }; }); forEach("test getTransactionCount(address)", testAddress, (providerName, test) => { if (test.balance == null) { return null; } return async (provider) => { assert.equal(await provider.getTransactionCount(test.address), test.nonce); }; }); }); function assertObj(prefix: string, actual: any, expected: any): void { assert.ok(actual != null, `${ prefix } is null`); if (typeof(expected) !== "object") { assert.equal(actual, expected, prefix); return; } for (const key in expected) { if (expected[key] === undefined) { continue; } if (Array.isArray(expected[key])) { assert.ok(Array.isArray(actual[key]), `Array.isArray(${ prefix }.${ key })`); assert.equal(actual[key].length, expected[key].length, `${ prefix }.${ key }.length`); for (let i = 0; i < expected[key].length; i++) { assertObj(`${ prefix }[${ i }]`, actual[key][i], expected[key][i]); } } else { assert.equal(actual[key], expected[key], `${ prefix }.${ key }`); } } } function assertBlock(actual: any, expected: any): void { // Check transactions for (let i = 0; i < expected.transactions.length; i++) { const expectedTx = expected.transactions[i]; if (typeof(expectedTx) === "string") { assert.equal(actual.transactions[i], expectedTx, `block.transactions[${ i }]`); } else { throw new Error("@TODO"); } } // Remove the transactions and test keys expected = Object.assign({ }, expected, { transactions: undefined, test: undefined }); // Check remaining keys assertObj("block", actual, expected); } function assertTransaction(actual: any, expected: any): void { // @TODO: Accesslist // Check signature assertObj("tx.signature", actual.signature, expected.signature); // Remove the transactions and test keys expected = Object.assign({ }, expected, { accessList: undefined, signature: undefined, test: undefined }); // Check remaining keys assertObj("tx", actual, expected); } function assertReceipt(actual: any, expected: any): void { // Check logs for (let i = 0; i < expected.logs.length; i++) { let expectedLog = expected.logs[i]; for (let j = 0; j < expectedLog.topics.length; j++) { assert.equal(actual.logs[i].topics[j], expectedLog.topics[j], `receipt.logs[${ i }].topics[${ j }]`); } expectedLog = Object.assign({ }, expectedLog, { topics: undefined }); assertObj(`receipt.log[${ i }]`, actual.logs[i], expectedLog); } // Remove the transactions and test keys expected = Object.assign({ }, expected, { logs: undefined, test: undefined }); // In Byzantium, the root was dropped and the status was added if (networkFeatureAtBlock("byzantium", expected.blockNumber)) { expected = Object.assign({ }, expected, { root: undefined }); } else { expected = Object.assign({ }, expected, { status: undefined }); } // Check remaining keys assertObj("receipt", actual, expected); } describe("Test Provider Block operations", function() { forEach("test getBlock(blockHash)", testBlock, (providerName, test) => { // Etherscan does not support getting a block by blockhash if (providerName === "EtherscanProvider") { return null; } return async (provider) => { assertBlock(await provider.getBlock(test.hash), test); }; }); forEach("test getBlock(blockNumber)", testBlock, (providerName, test) => { return async (provider) => { assertBlock(await provider.getBlock(test.number), test); }; }); }); describe("Test Provider Transaction operations", function() { forEach("test getTransaction(hash)", testTransaction, (providerName, test) => { return async (provider) => { assertTransaction(await provider.getTransaction(test.hash), test); }; }); forEach("test getTransactionReceipt(hash)", testReceipt, (providerName, test) => { return async (provider) => { const receipt = await provider.getTransactionReceipt(test.hash) assert.ok(receipt != null, "receipt != null"); // Cloudflare doesn't return the root in legacy receipts; but it isn't // *actually* that important, so we'll give it a pass... if (providerName === "CloudflareProvider" || providerName === "AnkrProvider" || providerName === "PocketProvider") { test = Object.assign({ } , test, { root: undefined }); } //if (providerName === "PocketProvider") { //} assertReceipt(receipt, test); }; }); forEach("test lookupAddress(addr) == null", testReceipt, (providerName, test) => { return async (provider) => { const name = await provider.lookupAddress("0x0123456789012345678901234567890123456789") assert.ok(name == null, "name == null"); }; }); }); describe("Test Networks", function() { const networks = [ "mainnet", "goerli", "sepolia", "holesky", "arbitrum", "arbitrum-goerli", "arbitrum-sepolia", "base", "base-goerli", "base-sepolia", "bnb", "bnbt", "linea", "linea-goerli", "matic", "matic-mumbai", "optimism", "optimism-goerli", "optimism-sepolia", "xdai", ]; const providerNames = [ "AlchemyProvider", "InfuraProvider", "AnkrProvider", "QuickNodeProvider", ]; for (const providerName of providerNames) { for (const networkName of networks) { const network = Network.from(networkName); const provider = getProvider(providerName, networkName); if (provider == null || !(provider instanceof JsonRpcProvider)) { continue; } it(`checks network chainId: ${ providerName }/${ networkName }`, async function() { this.timeout(10000); const chainId = await provider.send("eth_chainId", [ ]); assert.equal(parseInt(chainId), network.chainId, "chainId"); }); } } });