From 6409299a4b31454d3265c76325bb87b58e82b22a Mon Sep 17 00:00:00 2001 From: VGau Date: Thu, 20 Nov 2025 15:38:44 +0100 Subject: [PATCH 1/3] feat: add opcode test suite to e2e tests --- .../config/tests-config/environments/local.ts | 1 + e2e/src/config/tests-config/setup.ts | 25 ++++++++- e2e/src/config/tests-config/types.ts | 2 + e2e/src/opcodes.spec.ts | 56 +++++++++++++++++++ 4 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 e2e/src/opcodes.spec.ts diff --git a/e2e/src/config/tests-config/environments/local.ts b/e2e/src/config/tests-config/environments/local.ts index a066a1d645..7206029e73 100644 --- a/e2e/src/config/tests-config/environments/local.ts +++ b/e2e/src/config/tests-config/environments/local.ts @@ -52,6 +52,7 @@ const config: Config = { shomeiFrontendEndpoint: SHOMEI_FRONTEND_ENDPOINT, sequencerEndpoint: SEQUENCER_ENDPOINT, transactionExclusionEndpoint: TRANSACTION_EXCLUSION_ENDPOINT, + opcodeTesterAddress: "0xa50a51c09a5c451C52BB714527E1974b686D8e77", }, }; diff --git a/e2e/src/config/tests-config/setup.ts b/e2e/src/config/tests-config/setup.ts index ccf6747bf0..31737c1df1 100644 --- a/e2e/src/config/tests-config/setup.ts +++ b/e2e/src/config/tests-config/setup.ts @@ -11,6 +11,8 @@ import { LineaRollupV6__factory, LineaSequencerUptimeFeed, LineaSequencerUptimeFeed__factory, + OpcodeTester, + OpcodeTester__factory, ProxyAdmin, ProxyAdmin__factory, SparseMerkleProof, @@ -25,7 +27,7 @@ import { import { AccountManager } from "./accounts/account-manager"; export default class TestSetup { - constructor(private readonly config: Config) {} + constructor(public readonly config: Config) {} public getL1Provider(): JsonRpcProvider { return new JsonRpcProvider(this.config.L1.rpcUrl.toString()); @@ -267,13 +269,30 @@ export default class TestSetup { return this.config.L1.dummyContractAddress; } - private isLocalL2Config(config: L2Config): config is LocalL2Config { + public getL2OpcodeTester(signer?: Wallet): OpcodeTester | undefined { + if (this.config.L2.opcodeTesterAddress) { + const opcodeTester: OpcodeTester = OpcodeTester__factory.connect( + this.config.L2.opcodeTesterAddress, + this.getL2Provider(), + ); + + if (signer) { + return opcodeTester.connect(signer); + } + + return opcodeTester; + } + return undefined; + } + + public isLocalL2Config(config: L2Config): config is LocalL2Config { return ( (config as LocalL2Config).besuNodeRpcUrl !== undefined && (config as LocalL2Config).sequencerEndpoint !== undefined && (config as LocalL2Config).shomeiEndpoint !== undefined && (config as LocalL2Config).shomeiFrontendEndpoint !== undefined && - (config as LocalL2Config).transactionExclusionEndpoint !== undefined + (config as LocalL2Config).transactionExclusionEndpoint !== undefined && + (config as LocalL2Config).opcodeTesterAddress !== undefined ); } } diff --git a/e2e/src/config/tests-config/types.ts b/e2e/src/config/tests-config/types.ts index 830a0fcc8c..0cb1e7ac97 100644 --- a/e2e/src/config/tests-config/types.ts +++ b/e2e/src/config/tests-config/types.ts @@ -27,6 +27,7 @@ export type BaseL2Config = BaseConfig & { shomeiFrontendEndpoint?: URL; sequencerEndpoint?: URL; transactionExclusionEndpoint?: URL; + opcodeTesterAddress?: string; }; export type LocalL2Config = BaseL2Config & { @@ -35,6 +36,7 @@ export type LocalL2Config = BaseL2Config & { shomeiFrontendEndpoint: URL; sequencerEndpoint: URL; transactionExclusionEndpoint: URL; + opcodeTesterAddress: string; }; export type DevL2Config = BaseL2Config; diff --git a/e2e/src/opcodes.spec.ts b/e2e/src/opcodes.spec.ts new file mode 100644 index 0000000000..8e6ec09714 --- /dev/null +++ b/e2e/src/opcodes.spec.ts @@ -0,0 +1,56 @@ +import { describe, expect, it } from "@jest/globals"; +import { config } from "./config/tests-config"; +import { LineaEstimateGasClient } from "./common/utils"; + +const l2AccountManager = config.getL2AccountManager(); + +describe("Opcodes test suite", () => { + const lineaEstimateGasClient = new LineaEstimateGasClient(config.getL2BesuNodeEndpoint()!); + + it.concurrent("Should be able to estimate the opcode execution using linea_estimateGas endpoint", async () => { + const account = await l2AccountManager.generateAccount(); + const opcodeTester = config.getL2OpcodeTester(account); + + if (!opcodeTester) { + if (config.isLocalL2Config(config.config.L2)) { + throw new Error("Opcode tester contract address must be defined for local L2 config"); + } + logger.info("No opcode tester contract deployed, skipping test"); + return; + } + + const { maxPriorityFeePerGas, maxFeePerGas, gasLimit } = await lineaEstimateGasClient.lineaEstimateGas( + account.address, + await opcodeTester.getAddress(), + opcodeTester.interface.encodeFunctionData("executeAllOpcodes"), + ); + logger.debug( + `Fetched fee data. maxPriorityFeePerGas=${maxPriorityFeePerGas} maxFeePerGas=${maxFeePerGas} gasLimit=${gasLimit}`, + ); + + expect(maxPriorityFeePerGas).toBeGreaterThan(0n); + expect(maxFeePerGas).toBeGreaterThan(0n); + expect(gasLimit).toBeGreaterThan(0n); + }); + + it.concurrent("Should be able to execute all opcodes", async () => { + const account = await l2AccountManager.generateAccount(); + const opcodeTester = config.getL2OpcodeTester(account); + + if (!opcodeTester) { + if (config.isLocalL2Config(config.config.L2)) { + throw new Error("Opcode tester contract address must be defined for local L2 config"); + } + logger.info("No opcode tester contract deployed, skipping test"); + return; + } + + const valueBeforeExecution = await opcodeTester.rollingBlockDetailComputations(); + const executeTx = await opcodeTester.executeAllOpcodes({ gasLimit: 5_000_000 }); + await executeTx.wait(1, 20_000); + + const valueAfterExecution = await opcodeTester.rollingBlockDetailComputations(); + + expect(valueBeforeExecution).not.toEqual(valueAfterExecution); + }); +}); From 336e596f272625cf1098a763772475d67912648c Mon Sep 17 00:00:00 2001 From: VGau Date: Thu, 20 Nov 2025 15:55:07 +0100 Subject: [PATCH 2/3] fix: add logs --- e2e/src/opcodes.spec.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/e2e/src/opcodes.spec.ts b/e2e/src/opcodes.spec.ts index 8e6ec09714..652dadf15e 100644 --- a/e2e/src/opcodes.spec.ts +++ b/e2e/src/opcodes.spec.ts @@ -51,6 +51,9 @@ describe("Opcodes test suite", () => { const valueAfterExecution = await opcodeTester.rollingBlockDetailComputations(); + logger.debug(`Value before execution: ${valueBeforeExecution}, value after execution: ${valueAfterExecution}`); expect(valueBeforeExecution).not.toEqual(valueAfterExecution); + + logger.debug("All opcodes executed successfully"); }); }); From dae4c4dd9ac475a9747d738d75ebce27ffcc1f15 Mon Sep 17 00:00:00 2001 From: VGau Date: Thu, 20 Nov 2025 18:19:51 +0100 Subject: [PATCH 3/3] fix: make the opcode tester a required field --- e2e/src/config/tests-config/setup.ts | 25 +++++++++++-------------- e2e/src/config/tests-config/types.ts | 3 +-- e2e/src/opcodes.spec.ts | 22 +++------------------- 3 files changed, 15 insertions(+), 35 deletions(-) diff --git a/e2e/src/config/tests-config/setup.ts b/e2e/src/config/tests-config/setup.ts index 31737c1df1..4053b64a1b 100644 --- a/e2e/src/config/tests-config/setup.ts +++ b/e2e/src/config/tests-config/setup.ts @@ -27,7 +27,7 @@ import { import { AccountManager } from "./accounts/account-manager"; export default class TestSetup { - constructor(public readonly config: Config) {} + constructor(private readonly config: Config) {} public getL1Provider(): JsonRpcProvider { return new JsonRpcProvider(this.config.L1.rpcUrl.toString()); @@ -269,23 +269,20 @@ export default class TestSetup { return this.config.L1.dummyContractAddress; } - public getL2OpcodeTester(signer?: Wallet): OpcodeTester | undefined { - if (this.config.L2.opcodeTesterAddress) { - const opcodeTester: OpcodeTester = OpcodeTester__factory.connect( - this.config.L2.opcodeTesterAddress, - this.getL2Provider(), - ); - - if (signer) { - return opcodeTester.connect(signer); - } + public getL2OpcodeTesterContract(signer?: Wallet): OpcodeTester { + const opcodeTester: OpcodeTester = OpcodeTester__factory.connect( + this.config.L2.opcodeTesterAddress, + this.getL2Provider(), + ); - return opcodeTester; + if (signer) { + return opcodeTester.connect(signer); } - return undefined; + + return opcodeTester; } - public isLocalL2Config(config: L2Config): config is LocalL2Config { + private isLocalL2Config(config: L2Config): config is LocalL2Config { return ( (config as LocalL2Config).besuNodeRpcUrl !== undefined && (config as LocalL2Config).sequencerEndpoint !== undefined && diff --git a/e2e/src/config/tests-config/types.ts b/e2e/src/config/tests-config/types.ts index 0cb1e7ac97..9ca6968d92 100644 --- a/e2e/src/config/tests-config/types.ts +++ b/e2e/src/config/tests-config/types.ts @@ -23,11 +23,11 @@ export type BaseL2Config = BaseConfig & { l2TokenAddress: string; l2SparseMerkleProofAddress: string; l2LineaSequencerUptimeFeedAddress: string; + opcodeTesterAddress: string; shomeiEndpoint?: URL; shomeiFrontendEndpoint?: URL; sequencerEndpoint?: URL; transactionExclusionEndpoint?: URL; - opcodeTesterAddress?: string; }; export type LocalL2Config = BaseL2Config & { @@ -36,7 +36,6 @@ export type LocalL2Config = BaseL2Config & { shomeiFrontendEndpoint: URL; sequencerEndpoint: URL; transactionExclusionEndpoint: URL; - opcodeTesterAddress: string; }; export type DevL2Config = BaseL2Config; diff --git a/e2e/src/opcodes.spec.ts b/e2e/src/opcodes.spec.ts index 652dadf15e..507c756c3c 100644 --- a/e2e/src/opcodes.spec.ts +++ b/e2e/src/opcodes.spec.ts @@ -7,17 +7,9 @@ const l2AccountManager = config.getL2AccountManager(); describe("Opcodes test suite", () => { const lineaEstimateGasClient = new LineaEstimateGasClient(config.getL2BesuNodeEndpoint()!); - it.concurrent("Should be able to estimate the opcode execution using linea_estimateGas endpoint", async () => { + it.concurrent("Should be able to estimate the opcode execution gas using linea_estimateGas endpoint", async () => { const account = await l2AccountManager.generateAccount(); - const opcodeTester = config.getL2OpcodeTester(account); - - if (!opcodeTester) { - if (config.isLocalL2Config(config.config.L2)) { - throw new Error("Opcode tester contract address must be defined for local L2 config"); - } - logger.info("No opcode tester contract deployed, skipping test"); - return; - } + const opcodeTester = config.getL2OpcodeTesterContract(account); const { maxPriorityFeePerGas, maxFeePerGas, gasLimit } = await lineaEstimateGasClient.lineaEstimateGas( account.address, @@ -35,15 +27,7 @@ describe("Opcodes test suite", () => { it.concurrent("Should be able to execute all opcodes", async () => { const account = await l2AccountManager.generateAccount(); - const opcodeTester = config.getL2OpcodeTester(account); - - if (!opcodeTester) { - if (config.isLocalL2Config(config.config.L2)) { - throw new Error("Opcode tester contract address must be defined for local L2 config"); - } - logger.info("No opcode tester contract deployed, skipping test"); - return; - } + const opcodeTester = config.getL2OpcodeTesterContract(account); const valueBeforeExecution = await opcodeTester.rollingBlockDetailComputations(); const executeTx = await opcodeTester.executeAllOpcodes({ gasLimit: 5_000_000 });