mirror of
https://github.com/vacp2p/linea-monorepo.git
synced 2026-01-09 04:08:01 -05:00
* 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:
@@ -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
|
||||
|
||||
33
e2e/env-setup/custom.d.ts
vendored
33
e2e/env-setup/custom.d.ts
vendored
@@ -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;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { globalTestEnvironment } from "./global-test-env";
|
||||
|
||||
export default async (): Promise<void> => {
|
||||
await globalTestEnvironment.startEnv();
|
||||
global.testingEnv = globalTestEnvironment.testingEnv;
|
||||
};
|
||||
@@ -1,5 +0,0 @@
|
||||
import { globalTestEnvironment } from "./global-test-env";
|
||||
|
||||
export default async (): Promise<void> => {
|
||||
await globalTestEnvironment.stopEnv();
|
||||
};
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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
263
e2e/src/common/utils.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
39
e2e/src/config/jest/global-setup.ts
Normal file
39
e2e/src/config/jest/global-setup.ts
Normal 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;
|
||||
};
|
||||
3
e2e/src/config/jest/global-teardown.ts
Normal file
3
e2e/src/config/jest/global-teardown.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default async (): Promise<void> => {
|
||||
global.stopL2TrafficGeneration();
|
||||
};
|
||||
124
e2e/src/config/tests-config/accounts/account-manager.ts
Normal file
124
e2e/src/config/tests-config/accounts/account-manager.ts
Normal 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 };
|
||||
9
e2e/src/config/tests-config/accounts/account.ts
Normal file
9
e2e/src/config/tests-config/accounts/account.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
49
e2e/src/config/tests-config/environments/dev.ts
Normal file
49
e2e/src/config/tests-config/environments/dev.ts
Normal 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;
|
||||
44
e2e/src/config/tests-config/environments/local.ts
Normal file
44
e2e/src/config/tests-config/environments/local.ts
Normal 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;
|
||||
48
e2e/src/config/tests-config/environments/sepolia.ts
Normal file
48
e2e/src/config/tests-config/environments/sepolia.ts
Normal 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;
|
||||
26
e2e/src/config/tests-config/index.ts
Normal file
26
e2e/src/config/tests-config/index.ts
Normal 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 };
|
||||
97
e2e/src/config/tests-config/setup.ts
Normal file
97
e2e/src/config/tests-config/setup.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
24
e2e/src/config/tests-config/types.ts
Normal file
24
e2e/src/config/tests-config/types.ts
Normal 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;
|
||||
};
|
||||
@@ -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");
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -12,7 +12,6 @@
|
||||
"isolatedModules": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"strictPropertyInitialization": false,
|
||||
"typeRoots": ["./env-setup/custom.d.ts"],
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
|
||||
3110
pnpm-lock.yaml
generated
3110
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user