diff --git a/__tests__/fixtures.ts b/__tests__/fixtures.ts index 50436c5d9..c57823409 100644 --- a/__tests__/fixtures.ts +++ b/__tests__/fixtures.ts @@ -2,9 +2,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { Account, ProviderInterface, RpcProvider, SequencerProvider, json } from '../src'; -import { BaseUrl } from '../src/constants'; import { - CairoVersion, CompiledSierra, CompiledSierraCasm, LegacyCompiledContract, @@ -48,35 +46,12 @@ export const compiledC1v2Casm = readContractSierraCasm('cairo/helloCairo2/compil export const compiledC210 = readContractSierra('cairo/cairo210/cairo210.sierra'); export const compiledC210Casm = readContractSierraCasm('cairo/cairo210/cairo210'); -/* User defined config or default one */ -const BASE_URL = - process.env.TEST_PROVIDER_BASE_URL || process.env.GS_DEFAULT_TEST_PROVIDER_URL || ''; -const RPC_URL = process.env.TEST_RPC_URL; - -/* Detect user defined node or sequencer, if none default to sequencer if both default to node */ -const PROVIDER_URL = RPC_URL || BASE_URL; - -/** explicit is local devnet (undefined,'','1','true','TRUE') = true else false */ -const LOCAL_DEVNET = ['1', 'TRUE', ''].includes((process.env.LOCAL_DEVNET || '').toUpperCase()); - -/* Detect is localhost devnet, it can be also localhost RPC node */ -export const IS_LOCALHOST_DEVNET = - LOCAL_DEVNET && (PROVIDER_URL.includes('localhost') || PROVIDER_URL.includes('127.0.0.1')); - -export const IS_DEVNET_RPC = IS_LOCALHOST_DEVNET && PROVIDER_URL.includes('rpc'); -export const IS_DEVNET_SEQUENCER = IS_LOCALHOST_DEVNET && !PROVIDER_URL.includes('rpc'); - -/* Definitions */ -export const IS_RPC = !!RPC_URL; -export const IS_SEQUENCER = !RPC_URL; -export const IS_SEQUENCER_GOERLI = PROVIDER_URL.includes(BaseUrl.SN_GOERLI); - export const getTestProvider = (): ProviderInterface => { - const provider = RPC_URL - ? new RpcProvider({ nodeUrl: RPC_URL }) - : new SequencerProvider({ baseUrl: BASE_URL }); + const provider = process.env.TEST_RPC_URL + ? new RpcProvider({ nodeUrl: process.env.TEST_RPC_URL }) + : new SequencerProvider({ baseUrl: process.env.TEST_PROVIDER_BASE_URL || '' }); - if (IS_LOCALHOST_DEVNET) { + if (process.env.IS_LOCALHOST_DEVNET) { // accelerate the tests when running locally const originalWaitForTransaction = provider.waitForTransaction.bind(provider); provider.waitForTransaction = ( @@ -90,46 +65,22 @@ export const getTestProvider = (): ProviderInterface => { return provider; }; -// test account with fee token balance export const getTestAccount = (provider: ProviderInterface) => { - let testAccountAddress = process.env.TEST_ACCOUNT_ADDRESS; - let testAccountPrivateKey = process.env.TEST_ACCOUNT_PRIVATE_KEY; - - if (!IS_LOCALHOST_DEVNET) { - if (!testAccountPrivateKey) { - throw new Error('TEST_ACCOUNT_PRIVATE_KEY is not set'); - } - if (!testAccountAddress) { - throw new Error('TEST_ACCOUNT_ADDRESS is not set'); - } - } else if (!testAccountAddress || !testAccountPrivateKey) { - // use defaults for devnet only if they are not set - - // TODO: refactor to retrieve from devnet's /predeployed_accounts endpoint - [testAccountAddress, testAccountPrivateKey] = IS_RPC - ? [ - '0x64b48806902a367c8598f4f95c305e8c1a1acba5f082d294a43793113115691', - '0x71d7bb07b9a64f6f78ac4c816aff4da9', - ] - : [ - '0x7e00d496e324876bbc8531f2d9a82bf154d1a04a50218ee74cdd372f75a551a', - '0xe3e70682c2094cac629f6fbed82c07cd', - ]; - } - const cairoVersion = (process.env.ACCOUNT_CAIRO_VERSION as CairoVersion) || '0'; - - return new Account(provider, toHex(testAccountAddress), testAccountPrivateKey, cairoVersion); + return new Account( + provider, + toHex(process.env.TEST_ACCOUNT_ADDRESS || ''), + process.env.TEST_ACCOUNT_PRIVATE_KEY || '' + ); }; const describeIf = (condition: boolean) => (condition ? describe : describe.skip); -export const describeIfSequencer = describeIf(IS_SEQUENCER); -export const describeIfRpc = describeIf(IS_RPC); -export const describeIfNotRpc = describeIf(!IS_RPC); -export const describeIfNotDevnet = describeIf(!IS_LOCALHOST_DEVNET); -export const describeIfDevnet = describeIf(IS_LOCALHOST_DEVNET); -export const describeIfDevnetRpc = describeIf(IS_DEVNET_RPC); -export const describeIfDevnetSequencer = describeIf(IS_DEVNET_SEQUENCER); -export const describeIfSequencerGoerli = describeIf(IS_SEQUENCER_GOERLI); +export const describeIfSequencer = describeIf(process.env.IS_SEQUENCER === 'true'); +export const describeIfRpc = describeIf(process.env.IS_RPC === 'true'); +export const describeIfNotDevnet = describeIf(process.env.IS_LOCALHOST_DEVNET === 'false'); +export const describeIfDevnet = describeIf(process.env.IS_LOCALHOST_DEVNET === 'true'); +export const describeIfDevnetRpc = describeIf(process.env.IS_RPC_DEVNET === 'true'); +export const describeIfDevnetSequencer = describeIf(process.env.IS_SEQUENCER_DEVNET === 'true'); +export const describeIfSequencerGoerli = describeIf(process.env.IS_SEQUENCER_GOERLI === 'true'); export const erc20ClassHash = '0x54328a1075b8820eb43caf0caa233923148c983742402dcfc38541dd843d01a'; export const wrongClassHash = '0x000000000000000000000000000000000000000000000000000000000000000'; diff --git a/__tests__/jestGlobalSetup.ts b/__tests__/jestGlobalSetup.ts index 71907cfe5..19a53e437 100644 --- a/__tests__/jestGlobalSetup.ts +++ b/__tests__/jestGlobalSetup.ts @@ -1,9 +1,21 @@ +/* eslint-disable no-console */ /** * Asynchronous Global Test Setup * Run only once * ref: order of execution jestGlobalSetup.ts -> jest.setup.ts -> fixtures.ts */ +import { BaseUrl } from '../src/constants'; + +type DevnetStrategy = { + isDevnet: boolean; + isRS: boolean; +}; +type ProviderType = { + sequencer: boolean; + rpc: boolean; +}; + /** * Global Setup Fixtures */ @@ -11,56 +23,209 @@ /* Default test config based on run `starknet-devnet --seed 0` */ const GS_DEFAULT_TEST_PROVIDER_URL = 'http://127.0.0.1:5050/'; -/** - * We need to detect intention of the test runner - * Does test run on local devnet? - * Does it run Sequencer or RPC tests ? - */ +const localDevnetDetectionStrategy = async () => { + const setup = (strategy: DevnetStrategy) => { + // Setup ENV + process.env.IS_LOCALHOST_DEVNET = 'false'; + process.env.IS_RPC_DEVNET = 'false'; + process.env.IS_SEQUENCER_DEVNET = 'false'; + + if (strategy.isDevnet) { + process.env.IS_LOCALHOST_DEVNET = 'true'; + if (strategy.isRS) { + process.env.IS_RPC_DEVNET = 'true'; + } else { + process.env.IS_SEQUENCER_DEVNET = 'true'; + } + } + return strategy; + }; + + const strategy: DevnetStrategy = { + isDevnet: false, + isRS: false, + }; + // if is_alive work it is local devnet + try { + const response = await fetch(`${GS_DEFAULT_TEST_PROVIDER_URL}is_alive`); + const body = await response.text(); + if (body === 'Alive!!!') { + strategy.isDevnet = true; + } else { + return setup(strategy); + } + } catch (error) { + return setup(strategy); + } + // if on base url RPC endpoint work it is devnet-rs else it devnet-py + try { + const response = await fetch(`${GS_DEFAULT_TEST_PROVIDER_URL}`, { + method: 'POST', + headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, + body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'starknet_syncing' }), + }); + const json = await response.json(); + strategy.isRS = json.jsonrpc === '2.0'; + } catch (error) { + return setup(strategy); + } -const sequencerOrRpc = () => { + return setup(strategy); +}; + +const sequencerOrRpc = async (devnetStrategy?: DevnetStrategy) => { + const setup = (providerType: ProviderType) => { + process.env.IS_SEQUENCER = providerType.sequencer ? 'true' : 'false'; + process.env.IS_RPC = providerType.rpc ? 'true' : 'false'; + process.env.IS_SEQUENCER_GOERLI = ( + process.env.TEST_PROVIDER_BASE_URL || + process.env.TEST_RPC_URL || + '' + ).includes(BaseUrl.SN_GOERLI) + ? 'true' + : 'false'; + return providerType; + }; + let result: ProviderType = { sequencer: false, rpc: false }; if (process.env.TEST_PROVIDER_BASE_URL) { - return { sequencer: true, rpc: false }; + return setup({ ...result, sequencer: true }); } if (process.env.TEST_RPC_URL) { - return { sequencer: false, rpc: true }; + return setup({ ...result, rpc: true }); + } + // nor sequencer nor rpc provided, try with local devnet strategy + if (devnetStrategy && devnetStrategy.isDevnet) { + result = { sequencer: !devnetStrategy.isRS, rpc: devnetStrategy.isRS }; + if (result.sequencer) { + process.env.TEST_PROVIDER_BASE_URL = GS_DEFAULT_TEST_PROVIDER_URL; + } else if (result.rpc) { + process.env.TEST_RPC_URL = GS_DEFAULT_TEST_PROVIDER_URL; + } } - // nor sequencer nor rpc provided, try with local devnet detection - return { sequencer: false, rpc: false }; + return setup(result); }; -const isAccountProvided = () => { +const setAccount = async (devnetStrategy: DevnetStrategy) => { + const fetchAccount = async (URL: string) => { + const response = await fetch(`${URL}predeployed_accounts`); + const accounts = await response.json(); + process.env.TEST_ACCOUNT_ADDRESS = accounts[0].address; + process.env.TEST_ACCOUNT_PRIVATE_KEY = accounts[0].private_key; + process.env.INITIAL_BALANCE = accounts[0].initial_balance; + }; + if (process.env.TEST_ACCOUNT_ADDRESS && process.env.TEST_ACCOUNT_PRIVATE_KEY) { return true; } if (process.env.TEST_ACCOUNT_ADDRESS || process.env.TEST_ACCOUNT_PRIVATE_KEY) { throw new Error( - 'If you are providing test Account you need to provide both TEST_ACCOUNT_ADDRESS & TEST_ACCOUNT_PRIVATE_KEY' + 'If you are providing one of you need to provide both: TEST_ACCOUNT_ADDRESS & TEST_ACCOUNT_PRIVATE_KEY' ); } - return false; + const providedURL = process.env.TEST_PROVIDER_BASE_URL || process.env.TEST_RPC_URL; + if (devnetStrategy.isDevnet) { + // get account from devnet + try { + await fetchAccount(GS_DEFAULT_TEST_PROVIDER_URL); + return true; + } catch (error) { + console.error('Fetching account from devnet failed'); + } + } else if (providedURL) { + // try to get it from remote devnet + try { + await fetchAccount(providedURL); + return true; + } catch (error) { + console.error(`Fetching account from provided url ${providedURL} failed`); + } + } - // if not provided try to get it from BASE_URL - // if unsecesfull throw an account error + throw new Error( + 'Setting Account using all known strategies failed, please provide TEST_ACCOUNT_ADDRESS & TEST_ACCOUNT_PRIVATE_KEY' + ); }; -const defineStrategy = async () => { - // TODO: implement strategy detection - // did user provide TEST_PROVIDER_BASE_URL or TEST_RPC_URL if so we know it is RPC or Sequencer test - // if not we need to aute detect it - // did user provide TEST_ACCOUNT_ADDRESS and TEST_ACCOUNT_PRIVATE_KEY - // - if yes, use provided account - // - if only one throw error - // - if no run local devnet strategy - // did user provide TEST_PROVIDER_BASE_URL or TEST_RPC_URL - // - is local devnet running? - // - if yes, use local devnet - // get local devnet version and accounts - sequencerOrRpc(); - isAccountProvided(); +const verifySetup = (final?: boolean) => { + if (!process.env.TEST_ACCOUNT_ADDRESS) { + if (final) { + throw new Error('TEST_ACCOUNT_ADDRESS env is not set'); + } else return false; + } + if (!process.env.TEST_ACCOUNT_PRIVATE_KEY) { + if (final) { + throw new Error('TEST_ACCOUNT_PRIVATE_KEY env is not set'); + } else return false; + } + if (!process.env.TEST_PROVIDER_BASE_URL && !process.env.TEST_RPC_URL) { + if (final) { + throw new Error('One of TEST_PROVIDER_BASE_URL or TEST_RPC_URL env is not set'); + } else return false; + } + + console.table({ + TEST_ACCOUNT_ADDRESS: process.env.TEST_ACCOUNT_ADDRESS, + TEST_ACCOUNT_PRIVATE_KEY: '****', + INITIAL_BALANCE: process.env.INITIAL_BALANCE, + TEST_PROVIDER_BASE_URL: process.env.TEST_PROVIDER_BASE_URL, + TEST_RPC_URL: process.env.TEST_RPC_URL, + }); + + console.table({ + IS_LOCALHOST_DEVNET: process.env.IS_LOCALHOST_DEVNET, + IS_RPC_DEVNET: process.env.IS_RPC_DEVNET, + IS_SEQUENCER_DEVNET: process.env.IS_SEQUENCER_DEVNET, + IS_RPC: process.env.IS_RPC, + IS_SEQUENCER: process.env.IS_SEQUENCER, + IS_SEQUENCER_GOERLI: process.env.IS_SEQUENCER_GOERLI, + }); + + console.log('Global Test Environment is Ready'); + return true; +}; + +const executeStrategy = async () => { + // 1. Assume setup is provided and ready; + let ready = verifySetup(); + if (ready) { + console.log('Using Provided Test Setup'); + return true; + } + + // 2. Try to detect setup + console.log('Global Test Environment Auto Setup Started'); + const devnetStrategy = await localDevnetDetectionStrategy(); + if (devnetStrategy.isDevnet) { + if (devnetStrategy.isRS) { + console.log('Detected Devnet-RS'); + } else { + console.log('Detected Devnet-PY'); + } + } + + const providerType = await sequencerOrRpc(devnetStrategy); + if (providerType.sequencer) { + console.log('Detected Sequencer'); + } else if (providerType.rpc) { + console.log('Detected RPC'); + } + + const isAccountSet = await setAccount(devnetStrategy); + if (isAccountSet) { + console.log('Detected Account'); + } + + ready = await verifySetup(true); + if (ready) { + return true; + } + + return false; }; -module.exports = async function (_globalConfig: any, _projectConfig: any) { - const devnetAccs = await defineStrategy(); - console.log(devnetAccs); +export default async (_globalConfig: any, _projectConfig: any) => { + const isSet = await executeStrategy(); + if (!isSet) console.error('Test Setup Environment is NOT Ready'); + // SET GLOBALS process.env.GS_DEFAULT_TEST_PROVIDER_URL = GS_DEFAULT_TEST_PROVIDER_URL; }; diff --git a/__tests__/rpcProvider.test.ts b/__tests__/rpcProvider.test.ts index a7153ef01..ad2f97da7 100644 --- a/__tests__/rpcProvider.test.ts +++ b/__tests__/rpcProvider.test.ts @@ -60,9 +60,9 @@ describeIfRpc('RPCProvider', () => { expect(stateUpdate).toMatchSchemaRef('StateUpdateResponse'); }); - test('getSpecVersion - pathfinder not implemented', async () => { + test('getSpecVersion', async () => { const spec = await rpcProvider.getSpecVersion(); - console.log(spec); + expect(typeof spec).toBe('string'); }); test('getCode - not implemented', async () => { diff --git a/src/provider/rpc.ts b/src/provider/rpc.ts index 9434181e0..995cadbe8 100644 --- a/src/provider/rpc.ts +++ b/src/provider/rpc.ts @@ -160,7 +160,7 @@ export class RpcProvider implements ProviderInterface { } /** - * Returns the version of the Starknet JSON-RPC specification being used + * NEW: Returns the version of the Starknet JSON-RPC specification being used */ public async getSpecVersion() { return this.fetchEndpoint('starknet_specVersion'); @@ -446,6 +446,34 @@ export class RpcProvider implements ProviderInterface { return this.fetchEndpoint('starknet_traceTransaction', { transaction_hash: transactionHash }); } + // TODO: implement in waitforTransaction, add tests + /** + * NEW: Get the status of a transaction + * @param transactionHash + */ + public async getTransactionStatus(transactionHash: RPC.TransactionHash) { + return this.fetchEndpoint('starknet_getTransactionStatus', { + transaction_hash: transactionHash, + }); + } + + // TODO: add tests + /** + * NEW: Estimate the fee for a message from L1 + * @param message Message From L1 + * @param blockIdentifier + */ + public async estimateMessageFee( + message: RPC.L1Message, + blockIdentifier: BlockIdentifier = this.blockIdentifier + ) { + const block_id = new Block(blockIdentifier).identifier; + return this.fetchEndpoint('starknet_estimateMessageFee', { + message, + block_id, + }); + } + public async traceBlockTransactions(blockIdentifier: BlockIdentifier = this.blockIdentifier) { const block_id = new Block(blockIdentifier).identifier; return this.fetchEndpoint('starknet_traceBlockTransactions', { block_id }); diff --git a/src/types/api/rpc.ts b/src/types/api/rpc.ts index df5e0db77..9d9c40207 100644 --- a/src/types/api/rpc.ts +++ b/src/types/api/rpc.ts @@ -9,6 +9,7 @@ import { EVENT_FILTER, FEE_ESTIMATE, FELT, + MSG_FROM_L1, PENDING_BLOCK_WITH_TX_HASHES, PENDING_TXN_RECEIPT, REPLACED_CLASS, @@ -65,6 +66,7 @@ export type SimulateTransactionResponse = { transaction_trace: TRANSACTION_TRACE; fee_estimation: FEE_ESTIMATE; }[]; +export type L1Message = MSG_FROM_L1; export type TransactionType = TXN_TYPE; export type SimulationFlag = SIMULATION_FLAG;