Feat(#133): run e2e tests in parallel (#164)

* feat: run e2e tests in paralell + refactor test setup

* fix: open handle issue

* fix: sequencer finalized tag test issue + clean l2.spec tests

* fix: refactor config structure

* fix: genesis path issue

* fix: pnpm lock file issue

* fix: make each test run concurrently

* fix: remove nested describes in tests

* fix: refactor coordinator tests + add global L2 traffic generation

* fix: change l2 genesis file for getting whale accounts + refactor utils function

* fix: refactor coordinator restart util function

* fix: remove conflation e2e tests

* fix: add environment variable in e2e test ci workflowk

* fix: coordinator restart test issue

* fix: instanciate account manager only once per test file

* fix: add restart option to zkbesu shomei and postman in docker compose file

* fix: remove getAndIncreaseFeeData function and unsed utils functions

* fix: increase messaging L2 to l1 timeout
This commit is contained in:
Victorien Gauch
2024-10-15 19:26:23 +02:00
committed by GitHub
parent bb78efe353
commit 215b60c9f0
46 changed files with 2754 additions and 3431 deletions

View File

@@ -182,6 +182,7 @@ services:
image: consensys/linea-postman:${POSTMAN_TAG:-5a6fdf3}
profiles: [ "l2", "debug" ]
platform: linux/amd64
restart: on-failure
depends_on:
sequencer:
condition: service_healthy
@@ -468,6 +469,7 @@ services:
sequencer:
condition: service_healthy
privileged: true
restart: always
user: root
ports:
- "8945:8545" # http

View File

@@ -1,33 +0,0 @@
/* eslint-disable no-var */
import { JsonRpcProvider } from "@ethersproject/providers";
import { TestEnvironment } from "./test-env";
import { DummyContract, L2MessageService, L2TestContract, LineaRollup } from "src/typechain";
declare global {
var testingEnv: TestEnvironment;
var l1Provider: JsonRpcProvider;
var l2Provider: JsonRpcProvider;
var l2MessageService: L2MessageService;
var dummyContract: DummyContract;
var l1DummyContract: DummyContract;
var l2TestContract: L2TestContract;
var lineaRollup: LineaRollup;
var useLocalSetup: boolean;
var chainId: number;
var CHAIN_ID: number;
var L2_MESSAGE_SERVICE_ADDRESS: string;
var LINEA_ROLLUP_CONTRACT_ADDRESS: string;
var L1_ACCOUNT_0_PRIVATE_KEY: string;
var L2_ACCOUNT_0_PRIVATE_KEY: string;
var L2_ACCOUNT_1_PRIVATE_KEY: string;
var L1_DEPLOYER_ACCOUNT_PRIVATE_KEY: string;
var L2_DEPLOYER_ACCOUNT_PRIVATE_KEY: string;
var TRANSACTION_CALLDATA_LIMIT: number;
var OPERATOR_0_PRIVATE_KEY: string;
var SHOMEI_ENDPOINT: URL | null;
var SHOMEI_FRONTEND_ENDPOINT: URL | null;
var SEQUENCER_ENDPOINT: URL | null;
var OPERATOR_1_ADDRESS: string;
var SECURITY_COUNCIL_PRIVATE_KEY: string;
var CONTRACT_GAS_OPTIMIZATION_SWITCH_BLOCK: number;
}

View File

@@ -1,6 +0,0 @@
import { globalTestEnvironment } from "./global-test-env";
export default async (): Promise<void> => {
await globalTestEnvironment.startEnv();
global.testingEnv = globalTestEnvironment.testingEnv;
};

View File

@@ -1,5 +0,0 @@
import { globalTestEnvironment } from "./global-test-env";
export default async (): Promise<void> => {
await globalTestEnvironment.stopEnv();
};

View File

@@ -1,19 +0,0 @@
import { TestEnvironment } from "./test-env";
export class GlobalTestEnvironment {
public readonly testingEnv = new TestEnvironment();
public async startEnv(): Promise<void> {
await this.testingEnv.startEnv();
}
public async stopEnv(): Promise<void> {
await this.testingEnv.stopEnv();
}
public async restartCoordinator(): Promise<void> {
await this.testingEnv.restartCoordinator(global.useLocalSetup);
}
}
export const globalTestEnvironment = new GlobalTestEnvironment();

View File

@@ -1,53 +0,0 @@
import { beforeAll, jest } from "@jest/globals";
import {
DummyContract,
DummyContract__factory,
L2MessageService,
L2MessageService__factory,
LineaRollup,
LineaRollup__factory,
} from "../src/typechain";
import {
getL1Provider,
getL2Provider,
CHAIN_ID,
DEPLOYER_ACCOUNT_PRIVATE_KEY,
MESSAGE_SERVICE_ADDRESS,
DUMMY_CONTRACT_ADDRESS,
ACCOUNT_0_PRIVATE_KEY,
TRANSACTION_CALLDATA_LIMIT,
OPERATOR_0_PRIVATE_KEY,
SHOMEI_ENDPOINT,
SHOMEI_FRONTEND_ENDPOINT,
LINEA_ROLLUP_CONTRACT_ADDRESS,
} from "../src/utils/constants.dev";
jest.setTimeout(5 * 60 * 1000);
beforeAll(async () => {
/*********** PROVIDERS SETUP ***********/
const l1Provider = getL1Provider();
const l2Provider = getL2Provider();
const dummyContract: DummyContract = DummyContract__factory.connect(DUMMY_CONTRACT_ADDRESS, l2Provider);
// L2MessageService contract
const l2MessageService: L2MessageService = L2MessageService__factory.connect(MESSAGE_SERVICE_ADDRESS, l2Provider);
/*********** L1 Contracts ***********/
// LineaRollup deployment
const lineaRollup: LineaRollup = LineaRollup__factory.connect(LINEA_ROLLUP_CONTRACT_ADDRESS, l1Provider);
global.l1Provider = l1Provider;
global.l2Provider = l2Provider;
global.dummyContract = dummyContract;
global.l2MessageService = l2MessageService;
global.lineaRollup = lineaRollup;
global.chainId = CHAIN_ID;
global.L1_ACCOUNT_0_PRIVATE_KEY = ACCOUNT_0_PRIVATE_KEY;
global.L1_DEPLOYER_ACCOUNT_PRIVATE_KEY = DEPLOYER_ACCOUNT_PRIVATE_KEY;
global.L2_DEPLOYER_ACCOUNT_PRIVATE_KEY = DEPLOYER_ACCOUNT_PRIVATE_KEY;
global.TRANSACTION_CALLDATA_LIMIT = TRANSACTION_CALLDATA_LIMIT;
global.OPERATOR_0_PRIVATE_KEY = OPERATOR_0_PRIVATE_KEY;
global.SHOMEI_ENDPOINT = SHOMEI_ENDPOINT;
global.SHOMEI_FRONTEND_ENDPOINT = SHOMEI_FRONTEND_ENDPOINT;
});

View File

@@ -1,81 +0,0 @@
import { beforeAll, jest } from "@jest/globals";
import { Wallet } from "ethers";
import {
DummyContract,
DummyContract__factory,
L2MessageService,
L2MessageService__factory,
LineaRollup,
LineaRollup__factory,
TestPlonkVerifierForDataAggregation__factory,
} from "../src/typechain";
import {
L2_ACCOUNT_0,
L2_ACCOUNT_0_PRIVATE_KEY,
L2_ACCOUNT_1_PRIVATE_KEY,
L1_DEPLOYER_ACCOUNT_PRIVATE_KEY,
L2_DEPLOYER_ACCOUNT_PRIVATE_KEY,
INITIAL_WITHDRAW_LIMIT,
LINEA_ROLLUP_INITIAL_L2_BLOCK_NR,
LINEA_ROLLUP_INITIAL_STATE_ROOT_HASH,
LINEA_ROLLUP_OPERATORS,
LINEA_ROLLUP_RATE_LIMIT_AMOUNT,
LINEA_ROLLUP_RATE_LIMIT_PERIOD,
LINEA_ROLLUP_SECURITY_COUNCIL,
getL1Provider,
getL2Provider,
TRANSACTION_CALLDATA_LIMIT,
OPERATOR_0_PRIVATE_KEY,
SHOMEI_ENDPOINT,
SHOMEI_FRONTEND_ENDPOINT,
} from "../src/utils/constants.local";
import { deployContract, deployUpgradableContractWithProxyAdmin } from "../src/utils/deployments";
jest.setTimeout(5 * 60 * 1000);
beforeAll(async () => {
/*********** PROVIDERS SETUP ***********/
const l1Provider = getL1Provider();
const l2Provider = getL2Provider();
const l2Deployer = new Wallet(L2_DEPLOYER_ACCOUNT_PRIVATE_KEY, l2Provider);
const dummyContract = (await deployContract(new DummyContract__factory(), l2Deployer)) as DummyContract;
// L2MessageService deployment
const l2MessageService = (await deployUpgradableContractWithProxyAdmin(new L2MessageService__factory(), l2Deployer, [
L2_ACCOUNT_0,
86400,
INITIAL_WITHDRAW_LIMIT,
])) as L2MessageService;
/*********** L1 DEPLOYMENTS ***********/
const l1Deployer = new Wallet(L1_DEPLOYER_ACCOUNT_PRIVATE_KEY, l1Provider);
// PlonkVerifier and LineaRollup deployment
const plonkVerifier = await deployContract(new TestPlonkVerifierForDataAggregation__factory(), l1Deployer);
const lineaRollup = (await deployUpgradableContractWithProxyAdmin(new LineaRollup__factory(), l1Deployer, [
LINEA_ROLLUP_INITIAL_STATE_ROOT_HASH,
LINEA_ROLLUP_INITIAL_L2_BLOCK_NR,
plonkVerifier.address,
LINEA_ROLLUP_SECURITY_COUNCIL,
LINEA_ROLLUP_OPERATORS,
LINEA_ROLLUP_RATE_LIMIT_PERIOD,
LINEA_ROLLUP_RATE_LIMIT_AMOUNT,
])) as LineaRollup;
global.l1Provider = l1Provider;
global.l2Provider = l2Provider;
global.dummyContract = dummyContract;
global.l2MessageService = l2MessageService;
global.lineaRollup = lineaRollup;
global.useLocalSetup = true;
global.L2_ACCOUNT_0_PRIVATE_KEY = L2_ACCOUNT_0_PRIVATE_KEY;
global.L2_ACCOUNT_1_PRIVATE_KEY = L2_ACCOUNT_1_PRIVATE_KEY;
global.L1_DEPLOYER_ACCOUNT_PRIVATE_KEY = L1_DEPLOYER_ACCOUNT_PRIVATE_KEY;
global.L2_DEPLOYER_ACCOUNT_PRIVATE_KEY = L2_DEPLOYER_ACCOUNT_PRIVATE_KEY;
global.TRANSACTION_CALLDATA_LIMIT = TRANSACTION_CALLDATA_LIMIT;
global.OPERATOR_0_PRIVATE_KEY = OPERATOR_0_PRIVATE_KEY;
global.SHOMEI_ENDPOINT = SHOMEI_ENDPOINT;
global.SHOMEI_FRONTEND_ENDPOINT = SHOMEI_FRONTEND_ENDPOINT;
});

View File

@@ -1,92 +0,0 @@
import { beforeAll, jest } from "@jest/globals";
import { Wallet, ethers } from "ethers";
import {
DummyContract,
DummyContract__factory,
L2MessageService,
L2MessageService__factory,
LineaRollup,
LineaRollup__factory,
} from "../src/typechain";
import {
getL1Provider,
getL2Provider,
CHAIN_ID,
L1_DEPLOYER_ACCOUNT_PRIVATE_KEY,
L2_DEPLOYER_ACCOUNT_PRIVATE_KEY,
LINEA_ROLLUP_CONTRACT_ADDRESS,
MESSAGE_SERVICE_ADDRESS,
L1_ACCOUNT_0_PRIVATE_KEY,
L2_ACCOUNT_0_PRIVATE_KEY,
L2_ACCOUNT_1_PRIVATE_KEY,
TRANSACTION_CALLDATA_LIMIT,
OPERATOR_0_PRIVATE_KEY,
SHOMEI_ENDPOINT,
SHOMEI_FRONTEND_ENDPOINT,
SEQUENCER_ENDPOINT,
OPERATOR_1,
SECURITY_COUNCIL_PRIVATE_KEY,
CONTRACT_GAS_OPTIMIZATION_SWITCH_BLOCK,
} from "../src/utils/constants.local";
import { deployContract } from "../src/utils/deployments";
import { getAndIncreaseFeeData } from "../src/utils/helpers";
jest.setTimeout(3 * 60 * 1000);
beforeAll(async () => {
/*********** PROVIDERS SETUP ***********/
const l1JsonRpcProvider = getL1Provider();
const l2JsonRpcProvider = getL2Provider();
const l1Deployer = new Wallet(L1_DEPLOYER_ACCOUNT_PRIVATE_KEY, l1JsonRpcProvider);
const l2Deployer = new Wallet(L2_DEPLOYER_ACCOUNT_PRIVATE_KEY, l2JsonRpcProvider);
const [dummyContract, l1DummyContract] = await Promise.all([
deployContract(new DummyContract__factory(), l2Deployer) as unknown as DummyContract,
deployContract(new DummyContract__factory(), l1Deployer) as unknown as DummyContract,
]);
// Old LineaRollup contract instanciation
// LineaRollup ABI contains both old and new functions
const lineaRollup: LineaRollup = LineaRollup__factory.connect(LINEA_ROLLUP_CONTRACT_ADDRESS, l1JsonRpcProvider);
// L2 MessageService contract instanciation
const l2MessageService: L2MessageService = L2MessageService__factory.connect(
MESSAGE_SERVICE_ADDRESS,
l2JsonRpcProvider,
);
// Send ETH to the LineaRollup contract
const value = ethers.utils.parseEther("500");
const fee = ethers.utils.parseEther("3");
const to = "0x8D97689C9818892B700e27F316cc3E41e17fBeb9";
const calldata = "0x";
const [maxPriorityFeePerGas, maxFeePerGas] = getAndIncreaseFeeData(await l1JsonRpcProvider.getFeeData());
const tx = await lineaRollup
.connect(l1Deployer)
.sendMessage(to, fee, calldata, { value, maxPriorityFeePerGas, maxFeePerGas });
await tx.wait();
global.l1Provider = l1JsonRpcProvider;
global.l2Provider = l2JsonRpcProvider;
global.dummyContract = dummyContract;
global.l1DummyContract = l1DummyContract;
global.l2MessageService = l2MessageService;
global.L2_MESSAGE_SERVICE_ADDRESS = MESSAGE_SERVICE_ADDRESS;
global.LINEA_ROLLUP_CONTRACT_ADDRESS = LINEA_ROLLUP_CONTRACT_ADDRESS;
global.lineaRollup = lineaRollup;
global.useLocalSetup = true;
global.chainId = CHAIN_ID;
global.L1_ACCOUNT_0_PRIVATE_KEY = L1_ACCOUNT_0_PRIVATE_KEY;
global.L2_ACCOUNT_0_PRIVATE_KEY = L2_ACCOUNT_0_PRIVATE_KEY;
global.L2_ACCOUNT_1_PRIVATE_KEY = L2_ACCOUNT_1_PRIVATE_KEY;
global.L1_DEPLOYER_ACCOUNT_PRIVATE_KEY = L1_DEPLOYER_ACCOUNT_PRIVATE_KEY;
global.L2_DEPLOYER_ACCOUNT_PRIVATE_KEY = L2_DEPLOYER_ACCOUNT_PRIVATE_KEY;
global.TRANSACTION_CALLDATA_LIMIT = TRANSACTION_CALLDATA_LIMIT;
global.OPERATOR_0_PRIVATE_KEY = OPERATOR_0_PRIVATE_KEY;
global.SHOMEI_ENDPOINT = SHOMEI_ENDPOINT;
global.SHOMEI_FRONTEND_ENDPOINT = SHOMEI_FRONTEND_ENDPOINT;
global.SEQUENCER_ENDPOINT = SEQUENCER_ENDPOINT;
global.OPERATOR_1_ADDRESS = OPERATOR_1;
global.SECURITY_COUNCIL_PRIVATE_KEY = SECURITY_COUNCIL_PRIVATE_KEY;
global.CONTRACT_GAS_OPTIMIZATION_SWITCH_BLOCK = CONTRACT_GAS_OPTIMIZATION_SWITCH_BLOCK;
});

View File

@@ -1,62 +0,0 @@
import { beforeAll, jest } from "@jest/globals";
import {
DummyContract,
DummyContract__factory,
L2MessageService,
L2MessageService__factory,
L2TestContract__factory,
L2TestContract,
LineaRollup,
LineaRollup__factory,
} from "../src/typechain";
import {
getL1Provider,
getL2Provider,
CHAIN_ID,
SHADOW_ZKEVMV2_CONTRACT_ADDRESS,
SHADOW_MESSAGE_SERVICE_ADDRESS,
DUMMY_CONTRACT_ADDRESS,
ACCOUNT_0_PRIVATE_KEY,
TRANSACTION_CALLDATA_LIMIT,
L1_DUMMY_CONTRACT_ADDRESS,
SHOMEI_ENDPOINT,
SHOMEI_FRONTEND_ENDPOINT,
} from "../src/utils/constants.uat";
jest.setTimeout(5 * 60 * 1000);
beforeAll(async () => {
/*********** PROVIDERS SETUP ***********/
const l1Provider = getL1Provider();
const l2Provider = getL2Provider();
const l1DummyContract: DummyContract = DummyContract__factory.connect(L1_DUMMY_CONTRACT_ADDRESS, l1Provider);
const dummyContract: DummyContract = DummyContract__factory.connect(DUMMY_CONTRACT_ADDRESS, l2Provider);
const l2TestContract: L2TestContract = L2TestContract__factory.connect(
"0xaD711a736ae454Be345078C0bc849997b78A30B9",
l2Provider,
);
// L2MessageService contract
const l2MessageService: L2MessageService = L2MessageService__factory.connect(
SHADOW_MESSAGE_SERVICE_ADDRESS,
l2Provider,
);
/*********** L1 Contracts ***********/
// LineaRollup deployment
const lineaRollup: LineaRollup = LineaRollup__factory.connect(SHADOW_ZKEVMV2_CONTRACT_ADDRESS, l1Provider);
global.l1Provider = l1Provider;
global.l2Provider = l2Provider;
global.l2TestContract = l2TestContract;
global.l1DummyContract = l1DummyContract;
global.dummyContract = dummyContract;
global.l2MessageService = l2MessageService;
global.lineaRollup = lineaRollup;
global.chainId = CHAIN_ID;
global.L2_ACCOUNT_0_PRIVATE_KEY = ACCOUNT_0_PRIVATE_KEY;
global.TRANSACTION_CALLDATA_LIMIT = TRANSACTION_CALLDATA_LIMIT;
global.SHOMEI_ENDPOINT = SHOMEI_ENDPOINT;
global.SHOMEI_FRONTEND_ENDPOINT = SHOMEI_FRONTEND_ENDPOINT;
});

View File

@@ -1,59 +0,0 @@
import { beforeAll, jest } from "@jest/globals";
import {
DummyContract,
DummyContract__factory,
L2MessageService,
L2MessageService__factory,
L2TestContract__factory,
L2TestContract,
LineaRollup,
LineaRollup__factory,
} from "../src/typechain";
import {
getL1Provider,
getL2Provider,
CHAIN_ID,
LINEA_ROLLUP_CONTRACT_ADDRESS,
MESSAGE_SERVICE_ADDRESS,
DUMMY_CONTRACT_ADDRESS,
ACCOUNT_0_PRIVATE_KEY,
TRANSACTION_CALLDATA_LIMIT,
L1_DUMMY_CONTRACT_ADDRESS,
SHOMEI_ENDPOINT,
SHOMEI_FRONTEND_ENDPOINT,
} from "../src/utils/constants.uat";
jest.setTimeout(5 * 60 * 1000);
beforeAll(async () => {
/*********** PROVIDERS SETUP ***********/
const l1Provider = getL1Provider();
const l2Provider = getL2Provider();
const l1DummyContract: DummyContract = DummyContract__factory.connect(L1_DUMMY_CONTRACT_ADDRESS, l1Provider);
const dummyContract: DummyContract = DummyContract__factory.connect(DUMMY_CONTRACT_ADDRESS, l2Provider);
const l2TestContract: L2TestContract = L2TestContract__factory.connect(
"0xaD711a736ae454Be345078C0bc849997b78A30B9",
l2Provider,
);
// L2MessageService contract
const l2MessageService: L2MessageService = L2MessageService__factory.connect(MESSAGE_SERVICE_ADDRESS, l2Provider);
/*********** L1 Contracts ***********/
// LineaRollup deployment
const lineaRollup: LineaRollup = LineaRollup__factory.connect(LINEA_ROLLUP_CONTRACT_ADDRESS, l1Provider);
global.l1Provider = l1Provider;
global.l2Provider = l2Provider;
global.l2TestContract = l2TestContract;
global.l1DummyContract = l1DummyContract;
global.dummyContract = dummyContract;
global.l2MessageService = l2MessageService;
global.lineaRollup = lineaRollup;
global.chainId = CHAIN_ID;
global.L1_ACCOUNT_0_PRIVATE_KEY = ACCOUNT_0_PRIVATE_KEY;
global.TRANSACTION_CALLDATA_LIMIT = TRANSACTION_CALLDATA_LIMIT;
global.SHOMEI_ENDPOINT = SHOMEI_ENDPOINT;
global.SHOMEI_FRONTEND_ENDPOINT = SHOMEI_FRONTEND_ENDPOINT;
});

View File

@@ -3,12 +3,15 @@ import type { Config } from "jest";
const config: Config = {
preset: "ts-jest",
testEnvironment: "node",
rootDir: ".",
testRegex: "(spec|test).ts$",
rootDir: "src",
testRegex: ".spec.ts$",
verbose: true,
globalSetup: "./env-setup/global-setup.ts",
setupFilesAfterEnv: ["./env-setup/setup.ts"],
globalTeardown: "./env-setup/global-teardown.ts",
globalSetup: "./config/jest/global-setup.ts",
globalTeardown: "./config/jest/global-teardown.ts",
maxWorkers: "50%",
maxConcurrency: 5,
testTimeout: 3 * 60 * 1000,
workerThreads: true,
};
export default config;

View File

@@ -1,15 +0,0 @@
import type { Config } from "jest";
const config: Config = {
preset: "ts-jest",
testEnvironment: "node",
rootDir: ".",
testRegex: "ordered-test.ts$",
verbose: true,
setupFilesAfterEnv: ["./env-setup/setup-local.ts"],
bail: 1,
forceExit: true,
detectOpenHandles: true,
};
export default config;

View File

@@ -1,12 +0,0 @@
import type { Config } from "jest";
const config: Config = {
preset: "ts-jest",
testEnvironment: "node",
rootDir: ".",
testRegex: "(spec|test).ts$",
verbose: true,
setupFilesAfterEnv: ["./env-setup/setup-shadow.ts"],
};
export default config;

View File

@@ -3,10 +3,11 @@ import type { Config } from "jest";
const config: Config = {
preset: "ts-jest",
testEnvironment: "node",
rootDir: ".",
testRegex: "(spec|test).ts$",
rootDir: "src",
testRegex: ".spec.ts$",
verbose: true,
setupFilesAfterEnv: ["./env-setup/setup-dev.ts"],
maxWorkers: "50%",
testTimeout: 3 * 60 * 1000,
};
export default config;

View File

@@ -1,12 +0,0 @@
import type { Config } from "jest";
const config: Config = {
preset: "ts-jest",
testEnvironment: "node",
rootDir: ".",
testRegex: "(spec|test).ts$",
verbose: true,
setupFilesAfterEnv: ["./env-setup/setup-uat.ts"],
};
export default config;

View File

@@ -1,14 +0,0 @@
import type { Config } from "jest";
const config: Config = {
preset: "ts-jest",
testEnvironment: "node",
rootDir: ".",
testRegex: "(spec|test).ts$",
verbose: true,
globalSetup: "",
setupFilesAfterEnv: ["./env-setup/setup-local.ts"],
globalTeardown: "",
};
export default config;

View File

@@ -8,30 +8,25 @@
"lint:ts:fix": "npx eslint --fix '**/*.{js,ts}'",
"prettier": "prettier -c '**/*.{js,ts}'",
"prettier:fix": "prettier -w '**/*.{js,ts}'",
"test:e2e:local": "npx jest --config ./jest.local.config.ts --runInBand",
"test:e2e:local:testenv": "npx jest --config ./jest.local.config.ts --runInBand --globalSetup ./env-setup/global-setup.ts --setupFilesAfterEnv ./env-setup/setup-local-deploy.ts --globalTeardown ./env-setup/global-teardown.ts",
"test:e2e:dev": "npx jest --config ./jest.dev.config.ts --bail --runInBand --testPathIgnorePatterns=restart.spec.ts",
"test:e2e:uat": "npx jest --config ./jest.uat.config.ts --bail --runInBand --testPathIgnorePatterns=restart.spec.ts",
"test:e2e:uat:shadow": "npx jest --config ./jest.uat.config.ts --bail --runInBand --testPathIgnorePatterns=restart.spec.ts",
"postinstall": "typechain --target ethers-v5 --out-dir ./src/typechain './src/abi/*.json'",
"test:e2e:vscode": "npx jest --config ./jest.vscode.config.ts --runInBand --detectOpenHandles --forceExit",
"test:e2e:local": "TEST_ENV=local npx jest",
"test:e2e:dev": "TEST_ENV=dev npx jest --config ./jest.testnet.config.ts --bail --runInBand --testPathIgnorePatterns=restart.spec.ts",
"test:e2e:sepolia": "TEST_ENV=sepolia npx jest --config ./jest.testnet.config.ts --bail --runInBand --testPathIgnorePatterns=restart.spec.ts",
"postinstall": "typechain --target ethers-v6 --out-dir ./src/typechain './src/abi/*.json'",
"lint:fix": "pnpm run lint:ts:fix && pnpm run prettier:fix",
"clean": "rimraf node_modules"
"clean": "rimraf node_modules src/typechain"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@ethersproject/providers": "5.7.2",
"@jest/globals": "29.7.0",
"@openzeppelin/upgrades-core": "1.33.1",
"@typechain/ethers-v5": "11.1.2",
"@types/jest": "29.5.12",
"@typechain/ethers-v6": "0.5.1",
"@types/jest": "29.5.13",
"child_process": "1.0.2",
"dotenv": "16.4.5",
"ethers": "5.7.2",
"ethers": "6.13.3",
"jest": "29.7.0",
"testcontainers": "10.9.0",
"ts-jest": "29.1.2",
"ts-jest": "29.2.5",
"typechain": "8.3.2"
}
}

View File

@@ -6,4 +6,6 @@ export const OPERATOR_ROLE = "0x97667070c54ef182b0f5858b034beac1b6f3089aa2d3188b
export const VERIFIER_SETTER_ROLE = "0x32937fd5162e282df7e9a14a5073a2425321c7966eaf70ed6c838a1006d84c4c";
export const MESSAGE_SENT_EVENT_SIGNATURE = "0xe856c2b8bd4eb0027ce32eeaf595c21b0b6b4644b326e5b7bd80a1cf8db72e6c";
export const HASH_ZERO = ethers.constants.HashZero;
export const HASH_ZERO = ethers.ZeroHash;
export const TRANSACTION_CALLDATA_LIMIT = 30_000;

View File

@@ -1,14 +1,21 @@
import { Contract, ContractFactory, Overrides, Wallet, ethers, utils } from "ethers";
import { AbiCoder, BaseContract, ContractFactory, Wallet, ethers } from "ethers";
import { ProxyAdmin__factory, TransparentUpgradeableProxy__factory, ProxyAdmin } from "../typechain";
function getInitializerData(contractInterface: ethers.utils.Interface, args: unknown[]) {
export const encodeData = (types: string[], values: unknown[], packed?: boolean) => {
if (packed) {
return ethers.solidityPacked(types, values);
}
return AbiCoder.defaultAbiCoder().encode(types, values);
};
function getInitializerData(contractInterface: ethers.Interface, args: unknown[]) {
const initializer = "initialize";
const fragment = contractInterface.getFunction(initializer);
return contractInterface.encodeFunctionData(fragment, args);
return contractInterface.encodeFunctionData(fragment!, args);
}
export const encodeLibraryName = (libraryName: string) => {
const encodedLibraryName = utils.solidityKeccak256(["string"], [libraryName]).slice(2, 36);
const encodedLibraryName = ethers.keccak256(encodeData(["string"], [libraryName])).slice(2, 36);
return `__$${encodedLibraryName}$__`;
};
@@ -17,29 +24,28 @@ export const deployContract = async <T extends ContractFactory>(
deployer: Wallet,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args?: any[],
overrides?: Overrides,
): Promise<Contract> => {
): Promise<BaseContract> => {
const deploymentArgs = args || [];
const instance = await contractFactory.connect(deployer).deploy(...deploymentArgs, overrides);
await instance.deployed();
const instance = await contractFactory.connect(deployer).deploy(...deploymentArgs);
await instance.waitForDeployment();
return instance;
};
export const deployUpgradableContract = async <T extends ContractFactory>(
const deployUpgradableContract = async <T extends ContractFactory>(
contractFactory: T,
deployer: Wallet,
admin: ProxyAdmin,
initializerData = "0x",
): Promise<Contract> => {
): Promise<BaseContract> => {
const instance = await contractFactory.connect(deployer).deploy();
await instance.deployed();
await instance.waitForDeployment();
const proxy = await new TransparentUpgradeableProxy__factory()
.connect(deployer)
.deploy(instance.address, admin.address, initializerData);
await proxy.deployed();
.deploy(await instance.getAddress(), await admin.getAddress(), initializerData);
await proxy.waitForDeployment();
return instance.attach(proxy.address);
return instance.attach(await proxy.getAddress());
};
export async function deployUpgradableContractWithProxyAdmin<T extends ContractFactory>(
@@ -49,8 +55,8 @@ export async function deployUpgradableContractWithProxyAdmin<T extends ContractF
) {
const proxyFactory = new ProxyAdmin__factory(deployer);
const proxyAdmin = await proxyFactory.connect(deployer).deploy();
await proxyAdmin.deployed();
console.log(`ProxyAdmin contract deployed at address: ${proxyAdmin.address}`);
await proxyAdmin.waitForDeployment();
console.log(`ProxyAdmin contract deployed at address: ${await proxyAdmin.getAddress()}`);
const contract = await deployUpgradableContract(
contractFactory,
@@ -58,6 +64,6 @@ export async function deployUpgradableContractWithProxyAdmin<T extends ContractF
proxyAdmin,
getInitializerData(contractFactory.interface, args),
);
console.log(`Contract deployed at address: ${contract.address}`);
console.log(`Contract deployed at address: ${await contract.getAddress()}`);
return contract;
}

View File

@@ -1,4 +1,4 @@
import { BigNumber, BytesLike } from "ethers";
import { BytesLike } from "ethers";
export type FinalizationData = {
aggregatedProof: string;
@@ -21,9 +21,9 @@ export type FinalizationData = {
export type MessageEvent = {
from: string;
to: string;
fee: BigNumber;
value: BigNumber;
messageNumber: BigNumber;
fee: bigint;
value: bigint;
messageNumber: bigint;
calldata: string;
messageHash: string;
blockNumber: number;
@@ -31,6 +31,6 @@ export type MessageEvent = {
export type SendMessageArgs = {
to: string;
fee: BigNumber;
fee: bigint;
calldata: BytesLike;
};

View File

@@ -1,13 +1,13 @@
import { ContractReceipt, Wallet, ethers } from "ethers";
import { ProxyAdmin__factory } from "../typechain";
import { ContractTransactionReceipt, Wallet, ethers } from "ethers";
import { ProxyAdmin, ProxyAdmin__factory } from "../typechain";
export function getInitializerData(
contractInterface: ethers.utils.Interface,
contractInterface: ethers.Interface,
initializerFunctionName: string,
args: unknown[],
) {
const fragment = contractInterface.getFunction(initializerFunctionName);
return contractInterface.encodeFunctionData(fragment, args);
return contractInterface.encodeFunctionData(fragment!, args);
}
export async function upgradeContractAndCall(
@@ -16,11 +16,10 @@ export async function upgradeContractAndCall(
proxyContractAddress: string,
implementationContractAddress: string,
initializerData = "0x",
): Promise<ContractReceipt> {
): Promise<ContractTransactionReceipt | null> {
const proxyAdminFactory = new ProxyAdmin__factory(deployer);
const proxyAdmin = proxyAdminFactory.connect(deployer).attach(proxyAdminContractAddress);
const proxyAdmin = proxyAdminFactory.connect(deployer).attach(proxyAdminContractAddress) as ProxyAdmin;
const tx = await proxyAdmin.upgradeAndCall(proxyContractAddress, implementationContractAddress, initializerData);
return tx.wait();
}
@@ -29,9 +28,9 @@ export async function upgradeContract(
proxyAdminContractAddress: string,
proxyContractAddress: string,
implementationContractAddress: string,
): Promise<ContractReceipt> {
): Promise<ContractTransactionReceipt | null> {
const proxyAdminFactory = new ProxyAdmin__factory(deployer);
const proxyAdmin = proxyAdminFactory.connect(deployer).attach(proxyAdminContractAddress);
const proxyAdmin = proxyAdminFactory.connect(deployer).attach(proxyAdminContractAddress) as ProxyAdmin;
const tx = await proxyAdmin.upgrade(proxyContractAddress, implementationContractAddress);
return tx.wait();

263
e2e/src/common/utils.ts Normal file
View File

@@ -0,0 +1,263 @@
import * as fs from "fs";
import assert from "assert";
import { BaseContract, BlockTag, TransactionReceipt, Wallet, ethers } from "ethers";
import path from "path";
import { exec } from "child_process";
import { L2MessageService, LineaRollup } from "../typechain";
import { PayableOverrides, TypedContractEvent, TypedDeferredTopicFilter, TypedEventLog } from "../typechain/common";
import { MessageEvent, SendMessageArgs } from "./types";
export function etherToWei(amount: string): bigint {
return ethers.parseEther(amount.toString());
}
export function readJsonFile(filePath: string): unknown {
const data = fs.readFileSync(filePath, "utf8");
return JSON.parse(data);
}
export const wait = (timeout: number) => new Promise((resolve) => setTimeout(resolve, timeout));
export function increaseDate(currentDate: Date, seconds: number): Date {
const newDate = new Date(currentDate.getTime());
newDate.setSeconds(newDate.getSeconds() + seconds);
return newDate;
}
export const subtractSecondsToDate = (date: Date, seconds: number): Date => {
const dateCopy = new Date(date);
dateCopy.setSeconds(date.getSeconds() - seconds);
return dateCopy;
};
export function getWallet(privateKey: string, provider: ethers.JsonRpcProvider) {
return new ethers.Wallet(privateKey, provider);
}
export function encodeFunctionCall(contractInterface: ethers.Interface, functionName: string, args: unknown[]) {
return contractInterface.encodeFunctionData(functionName, args);
}
export const generateKeccak256 = (types: string[], values: unknown[], packed?: boolean) =>
ethers.keccak256(encodeData(types, values, packed));
export const encodeData = (types: string[], values: unknown[], packed?: boolean) => {
if (packed) {
return ethers.solidityPacked(types, values);
}
return ethers.AbiCoder.defaultAbiCoder().encode(types, values);
};
export class RollupGetZkEVMBlockNumberClient {
private endpoint: URL;
private request = {
method: "post",
body: JSON.stringify({
jsonrpc: "2.0",
method: "rollup_getZkEVMBlockNumber",
params: [],
id: 1,
}),
};
public constructor(endpoint: URL) {
this.endpoint = endpoint;
}
public async rollupGetZkEVMBlockNumber(): Promise<number> {
const response = await fetch(this.endpoint, this.request);
const data = await response.json();
assert("result" in data);
return Number.parseInt(data.result);
}
}
export async function getBlockByNumberOrBlockTag(rpcUrl: URL, blockTag: BlockTag): Promise<ethers.Block | null> {
const provider = new ethers.JsonRpcProvider(rpcUrl.href);
try {
const blockNumber = await provider.getBlock(blockTag);
return blockNumber;
} catch (error) {
return null;
}
}
export async function getEvents<TContract extends LineaRollup | L2MessageService, TEvent extends TypedContractEvent>(
contract: TContract,
eventFilter: TypedDeferredTopicFilter<TEvent>,
fromBlock?: BlockTag,
toBlock?: BlockTag,
criteria?: (events: TypedEventLog<TEvent>[]) => Promise<TypedEventLog<TEvent>[]>,
): Promise<Array<TypedEventLog<TEvent>>> {
const events = await contract.queryFilter(
eventFilter,
fromBlock as string | number | undefined,
toBlock as string | number | undefined,
);
if (criteria) {
return await criteria(events);
}
return events;
}
export async function waitForEvents<
TContract extends LineaRollup | L2MessageService,
TEvent extends TypedContractEvent,
>(
contract: TContract,
eventFilter: TypedDeferredTopicFilter<TEvent>,
pollingInterval: number = 500,
fromBlock?: BlockTag,
toBlock?: BlockTag,
criteria?: (events: TypedEventLog<TEvent>[]) => Promise<TypedEventLog<TEvent>[]>,
): Promise<TypedEventLog<TEvent>[]> {
let events = await getEvents(contract, eventFilter, fromBlock, toBlock, criteria);
while (events.length === 0) {
events = await getEvents(contract, eventFilter, fromBlock, toBlock, criteria);
await wait(pollingInterval);
}
return events;
}
export function getFiles(directory: string, fileRegex: RegExp[]): string[] {
const files = fs.readdirSync(directory, { withFileTypes: true });
const filteredFiles = files.filter((file) => fileRegex.map((regex) => regex.test(file.name)).includes(true));
return filteredFiles.map((file) => fs.readFileSync(path.join(directory, file.name), "utf-8"));
}
export async function waitForFile(
directory: string,
regex: RegExp,
pollingInterval: number,
timeout: number,
criteria?: (fileName: string) => boolean,
): Promise<string> {
const endTime = Date.now() + timeout;
while (Date.now() < endTime) {
try {
const files = fs.readdirSync(directory);
for (const file of files) {
if (regex.test(file) && (!criteria || criteria(file))) {
const filePath = path.join(directory, file);
const content = fs.readFileSync(filePath, "utf-8");
return content;
}
}
} catch (err) {
throw new Error(`Error reading directory: ${(err as Error).message}`);
}
await new Promise((resolve) => setTimeout(resolve, pollingInterval));
}
throw new Error("File check timed out");
}
export async function sendTransactionsToGenerateTrafficWithInterval(signer: Wallet, pollingInterval: number = 1_000) {
const { maxPriorityFeePerGas, maxFeePerGas } = await signer.provider!.getFeeData();
const transactionRequest = {
to: signer.address,
value: etherToWei("0.000001"),
maxPriorityFeePerGas: maxPriorityFeePerGas,
maxFeePerGas: maxFeePerGas,
};
let timeoutId: NodeJS.Timeout | null = null;
let isRunning = true;
const sendTransaction = async () => {
if (!isRunning) return;
try {
const tx = await signer.sendTransaction(transactionRequest);
await tx.wait();
} catch (error) {
console.error("Error sending transaction:", error);
} finally {
if (isRunning) {
timeoutId = setTimeout(sendTransaction, pollingInterval);
}
}
};
const stop = () => {
isRunning = false;
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
console.log("Transaction loop stopped.");
};
sendTransaction();
return stop;
}
export function getMessageSentEventFromLogs<T extends BaseContract>(
contract: T,
receipts: TransactionReceipt[],
): MessageEvent[] {
return receipts
.flatMap((receipt) => receipt.logs)
.filter((log) => log.topics[0] === "0xe856c2b8bd4eb0027ce32eeaf595c21b0b6b4644b326e5b7bd80a1cf8db72e6c")
.map((log) => {
const logDescription = contract.interface.parseLog(log);
if (!logDescription) {
throw new Error("Invalid log description");
}
const { args } = logDescription;
return {
from: args._from,
to: args._to,
fee: args._fee,
value: args._value,
messageNumber: args._nonce,
calldata: args._calldata,
messageHash: args._messageHash,
blockNumber: log.blockNumber,
};
});
}
export const sendMessage = async <T extends LineaRollup | L2MessageService>(
signer: Wallet,
contract: T,
args: SendMessageArgs,
overrides?: PayableOverrides,
): Promise<TransactionReceipt> => {
const tx = await signer.sendTransaction({
to: await contract.getAddress(),
value: overrides?.value || 0n,
data: contract.interface.encodeFunctionData("sendMessage", [args.to, args.fee, args.calldata]),
...overrides,
});
const receipt = await tx.wait();
if (!receipt) {
throw new Error("Transaction receipt is undefined");
}
return receipt;
};
export async function execDockerCommand(command: string, containerName: string): Promise<string> {
const dockerCommand = `docker ${command} ${containerName}`;
console.log(`Executing: ${dockerCommand}...`);
return new Promise((resolve, reject) => {
exec(dockerCommand, (error, stdout, stderr) => {
if (error) {
console.error(`Error executing (${dockerCommand}): ${stderr}`);
reject(error);
}
console.log(`Execution success (${dockerCommand}): ${stdout}`);
resolve(stdout);
});
});
}

View File

@@ -0,0 +1,39 @@
/* eslint-disable no-var */
import { config } from "../tests-config";
import { deployContract } from "../../common/deployments";
import { DummyContract__factory } from "../../typechain";
import { etherToWei, sendTransactionsToGenerateTrafficWithInterval } from "../../common/utils";
declare global {
var stopL2TrafficGeneration: () => void;
}
export default async (): Promise<void> => {
const account = config.getL1AccountManager().whaleAccount(0);
const l2Account = config.getL2AccountManager().whaleAccount(0);
const [dummyContract, l2DummyContract] = await Promise.all([
deployContract(new DummyContract__factory(), account),
deployContract(new DummyContract__factory(), l2Account),
]);
console.log(`L1 Dummy contract deployed at address: ${await dummyContract.getAddress()}`);
console.log(`L2 Dummy contract deployed at address: ${await l2DummyContract.getAddress()}`);
// Send ETH to the LineaRollup contract
const lineaRollup = config.getLineaRollupContract(account);
const l1JsonRpcProvider = config.getL1Provider();
const value = etherToWei("500");
const fee = etherToWei("3");
const to = "0x8D97689C9818892B700e27F316cc3E41e17fBeb9";
const calldata = "0x";
const { maxPriorityFeePerGas, maxFeePerGas } = await l1JsonRpcProvider.getFeeData();
const tx = await lineaRollup.sendMessage(to, fee, calldata, { value, maxPriorityFeePerGas, maxFeePerGas });
await tx.wait();
console.log("Generating L2 traffic...");
const stopPolling = await sendTransactionsToGenerateTrafficWithInterval(l2Account, 2_000);
global.stopL2TrafficGeneration = stopPolling;
};

View File

@@ -0,0 +1,3 @@
export default async (): Promise<void> => {
global.stopL2TrafficGeneration();
};

View File

@@ -0,0 +1,124 @@
import { ethers, Provider, Wallet } from "ethers";
import Account from "./account";
import { etherToWei } from "../../../common/utils";
interface IAccountManager {
whaleAccount(accIndex?: number): Wallet;
generateAccount(initialBalanceWei?: bigint): Promise<Wallet>;
generateAccounts(numberOfAccounts: number, initialBalanceWei?: bigint): Promise<Wallet[]>;
getWallet(account: Account): Wallet;
}
function getWallet(provider: Provider, privateKey: string): Wallet {
if (!privateKey.startsWith("0x")) {
privateKey = "0x" + privateKey;
}
let keyWithoutPrefix = privateKey.slice(2);
// Pad the private key to 64 hex characters (32 bytes) if it's shorter
if (keyWithoutPrefix.length < 64) {
keyWithoutPrefix = keyWithoutPrefix.padStart(64, "0");
}
return new Wallet(`0x${keyWithoutPrefix}`, provider);
}
abstract class AccountManager implements IAccountManager {
protected readonly chainId: number;
protected readonly whaleAccounts: Account[];
protected provider: Provider;
protected accountWallets: Wallet[];
constructor(provider: Provider, whaleAccounts: Account[], chainId: number) {
this.provider = provider;
this.whaleAccounts = whaleAccounts;
this.chainId = chainId;
this.accountWallets = this.whaleAccounts.map((account) => getWallet(this.provider, account.privateKey));
}
selectWhaleAccount(accIndex?: number): { account: Account; accountWallet: Wallet } {
if (accIndex) {
return { account: this.whaleAccounts[accIndex], accountWallet: this.accountWallets[accIndex] };
}
const workerIdEnv = process.env.JEST_WORKER_ID || "1";
const workerId = parseInt(workerIdEnv, 10) - 1;
const accountIndex = workerId;
const whaleAccount = this.whaleAccounts[accountIndex];
const whaleTxManager = this.accountWallets[this.whaleAccounts.indexOf(whaleAccount)];
return { account: whaleAccount, accountWallet: whaleTxManager };
}
whaleAccount(accIndex?: number): Wallet {
return this.selectWhaleAccount(accIndex).accountWallet;
}
async generateAccount(initialBalanceWei = etherToWei("10")): Promise<Wallet> {
const accounts = await this.generateAccounts(1, initialBalanceWei);
return this.getWallet(accounts[0]);
}
async generateAccounts(
numberOfAccounts: number,
initialBalanceWei = etherToWei("10"),
retryDelayMs = 1_000,
): Promise<Wallet[]> {
const { account: whaleAccount, accountWallet: whaleAccountWallet } = this.selectWhaleAccount();
console.log(
`Generating accounts: chainId=${this.chainId} numberOfAccounts=${numberOfAccounts} whaleAccount=${whaleAccount.address}`,
);
const accounts: Account[] = [];
for (let i = 0; i < numberOfAccounts; i++) {
const randomBytes = ethers.randomBytes(32);
const randomPrivKey = ethers.hexlify(randomBytes);
const newAccount = new Account(randomPrivKey, ethers.computeAddress(randomPrivKey));
accounts.push(newAccount);
let success = false;
while (!success) {
try {
const tx = {
to: newAccount.address,
value: initialBalanceWei,
gasPrice: ethers.parseUnits("300", "gwei"),
gasLimit: 21000n,
};
const transactionResponse = await whaleAccountWallet.sendTransaction(tx);
console.log(
`Waiting for account funding: newAccount=${newAccount.address} txHash=${transactionResponse.hash} whaleAccount=${whaleAccount.address}`,
);
const receipt = await transactionResponse.wait();
if (!receipt) {
throw new Error(`Transaction failed to be mined`);
}
if (receipt.status !== 1) {
throw new Error(`Transaction failed with status ${receipt.status}`);
}
console.log(
`Account funded: newAccount=${newAccount.address} balance=${(
await this.provider.getBalance(newAccount.address)
).toString()} wei`,
);
success = true;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
console.log(
`Failed to send funds from accAddress=${whaleAccount.address}. Retrying funding in ${retryDelayMs}ms...`,
);
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
}
}
}
return accounts.map((account) => getWallet(this.provider, account.privateKey));
}
getWallet(account: Account): Wallet {
return getWallet(this.provider, account.privateKey);
}
}
export { AccountManager };

View File

@@ -0,0 +1,9 @@
export default class Account {
public readonly privateKey: string;
public readonly address: string;
constructor(privateKey: string, address: string) {
this.privateKey = privateKey;
this.address = address;
}
}

View File

@@ -0,0 +1,11 @@
import { Provider } from "ethers";
import Account from "./account";
import { AccountManager } from "./account-manager";
class EnvironmentBasedAccountManager extends AccountManager {
constructor(provider: Provider, whaleAccounts: Account[], chainId: number) {
super(provider, whaleAccounts, chainId);
}
}
export { EnvironmentBasedAccountManager };

View File

@@ -0,0 +1,41 @@
import { ethers, Provider } from "ethers";
import Account from "./account";
import { AccountManager } from "./account-manager";
import { readJsonFile } from "../../../common/utils";
interface GenesisJson {
config: {
chainId: number;
};
alloc: {
[address: string]: {
privateKey?: string;
};
};
}
function readGenesisFileAccounts(genesisJson: GenesisJson): Account[] {
const alloc = genesisJson.alloc;
const accounts: Account[] = [];
for (const address in alloc) {
const accountData = alloc[address];
if (accountData.privateKey) {
const addr = ethers.getAddress(address);
accounts.push(new Account(accountData.privateKey, addr));
}
}
return accounts;
}
class GenesisBasedAccountManager extends AccountManager {
constructor(provider: Provider, genesisFilePath: string) {
const genesisJson = readJsonFile(genesisFilePath);
const genesis = genesisJson as GenesisJson;
const chainId = genesis.config.chainId;
const whaleAccounts = readGenesisFileAccounts(genesis);
super(provider, whaleAccounts, chainId);
}
}
export { GenesisBasedAccountManager };

View File

@@ -0,0 +1,49 @@
import { ethers } from "ethers";
import { EnvironmentBasedAccountManager } from "../accounts/environment-based-account-manager";
import { Config } from "../types";
import Account from "../accounts/account";
const L1_RPC_URL = new URL(`https://sepolia.infura.io/v3/${process.env.INFURA_PROJECT_ID}`);
const L2_RPC_URL = new URL("https://rpc.devnet.linea.build");
const L1_CHAIN_ID = 11155111;
const L2_CHAIN_ID = 59139;
const L1_WHALE_ACCOUNTS_PRIVATE_KEYS: string[] = process.env.L1_WHALE_ACCOUNTS_PRIVATE_KEYS?.split(",") ?? [];
const L2_WHALE_ACCOUNTS_PRIVATE_KEYS: string[] = process.env.L2_WHALE_ACCOUNTS_PRIVATE_KEYS?.split(",") ?? [];
const L1_WHALE_ACCOUNTS_ADDRESSES: string[] = process.env.L1_WHALE_ACCOUNTS_ADDRESSES?.split(",") ?? [];
const L2_WHALE_ACCOUNTS_ADDRESSES: string[] = process.env.L2_WHALE_ACCOUNTS_ADDRESSES?.split(",") ?? [];
const L1_WHALE_ACCOUNTS: Account[] = L1_WHALE_ACCOUNTS_PRIVATE_KEYS.map((privateKey, index) => {
return new Account(privateKey, L1_WHALE_ACCOUNTS_ADDRESSES[index]);
});
const L2_WHALE_ACCOUNTS: Account[] = L2_WHALE_ACCOUNTS_PRIVATE_KEYS.map((privateKey, index) => {
return new Account(privateKey, L2_WHALE_ACCOUNTS_ADDRESSES[index]);
});
const config: Config = {
L1: {
rpcUrl: L1_RPC_URL,
chainId: L1_CHAIN_ID,
lineaRollupAddress: "0x2A5CDCfc38856e2590E9Bd32F54Fa348e5De5f48",
accountManager: new EnvironmentBasedAccountManager(
new ethers.JsonRpcProvider(L1_RPC_URL.toString()),
L1_WHALE_ACCOUNTS,
L1_CHAIN_ID,
),
dummyContractAddress: "",
},
L2: {
rpcUrl: L2_RPC_URL,
chainId: L2_CHAIN_ID,
l2MessageServiceAddress: "0x33bf916373159A8c1b54b025202517BfDbB7863D",
accountManager: new EnvironmentBasedAccountManager(
new ethers.JsonRpcProvider(L2_RPC_URL.toString()),
L2_WHALE_ACCOUNTS,
L2_CHAIN_ID,
),
dummyContractAddress: "",
},
};
export default config;

View File

@@ -0,0 +1,44 @@
import { ethers } from "ethers";
import path from "path";
import { GenesisBasedAccountManager } from "../accounts/genesis-based-account-manager";
import { Config } from "../types";
const L1_RPC_URL = new URL("http://localhost:8445");
const L2_RPC_URL = new URL("http://localhost:8845");
const SHOMEI_ENDPOINT = new URL("http://localhost:8998");
const SHOMEI_FRONTEND_ENDPOINT = new URL("http://localhost:8889");
const SEQUENCER_ENDPOINT = new URL("http://localhost:8545");
const config: Config = {
L1: {
rpcUrl: L1_RPC_URL,
chainId: 31648428,
lineaRollupAddress: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
dummyContractAddress: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9",
accountManager: new GenesisBasedAccountManager(
new ethers.JsonRpcProvider(L1_RPC_URL.toString()),
path.resolve(
process.env.LOCAL_L1_GENESIS ||
path.resolve(__dirname, "../../../../..", "docker/config/l1-node/el", "genesis.json"),
),
),
},
L2: {
rpcUrl: L2_RPC_URL,
chainId: 1337,
l2MessageServiceAddress: "0xe537D669CA013d86EBeF1D64e40fC74CADC91987",
dummyContractAddress: "0x2f6dAaF8A81AB675fbD37Ca6Ed5b72cf86237453",
accountManager: new GenesisBasedAccountManager(
new ethers.JsonRpcProvider(L2_RPC_URL.toString()),
path.resolve(
process.env.LOCAL_L2_GENESIS ||
path.resolve(__dirname, "../../../../..", "docker/config", "linea-local-dev-genesis-PoA.json"),
),
),
shomeiEndpoint: SHOMEI_ENDPOINT,
shomeiFrontendEndpoint: SHOMEI_FRONTEND_ENDPOINT,
sequencerEndpoint: SEQUENCER_ENDPOINT,
},
};
export default config;

View File

@@ -0,0 +1,48 @@
import { ethers } from "ethers";
import { EnvironmentBasedAccountManager } from "../accounts/environment-based-account-manager";
import Account from "../accounts/account";
import { Config } from "../types";
const L1_RPC_URL = new URL(`https://sepolia.infura.io/v3/${process.env.INFURA_PROJECT_ID}`);
const L2_RPC_URL = new URL(`https://linea-sepolia.infura.io/v3/${process.env.INFURA_PROJECT_ID}`);
const L1_CHAIN_ID = 11155111;
const L2_CHAIN_ID = 59141;
const L1_WHALE_ACCOUNTS_PRIVATE_KEYS: string[] = process.env.L1_WHALE_ACCOUNTS_PRIVATE_KEYS?.split(",") ?? [];
const L2_WHALE_ACCOUNTS_PRIVATE_KEYS: string[] = process.env.L2_WHALE_ACCOUNTS_PRIVATE_KEYS?.split(",") ?? [];
const L1_WHALE_ACCOUNTS_ADDRESSES: string[] = process.env.L1_WHALE_ACCOUNTS_ADDRESSES?.split(",") ?? [];
const L2_WHALE_ACCOUNTS_ADDRESSES: string[] = process.env.L2_WHALE_ACCOUNTS_ADDRESSES?.split(",") ?? [];
const L1_WHALE_ACCOUNTS: Account[] = L1_WHALE_ACCOUNTS_PRIVATE_KEYS.map((privateKey, index) => {
return new Account(privateKey, L1_WHALE_ACCOUNTS_ADDRESSES[index]);
});
const L2_WHALE_ACCOUNTS: Account[] = L2_WHALE_ACCOUNTS_PRIVATE_KEYS.map((privateKey, index) => {
return new Account(privateKey, L2_WHALE_ACCOUNTS_ADDRESSES[index]);
});
const config: Config = {
L1: {
rpcUrl: L1_RPC_URL,
chainId: L1_CHAIN_ID,
lineaRollupAddress: "0xB218f8A4Bc926cF1cA7b3423c154a0D627Bdb7E5",
accountManager: new EnvironmentBasedAccountManager(
new ethers.JsonRpcProvider(L1_RPC_URL.toString()),
L1_WHALE_ACCOUNTS,
L1_CHAIN_ID,
),
dummyContractAddress: "",
},
L2: {
rpcUrl: L2_RPC_URL,
chainId: L2_CHAIN_ID,
l2MessageServiceAddress: "0x971e727e956690b9957be6d51Ec16E73AcAC83A7",
accountManager: new EnvironmentBasedAccountManager(
new ethers.JsonRpcProvider(L2_RPC_URL.toString()),
L2_WHALE_ACCOUNTS,
L2_CHAIN_ID,
),
dummyContractAddress: "",
},
};
export default config;

View File

@@ -0,0 +1,26 @@
import TestSetup from "./setup";
import localConfig from "./environments/local";
import devConfig from "./environments/dev";
import sepoliaConfig from "./environments/sepolia";
import { Config } from "./types";
const testEnv = process.env.TEST_ENV || "local"; // Default to local environment
let config: Config;
switch (testEnv) {
case "dev":
config = devConfig;
break;
case "sepolia":
config = sepoliaConfig;
break;
case "local":
default:
config = localConfig;
break;
}
const setup = new TestSetup(config);
export { setup as config };

View File

@@ -0,0 +1,97 @@
import { JsonRpcProvider, Wallet } from "ethers";
import { Config } from "./types";
import {
DummyContract,
DummyContract__factory,
L2MessageService,
L2MessageService__factory,
LineaRollup,
LineaRollup__factory,
} from "../../typechain";
import { AccountManager } from "./accounts/account-manager";
export default class TestSetup {
constructor(private readonly config: Config) {}
public getL1Provider(): JsonRpcProvider {
return new JsonRpcProvider(this.config.L1.rpcUrl.toString());
}
public getL2Provider(): JsonRpcProvider {
return new JsonRpcProvider(this.config.L2.rpcUrl.toString());
}
public getL1ChainId(): number {
return this.config.L1.chainId;
}
public getL2ChainId(): number {
return this.config.L2.chainId;
}
public getShomeiEndpoint(): URL | undefined {
return this.config.L2.shomeiEndpoint;
}
public getShomeiFrontendEndpoint(): URL | undefined {
return this.config.L2.shomeiFrontendEndpoint;
}
public getSequencerEndpoint(): URL | undefined {
return this.config.L2.sequencerEndpoint;
}
public getLineaRollupContract(signer?: Wallet): LineaRollup {
const lineaRollup: LineaRollup = LineaRollup__factory.connect(
this.config.L1.lineaRollupAddress,
this.getL1Provider(),
);
if (signer) {
return lineaRollup.connect(signer);
}
return lineaRollup;
}
public getL2MessageServiceContract(signer?: Wallet): L2MessageService {
const l2MessageService: L2MessageService = L2MessageService__factory.connect(
this.config.L2.l2MessageServiceAddress,
this.getL2Provider(),
);
if (signer) {
return l2MessageService.connect(signer);
}
return l2MessageService;
}
public getL1DummyContract(signer?: Wallet): DummyContract {
const dummyContract = DummyContract__factory.connect(this.config.L1.dummyContractAddress, this.getL1Provider());
if (signer) {
return dummyContract.connect(signer);
}
return dummyContract;
}
public getL2DummyContract(signer?: Wallet): DummyContract {
const dummyContract = DummyContract__factory.connect(this.config.L2.dummyContractAddress, this.getL2Provider());
if (signer) {
return dummyContract.connect(signer);
}
return dummyContract;
}
public getL1AccountManager(): AccountManager {
return this.config.L1.accountManager;
}
public getL2AccountManager(): AccountManager {
return this.config.L2.accountManager;
}
}

View File

@@ -0,0 +1,24 @@
import { AccountManager } from "./accounts/account-manager";
export type BaseConfig = {
rpcUrl: URL;
chainId: number;
accountManager: AccountManager;
dummyContractAddress: string;
};
export type L1Config = BaseConfig & {
lineaRollupAddress: string;
};
export type L2Config = BaseConfig & {
l2MessageServiceAddress: string;
shomeiEndpoint?: URL;
shomeiFrontendEndpoint?: URL;
sequencerEndpoint?: URL;
};
export type Config = {
L1: L1Config;
L2: L2Config;
};

View File

@@ -1,12 +0,0 @@
// Here we want to guarantee the running order of test files
// to minimize timeout errors in ci pipeline
import submissionAndFinalizationTestSuite from "./submission-finalization.spec";
import layer2TestSuite from "./l2.spec";
import messagingTestSuite from "./messaging.spec";
import coordinatorRestartTestSuite from "./restart.spec";
messagingTestSuite("Messaging test suite");
// NOTES: The coordinator restart test must not be run first in the sequence of tests.
coordinatorRestartTestSuite("Coordinator restart test suite");
layer2TestSuite("Layer 2 test suite");
submissionAndFinalizationTestSuite("Submission and finalization test suite");

View File

@@ -1,339 +1,157 @@
import { Wallet, ethers } from "ethers";
import { describe, expect, it } from "@jest/globals";
import { TransactionRequest } from "@ethersproject/providers";
import { getAndIncreaseFeeData } from "./utils/helpers";
import { RollupGetZkEVMBlockNumberClient, getEvents, wait } from "./utils/utils";
import { JsonRpcProvider, ethers } from "ethers";
import { beforeAll, describe, expect, it } from "@jest/globals";
import { config } from "./config/tests-config";
import { RollupGetZkEVMBlockNumberClient, etherToWei } from "./common/utils";
import { TRANSACTION_CALLDATA_LIMIT } from "./common/constants";
const layer2TestSuite = (title: string) => {
describe(title, () => {
describe("Transaction data size", () => {
it("Should revert if transaction data size is above the limit", async () => {
const account = new Wallet(L2_DEPLOYER_ACCOUNT_PRIVATE_KEY, l2Provider);
await expect(
dummyContract.connect(account).setPayload(ethers.utils.randomBytes(TRANSACTION_CALLDATA_LIMIT)),
).rejects.toThrow("err: tx data is too large (in bytes)");
});
const l2AccountManager = config.getL2AccountManager();
it("Should succeed if transaction data size is below the limit", async () => {
const account = new Wallet(L2_ACCOUNT_0_PRIVATE_KEY, l2Provider);
describe("Layer 2 test suite", () => {
let l2Provider: JsonRpcProvider;
const [nonce, feeData] = await Promise.all([
l2Provider.getTransactionCount(account.address),
l2Provider.getFeeData(),
]);
beforeAll(() => {
l2Provider = config.getL2Provider();
});
const [maxPriorityFeePerGas, maxFeePerGas] = getAndIncreaseFeeData(feeData);
const tx = await dummyContract.connect(account).setPayload(ethers.utils.randomBytes(1000), {
nonce,
it.concurrent("Should revert if transaction data size is above the limit", async () => {
const account = await l2AccountManager.generateAccount();
const dummyContract = config.getL2DummyContract(account);
await expect(
dummyContract.connect(account).setPayload(ethers.randomBytes(TRANSACTION_CALLDATA_LIMIT)),
).rejects.toThrow("missing revert data");
});
it.concurrent("Should succeed if transaction data size is below the limit", async () => {
const account = await l2AccountManager.generateAccount();
const dummyContract = config.getL2DummyContract(account);
const tx = await dummyContract.connect(account).setPayload(ethers.randomBytes(1000));
const receipt = await tx.wait();
expect(receipt?.status).toEqual(1);
});
it.concurrent("Should successfully send a legacy transaction", async () => {
const account = await l2AccountManager.generateAccount();
const { gasPrice } = await l2Provider.getFeeData();
const receipt = await (
await account.sendTransaction({
type: 0,
to: "0x8D97689C9818892B700e27F316cc3E41e17fBeb9",
gasPrice,
value: etherToWei("0.01"),
gasLimit: "0x466124",
chainId: config.getL2ChainId(),
})
).wait();
expect(receipt).not.toBeNull();
});
it.concurrent("Should successfully send an EIP1559 transaction", async () => {
const account = await l2AccountManager.generateAccount();
const { maxPriorityFeePerGas, maxFeePerGas } = await l2Provider.getFeeData();
const receipt = await (
await account.sendTransaction({
type: 2,
to: "0x8D97689C9818892B700e27F316cc3E41e17fBeb9",
maxPriorityFeePerGas,
maxFeePerGas,
value: etherToWei("0.01"),
gasLimit: "21000",
chainId: config.getL2ChainId(),
})
).wait();
expect(receipt).not.toBeNull();
});
it.concurrent("Should successfully send an access list transaction with empty access list", async () => {
const account = await l2AccountManager.generateAccount();
const { gasPrice } = await l2Provider.getFeeData();
const receipt = await (
await account.sendTransaction({
type: 1,
to: "0x8D97689C9818892B700e27F316cc3E41e17fBeb9",
gasPrice,
value: etherToWei("0.01"),
gasLimit: "21000",
chainId: config.getL2ChainId(),
})
).wait();
expect(receipt).not.toBeNull();
});
it.concurrent("Should successfully send an access list transaction with access list", async () => {
const account = await l2AccountManager.generateAccount();
const { gasPrice } = await l2Provider.getFeeData();
const accessList = {
"0x8D97689C9818892B700e27F316cc3E41e17fBeb9": [
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0x0000000000000000000000000000000000000000000000000000000000000001",
],
};
const receipt = await (
await account.sendTransaction({
type: 1,
to: "0x8D97689C9818892B700e27F316cc3E41e17fBeb9",
gasPrice,
value: etherToWei("0.01"),
gasLimit: "200000",
chainId: config.getL2ChainId(),
accessList: ethers.accessListify(accessList),
})
).wait();
expect(receipt).not.toBeNull();
});
// TODO: discuss new frontend
it.skip("Shomei frontend always behind while conflating multiple blocks and proving on L1", async () => {
const account = await l2AccountManager.generateAccount();
const shomeiEndpoint = config.getShomeiEndpoint();
const shomeiFrontendEndpoint = config.getShomeiFrontendEndpoint();
if (!shomeiEndpoint || !shomeiFrontendEndpoint) {
// Skip this test for dev and uat environments
return;
}
const shomeiClient = new RollupGetZkEVMBlockNumberClient(shomeiEndpoint);
const shomeiFrontendClient = new RollupGetZkEVMBlockNumberClient(shomeiFrontendEndpoint);
for (let i = 0; i < 5; i++) {
const { maxPriorityFeePerGas, maxFeePerGas } = await l2Provider.getFeeData();
await (
await account.sendTransaction({
type: 2,
to: "0x8D97689C9818892B700e27F316cc3E41e17fBeb9",
maxPriorityFeePerGas,
maxFeePerGas,
});
value: etherToWei("0.01"),
gasLimit: "21000",
chainId: config.getL2ChainId(),
})
).wait();
const receipt = await tx.wait();
expect(receipt.status).toEqual(1);
});
});
const [shomeiBlock, shomeiFrontendBlock] = await Promise.all([
shomeiClient.rollupGetZkEVMBlockNumber(),
shomeiFrontendClient.rollupGetZkEVMBlockNumber(),
]);
console.log(`shomeiBlock = ${shomeiBlock}, shomeiFrontendBlock = ${shomeiFrontendBlock}`);
describe("Block conflation", () => {
it("Should succeed in conflating multiple blocks and proving on L1", async () => {
const account = new Wallet(L2_ACCOUNT_0_PRIVATE_KEY, l2Provider);
const l2BlockNumbers: number[] = [];
for (let i = 0; i < 2; i++) {
const [nonce, feeData] = await Promise.all([
l2Provider.getTransactionCount(account.address),
l2Provider.getFeeData(),
]);
const [maxPriorityFeePerGas, maxFeePerGas] = getAndIncreaseFeeData(feeData);
const tx: TransactionRequest = {
type: 2,
nonce,
to: "0x8D97689C9818892B700e27F316cc3E41e17fBeb9",
maxPriorityFeePerGas,
maxFeePerGas,
value: ethers.utils.parseEther("0.01"),
gasLimit: "21000",
chainId,
};
const signedTx = await account.signTransaction(tx);
const receipt = await (await l2Provider.sendTransaction(signedTx)).wait();
l2BlockNumbers.push(receipt.blockNumber);
}
for (let i = 0; i < 2; i++) {
const [nonce, feeData] = await Promise.all([
l2Provider.getTransactionCount(account.address),
l2Provider.getFeeData(),
]);
const [maxPriorityFeePerGas, maxFeePerGas] = getAndIncreaseFeeData(feeData);
const tx = await dummyContract
.connect(account)
.setPayload(ethers.utils.randomBytes(TRANSACTION_CALLDATA_LIMIT / 2 - 1000), {
nonce,
maxPriorityFeePerGas,
maxFeePerGas,
});
const receipt = await tx.wait();
l2BlockNumbers.push(receipt.blockNumber);
}
// These is just to push the L1 verified block forward to the max number in
// l2BlockNumbers as it's always 2 blocks behind the current L2 block number
for (let i = 0; i < 4; i++) {
const [nonce, feeData] = await Promise.all([
l2Provider.getTransactionCount(account.address),
l2Provider.getFeeData(),
]);
const [maxPriorityFeePerGas, maxFeePerGas] = getAndIncreaseFeeData(feeData);
const tx = await dummyContract.connect(account).setPayload(ethers.utils.randomBytes(10), {
nonce,
maxPriorityFeePerGas,
maxFeePerGas,
});
await tx.wait();
}
const maxL2BlockNumber = Math.max(...l2BlockNumbers);
let currentL2BlockNumber = (await lineaRollup.currentL2BlockNumber()).toNumber();
console.log(`maxL2BlockNumber: ${maxL2BlockNumber}`);
console.log(`l2BlockNumbers: ${l2BlockNumbers}`);
console.log(`initial currentL2BlockNumber: ${currentL2BlockNumber}`);
while (maxL2BlockNumber > currentL2BlockNumber) {
await wait(2000);
currentL2BlockNumber = (await lineaRollup.currentL2BlockNumber()).toNumber();
}
const events = await getEvents(lineaRollup, lineaRollup.filters.BlocksVerificationDone());
console.log(`Last blockVerification: ${JSON.stringify(events.at(-1))}`);
console.log(`currentL2BlockNumber: ${currentL2BlockNumber}`);
expect(currentL2BlockNumber).toBeGreaterThanOrEqual(maxL2BlockNumber);
}, 300000);
it("Should succeed in conflating transactions with large calldata with low gas into multiple L1 blocks", async () => {
const account = new Wallet(L2_ACCOUNT_0_PRIVATE_KEY, l2Provider);
const l2BlockNumbers: number[] = [];
const txList = [];
for (let i = 0; i < 4; i++) {
const [nonce, feeData] = await Promise.all([
l2Provider.getTransactionCount(account.address, "pending"),
l2Provider.getFeeData(),
]);
const [maxPriorityFeePerGas, maxFeePerGas] = getAndIncreaseFeeData(feeData);
const tx = await dummyContract
.connect(account)
.setPayload(ethers.utils.randomBytes(TRANSACTION_CALLDATA_LIMIT / 2 - 1000), {
nonce,
maxPriorityFeePerGas,
maxFeePerGas,
});
txList.push(tx);
}
await Promise.all(
txList.map(async (tx) => {
const receipt = await tx.wait();
l2BlockNumbers.push(receipt.blockNumber);
}),
);
// These is just to push the L1 verified block forward to the max number in
// l2BlockNumbers as it's always 2 blocks behind the current L2 block number
for (let i = 0; i < 4; i++) {
const [nonce, feeData] = await Promise.all([
l2Provider.getTransactionCount(account.address),
l2Provider.getFeeData(),
]);
const [maxPriorityFeePerGas, maxFeePerGas] = getAndIncreaseFeeData(feeData);
const tx = await dummyContract.connect(account).setPayload(ethers.utils.randomBytes(10), {
nonce,
maxPriorityFeePerGas,
maxFeePerGas,
});
await tx.wait();
}
const maxL2BlockNumber = Math.max(...l2BlockNumbers);
let currentL2BlockNumber = (await lineaRollup.currentL2BlockNumber()).toNumber();
console.log(`l2BlockNumbers: ${l2BlockNumbers}`);
console.log(`initial currentL2BlockNumber: ${currentL2BlockNumber}`);
while (maxL2BlockNumber > currentL2BlockNumber) {
await wait(2000);
currentL2BlockNumber = (await lineaRollup.currentL2BlockNumber()).toNumber();
}
const events = await getEvents(lineaRollup, lineaRollup.filters.BlocksVerificationDone());
console.log(`Last blockVerification: ${JSON.stringify(events.at(-1))}`);
console.log(`currentL2BlockNumber: ${currentL2BlockNumber}`);
expect(currentL2BlockNumber).toBeGreaterThanOrEqual(maxL2BlockNumber);
}, 600000);
});
describe("Different transaction types", () => {
it("Should successfully send a legacy transaction", async () => {
const account = new Wallet(L2_DEPLOYER_ACCOUNT_PRIVATE_KEY, l2Provider);
const [nonce, feeData] = await Promise.all([
l2Provider.getTransactionCount(account.address),
l2Provider.getFeeData(),
]);
const [, , gasPrice] = getAndIncreaseFeeData(feeData);
const receipt = await (
await account.sendTransaction({
type: 0,
nonce,
to: "0x8D97689C9818892B700e27F316cc3E41e17fBeb9",
gasPrice,
value: ethers.utils.parseEther("0.01"),
gasLimit: "0x466124",
chainId,
})
).wait();
expect(receipt).not.toBeNull();
});
it("Should successfully send an EIP1559 transaction", async () => {
const account = new Wallet(L2_DEPLOYER_ACCOUNT_PRIVATE_KEY, l2Provider);
const [nonce, feeData] = await Promise.all([
l2Provider.getTransactionCount(account.address),
l2Provider.getFeeData(),
]);
const [maxPriorityFeePerGas, maxFeePerGas] = getAndIncreaseFeeData(feeData);
const receipt = await (
await account.sendTransaction({
type: 2,
nonce,
to: "0x8D97689C9818892B700e27F316cc3E41e17fBeb9",
maxPriorityFeePerGas,
maxFeePerGas,
value: ethers.utils.parseEther("0.01"),
gasLimit: "21000",
chainId,
})
).wait();
expect(receipt).not.toBeNull();
});
it("Should successfully send an access list transaction with empty access list", async () => {
const account = new Wallet(L2_DEPLOYER_ACCOUNT_PRIVATE_KEY, l2Provider);
const [nonce, feeData] = await Promise.all([
l2Provider.getTransactionCount(account.address),
l2Provider.getFeeData(),
]);
const [, , gasPrice] = getAndIncreaseFeeData(feeData);
const receipt = await (
await account.sendTransaction({
type: 1,
nonce,
to: "0x8D97689C9818892B700e27F316cc3E41e17fBeb9",
gasPrice,
value: ethers.utils.parseEther("0.01"),
gasLimit: "21000",
chainId,
})
).wait();
expect(receipt).not.toBeNull();
});
it("Should successfully send an access list transaction with access list", async () => {
const account = new Wallet(L2_DEPLOYER_ACCOUNT_PRIVATE_KEY, l2Provider);
const [nonce, feeData] = await Promise.all([
l2Provider.getTransactionCount(account.address),
l2Provider.getFeeData(),
]);
const [, , gasPrice] = getAndIncreaseFeeData(feeData);
const accessList = {
"0x8D97689C9818892B700e27F316cc3E41e17fBeb9": [
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0x0000000000000000000000000000000000000000000000000000000000000001",
],
};
const receipt = await (
await account.sendTransaction({
type: 1,
nonce,
to: "0x8D97689C9818892B700e27F316cc3E41e17fBeb9",
gasPrice,
value: ethers.utils.parseEther("0.01"),
gasLimit: "200000",
chainId,
accessList: ethers.utils.accessListify(accessList),
})
).wait();
expect(receipt).not.toBeNull();
});
});
describe("Block finalization notifications", () => {
// TODO: discuss new frontend
it.skip("Shomei frontend always behind while conflating multiple blocks and proving on L1", async () => {
if (SHOMEI_ENDPOINT == null || SHOMEI_FRONTEND_ENDPOINT == null) {
// Skip this test for dev and uat environments
return;
}
const account = new Wallet(L2_ACCOUNT_0_PRIVATE_KEY, l2Provider);
const shomeiClient = new RollupGetZkEVMBlockNumberClient(SHOMEI_ENDPOINT);
const shomeiFrontendClient = new RollupGetZkEVMBlockNumberClient(SHOMEI_FRONTEND_ENDPOINT);
for (let i = 0; i < 5; i++) {
const [nonce, feeData] = await Promise.all([
l2Provider.getTransactionCount(account.address),
l2Provider.getFeeData(),
]);
const [maxPriorityFeePerGas, maxFeePerGas] = getAndIncreaseFeeData(feeData);
await (
await account.sendTransaction({
type: 2,
nonce,
to: "0x8D97689C9818892B700e27F316cc3E41e17fBeb9",
maxPriorityFeePerGas,
maxFeePerGas,
value: ethers.utils.parseEther("0.01"),
gasLimit: "21000",
chainId,
})
).wait();
const [shomeiBlock, shomeiFrontendBlock] = await Promise.all([
shomeiClient.rollupGetZkEVMBlockNumber(),
shomeiFrontendClient.rollupGetZkEVMBlockNumber(),
]);
console.log(`shomeiBlock = ${shomeiBlock}, shomeiFrontendBlock = ${shomeiFrontendBlock}`);
expect(shomeiBlock).toBeGreaterThan(shomeiFrontendBlock);
}
}, 300000);
});
});
};
export default layer2TestSuite;
expect(shomeiBlock).toBeGreaterThan(shomeiFrontendBlock);
}
}, 150_000);
});

View File

@@ -1,129 +1,211 @@
import { Wallet, ethers } from "ethers";
import { beforeAll, describe, expect, it } from "@jest/globals";
import {
encodeFunctionCall,
sendTransactionsToGenerateTrafficWithInterval,
waitForEvents
} from "./utils/utils";
import { getAndIncreaseFeeData } from "./utils/helpers";
import { MESSAGE_SENT_EVENT_SIGNATURE } from "./utils/constants";
import { ethers, Wallet } from "ethers";
import { describe, expect, it } from "@jest/globals";
import { config } from "./config/tests-config";
import { encodeFunctionCall, etherToWei, waitForEvents } from "./common/utils";
import { MESSAGE_SENT_EVENT_SIGNATURE } from "./common/constants";
const messagingTestSuite = (title: string) => {
describe(title, () => {
let l1Account: Wallet;
let l2Account0: Wallet;
let l2AccountForLiveness: Wallet;
async function sendL1ToL2Message({
l1Account,
l2Account,
withCalldata = false,
}: {
l1Account: Wallet;
l2Account: Wallet;
withCalldata: boolean;
}) {
const dummyContract = config.getL2DummyContract(l2Account);
const lineaRollup = config.getLineaRollupContract(l1Account);
beforeAll(() => {
l1Account = new Wallet(L1_ACCOUNT_0_PRIVATE_KEY, l1Provider);
l2Account0 = new Wallet(L2_ACCOUNT_0_PRIVATE_KEY, l2Provider);
l2AccountForLiveness = new Wallet(L2_ACCOUNT_1_PRIVATE_KEY, l2Provider);
});
const valueAndFee = etherToWei("1.1");
const calldata = withCalldata
? encodeFunctionCall(dummyContract.interface, "setPayload", [ethers.randomBytes(100)])
: "0x";
const destinationAddress = withCalldata
? await dummyContract.getAddress()
: "0x8D97689C9818892B700e27F316cc3E41e17fBeb9";
describe("Message Service L1 -> L2", () => {
it.each([
{
subTitle: "with calldata",
withCalldata: true,
},
{
subTitle: "without calldata",
withCalldata: false,
},
])(
"Should send a transaction $subTitle to L1 message service, be successfully claimed it on L2",
async ({ withCalldata }) => {
const valueAndFee = ethers.utils.parseEther("1.1");
const calldata = withCalldata
? encodeFunctionCall(dummyContract.interface, "setPayload", [ethers.utils.randomBytes(100)])
: "0x";
const destinationAddress = withCalldata
? dummyContract.address
: "0x8D97689C9818892B700e27F316cc3E41e17fBeb9";
const [maxPriorityFeePerGas, maxFeePerGas] = getAndIncreaseFeeData(await l1Provider.getFeeData());
const nonce = await l1Provider.getTransactionCount(l1Account.address, "pending");
const tx = await lineaRollup.connect(l1Account).sendMessage(destinationAddress, valueAndFee, calldata, {
value: valueAndFee,
nonce: nonce,
maxPriorityFeePerGas: maxPriorityFeePerGas,
maxFeePerGas: maxFeePerGas,
});
const receipt = await tx.wait();
console.log("Moving the L2 chain forward to trigger anchoring...");
const intervalId = await sendTransactionsToGenerateTrafficWithInterval(l2AccountForLiveness);
const [messageSentEvent] = receipt.logs.filter((log) => log.topics[0] === MESSAGE_SENT_EVENT_SIGNATURE);
const messageHash = messageSentEvent.topics[3];
console.log(`L1 message sent: messageHash=${messageHash} transaction=${JSON.stringify(tx)}`);
console.log("Waiting for MessageClaimed event on L2.");
const [messageClaimedEvent] = await waitForEvents(
l2MessageService,
l2MessageService.filters.MessageClaimed(messageHash),
);
clearInterval(intervalId);
console.log(`Message claimed on L2: ${JSON.stringify(messageClaimedEvent)}`);
expect(messageClaimedEvent).toBeDefined();
},
300_000,
);
});
describe("Message Service L2 -> L1", () => {
it.each([
{
subTitle: "with calldata",
withCalldata: true,
},
{
subTitle: "without calldata",
withCalldata: false,
},
])(
"Should send a transaction $subTitle to L2 message service, be successfully claimed it on L1",
async ({ withCalldata }) => {
const valueAndFee = ethers.utils.parseEther("0.001");
const calldata = withCalldata
? encodeFunctionCall(l1DummyContract.interface, "setPayload", [ethers.utils.randomBytes(100)])
: "0x";
const destinationAddress = withCalldata ? l1DummyContract.address : l1Account.address;
const nonce = await l2Provider.getTransactionCount(l2Account0.address, "pending");
const [maxPriorityFeePerGas, maxFeePerGas] = getAndIncreaseFeeData(await l2Provider.getFeeData());
const tx = await l2MessageService.connect(l2Account0).sendMessage(destinationAddress, valueAndFee, calldata, {
value: valueAndFee,
nonce: nonce,
maxPriorityFeePerGas,
maxFeePerGas,
});
const receipt = await tx.wait();
const [messageSentEvent] = receipt.logs.filter((log) => log.topics[0] === MESSAGE_SENT_EVENT_SIGNATURE);
const messageHash = messageSentEvent.topics[3];
console.log(`L2 message sent: messageHash=${messageHash} transaction=${JSON.stringify(tx)}`);
console.log("Moving the L2 chain forward to trigger conflation...");
const intervalId = await sendTransactionsToGenerateTrafficWithInterval(l2AccountForLiveness);
console.log("Waiting for MessageClaimed event on L1.");
const [messageClaimedEvent] = await waitForEvents(
lineaRollup,
lineaRollup.filters.MessageClaimed(messageHash)
);
clearInterval(intervalId);
console.log(`Message claimed on L1: ${JSON.stringify(messageClaimedEvent)}`);
expect(messageClaimedEvent).toBeDefined();
},
300_000,
);
});
const l1Provider = config.getL1Provider();
const { maxPriorityFeePerGas, maxFeePerGas } = await l1Provider.getFeeData();
const nonce = await l1Provider.getTransactionCount(l1Account.address, "pending");
const tx = await lineaRollup.sendMessage(destinationAddress, valueAndFee, calldata, {
value: valueAndFee,
nonce,
maxPriorityFeePerGas,
maxFeePerGas,
});
};
export default messagingTestSuite;
let receipt = await tx.wait();
while (!receipt) {
console.log("Waiting for transaction to be mined...");
receipt = await tx.wait();
}
return { tx, receipt };
}
async function sendL2ToL1Message({
l1Account,
l2Account,
withCalldata = false,
}: {
l1Account: Wallet;
l2Account: Wallet;
withCalldata: boolean;
}) {
const l2Provider = config.getL2Provider();
const dummyContract = config.getL1DummyContract(l1Account);
const l2MessageService = config.getL2MessageServiceContract(l2Account);
const valueAndFee = etherToWei("0.001");
const calldata = withCalldata
? encodeFunctionCall(dummyContract.interface, "setPayload", [ethers.randomBytes(100)])
: "0x";
const destinationAddress = withCalldata ? await dummyContract.getAddress() : l1Account.address;
const nonce = await l2Provider.getTransactionCount(l2Account.address, "pending");
const { maxPriorityFeePerGas, maxFeePerGas } = await l2Provider.getFeeData();
const tx = await l2MessageService.sendMessage(destinationAddress, valueAndFee, calldata, {
value: valueAndFee,
nonce,
maxPriorityFeePerGas,
maxFeePerGas,
});
let receipt = await tx.wait();
while (!receipt) {
console.log("Waiting for transaction to be mined...");
receipt = await tx.wait();
}
return { tx, receipt };
}
const l1AccountManager = config.getL1AccountManager();
const l2AccountManager = config.getL2AccountManager();
describe("Messaging test suite", () => {
it.concurrent(
"Should send a transaction with calldata to L1 message service, be successfully claimed it on L2",
async () => {
const [l1Account, l2Account] = await Promise.all([
l1AccountManager.generateAccount(),
l2AccountManager.generateAccount(),
]);
const { tx, receipt } = await sendL1ToL2Message({ l1Account, l2Account, withCalldata: true });
const [messageSentEvent] = receipt.logs.filter((log) => log.topics[0] === MESSAGE_SENT_EVENT_SIGNATURE);
const messageHash = messageSentEvent.topics[3];
console.log(`L1 message sent: messageHash=${messageHash} transaction=${JSON.stringify(tx)}`);
console.log("Waiting for MessageClaimed event on L2.");
const l2MessageService = config.getL2MessageServiceContract();
const [messageClaimedEvent] = await waitForEvents(
l2MessageService,
l2MessageService.filters.MessageClaimed(messageHash),
);
console.log(`Message claimed on L2: ${JSON.stringify(messageClaimedEvent)}`);
expect(messageClaimedEvent).toBeDefined();
},
100_000,
);
it.concurrent(
"Should send a transaction without calldata to L1 message service, be successfully claimed it on L2",
async () => {
const [l1Account, l2Account] = await Promise.all([
l1AccountManager.generateAccount(),
l2AccountManager.generateAccount(),
]);
const { tx, receipt } = await sendL1ToL2Message({ l1Account, l2Account, withCalldata: false });
const [messageSentEvent] = receipt.logs.filter((log) => log.topics[0] === MESSAGE_SENT_EVENT_SIGNATURE);
const messageHash = messageSentEvent.topics[3];
console.log(`L1 message sent: messageHash=${messageHash} transaction=${JSON.stringify(tx)}`);
console.log("Waiting for MessageClaimed event on L2.");
const l2MessageService = config.getL2MessageServiceContract();
const [messageClaimedEvent] = await waitForEvents(
l2MessageService,
l2MessageService.filters.MessageClaimed(messageHash),
);
console.log(`Message claimed on L2: ${JSON.stringify(messageClaimedEvent)}`);
expect(messageClaimedEvent).toBeDefined();
},
100_000,
);
it.concurrent(
"Should send a transaction with calldata to L2 message service, be successfully claimed it on L1",
async () => {
const [l1Account, l2Account] = await Promise.all([
l1AccountManager.generateAccount(),
l2AccountManager.generateAccount(),
]);
const lineaRollup = config.getLineaRollupContract();
const { tx, receipt } = await sendL2ToL1Message({ l1Account, l2Account, withCalldata: true });
const [messageSentEvent] = receipt.logs.filter((log) => log.topics[0] === MESSAGE_SENT_EVENT_SIGNATURE);
const messageHash = messageSentEvent.topics[3];
console.log(`L2 message sent: messageHash=${messageHash} transaction=${JSON.stringify(tx)}`);
console.log(`Waiting for L2MessagingBlockAnchored... with blockNumber=${messageSentEvent.blockNumber}`);
await waitForEvents(
lineaRollup,
lineaRollup.filters.L2MessagingBlockAnchored(messageSentEvent.blockNumber),
1_000,
);
console.log("Waiting for MessageClaimed event on L1.");
const [messageClaimedEvent] = await waitForEvents(
lineaRollup,
lineaRollup.filters.MessageClaimed(messageHash),
1_000,
);
console.log(`Message claimed on L1: ${JSON.stringify(messageClaimedEvent)}`);
expect(messageClaimedEvent).toBeDefined();
},
150_000,
);
it.concurrent(
"Should send a transaction without calldata to L2 message service, be successfully claimed it on L1",
async () => {
const [l1Account, l2Account] = await Promise.all([
l1AccountManager.generateAccount(),
l2AccountManager.generateAccount(),
]);
const lineaRollup = config.getLineaRollupContract();
const { tx, receipt } = await sendL2ToL1Message({ l1Account, l2Account, withCalldata: false });
const [messageSentEvent] = receipt.logs.filter((log) => log.topics[0] === MESSAGE_SENT_EVENT_SIGNATURE);
const messageHash = messageSentEvent.topics[3];
console.log(`L2 message sent: messageHash=${messageHash} transaction=${JSON.stringify(tx)}`);
console.log(`Waiting for L2MessagingBlockAnchored... with blockNumber=${messageSentEvent.blockNumber}`);
await waitForEvents(
lineaRollup,
lineaRollup.filters.L2MessagingBlockAnchored(messageSentEvent.blockNumber),
1_000,
);
console.log("Waiting for MessageClaimed event on L1.");
const [messageClaimedEvent] = await waitForEvents(
lineaRollup,
lineaRollup.filters.MessageClaimed(messageHash),
1_000,
);
console.log(`Message claimed on L1: ${JSON.stringify(messageClaimedEvent)}`);
expect(messageClaimedEvent).toBeDefined();
},
150_000,
);
});

View File

@@ -1,53 +1,73 @@
import {describe, expect, it} from "@jest/globals";
import { describe, expect, it } from "@jest/globals";
import {
getEvents,
execDockerCommand,
waitForEvents,
getMessageSentEventFromLogs,
sendMessage,
sendTransactionsToGenerateTrafficWithInterval,
} from "./utils/utils";
import {getAndIncreaseFeeData} from "./utils/helpers";
import {Wallet, ethers} from "ethers";
etherToWei,
wait,
} from "./common/utils";
import { config } from "./config/tests-config";
const coordinatorRestartTestSuite = (title: string) => {
describe(title, () => {
it("When the coordinator restarts it should resume blob submission and finalization", async () => {
const l2AccountForLiveness = new Wallet(L2_ACCOUNT_1_PRIVATE_KEY, l2Provider);
let testsWaitingForRestart = 0;
const TOTAL_TESTS_WAITING = 2;
let coordinatorHasRestarted = false;
console.log("Moving the L2 chain forward to trigger conflation...");
const intervalId = await sendTransactionsToGenerateTrafficWithInterval(l2AccountForLiveness);
async function waitForCoordinatorRestart() {
testsWaitingForRestart += 1;
while (testsWaitingForRestart < TOTAL_TESTS_WAITING) {
console.log("Both tests have reached the restart point. Restarting coordinator...");
await wait(1_000);
if (!coordinatorHasRestarted) {
coordinatorHasRestarted = true;
try {
await execDockerCommand("restart", "coordinator");
console.log("Coordinator restarted.");
return;
} catch (error) {
console.error("Failed to restart coordinator:", error);
throw error;
}
}
}
}
describe("Coordinator restart test suite", () => {
it.concurrent(
"When the coordinator restarts it should resume blob submission and finalization",
async () => {
if (process.env.TEST_ENV !== "local") {
console.log("Skipping test because it's not running on a local environment.");
return;
}
const lineaRollup = config.getLineaRollupContract();
const l1Provider = config.getL1Provider();
// await for a finalization to happen on L1
await Promise.all([
const [dataSubmittedEventsBeforeRestart, dataFinalizedEventsBeforeRestart] = await Promise.all([
waitForEvents(lineaRollup, lineaRollup.filters.DataSubmittedV2(), 0, "latest"),
waitForEvents(lineaRollup, lineaRollup.filters.DataFinalized(), 0, "latest"),
]);
await execDockerCommand("stop", "coordinator");
const currentBlockNumberBeforeRestart = await l1Provider.getBlockNumber();
const [dataSubmittedEventsBeforeRestart, dataFinalizedEventsBeforeRestart] = await Promise.all([
getEvents(lineaRollup, lineaRollup.filters.DataSubmittedV2(), 0, currentBlockNumberBeforeRestart),
getEvents(lineaRollup, lineaRollup.filters.DataFinalized(), 0, currentBlockNumberBeforeRestart),
]);
const lastDataSubmittedEventBeforeRestart = dataSubmittedEventsBeforeRestart.slice(-1)[0];
const lastDataFinalizedEventsBeforeRestart = dataFinalizedEventsBeforeRestart.slice(-1)[0];
// Just some sanity checks
// Check that the coordinator has submitted and finalized data before the restart
expect(lastDataSubmittedEventBeforeRestart.args.endBlock.toNumber()).toBeGreaterThan(0)
expect(lastDataFinalizedEventsBeforeRestart.args.lastBlockFinalized.toNumber()).toBeGreaterThan(0)
expect(lastDataSubmittedEventBeforeRestart.args.endBlock).toBeGreaterThan(0n);
expect(lastDataFinalizedEventsBeforeRestart.args.lastBlockFinalized).toBeGreaterThan(0n);
await waitForCoordinatorRestart();
await execDockerCommand("start", "coordinator");
const currentBlockNumberAfterRestart = await l1Provider.getBlockNumber();
console.log("Waiting for DataSubmittedV2 event after coordinator restart...");
const [dataSubmittedV2EventAfterRestart] = await waitForEvents(
lineaRollup,
lineaRollup.filters.DataSubmittedV2(null, lastDataSubmittedEventBeforeRestart.args.endBlock.add(1)),
lineaRollup.filters.DataSubmittedV2(),
1_000,
currentBlockNumberAfterRestart,
"latest",
async (events) =>
events.filter((event) => event.args.startBlock > lastDataSubmittedEventBeforeRestart.args.endBlock),
);
console.log(`New DataSubmittedV2 event found: event=${JSON.stringify(dataSubmittedV2EventAfterRestart)}`);
@@ -58,37 +78,48 @@ const coordinatorRestartTestSuite = (title: string) => {
1_000,
currentBlockNumberAfterRestart,
"latest",
async (events) => {
return events.filter((event) =>
event.args.lastBlockFinalized.gt(lastDataFinalizedEventsBeforeRestart.args.lastBlockFinalized),
);
},
async (events) =>
events.filter(
(event) => event.args.lastBlockFinalized > lastDataFinalizedEventsBeforeRestart.args.lastBlockFinalized,
),
);
console.log(`New DataFinalized event found: event=${JSON.stringify(dataFinalizedEventAfterRestart)}`);
clearInterval(intervalId)
expect(dataFinalizedEventAfterRestart.args.lastBlockFinalized.toNumber()).toBeGreaterThan(
lastDataFinalizedEventsBeforeRestart.args.lastBlockFinalized.toNumber(),
expect(dataFinalizedEventAfterRestart.args.lastBlockFinalized).toBeGreaterThan(
lastDataFinalizedEventsBeforeRestart.args.lastBlockFinalized,
);
}, 300_000);
},
150_000,
);
it("When the coordinator restarts it should resume anchoring", async () => {
const l1MessageSender = new Wallet(L1_ACCOUNT_0_PRIVATE_KEY, l1Provider);
const l2AccountForLiveness = new Wallet(L2_ACCOUNT_1_PRIVATE_KEY, l2Provider);
it.concurrent(
"When the coordinator restarts it should resume anchoring",
async () => {
if (process.env.TEST_ENV !== "local") {
console.log("Skipping test because it's not running on a local environment.");
return;
}
const l1Provider = config.getL1Provider();
const l1MessageSender = await config.getL1AccountManager().generateAccount();
const lineaRollup = config.getLineaRollupContract();
const l2MessageService = config.getL2MessageServiceContract();
// Send Messages L1 -> L2
const messageFee = ethers.utils.parseEther("0.0001");
const messageValue = ethers.utils.parseEther("0.0051");
const messageFee = etherToWei("0.0001");
const messageValue = etherToWei("0.0051");
const destinationAddress = "0x8D97689C9818892B700e27F316cc3E41e17fBeb9";
const l1MessagesPromises = [];
let l1MessageSenderNonce = await l1Provider.getTransactionCount(l1MessageSender.address);
const l1Fees = getAndIncreaseFeeData(await l1Provider.getFeeData());
const { maxPriorityFeePerGas, maxFeePerGas } = await l1Provider.getFeeData();
for (let i = 0; i < 5; i++) {
l1MessagesPromises.push(
sendMessage(
lineaRollup.connect(l1MessageSender),
l1MessageSender,
lineaRollup,
{
to: destinationAddress,
fee: messageFee,
@@ -97,8 +128,8 @@ const coordinatorRestartTestSuite = (title: string) => {
{
value: messageValue,
nonce: l1MessageSenderNonce,
maxPriorityFeePerGas: l1Fees[0],
maxFeePerGas: l1Fees[1],
maxPriorityFeePerGas,
maxFeePerGas,
},
),
);
@@ -112,15 +143,26 @@ const coordinatorRestartTestSuite = (title: string) => {
const lastNewL1MessageNumber = l1Messages.slice(-1)[0].messageNumber;
console.log(`Waiting L1->L2 anchoring messageNumber=${lastNewL1MessageNumber}`);
await waitForEvents(l2MessageService, l2MessageService.filters.RollingHashUpdated(lastNewL1MessageNumber), 1_000);
await waitForEvents(
l2MessageService,
l2MessageService.filters.RollingHashUpdated(),
1_000,
0,
"latest",
async (events) => {
return events.filter((event) => event.args.messageNumber >= lastNewL1MessageNumber);
},
);
// Restart Coordinator
await execDockerCommand("restart", "coordinator");
await waitForCoordinatorRestart();
const l1Fees = await l1Provider.getFeeData();
// Send more messages L1 -> L2
for (let i = 0; i < 5; i++) {
l1MessagesPromises.push(
sendMessage(
l1MessageSender,
lineaRollup.connect(l1MessageSender),
{
to: destinationAddress,
@@ -130,8 +172,8 @@ const coordinatorRestartTestSuite = (title: string) => {
{
value: messageValue,
nonce: l1MessageSenderNonce,
maxPriorityFeePerGas: l1Fees[0],
maxFeePerGas: l1Fees[1],
maxPriorityFeePerGas: l1Fees.maxPriorityFeePerGas,
maxFeePerGas: l1Fees.maxFeePerGas,
},
),
);
@@ -141,33 +183,31 @@ const coordinatorRestartTestSuite = (title: string) => {
const l1ReceiptsAfterRestart = await Promise.all(l1MessagesPromises);
const l1MessagesAfterRestart = getMessageSentEventFromLogs(lineaRollup, l1ReceiptsAfterRestart);
console.log("Moving the L2 chain forward to trigger anchoring...");
// Using 5 messages to give the coordinator time to restart
const intervalId = await sendTransactionsToGenerateTrafficWithInterval(l2AccountForLiveness);
// Wait for messages to be anchored on L2
const lastNewL1MessageNumberAfterRestart = l1MessagesAfterRestart.slice(-1)[0].messageNumber;
console.log(
`Waiting L1->L2 anchoring after coordinator restart messageNumber=${lastNewL1MessageNumberAfterRestart}`
`Waiting L1->L2 anchoring after coordinator restart messageNumber=${lastNewL1MessageNumberAfterRestart}`,
);
const [rollingHashUpdatedEventAfterRestart] = await waitForEvents(
l2MessageService,
l2MessageService.filters.RollingHashUpdated(lastNewL1MessageNumberAfterRestart),
1_000,
0,
"latest",
async (events) => {
return events.filter((event) => event.args.messageNumber >= lastNewL1MessageNumberAfterRestart);
},
);
const [lastNewMessageRollingHashAfterRestart, lastAnchoredL1MessageNumberAfterRestart] = await Promise.all([
lineaRollup.rollingHashes(lastNewL1MessageNumberAfterRestart),
lineaRollup.rollingHashes(rollingHashUpdatedEventAfterRestart.args.messageNumber),
l2MessageService.lastAnchoredL1MessageNumber(),
]);
clearInterval(intervalId)
expect(lastNewMessageRollingHashAfterRestart).toEqual(rollingHashUpdatedEventAfterRestart.args.rollingHash);
expect(lastAnchoredL1MessageNumberAfterRestart).toEqual(lastNewL1MessageNumberAfterRestart);
}, 300_000);
});
};
export default coordinatorRestartTestSuite;
expect(lastAnchoredL1MessageNumberAfterRestart).toEqual(rollingHashUpdatedEventAfterRestart.args.messageNumber);
},
150_000,
);
});

View File

@@ -1,146 +1,125 @@
import { beforeAll, describe, expect, it } from "@jest/globals";
import { Wallet, ethers } from "ethers";
import { OPERATOR_ROLE, ROLLING_HASH_UPDATED_EVENT_SIGNATURE, VERIFIER_SETTER_ROLE } from "./utils/constants";
import { getAndIncreaseFeeData } from "./utils/helpers";
import { MessageEvent } from "./utils/types";
import { getMessageSentEventFromLogs, sendMessage, sendTransactionsWithInterval, waitForEvents, wait, getBlockByNumberOrBlockTag } from "./utils/utils";
import { describe, expect, it } from "@jest/globals";
import { JsonRpcProvider } from "ethers";
import {
getMessageSentEventFromLogs,
sendMessage,
waitForEvents,
wait,
getBlockByNumberOrBlockTag,
etherToWei,
} from "./common/utils";
import { config } from "./config/tests-config";
import { LineaRollup } from "./typechain";
const submissionAndFinalizationTestSuite = (title: string) => {
describe(title, () => {
let securityCouncil: Wallet;
let l1Messages: MessageEvent[];
let l2Messages: MessageEvent[];
describe("Submission and finalization test suite", () => {
let l1Provider: JsonRpcProvider;
beforeAll(async () => {
// Deploy new contracts implementation and grant roles
securityCouncil = new Wallet(SECURITY_COUNCIL_PRIVATE_KEY, l1Provider);
const securityCouncilNonce = await securityCouncil.getTransactionCount();
beforeAll(() => {
l1Provider = config.getL1Provider();
});
const rolesTransactions = await Promise.all([
lineaRollup
.connect(securityCouncil)
.grantRole(OPERATOR_ROLE, OPERATOR_1_ADDRESS, { nonce: securityCouncilNonce }),
lineaRollup
.connect(securityCouncil)
.grantRole(VERIFIER_SETTER_ROLE, securityCouncil.address, { nonce: securityCouncilNonce + 1 }),
]);
const sendMessages = async () => {
const messageFee = etherToWei("0.0001");
const messageValue = etherToWei("0.0051");
const destinationAddress = "0x8D97689C9818892B700e27F316cc3E41e17fBeb9";
await Promise.all(rolesTransactions.map((tx) => tx.wait()));
});
const l1MessageSender = await config.getL1AccountManager().generateAccount();
const lineaRollup = config.getLineaRollupContract();
it("Send messages on L1 and L2", async () => {
const messageFee = ethers.utils.parseEther("0.0001");
const messageValue = ethers.utils.parseEther("0.0051");
const destinationAddress = "0x8D97689C9818892B700e27F316cc3E41e17fBeb9";
console.log("Sending messages on L1");
const l1MessageSender = new Wallet(L1_ACCOUNT_0_PRIVATE_KEY, l1Provider);
const l2MessageSender = new Wallet(L2_ACCOUNT_0_PRIVATE_KEY, l2Provider);
// Send L1 messages
const l1MessagesPromises = [];
// eslint-disable-next-line prefer-const
let [l1MessageSenderNonce, { maxPriorityFeePerGas, maxFeePerGas }] = await Promise.all([
l1Provider.getTransactionCount(l1MessageSender.address),
l1Provider.getFeeData(),
]);
console.log("Sending messages on L1 and L2...");
for (let i = 0; i < 5; i++) {
l1MessagesPromises.push(
sendMessage(
l1MessageSender,
lineaRollup,
{
to: destinationAddress,
fee: messageFee,
calldata: "0x",
},
{
value: messageValue,
nonce: l1MessageSenderNonce,
maxPriorityFeePerGas,
maxFeePerGas,
},
),
);
l1MessageSenderNonce++;
}
// Send L1 messages
const l1MessagesPromises = [];
let l1MessageSenderNonce = await l1Provider.getTransactionCount(l1MessageSender.address);
const l1Fees = getAndIncreaseFeeData(await l1Provider.getFeeData());
const l1Receipts = await Promise.all(l1MessagesPromises);
const l2MessagesPromises = [];
let l2MessageSenderNonce = await l2Provider.getTransactionCount(l2MessageSender.address);
const l2Fees = getAndIncreaseFeeData(await l2Provider.getFeeData());
console.log("Messages sent on L1.");
for (let i = 0; i < 5; i++) {
l1MessagesPromises.push(
sendMessage(
lineaRollup.connect(l1MessageSender),
{
to: destinationAddress,
fee: messageFee,
calldata: "0x",
},
{
value: messageValue,
nonce: l1MessageSenderNonce,
maxPriorityFeePerGas: l1Fees[0],
maxFeePerGas: l1Fees[1],
},
),
);
l1MessageSenderNonce++;
// Extract message events
const l1Messages = getMessageSentEventFromLogs(lineaRollup, l1Receipts);
l2MessagesPromises.push(
sendMessage(
l2MessageService.connect(l2MessageSender),
{
to: destinationAddress,
fee: messageFee,
calldata: "0x",
},
{
value: messageValue,
nonce: l2MessageSenderNonce,
maxPriorityFeePerGas: l2Fees[0],
maxFeePerGas: l2Fees[1],
},
),
);
l2MessageSenderNonce++;
return { l1Messages, l1Receipts };
};
async function getFinalizedL2BlockNumber(lineaRollup: LineaRollup) {
let blockNumber = null;
while (!blockNumber) {
try {
blockNumber = await lineaRollup.currentL2BlockNumber({ blockTag: "finalized" });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
console.log("No finalized block yet, retrying in 5 seconds...");
await new Promise((resolve) => setTimeout(resolve, 5_000));
}
}
const l1Receipts = await Promise.all(l1MessagesPromises);
const l2Receipts = await Promise.all(l2MessagesPromises);
return blockNumber;
}
console.log("Messages sent on L1 and L2.");
it.concurrent(
"Check L2 anchoring",
async () => {
const lineaRollup = config.getLineaRollupContract();
const l2MessageService = config.getL2MessageServiceContract();
// Check that L1 messages emit RollingHashUpdated events
expect(l1Receipts.length).toBeGreaterThan(0);
const { l1Messages } = await sendMessages();
const newL1MessagesRollingHashUpdatedLogs = l1Receipts
.flatMap((receipt) => receipt.logs)
.filter((log) => log.topics[0] === ROLLING_HASH_UPDATED_EVENT_SIGNATURE);
expect(newL1MessagesRollingHashUpdatedLogs).toHaveLength(l1Receipts.length);
// Check that there are L2 messages
expect(l2Receipts.length).toBeGreaterThan(0);
l1Messages = getMessageSentEventFromLogs(lineaRollup, l1Receipts);
l2Messages = getMessageSentEventFromLogs(l2MessageService, l2Receipts);
}, 300_000);
it("Check L2 anchoring", async () => {
// Wait for the last L1->L2 message to be anchored on L2
const lastNewL1MessageNumber = l1Messages.slice(-1)[0].messageNumber;
console.log("Waiting for the anchoring using rolling hash...");
const [rollingHashUpdatedEvent] = await waitForEvents(
l2MessageService,
l2MessageService.filters.RollingHashUpdated(lastNewL1MessageNumber),
l2MessageService.filters.RollingHashUpdated(),
1_000,
0,
"latest",
async (events) => events.filter((event) => event.args.messageNumber >= lastNewL1MessageNumber),
);
const [lastNewMessageRollingHash, lastAnchoredL1MessageNumber] = await Promise.all([
lineaRollup.rollingHashes(lastNewL1MessageNumber),
lineaRollup.rollingHashes(rollingHashUpdatedEvent.args.messageNumber),
l2MessageService.lastAnchoredL1MessageNumber(),
]);
expect(lastNewMessageRollingHash).toEqual(rollingHashUpdatedEvent.args.rollingHash);
expect(lastAnchoredL1MessageNumber).toEqual(lastNewL1MessageNumber);
expect(lastAnchoredL1MessageNumber).toEqual(rollingHashUpdatedEvent.args.messageNumber);
console.log("New anchoring using rolling hash done.");
}, 300_000);
},
150_000,
);
it("Check L1 data submission and finalization", async () => {
// Send transactions on L2 in the background to make the L2 chain moving forward
const l2MessageSender = new Wallet(L2_ACCOUNT_0_PRIVATE_KEY, l2Provider);
const [maxPriorityFeePerGas, maxFeePerGas] = getAndIncreaseFeeData(await l2Provider.getFeeData());
const sendTransactionsPromise = sendTransactionsWithInterval(
l2MessageSender,
{
to: "0x8D97689C9818892B700e27F316cc3E41e17fBeb9",
value: ethers.utils.parseEther("0.0001"),
maxPriorityFeePerGas,
maxFeePerGas,
},
5_000,
);
it.concurrent(
"Check L1 data submission and finalization",
async () => {
const lineaRollup = config.getLineaRollupContract();
const [currentL2BlockNumber, startingRootHash] = await Promise.all([
lineaRollup.currentL2BlockNumber(),
@@ -149,13 +128,17 @@ const submissionAndFinalizationTestSuite = (title: string) => {
console.log("Waiting for data submission used to finalize with proof...");
// Waiting for data submission starting from migration block number
await waitForEvents(lineaRollup, lineaRollup.filters.DataSubmittedV2(null, currentL2BlockNumber.add(1)), 1_000);
await waitForEvents(
lineaRollup,
lineaRollup.filters.DataSubmittedV2(undefined, currentL2BlockNumber + 1n),
1_000,
);
console.log("Waiting for the first DataFinalized event with proof...");
// Waiting for first DataFinalized event with proof
const [dataFinalizedEvent] = await waitForEvents(
lineaRollup,
lineaRollup.filters.DataFinalized(null, startingRootHash),
lineaRollup.filters.DataFinalized(undefined, startingRootHash),
1_000,
);
@@ -164,69 +147,47 @@ const submissionAndFinalizationTestSuite = (title: string) => {
lineaRollup.stateRootHashes(dataFinalizedEvent.args.lastBlockFinalized),
]);
expect(lastBlockFinalized.toBigInt()).toBeGreaterThanOrEqual(dataFinalizedEvent.args.lastBlockFinalized.toBigInt());
expect(lastBlockFinalized).toBeGreaterThanOrEqual(dataFinalizedEvent.args.lastBlockFinalized);
expect(newStateRootHash).toEqual(dataFinalizedEvent.args.finalRootHash);
expect(dataFinalizedEvent.args.withProof).toBeTruthy();
console.log("Finalization with proof done.");
},
150_000,
);
clearInterval(sendTransactionsPromise);
}, 300_000);
it( "Check L2 safe/finalized tag update on sequencer", async () => {
if (SEQUENCER_ENDPOINT == null) {
console.log("Skipped the \"Check L2 safe/finalized tag update on sequencer\" test");
it.concurrent(
"Check L2 safe/finalized tag update on sequencer",
async () => {
const lineaRollup = config.getLineaRollupContract();
const sequencerEndpoint = config.getSequencerEndpoint();
if (!sequencerEndpoint) {
console.log('Skipped the "Check L2 safe/finalized tag update on sequencer" test');
return;
}
const lastFinalizedL2BlockNumberOnL1 = (await lineaRollup.currentL2BlockNumber({ blockTag: "finalized" })).toNumber();
console.log(`lastFinalizedL2BlockNumberOnL1=${lastFinalizedL2BlockNumberOnL1}`)
let safeL2BlockNumber = -1, finalizedL2BlockNumber = -1
while (safeL2BlockNumber < lastFinalizedL2BlockNumberOnL1 || finalizedL2BlockNumber < lastFinalizedL2BlockNumberOnL1) {
safeL2BlockNumber = (await getBlockByNumberOrBlockTag(SEQUENCER_ENDPOINT, "safe")).number;
finalizedL2BlockNumber = (await getBlockByNumberOrBlockTag(SEQUENCER_ENDPOINT, "finalized")).number;
const lastFinalizedL2BlockNumberOnL1 = (await getFinalizedL2BlockNumber(lineaRollup)).toString();
console.log(`lastFinalizedL2BlockNumberOnL1=${lastFinalizedL2BlockNumberOnL1}`);
let safeL2BlockNumber = -1,
finalizedL2BlockNumber = -1;
while (
safeL2BlockNumber < parseInt(lastFinalizedL2BlockNumberOnL1) ||
finalizedL2BlockNumber < parseInt(lastFinalizedL2BlockNumberOnL1)
) {
safeL2BlockNumber = (await getBlockByNumberOrBlockTag(sequencerEndpoint, "safe"))?.number || safeL2BlockNumber;
finalizedL2BlockNumber =
(await getBlockByNumberOrBlockTag(sequencerEndpoint, "finalized"))?.number || finalizedL2BlockNumber;
await wait(1_000);
}
console.log(`safeL2BlockNumber=${safeL2BlockNumber} finalizedL2BlockNumber=${finalizedL2BlockNumber}`);
expect(safeL2BlockNumber).toBeGreaterThanOrEqual(lastFinalizedL2BlockNumberOnL1);
expect(finalizedL2BlockNumber).toBeGreaterThanOrEqual(lastFinalizedL2BlockNumberOnL1);
expect(safeL2BlockNumber).toBeGreaterThanOrEqual(parseInt(lastFinalizedL2BlockNumberOnL1));
expect(finalizedL2BlockNumber).toBeGreaterThanOrEqual(parseInt(lastFinalizedL2BlockNumberOnL1));
console.log("L2 safe/finalized tag update on sequencer done.");
}, 300_000)
it("Check L1 claiming", async () => {
// Send transactions on L2 in the background to make the L2 chain moving forward
const l2MessageSender = new Wallet(L2_ACCOUNT_0_PRIVATE_KEY, l2Provider);
const [maxPriorityFeePerGas, maxFeePerGas] = getAndIncreaseFeeData(await l2Provider.getFeeData());
const sendTransactionsPromise = sendTransactionsWithInterval(
l2MessageSender,
{
to: "0x8D97689C9818892B700e27F316cc3E41e17fBeb9",
value: ethers.utils.parseEther("0.0001"),
maxPriorityFeePerGas,
maxFeePerGas,
},
1_000,
);
const { messageHash, messageNumber, blockNumber } = l2Messages[0];
console.log(`Waiting for L2MessagingBlockAnchored... with blockNumber=${blockNumber}`);
await waitForEvents(lineaRollup, lineaRollup.filters.L2MessagingBlockAnchored(blockNumber), 1_000);
console.log("L2MessagingBlockAnchored event found.");
await waitForEvents(lineaRollup, lineaRollup.filters.MessageClaimed(messageHash), 1_000);
expect(await lineaRollup.isMessageClaimed(messageNumber)).toBeTruthy();
console.log("L1 claiming done.");
clearInterval(sendTransactionsPromise);
}, 400_000);
});
};
export default submissionAndFinalizationTestSuite;
},
150_000,
);
});

View File

@@ -1,44 +0,0 @@
import { ethers } from "ethers";
import * as dotenv from "dotenv";
dotenv.config();
export const TRANSACTION_CALLDATA_LIMIT = 30000;
export const L1_RPC_URL = "https://l1-rpc.dev.zkevm.consensys.net/";
export const L2_RPC_URL = "https://archive.dev.zkevm.consensys.net/";
export const CHAIN_ID = 59139;
export function getL1Provider() {
return new ethers.providers.JsonRpcProvider(L1_RPC_URL);
}
export function getL2Provider() {
return new ethers.providers.JsonRpcProvider(L2_RPC_URL);
}
export const DEPLOYER_ACCOUNT_PRIVATE_KEY = process.env.LOCAL_DEPLOYER_ACCOUNT_PRIVATE_KEY ?? "";
export const INITIAL_WITHDRAW_LIMIT = ethers.utils.parseEther("5");
export const ACCOUNT_0 = "0xc8c92fe825d8930b9357c006e0af160dfa727a62";
export const ACCOUNT_0_PRIVATE_KEY = process.env.LOCAL_ACCOUNT_0_PRIVATE_KEY ?? "";
export const ACCOUNT_1 = "";
export const OPERATOR_0 = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8";
export const OPERATOR_0_PRIVATE_KEY = process.env.LOCAL_OPERATOR_0_PRIVATE_KEY ?? "";
export const LINEA_ROLLUP_CONTRACT_ADDRESS = "0xFE63fc3C8898F83B1c5F199133f89bDBA88B1C37";
export const LINEA_ROLLUP_INITIAL_STATE_ROOT_HASH =
"0x0000000000000000000000000000000000000000000000000000000000000000";
export const LINEA_ROLLUP_INITIAL_L2_BLOCK_NR = "123456";
export const LINEA_ROLLUP_SECURITY_COUNCIL = OPERATOR_0;
export const LINEA_ROLLUP_OPERATORS = [OPERATOR_0];
export const LINEA_ROLLUP_RATE_LIMIT_PERIOD = "86400"; //24Hours in seconds
export const LINEA_ROLLUP_RATE_LIMIT_AMOUNT = "1000000000000000000000"; //1000ETH
export const TRANSACTION_DECODER_ADDRESS = "";
export const PLONK_VERIFIER_ADDRESS = "";
export const MESSAGE_SERVICE_ADDRESS = "0xa2d2C55e9B7054d9C4EA075Df35935BeA1693e27";
export const DUMMY_CONTRACT_ADDRESS = "";
export const SHOMEI_ENDPOINT = null;
export const SHOMEI_FRONTEND_ENDPOINT = null;

View File

@@ -1,60 +0,0 @@
import { ethers } from "ethers";
import * as dotenv from "dotenv";
dotenv.config();
export const TRANSACTION_CALLDATA_LIMIT = 30000;
export const L1_RPC_URL = "http://localhost:8445";
export const L2_RPC_URL = "http://localhost:8845";
export const CHAIN_ID = 1337;
export function getL1Provider() {
return new ethers.providers.JsonRpcProvider(L1_RPC_URL);
}
export function getL2Provider() {
return new ethers.providers.JsonRpcProvider(L2_RPC_URL);
}
// WARNING: FOR LOCAL DEV ONLY - DO NOT REUSE THESE KEYS ELSEWHERE
export const L1_DEPLOYER_ACCOUNT_PRIVATE_KEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
// WARNING: FOR LOCAL DEV ONLY - DO NOT REUSE THESE KEYS ELSEWHERE
export const L2_DEPLOYER_ACCOUNT_PRIVATE_KEY = "0x1dd171cec7e2995408b5513004e8207fe88d6820aeff0d82463b3e41df251aae";
export const INITIAL_WITHDRAW_LIMIT = ethers.utils.parseEther("5");
export const L1_ACCOUNT_0 = "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65";
// WARNING: FOR LOCAL DEV ONLY - DO NOT REUSE THESE KEYS ELSEWHERE
export const L1_ACCOUNT_0_PRIVATE_KEY = "0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a";
export const L2_ACCOUNT_0 = "0xfe3b557e8fb62b89f4916b721be55ceb828dbd73";
// WARNING: FOR LOCAL DEV ONLY - DO NOT REUSE THESE KEYS ELSEWHERE
export const L2_ACCOUNT_0_PRIVATE_KEY = "0x8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63";
export const L2_ACCOUNT_1_PRIVATE_KEY = "0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3";
export const OPERATOR_0 = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8";
// WARNING: FOR LOCAL DEV ONLY - DO NOT REUSE THESE KEYS ELSEWHERE
export const OPERATOR_0_PRIVATE_KEY = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d";
export const OPERATOR_1 = "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC";
// WARNING: FOR LOCAL DEV ONLY - DO NOT REUSE THESE KEYS ELSEWHERE
export const OPERATOR_1_PRIVATE_KEY = "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a";
// WARNING: FOR LOCAL DEV ONLY - DO NOT REUSE THESE KEYS ELSEWHERE
export const SECURITY_COUNCIL_PRIVATE_KEY = "0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6";
export const LINEA_ROLLUP_INITIAL_STATE_ROOT_HASH =
"0x0000000000000000000000000000000000000000000000000000000000000000";
export const LINEA_ROLLUP_INITIAL_L2_BLOCK_NR = "123456";
export const LINEA_ROLLUP_SECURITY_COUNCIL = OPERATOR_0;
export const LINEA_ROLLUP_OPERATORS = [OPERATOR_0];
export const LINEA_ROLLUP_RATE_LIMIT_PERIOD = "86400"; //24Hours in seconds
export const LINEA_ROLLUP_RATE_LIMIT_AMOUNT = "1000000000000000000000"; //1000ETH
export const LINEA_ROLLUP_CONTRACT_ADDRESS = "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9";
export const MESSAGE_SERVICE_ADDRESS = "0xe537D669CA013d86EBeF1D64e40fC74CADC91987";
export const SHOMEI_ENDPOINT = new URL("http://localhost:8998");
export const SHOMEI_FRONTEND_ENDPOINT = new URL("http://localhost:8889");
export const SEQUENCER_ENDPOINT = new URL("http://localhost:8545")
export const CONTRACT_GAS_OPTIMIZATION_SWITCH_BLOCK = 12;

View File

@@ -1,43 +0,0 @@
import { ethers } from "ethers";
import * as dotenv from "dotenv";
dotenv.config();
export const TRANSACTION_CALLDATA_LIMIT = 59000;
export const L1_RPC_URL = "https://goerli.infura.io/v3/" + process.env.UAT_L1_RPC_KEY;
export const L2_RPC_URL = "https://linea-goerli.infura.io/v3/" + process.env.UAT_L2_RPC_KEY;
export const CHAIN_ID = 59140;
export function getL1Provider() {
return new ethers.providers.JsonRpcProvider(L1_RPC_URL);
}
export function getL2Provider() {
return new ethers.providers.JsonRpcProvider(L2_RPC_URL);
}
export const DEPLOYER_ACCOUNT_PRIVATE_KEY = process.env.UAT_DEPLOYER_ACCOUNT_PRIVATE_KEY ?? "";
export const INITIAL_WITHDRAW_LIMIT = ethers.utils.parseEther("5");
export const ACCOUNT_0 = "0x174634fbF3d3e243543c6F22102837A113DF9005";
export const ACCOUNT_0_PRIVATE_KEY = process.env.UAT_ACCOUNT_0_PRIVATE_KEY ?? "";
export const ACCOUNT_1 = "";
export const OPERATOR_0 = "0xA2689249FAeAf6D84b6087A2970a20432b86A53e";
export const LINEA_ROLLUP_INITIAL_STATE_ROOT_HASH =
"0x0000000000000000000000000000000000000000000000000000000000000000";
export const LINEA_ROLLUP_INITIAL_L2_BLOCK_NR = "123456";
export const LINEA_ROLLUP_SECURITY_COUNCIL = OPERATOR_0;
export const LINEA_ROLLUP_OPERATORS = [OPERATOR_0];
export const LINEA_ROLLUP_RATE_LIMIT_PERIOD = "86400"; //24Hours in seconds
export const LINEA_ROLLUP_RATE_LIMIT_AMOUNT = "1000000000000000000000"; //1000ETH
export const LINEA_ROLLUP_CONTRACT_ADDRESS = "0x70BaD09280FD342D02fe64119779BC1f0791BAC2";
export const MESSAGE_SERVICE_ADDRESS = "0xC499a572640B64eA1C8c194c43Bc3E19940719dC";
export const DUMMY_CONTRACT_ADDRESS = "0x91614d09f5A8c87E28ab79D5eC48164DA0988319";
export const L1_DUMMY_CONTRACT_ADDRESS = "0x75b33806dCdb0fC7BB06fd1121a3358a88EdC5E9";
export const SHOMEI_ENDPOINT = null;
export const SHOMEI_FRONTEND_ENDPOINT = null;

View File

@@ -1,9 +0,0 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { BigNumber, ethers } from "ethers";
export function getAndIncreaseFeeData(feeData: ethers.providers.FeeData): [BigNumber, BigNumber, BigNumber] {
const maxPriorityFeePerGas = BigNumber.from((parseFloat(feeData.maxPriorityFeePerGas!.toString()) * 1.1).toFixed(0));
const maxFeePerGas = BigNumber.from((parseFloat(feeData.maxFeePerGas!.toString()) * 1.1).toFixed(0));
const gasPrice = BigNumber.from((parseFloat(feeData.gasPrice!.toString()) * 1.1).toFixed(0));
return [maxPriorityFeePerGas, maxFeePerGas, gasPrice];
}

View File

@@ -1,288 +0,0 @@
import { BlockTag } from "@ethersproject/providers";
import * as fs from "fs";
import assert from "assert";
import {Contract, ContractReceipt, PayableOverrides, Wallet, ethers} from "ethers";
import path from "path";
import { exec } from "child_process";
import { L2MessageService, LineaRollup } from "../typechain";
import { TypedEvent, TypedEventFilter } from "../typechain/common";
import { MessageEvent, SendMessageArgs } from "./types";
import { getAndIncreaseFeeData } from "./helpers";
export const wait = (timeout: number) => new Promise((resolve) => setTimeout(resolve, timeout));
export function increaseDate(currentDate: Date, seconds: number): Date {
const newDate = new Date(currentDate.getTime());
newDate.setSeconds(newDate.getSeconds() + seconds);
return newDate;
}
export const subtractSecondsToDate = (date: Date, seconds: number): Date => {
const dateCopy = new Date(date);
dateCopy.setSeconds(date.getSeconds() - seconds);
return dateCopy;
};
export function getWallet(privateKey: string, provider: ethers.providers.JsonRpcProvider) {
return new ethers.Wallet(privateKey, provider);
}
export function encodeFunctionCall(contractInterface: ethers.utils.Interface, functionName: string, args: unknown[]) {
return contractInterface.encodeFunctionData(functionName, args);
}
export const generateKeccak256 = (types: string[], values: unknown[], packed?: boolean) =>
ethers.utils.keccak256(encodeData(types, values, packed));
export const encodeData = (types: string[], values: unknown[], packed?: boolean) => {
if (packed) {
return ethers.utils.solidityPack(types, values);
}
return ethers.utils.defaultAbiCoder.encode(types, values);
};
export class RollupGetZkEVMBlockNumberClient {
private endpoint: URL;
private request = {
method: "post",
body: JSON.stringify({
jsonrpc: "2.0",
method: "rollup_getZkEVMBlockNumber",
params: [],
id: 1,
}),
};
public constructor(endpoint: URL) {
this.endpoint = endpoint;
}
public async rollupGetZkEVMBlockNumber(): Promise<number> {
const response = await fetch(this.endpoint, this.request);
const data = await response.json();
assert("result" in data);
return Number.parseInt(data.result);
}
}
export async function getBlockByNumberOrBlockTag(
rpcUrl: URL,
blockTag: BlockTag
): Promise<ethers.providers.Block> {
const provider = new ethers.providers.JsonRpcProvider(rpcUrl.href);
return provider.getBlock(blockTag)
}
export async function getEvents<TContract extends LineaRollup | L2MessageService, TEvent extends TypedEvent>(
contract: TContract,
eventFilter: TypedEventFilter<TEvent>,
fromBlock?: BlockTag,
toBlock?: BlockTag,
criteria?: (events: TEvent[]) => Promise<TEvent[]>,
): Promise<Array<TEvent>> {
const events = await contract.queryFilter(eventFilter, fromBlock, toBlock);
if (criteria) {
return await criteria(events);
}
return events;
}
export async function waitForEvents<TContract extends LineaRollup | L2MessageService, TEvent extends TypedEvent>(
contract: TContract,
eventFilter: TypedEventFilter<TEvent>,
pollingInterval: number = 500,
fromBlock?: BlockTag,
toBlock?: BlockTag,
criteria?: (events: TEvent[]) => Promise<TEvent[]>,
): Promise<TEvent[]> {
let events = await getEvents(contract, eventFilter, fromBlock, toBlock, criteria);
while (events.length === 0) {
events = await getEvents(contract, eventFilter, fromBlock, toBlock, criteria);
await wait(pollingInterval);
}
return events;
}
export function getFiles(directory: string, fileRegex: RegExp[]): string[] {
const files = fs.readdirSync(directory, { withFileTypes: true });
const filteredFiles = files.filter((file) => fileRegex.map((regex) => regex.test(file.name)).includes(true));
return filteredFiles.map((file) => fs.readFileSync(path.join(directory, file.name), "utf-8"));
}
export async function waitForFile(
directory: string,
regex: RegExp,
pollingInterval: number,
timeout: number,
criteria?: (fileName: string) => boolean,
): Promise<string> {
return new Promise((resolve, reject) => {
const interval = setInterval(() => {
fs.readdir(directory, (err, files) => {
if (err) {
clearInterval(interval);
clearTimeout(timeoutId);
reject(new Error(`Error reading directory: ${err}`));
return;
}
for (const file of files) {
if (regex.test(file) && (!criteria || criteria(file))) {
clearInterval(interval);
clearTimeout(timeoutId);
resolve(fs.readFileSync(path.join(directory, file), "utf-8"));
return;
}
}
});
}, pollingInterval);
const timeoutId = setTimeout(() => {
clearInterval(interval);
reject(new Error("File check timed out"));
}, timeout);
});
}
export function sendTransactionsWithInterval(
signer: Wallet,
transactionRequest: ethers.providers.TransactionRequest,
pollingInterval: number,
) {
return setInterval(async function () {
const tx = await signer.sendTransaction(transactionRequest);
await tx.wait();
}, pollingInterval);
}
export async function sendXTransactions(
signer: Wallet,
transactionRequest: ethers.providers.TransactionRequest,
numberOfTransactions: number,
) {
for (let i = 0; i < numberOfTransactions; i++) {
const tx = await signer.sendTransaction(transactionRequest);
await tx.wait();
}
}
export async function sendTransactionsToGenerateTrafficWithInterval(
signer: Wallet,
pollingInterval: number = 1000,
) {
const [maxPriorityFeePerGas, maxFeePerGas] = getAndIncreaseFeeData(await signer.provider.getFeeData());
const transactionRequest = {
to: signer.address,
value: ethers.utils.parseEther("0.000001"),
maxPriorityFeePerGas: maxPriorityFeePerGas,
maxFeePerGas: maxFeePerGas,
}
return setInterval(async function () {
const tx = await signer.sendTransaction(transactionRequest);
await tx.wait();
}, pollingInterval);
}
export function getMessageSentEventFromLogs<T extends Contract>(
contract: T,
receipts: ContractReceipt[],
): MessageEvent[] {
return receipts
.flatMap((receipt) => receipt.logs)
.filter((log) => log.topics[0] === "0xe856c2b8bd4eb0027ce32eeaf595c21b0b6b4644b326e5b7bd80a1cf8db72e6c")
.map((log) => {
const { args } = contract.interface.parseLog(log);
return {
from: args._from,
to: args._to,
fee: args._fee,
value: args._value,
messageNumber: args._nonce,
calldata: args._calldata,
messageHash: args._messageHash,
blockNumber: log.blockNumber,
};
});
}
export async function waitForMessageAnchoring(
contract: L2MessageService,
messageHash: string,
pollingInterval: number,
) {
let messageStatus = await contract.inboxL1L2MessageStatus(messageHash);
while (messageStatus.toNumber() === 0) {
messageStatus = await contract.inboxL1L2MessageStatus(messageHash);
await wait(pollingInterval);
}
}
export const sendMessage = async <T extends Contract>(
contract: T,
args: SendMessageArgs,
overrides?: PayableOverrides,
): Promise<ContractReceipt> => {
const tx = await contract.sendMessage(args.to, args.fee, args.calldata, overrides);
return await tx.wait();
};
export const sendMessagesForNSeconds = async <T extends Contract>(
provider: ethers.providers.JsonRpcProvider,
signer: Wallet,
contract: T,
duration: number,
args: SendMessageArgs,
overrides?: PayableOverrides,
): Promise<ContractReceipt[]> => {
let nonce = await provider.getTransactionCount(signer.address);
const currentDate = new Date();
const endDate = increaseDate(currentDate, duration);
const sendMessagePromises: Promise<ContractReceipt | null>[] = [];
let currentTime = new Date().getTime();
const [maxPriorityFeePerGas, maxFeePerGas] = getAndIncreaseFeeData(await provider.getFeeData());
while (currentTime < endDate.getTime()) {
sendMessagePromises.push(
sendMessage(contract.connect(signer), args, {
...overrides,
maxPriorityFeePerGas,
maxFeePerGas,
nonce,
}).catch(() => {
return null;
}),
);
nonce++;
if (sendMessagePromises.length % 10 === 0) {
await wait(10_000);
}
currentTime = new Date().getTime();
}
const result = (await Promise.all(sendMessagePromises)).filter((receipt) => receipt !== null) as ContractReceipt[];
return result;
};
export async function execDockerCommand(command: string, containerName: string): Promise<string> {
const dockerCommand = `docker ${command} ${containerName}`;
console.log(`Executing: ${dockerCommand}...`);
return new Promise((resolve, reject) => {
exec(dockerCommand, (error, stdout, stderr) => {
if (error) {
console.error(`Error executing (${dockerCommand}): ${stderr}`);
reject(error);
}
console.log(`Execution success (${dockerCommand}): ${stdout}`);
resolve(stdout);
});
});
}

View File

@@ -12,7 +12,6 @@
"isolatedModules": true,
"noFallthroughCasesInSwitch": true,
"strictPropertyInitialization": false,
"typeRoots": ["./env-setup/custom.d.ts"],
},
"references": [
{

3110
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff