mirror of
https://github.com/vacp2p/linea-monorepo.git
synced 2026-01-09 04:08:01 -05:00
[Refactor] LineaRollup tests (#736)
* utils -> helpers in test * start to refactor helpers * did some deploy functions * running tests with all helper functions refactored out * slight cleanup * did BlobSubmission.ts * did Finalization.ts * more comments to before * clean up dependency order
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { ethers, upgrades } from "hardhat";
|
||||
import { getPermitData } from "../../../test/hardhat/bridging/token/utils/permitHelper";
|
||||
import { getPermitData } from "../../../test/hardhat/bridging/token/helpers/permitHelper";
|
||||
import { BridgedToken, MockTokenBridge } from "../../../typechain-types";
|
||||
import { deployBridgedTokenBeacon } from "../test/deployBridgedTokenBeacon";
|
||||
import { deployTokens } from "../test/deployTokens";
|
||||
|
||||
@@ -4,7 +4,7 @@ The following document serves to be a guide on the best practices for the Linea
|
||||
|
||||
**Note:** some areas might not conform to the practices - feel free to open a PR to have them comply. Work is constantly being done to improve.
|
||||
|
||||
The current supported framework is Hardhat and TypeScript testing. Future improvements will include Foundry.
|
||||
The current supported framework is Hardhat and TypeScript testing.
|
||||
|
||||
## Folder Structure and Naming Conventions
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ethers, upgrades } from "hardhat";
|
||||
import { deployTokenBridgeWithMockMessaging } from "../../../../scripts/tokenBridge/test/deployTokenBridges";
|
||||
import { deployTokens } from "../../../../scripts/tokenBridge/test/deployTokens";
|
||||
import { BridgedToken, TestTokenBridge } from "../../../../typechain-types";
|
||||
import { getPermitData } from "./utils/permitHelper";
|
||||
import { getPermitData } from "./helpers/permitHelper";
|
||||
import { Contract } from "ethers";
|
||||
import {
|
||||
ADDRESS_ZERO,
|
||||
|
||||
1
contracts/test/hardhat/common/constants/address.ts
Normal file
1
contracts/test/hardhat/common/constants/address.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const FALLBACK_OPERATOR_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11";
|
||||
@@ -8,3 +8,4 @@ export * from "./merkleTree";
|
||||
export * from "./calldata";
|
||||
export * from "./pauseTypes";
|
||||
export * from "./roles";
|
||||
export * from "./address";
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
716
contracts/test/hardhat/rollup/LineaRollup/BlobSubmission.ts
Normal file
716
contracts/test/hardhat/rollup/LineaRollup/BlobSubmission.ts
Normal file
@@ -0,0 +1,716 @@
|
||||
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
|
||||
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
|
||||
import * as kzg from "c-kzg";
|
||||
import { expect } from "chai";
|
||||
import { BaseContract, Transaction } from "ethers";
|
||||
import { ethers } from "hardhat";
|
||||
|
||||
import blobAggregatedProof1To155 from "../../_testData/compressedDataEip4844/aggregatedProof-1-155.json";
|
||||
import blobMultipleAggregatedProof1To81 from "../../_testData/compressedDataEip4844/multipleProofs/aggregatedProof-1-81.json";
|
||||
import firstCompressedDataContent from "../../_testData/compressedData/blocks-1-46.json";
|
||||
import secondCompressedDataContent from "../../_testData/compressedData/blocks-47-81.json";
|
||||
import fourthCompressedDataContent from "../../_testData/compressedData/blocks-115-155.json";
|
||||
|
||||
import { TestLineaRollup } from "contracts/typechain-types";
|
||||
import {
|
||||
deployLineaRollupFixture,
|
||||
deployRevertingVerifier,
|
||||
expectSuccessfulFinalize,
|
||||
getAccountsFixture,
|
||||
getWalletForIndex,
|
||||
sendBlobTransaction,
|
||||
} from "../helpers";
|
||||
import {
|
||||
GENERAL_PAUSE_TYPE,
|
||||
HASH_ZERO,
|
||||
OPERATOR_ROLE,
|
||||
TEST_PUBLIC_VERIFIER_INDEX,
|
||||
BLOB_SUBMISSION_PAUSE_TYPE,
|
||||
} from "../../common/constants";
|
||||
import {
|
||||
generateFinalizationData,
|
||||
generateRandomBytes,
|
||||
buildAccessErrorMessage,
|
||||
expectRevertWithCustomError,
|
||||
expectRevertWithReason,
|
||||
generateBlobDataSubmission,
|
||||
generateBlobParentShnarfData,
|
||||
expectEventDirectFromReceiptData,
|
||||
} from "../../common/helpers";
|
||||
|
||||
describe("Linea Rollup contract: EIP-4844 Blob submission tests", () => {
|
||||
let lineaRollup: TestLineaRollup;
|
||||
let revertingVerifier: string;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
let securityCouncil: SignerWithAddress;
|
||||
let operator: SignerWithAddress;
|
||||
let nonAuthorizedAccount: SignerWithAddress;
|
||||
|
||||
const { prevShnarf, parentDataHash, parentStateRootHash } = firstCompressedDataContent;
|
||||
|
||||
before(async () => {
|
||||
({ securityCouncil, operator, nonAuthorizedAccount } = await loadFixture(getAccountsFixture));
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
({ lineaRollup } = await loadFixture(deployLineaRollupFixture));
|
||||
await lineaRollup.setLastFinalizedBlock(0);
|
||||
await lineaRollup.setupParentShnarf(prevShnarf);
|
||||
await lineaRollup.setupParentDataShnarf(parentDataHash, prevShnarf);
|
||||
await lineaRollup.setupParentFinalizedStateRoot(parentDataHash, parentStateRootHash);
|
||||
});
|
||||
|
||||
it("Should successfully submit blobs", async () => {
|
||||
const operatorHDSigner = getWalletForIndex(2);
|
||||
const lineaRollupAddress = await lineaRollup.getAddress();
|
||||
const { blobDataSubmission, compressedBlobs, parentShnarf, finalShnarf } = generateBlobDataSubmission(0, 1);
|
||||
|
||||
const encodedCall = lineaRollup.interface.encodeFunctionData("submitBlobs", [
|
||||
blobDataSubmission,
|
||||
parentShnarf,
|
||||
finalShnarf,
|
||||
]);
|
||||
|
||||
const { maxFeePerGas, maxPriorityFeePerGas } = await ethers.provider.getFeeData();
|
||||
const nonce = await operatorHDSigner.getNonce();
|
||||
|
||||
const transaction = Transaction.from({
|
||||
data: encodedCall,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGas!,
|
||||
maxFeePerGas: maxFeePerGas!,
|
||||
to: lineaRollupAddress,
|
||||
chainId: (await ethers.provider.getNetwork()).chainId,
|
||||
type: 3,
|
||||
nonce,
|
||||
value: 0,
|
||||
gasLimit: 5_000_000,
|
||||
kzg,
|
||||
maxFeePerBlobGas: 1n,
|
||||
blobs: compressedBlobs,
|
||||
});
|
||||
|
||||
const signedTx = await operatorHDSigner.signTransaction(transaction);
|
||||
|
||||
const txResponse = await ethers.provider.broadcastTransaction(signedTx);
|
||||
const receipt = await ethers.provider.getTransactionReceipt(txResponse.hash);
|
||||
expect(receipt).is.not.null;
|
||||
|
||||
const expectedEventArgs = [
|
||||
parentShnarf,
|
||||
finalShnarf,
|
||||
blobDataSubmission[blobDataSubmission.length - 1].finalStateRootHash,
|
||||
];
|
||||
|
||||
expectEventDirectFromReceiptData(lineaRollup as BaseContract, receipt!, "DataSubmittedV3", expectedEventArgs);
|
||||
|
||||
const blobShnarfExists = await lineaRollup.blobShnarfExists(finalShnarf);
|
||||
expect(blobShnarfExists).to.equal(1n);
|
||||
});
|
||||
|
||||
it("Fails the blob submission when the parent shnarf is missing", async () => {
|
||||
const operatorHDSigner = getWalletForIndex(2);
|
||||
|
||||
const lineaRollupAddress = await lineaRollup.getAddress();
|
||||
const { blobDataSubmission, compressedBlobs, finalShnarf } = generateBlobDataSubmission(0, 1);
|
||||
const nonExistingParentShnarf = generateRandomBytes(32);
|
||||
|
||||
const encodedCall = lineaRollup.interface.encodeFunctionData("submitBlobs", [
|
||||
blobDataSubmission,
|
||||
nonExistingParentShnarf,
|
||||
finalShnarf,
|
||||
]);
|
||||
|
||||
const { maxFeePerGas, maxPriorityFeePerGas } = await ethers.provider.getFeeData();
|
||||
const nonce = await operatorHDSigner.getNonce();
|
||||
|
||||
const transaction = Transaction.from({
|
||||
data: encodedCall,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGas!,
|
||||
maxFeePerGas: maxFeePerGas!,
|
||||
to: lineaRollupAddress,
|
||||
chainId: (await ethers.provider.getNetwork()).chainId,
|
||||
type: 3,
|
||||
nonce,
|
||||
value: 0,
|
||||
gasLimit: 5_000_000,
|
||||
kzg,
|
||||
maxFeePerBlobGas: 1n,
|
||||
blobs: compressedBlobs,
|
||||
});
|
||||
|
||||
const signedTx = await operatorHDSigner.signTransaction(transaction);
|
||||
|
||||
await expectRevertWithCustomError(
|
||||
lineaRollup,
|
||||
ethers.provider.broadcastTransaction(signedTx),
|
||||
"ParentBlobNotSubmitted",
|
||||
[nonExistingParentShnarf],
|
||||
);
|
||||
});
|
||||
|
||||
it("Fails when the blob submission data is missing", async () => {
|
||||
const operatorHDSigner = getWalletForIndex(2);
|
||||
|
||||
const lineaRollupAddress = await lineaRollup.getAddress();
|
||||
const { compressedBlobs, parentShnarf, finalShnarf } = generateBlobDataSubmission(0, 1);
|
||||
|
||||
const encodedCall = lineaRollup.interface.encodeFunctionData("submitBlobs", [[], parentShnarf, finalShnarf]);
|
||||
|
||||
const { maxFeePerGas, maxPriorityFeePerGas } = await ethers.provider.getFeeData();
|
||||
const nonce = await operatorHDSigner.getNonce();
|
||||
|
||||
const transaction = Transaction.from({
|
||||
data: encodedCall,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGas!,
|
||||
maxFeePerGas: maxFeePerGas!,
|
||||
to: lineaRollupAddress,
|
||||
chainId: (await ethers.provider.getNetwork()).chainId,
|
||||
type: 3,
|
||||
nonce,
|
||||
value: 0,
|
||||
gasLimit: 5_000_000,
|
||||
kzg,
|
||||
maxFeePerBlobGas: 1n,
|
||||
blobs: compressedBlobs,
|
||||
});
|
||||
|
||||
const signedTx = await operatorHDSigner.signTransaction(transaction);
|
||||
|
||||
await expectRevertWithCustomError(
|
||||
lineaRollup,
|
||||
ethers.provider.broadcastTransaction(signedTx),
|
||||
"BlobSubmissionDataIsMissing",
|
||||
);
|
||||
});
|
||||
|
||||
it("Should revert if the caller does not have the OPERATOR_ROLE", async () => {
|
||||
const { blobDataSubmission, parentShnarf, finalShnarf } = generateBlobDataSubmission(0, 1);
|
||||
|
||||
await expectRevertWithReason(
|
||||
lineaRollup.connect(nonAuthorizedAccount).submitBlobs(blobDataSubmission, parentShnarf, finalShnarf),
|
||||
buildAccessErrorMessage(nonAuthorizedAccount, OPERATOR_ROLE),
|
||||
);
|
||||
});
|
||||
|
||||
it("Should revert if GENERAL_PAUSE_TYPE is enabled", async () => {
|
||||
const { blobDataSubmission, parentShnarf, finalShnarf } = generateBlobDataSubmission(0, 1);
|
||||
|
||||
await lineaRollup.connect(securityCouncil).pauseByType(GENERAL_PAUSE_TYPE);
|
||||
|
||||
await expectRevertWithCustomError(
|
||||
lineaRollup,
|
||||
lineaRollup.connect(operator).submitBlobs(blobDataSubmission, parentShnarf, finalShnarf),
|
||||
"IsPaused",
|
||||
[GENERAL_PAUSE_TYPE],
|
||||
);
|
||||
});
|
||||
|
||||
it("Should revert if BLOB_SUBMISSION_PAUSE_TYPE is enabled", async () => {
|
||||
const { blobDataSubmission, parentShnarf, finalShnarf } = generateBlobDataSubmission(0, 1);
|
||||
|
||||
await lineaRollup.connect(securityCouncil).pauseByType(BLOB_SUBMISSION_PAUSE_TYPE);
|
||||
|
||||
await expectRevertWithCustomError(
|
||||
lineaRollup,
|
||||
lineaRollup.connect(operator).submitBlobs(blobDataSubmission, parentShnarf, finalShnarf),
|
||||
"IsPaused",
|
||||
[BLOB_SUBMISSION_PAUSE_TYPE],
|
||||
);
|
||||
});
|
||||
|
||||
it("Should revert if the blob data is empty at any index", async () => {
|
||||
const operatorHDSigner = getWalletForIndex(2);
|
||||
const lineaRollupAddress = await lineaRollup.getAddress();
|
||||
const { blobDataSubmission, compressedBlobs, parentShnarf, finalShnarf } = generateBlobDataSubmission(0, 2);
|
||||
|
||||
const encodedCall = lineaRollup.interface.encodeFunctionData("submitBlobs", [
|
||||
blobDataSubmission,
|
||||
parentShnarf,
|
||||
finalShnarf,
|
||||
]);
|
||||
|
||||
const { maxFeePerGas, maxPriorityFeePerGas } = await ethers.provider.getFeeData();
|
||||
const nonce = await operatorHDSigner.getNonce();
|
||||
|
||||
const transaction = Transaction.from({
|
||||
data: encodedCall,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGas!,
|
||||
maxFeePerGas: maxFeePerGas!,
|
||||
to: lineaRollupAddress,
|
||||
chainId: (await ethers.provider.getNetwork()).chainId,
|
||||
type: 3,
|
||||
nonce,
|
||||
value: 0,
|
||||
gasLimit: 5_000_000,
|
||||
kzg,
|
||||
maxFeePerBlobGas: 1n,
|
||||
blobs: [compressedBlobs[0]],
|
||||
});
|
||||
|
||||
const signedTx = await operatorHDSigner.signTransaction(transaction);
|
||||
|
||||
await expectRevertWithCustomError(
|
||||
lineaRollup,
|
||||
ethers.provider.broadcastTransaction(signedTx),
|
||||
"EmptyBlobDataAtIndex",
|
||||
[1n],
|
||||
);
|
||||
});
|
||||
it("Should fail if the final state root hash is empty", async () => {
|
||||
const operatorHDSigner = getWalletForIndex(2);
|
||||
|
||||
const lineaRollupAddress = await lineaRollup.getAddress();
|
||||
const { blobDataSubmission, compressedBlobs, parentShnarf, finalShnarf } = generateBlobDataSubmission(0, 1);
|
||||
|
||||
blobDataSubmission[0].finalStateRootHash = HASH_ZERO;
|
||||
|
||||
const encodedCall = lineaRollup.interface.encodeFunctionData("submitBlobs", [
|
||||
blobDataSubmission,
|
||||
parentShnarf,
|
||||
finalShnarf,
|
||||
]);
|
||||
|
||||
const { maxFeePerGas, maxPriorityFeePerGas } = await ethers.provider.getFeeData();
|
||||
const nonce = await operatorHDSigner.getNonce();
|
||||
|
||||
const transaction = Transaction.from({
|
||||
data: encodedCall,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGas!,
|
||||
maxFeePerGas: maxFeePerGas!,
|
||||
to: lineaRollupAddress,
|
||||
chainId: (await ethers.provider.getNetwork()).chainId,
|
||||
type: 3,
|
||||
nonce,
|
||||
value: 0,
|
||||
gasLimit: 5_000_000,
|
||||
kzg,
|
||||
maxFeePerBlobGas: 1n,
|
||||
blobs: compressedBlobs,
|
||||
});
|
||||
|
||||
const signedTx = await operatorHDSigner.signTransaction(transaction);
|
||||
|
||||
// TODO: Make the failure shnarf dynamic and computed
|
||||
await expectRevertWithCustomError(lineaRollup, ethers.provider.broadcastTransaction(signedTx), "FinalShnarfWrong", [
|
||||
finalShnarf,
|
||||
"0x22f8fb954df8328627fe9c48b60192f4d970a92891417aaadea39300ca244d36",
|
||||
]);
|
||||
});
|
||||
|
||||
it("Should revert when snarkHash is zero hash", async () => {
|
||||
const operatorHDSigner = getWalletForIndex(2);
|
||||
|
||||
const lineaRollupAddress = await lineaRollup.getAddress();
|
||||
const { blobDataSubmission, compressedBlobs, parentShnarf, finalShnarf } = generateBlobDataSubmission(0, 1);
|
||||
|
||||
// Set the snarkHash to HASH_ZERO for a specific index
|
||||
const emptyDataIndex = 0;
|
||||
blobDataSubmission[emptyDataIndex].snarkHash = generateRandomBytes(32);
|
||||
|
||||
const encodedCall = lineaRollup.interface.encodeFunctionData("submitBlobs", [
|
||||
blobDataSubmission,
|
||||
parentShnarf,
|
||||
finalShnarf,
|
||||
]);
|
||||
|
||||
const { maxFeePerGas, maxPriorityFeePerGas } = await ethers.provider.getFeeData();
|
||||
const nonce = await operatorHDSigner.getNonce();
|
||||
|
||||
const transaction = Transaction.from({
|
||||
data: encodedCall,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGas!,
|
||||
maxFeePerGas: maxFeePerGas!,
|
||||
to: lineaRollupAddress,
|
||||
chainId: (await ethers.provider.getNetwork()).chainId,
|
||||
type: 3,
|
||||
nonce,
|
||||
value: 0,
|
||||
gasLimit: 5_000_000,
|
||||
kzg,
|
||||
maxFeePerBlobGas: 1n,
|
||||
blobs: compressedBlobs,
|
||||
});
|
||||
|
||||
const signedTx = await operatorHDSigner.signTransaction(transaction);
|
||||
|
||||
await expectRevertWithCustomError(
|
||||
lineaRollup,
|
||||
ethers.provider.broadcastTransaction(signedTx),
|
||||
"PointEvaluationFailed",
|
||||
);
|
||||
});
|
||||
|
||||
it("Should revert if the final shnarf is wrong", async () => {
|
||||
const operatorHDSigner = getWalletForIndex(2);
|
||||
const lineaRollupAddress = await lineaRollup.getAddress();
|
||||
const { blobDataSubmission, compressedBlobs, parentShnarf, finalShnarf } = generateBlobDataSubmission(0, 2);
|
||||
const badFinalShnarf = generateRandomBytes(32);
|
||||
|
||||
const encodedCall = lineaRollup.interface.encodeFunctionData("submitBlobs", [
|
||||
blobDataSubmission,
|
||||
parentShnarf,
|
||||
badFinalShnarf,
|
||||
]);
|
||||
|
||||
const { maxFeePerGas, maxPriorityFeePerGas } = await ethers.provider.getFeeData();
|
||||
const nonce = await operatorHDSigner.getNonce();
|
||||
|
||||
const transaction = Transaction.from({
|
||||
data: encodedCall,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGas!,
|
||||
maxFeePerGas: maxFeePerGas!,
|
||||
to: lineaRollupAddress,
|
||||
chainId: (await ethers.provider.getNetwork()).chainId,
|
||||
type: 3,
|
||||
nonce,
|
||||
value: 0,
|
||||
gasLimit: 5_000_000,
|
||||
kzg,
|
||||
maxFeePerBlobGas: 1n,
|
||||
blobs: compressedBlobs,
|
||||
});
|
||||
|
||||
const signedTx = await operatorHDSigner.signTransaction(transaction);
|
||||
|
||||
await expectRevertWithCustomError(lineaRollup, ethers.provider.broadcastTransaction(signedTx), "FinalShnarfWrong", [
|
||||
badFinalShnarf,
|
||||
finalShnarf,
|
||||
]);
|
||||
});
|
||||
|
||||
it("Should revert if the data has already been submitted", async () => {
|
||||
await sendBlobTransaction(lineaRollup, 0, 1);
|
||||
|
||||
const operatorHDSigner = getWalletForIndex(2);
|
||||
|
||||
const lineaRollupAddress = await lineaRollup.getAddress();
|
||||
const { blobDataSubmission, compressedBlobs, parentShnarf, finalShnarf } = generateBlobDataSubmission(0, 1);
|
||||
|
||||
// Try to submit the same blob data again
|
||||
const encodedCall2 = lineaRollup.interface.encodeFunctionData("submitBlobs", [
|
||||
blobDataSubmission,
|
||||
parentShnarf,
|
||||
finalShnarf,
|
||||
]);
|
||||
|
||||
const { maxFeePerGas: maxFeePerGas2, maxPriorityFeePerGas: maxPriorityFeePerGas2 } =
|
||||
await ethers.provider.getFeeData();
|
||||
const nonce2 = await operatorHDSigner.getNonce();
|
||||
|
||||
const transaction2 = Transaction.from({
|
||||
data: encodedCall2,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGas2!,
|
||||
maxFeePerGas: maxFeePerGas2!,
|
||||
to: lineaRollupAddress,
|
||||
chainId: (await ethers.provider.getNetwork()).chainId,
|
||||
type: 3,
|
||||
nonce: nonce2,
|
||||
value: 0,
|
||||
gasLimit: 5_000_000,
|
||||
kzg,
|
||||
maxFeePerBlobGas: 1n,
|
||||
blobs: compressedBlobs,
|
||||
});
|
||||
|
||||
const signedTx2 = await operatorHDSigner.signTransaction(transaction2);
|
||||
|
||||
await expectRevertWithCustomError(
|
||||
lineaRollup,
|
||||
ethers.provider.broadcastTransaction(signedTx2),
|
||||
"DataAlreadySubmitted",
|
||||
[finalShnarf],
|
||||
);
|
||||
});
|
||||
|
||||
it("Should revert with PointEvaluationFailed when point evaluation fails", async () => {
|
||||
const operatorHDSigner = getWalletForIndex(2);
|
||||
|
||||
const lineaRollupAddress = await lineaRollup.getAddress();
|
||||
const { blobDataSubmission, compressedBlobs, parentShnarf, finalShnarf } = generateBlobDataSubmission(0, 1);
|
||||
|
||||
// Modify the kzgProof to an invalid value to trigger the PointEvaluationFailed revert
|
||||
blobDataSubmission[0].kzgProof = HASH_ZERO;
|
||||
|
||||
const encodedCall = lineaRollup.interface.encodeFunctionData("submitBlobs", [
|
||||
blobDataSubmission,
|
||||
parentShnarf,
|
||||
finalShnarf,
|
||||
]);
|
||||
|
||||
const { maxFeePerGas, maxPriorityFeePerGas } = await ethers.provider.getFeeData();
|
||||
const nonce = await operatorHDSigner.getNonce();
|
||||
|
||||
const transaction = Transaction.from({
|
||||
data: encodedCall,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGas!,
|
||||
maxFeePerGas: maxFeePerGas!,
|
||||
to: lineaRollupAddress,
|
||||
chainId: (await ethers.provider.getNetwork()).chainId,
|
||||
type: 3,
|
||||
nonce,
|
||||
value: 0,
|
||||
gasLimit: 5_000_000,
|
||||
kzg,
|
||||
maxFeePerBlobGas: 1n,
|
||||
blobs: compressedBlobs,
|
||||
});
|
||||
|
||||
const signedTx = await operatorHDSigner.signTransaction(transaction);
|
||||
|
||||
await expectRevertWithCustomError(
|
||||
lineaRollup,
|
||||
ethers.provider.broadcastTransaction(signedTx),
|
||||
"PointEvaluationFailed",
|
||||
);
|
||||
});
|
||||
|
||||
it("Should submit 2 blobs, then submit another 2 blobs and finalize", async () => {
|
||||
// Submit 2 blobs
|
||||
await sendBlobTransaction(lineaRollup, 0, 2);
|
||||
// Submit another 2 blobs
|
||||
await sendBlobTransaction(lineaRollup, 2, 4);
|
||||
// Finalize 4 blobs
|
||||
await expectSuccessfulFinalize(
|
||||
lineaRollup,
|
||||
operator,
|
||||
blobAggregatedProof1To155,
|
||||
4,
|
||||
fourthCompressedDataContent.finalStateRootHash,
|
||||
generateBlobParentShnarfData,
|
||||
);
|
||||
});
|
||||
|
||||
it("Should revert if there is less data than blobs", async () => {
|
||||
const operatorHDSigner = getWalletForIndex(2);
|
||||
const lineaRollupAddress = await lineaRollup.getAddress();
|
||||
|
||||
const {
|
||||
blobDataSubmission: blobSubmission,
|
||||
compressedBlobs: compressedBlobs,
|
||||
parentShnarf: parentShnarf,
|
||||
finalShnarf: finalShnarf,
|
||||
} = generateBlobDataSubmission(0, 2, true);
|
||||
|
||||
const encodedCall = lineaRollup.interface.encodeFunctionData("submitBlobs", [
|
||||
[blobSubmission[0]],
|
||||
parentShnarf,
|
||||
finalShnarf,
|
||||
]);
|
||||
|
||||
const { maxFeePerGas, maxPriorityFeePerGas } = await ethers.provider.getFeeData();
|
||||
const nonce = await operatorHDSigner.getNonce();
|
||||
|
||||
const transaction = Transaction.from({
|
||||
data: encodedCall,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGas!,
|
||||
maxFeePerGas: maxFeePerGas!,
|
||||
to: lineaRollupAddress,
|
||||
chainId: (await ethers.provider.getNetwork()).chainId,
|
||||
type: 3,
|
||||
nonce: nonce,
|
||||
value: 0,
|
||||
gasLimit: 5_000_000,
|
||||
kzg,
|
||||
maxFeePerBlobGas: 1n,
|
||||
blobs: compressedBlobs,
|
||||
});
|
||||
|
||||
const signedTx = await operatorHDSigner.signTransaction(transaction);
|
||||
await expectRevertWithCustomError(
|
||||
lineaRollup,
|
||||
ethers.provider.broadcastTransaction(signedTx),
|
||||
"BlobSubmissionDataEmpty",
|
||||
[1],
|
||||
);
|
||||
});
|
||||
|
||||
it("Should fail to finalize with not enough gas for the rollup (pre-verifier)", async () => {
|
||||
// Submit 2 blobs
|
||||
await sendBlobTransaction(lineaRollup, 0, 2);
|
||||
// Submit another 2 blobs
|
||||
await sendBlobTransaction(lineaRollup, 2, 4);
|
||||
|
||||
// Finalize 4 blobs
|
||||
const finalizationData = await generateFinalizationData({
|
||||
l1RollingHash: blobAggregatedProof1To155.l1RollingHash,
|
||||
l1RollingHashMessageNumber: BigInt(blobAggregatedProof1To155.l1RollingHashMessageNumber),
|
||||
lastFinalizedTimestamp: BigInt(blobAggregatedProof1To155.parentAggregationLastBlockTimestamp),
|
||||
endBlockNumber: BigInt(blobAggregatedProof1To155.finalBlockNumber),
|
||||
parentStateRootHash: blobAggregatedProof1To155.parentStateRootHash,
|
||||
finalTimestamp: BigInt(blobAggregatedProof1To155.finalTimestamp),
|
||||
l2MerkleRoots: blobAggregatedProof1To155.l2MerkleRoots,
|
||||
l2MerkleTreesDepth: BigInt(blobAggregatedProof1To155.l2MerkleTreesDepth),
|
||||
l2MessagingBlocksOffsets: blobAggregatedProof1To155.l2MessagingBlocksOffsets,
|
||||
aggregatedProof: blobAggregatedProof1To155.aggregatedProof,
|
||||
shnarfData: generateBlobParentShnarfData(4, false),
|
||||
});
|
||||
finalizationData.lastFinalizedL1RollingHash = HASH_ZERO;
|
||||
finalizationData.lastFinalizedL1RollingHashMessageNumber = 0n;
|
||||
|
||||
await lineaRollup.setRollingHash(
|
||||
blobAggregatedProof1To155.l1RollingHashMessageNumber,
|
||||
blobAggregatedProof1To155.l1RollingHash,
|
||||
);
|
||||
|
||||
const finalizeCompressedCall = lineaRollup
|
||||
.connect(operator)
|
||||
.finalizeBlocks(blobAggregatedProof1To155.aggregatedProof, TEST_PUBLIC_VERIFIER_INDEX, finalizationData, {
|
||||
gasLimit: 50000,
|
||||
});
|
||||
|
||||
// there is no reason
|
||||
await expect(finalizeCompressedCall).to.be.reverted;
|
||||
});
|
||||
|
||||
it("Should fail to finalize with not enough gas to verify", async () => {
|
||||
// Submit 2 blobs
|
||||
await sendBlobTransaction(lineaRollup, 0, 2);
|
||||
// Submit another 2 blobs
|
||||
await sendBlobTransaction(lineaRollup, 2, 4);
|
||||
|
||||
// Finalize 4 blobs
|
||||
const finalizationData = await generateFinalizationData({
|
||||
l1RollingHash: blobAggregatedProof1To155.l1RollingHash,
|
||||
l1RollingHashMessageNumber: BigInt(blobAggregatedProof1To155.l1RollingHashMessageNumber),
|
||||
lastFinalizedTimestamp: BigInt(blobAggregatedProof1To155.parentAggregationLastBlockTimestamp),
|
||||
endBlockNumber: BigInt(blobAggregatedProof1To155.finalBlockNumber),
|
||||
parentStateRootHash: blobAggregatedProof1To155.parentStateRootHash,
|
||||
finalTimestamp: BigInt(blobAggregatedProof1To155.finalTimestamp),
|
||||
l2MerkleRoots: blobAggregatedProof1To155.l2MerkleRoots,
|
||||
l2MerkleTreesDepth: BigInt(blobAggregatedProof1To155.l2MerkleTreesDepth),
|
||||
l2MessagingBlocksOffsets: blobAggregatedProof1To155.l2MessagingBlocksOffsets,
|
||||
aggregatedProof: blobAggregatedProof1To155.aggregatedProof,
|
||||
shnarfData: generateBlobParentShnarfData(4, false),
|
||||
});
|
||||
finalizationData.lastFinalizedL1RollingHash = HASH_ZERO;
|
||||
finalizationData.lastFinalizedL1RollingHashMessageNumber = 0n;
|
||||
|
||||
await lineaRollup.setRollingHash(
|
||||
blobAggregatedProof1To155.l1RollingHashMessageNumber,
|
||||
blobAggregatedProof1To155.l1RollingHash,
|
||||
);
|
||||
|
||||
const finalizeCompressedCall = lineaRollup
|
||||
.connect(operator)
|
||||
.finalizeBlocks(blobAggregatedProof1To155.aggregatedProof, TEST_PUBLIC_VERIFIER_INDEX, finalizationData, {
|
||||
gasLimit: 400000,
|
||||
});
|
||||
|
||||
await expectRevertWithCustomError(
|
||||
lineaRollup,
|
||||
finalizeCompressedCall,
|
||||
"InvalidProofOrProofVerificationRanOutOfGas",
|
||||
["error pairing"],
|
||||
);
|
||||
});
|
||||
|
||||
const testCases = [
|
||||
{ revertScenario: 0n, title: "Should fail to finalize via EMPTY_REVERT scenario with 'Unknown'" },
|
||||
{ revertScenario: 1n, title: "Should fail to finalize via GAS_GUZZLE scenario with 'Unknown'" },
|
||||
];
|
||||
|
||||
testCases.forEach(({ revertScenario, title }) => {
|
||||
it(title, async () => {
|
||||
revertingVerifier = await deployRevertingVerifier(revertScenario);
|
||||
await lineaRollup.connect(securityCouncil).setVerifierAddress(revertingVerifier, 0);
|
||||
|
||||
// Submit 2 blobs
|
||||
await sendBlobTransaction(lineaRollup, 0, 2);
|
||||
// Submit another 2 blobs
|
||||
await sendBlobTransaction(lineaRollup, 2, 4);
|
||||
|
||||
// Finalize 4 blobs
|
||||
const finalizationData = await generateFinalizationData({
|
||||
l1RollingHash: blobAggregatedProof1To155.l1RollingHash,
|
||||
l1RollingHashMessageNumber: BigInt(blobAggregatedProof1To155.l1RollingHashMessageNumber),
|
||||
lastFinalizedTimestamp: BigInt(blobAggregatedProof1To155.parentAggregationLastBlockTimestamp),
|
||||
endBlockNumber: BigInt(blobAggregatedProof1To155.finalBlockNumber),
|
||||
parentStateRootHash: blobAggregatedProof1To155.parentStateRootHash,
|
||||
finalTimestamp: BigInt(blobAggregatedProof1To155.finalTimestamp),
|
||||
l2MerkleRoots: blobAggregatedProof1To155.l2MerkleRoots,
|
||||
l2MerkleTreesDepth: BigInt(blobAggregatedProof1To155.l2MerkleTreesDepth),
|
||||
l2MessagingBlocksOffsets: blobAggregatedProof1To155.l2MessagingBlocksOffsets,
|
||||
aggregatedProof: blobAggregatedProof1To155.aggregatedProof,
|
||||
shnarfData: generateBlobParentShnarfData(4, false),
|
||||
});
|
||||
finalizationData.lastFinalizedL1RollingHash = HASH_ZERO;
|
||||
finalizationData.lastFinalizedL1RollingHashMessageNumber = 0n;
|
||||
|
||||
await lineaRollup.setRollingHash(
|
||||
blobAggregatedProof1To155.l1RollingHashMessageNumber,
|
||||
blobAggregatedProof1To155.l1RollingHash,
|
||||
);
|
||||
|
||||
const finalizeCompressedCall = lineaRollup
|
||||
.connect(operator)
|
||||
.finalizeBlocks(blobAggregatedProof1To155.aggregatedProof, TEST_PUBLIC_VERIFIER_INDEX, finalizationData, {
|
||||
gasLimit: 400000,
|
||||
});
|
||||
|
||||
await expectRevertWithCustomError(
|
||||
lineaRollup,
|
||||
finalizeCompressedCall,
|
||||
"InvalidProofOrProofVerificationRanOutOfGas",
|
||||
["Unknown"],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("Should successfully submit 2 blobs twice then finalize in two separate finalizations", async () => {
|
||||
// Submit 2 blobs
|
||||
await sendBlobTransaction(lineaRollup, 0, 2, true);
|
||||
// Submit another 2 blobs
|
||||
await sendBlobTransaction(lineaRollup, 2, 4, true);
|
||||
// Finalize first 2 blobs
|
||||
await expectSuccessfulFinalize(
|
||||
lineaRollup,
|
||||
operator,
|
||||
blobMultipleAggregatedProof1To81,
|
||||
2,
|
||||
secondCompressedDataContent.finalStateRootHash,
|
||||
generateBlobParentShnarfData,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("Should fail to prove if last finalized is higher than proving range", async () => {
|
||||
// Submit 2 blobs
|
||||
await sendBlobTransaction(lineaRollup, 0, 2, true);
|
||||
// Submit another 2 blobs
|
||||
await sendBlobTransaction(lineaRollup, 2, 4, true);
|
||||
|
||||
await lineaRollup.setLastFinalizedBlock(10_000_000);
|
||||
|
||||
const finalizationData = await generateFinalizationData({
|
||||
l1RollingHash: blobAggregatedProof1To155.l1RollingHash,
|
||||
l1RollingHashMessageNumber: BigInt(blobAggregatedProof1To155.l1RollingHashMessageNumber),
|
||||
lastFinalizedTimestamp: BigInt(blobAggregatedProof1To155.parentAggregationLastBlockTimestamp),
|
||||
endBlockNumber: BigInt(blobAggregatedProof1To155.finalBlockNumber),
|
||||
parentStateRootHash: HASH_ZERO, // Manipulate for bypass
|
||||
finalTimestamp: BigInt(blobAggregatedProof1To155.finalTimestamp),
|
||||
l2MerkleRoots: blobAggregatedProof1To155.l2MerkleRoots,
|
||||
l2MerkleTreesDepth: BigInt(blobAggregatedProof1To155.l2MerkleTreesDepth),
|
||||
l2MessagingBlocksOffsets: blobAggregatedProof1To155.l2MessagingBlocksOffsets,
|
||||
aggregatedProof: blobAggregatedProof1To155.aggregatedProof,
|
||||
shnarfData: generateBlobParentShnarfData(4, false),
|
||||
lastFinalizedL1RollingHash: HASH_ZERO,
|
||||
lastFinalizedL1RollingHashMessageNumber: 0n,
|
||||
});
|
||||
|
||||
await lineaRollup.setRollingHash(
|
||||
blobAggregatedProof1To155.l1RollingHashMessageNumber,
|
||||
blobAggregatedProof1To155.l1RollingHash,
|
||||
);
|
||||
|
||||
await lineaRollup.setLastFinalizedBlock(10_000_000);
|
||||
|
||||
expectRevertWithCustomError(
|
||||
lineaRollup,
|
||||
lineaRollup
|
||||
.connect(operator)
|
||||
.finalizeBlocks(blobAggregatedProof1To155.aggregatedProof, TEST_PUBLIC_VERIFIER_INDEX, finalizationData),
|
||||
"InvalidProof",
|
||||
);
|
||||
});
|
||||
});
|
||||
857
contracts/test/hardhat/rollup/LineaRollup/Finalization.ts
Normal file
857
contracts/test/hardhat/rollup/LineaRollup/Finalization.ts
Normal file
@@ -0,0 +1,857 @@
|
||||
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
|
||||
import { loadFixture, time as networkTime } from "@nomicfoundation/hardhat-network-helpers";
|
||||
import { expect } from "chai";
|
||||
import * as fs from "fs";
|
||||
import { ethers } from "hardhat";
|
||||
|
||||
import aggregatedProof1To81 from "../../_testData/compressedData/multipleProofs/aggregatedProof-1-81.json";
|
||||
import aggregatedProof82To153 from "../../_testData/compressedData/multipleProofs/aggregatedProof-82-153.json";
|
||||
import betaV1FinalizationData from "../../_testData/betaV1/proof/7027059-7042723-d2221f5035e3dcbbc46e8a6130fef34fdec33c252b7d31fb8afa6848660260ba-getZkAggregatedProof.json";
|
||||
import calldataAggregatedProof1To155 from "../../_testData/compressedData/aggregatedProof-1-155.json";
|
||||
import secondCompressedDataContent from "../../_testData/compressedData/blocks-47-81.json";
|
||||
import fourthCompressedDataContent from "../../_testData/compressedData/blocks-115-155.json";
|
||||
import fourthMultipleCompressedDataContent from "../../_testData/compressedData/multipleProofs/blocks-120-153.json";
|
||||
|
||||
import { LINEA_ROLLUP_PAUSE_TYPES_ROLES, LINEA_ROLLUP_UNPAUSE_TYPES_ROLES } from "contracts/common/constants";
|
||||
import { TestLineaRollup } from "contracts/typechain-types";
|
||||
import {
|
||||
deployPlonkVerifierSepoliaFull,
|
||||
expectSuccessfulFinalize,
|
||||
getAccountsFixture,
|
||||
getBetaV1BlobFiles,
|
||||
getRoleAddressesFixture,
|
||||
deployLineaRollupFixture,
|
||||
sendBlobTransactionFromFile,
|
||||
} from "./../helpers";
|
||||
import {
|
||||
FALLBACK_OPERATOR_ADDRESS,
|
||||
GENERAL_PAUSE_TYPE,
|
||||
HASH_ZERO,
|
||||
INITIAL_WITHDRAW_LIMIT,
|
||||
ONE_DAY_IN_SECONDS,
|
||||
OPERATOR_ROLE,
|
||||
TEST_PUBLIC_VERIFIER_INDEX,
|
||||
EMPTY_CALLDATA,
|
||||
FINALIZATION_PAUSE_TYPE,
|
||||
DEFAULT_LAST_FINALIZED_TIMESTAMP,
|
||||
LINEA_ROLLUP_INITIALIZE_SIGNATURE,
|
||||
} from "../../common/constants";
|
||||
import { deployUpgradableFromFactory } from "../../common/deployment";
|
||||
import {
|
||||
calculateRollingHash,
|
||||
generateFinalizationData,
|
||||
generateRandomBytes,
|
||||
generateCallDataSubmission,
|
||||
generateCallDataSubmissionMultipleProofs,
|
||||
generateKeccak256,
|
||||
expectEvent,
|
||||
buildAccessErrorMessage,
|
||||
expectRevertWithCustomError,
|
||||
expectRevertWithReason,
|
||||
generateParentAndExpectedShnarfForIndex,
|
||||
generateParentAndExpectedShnarfForMulitpleIndex,
|
||||
generateParentShnarfData,
|
||||
} from "../../common/helpers";
|
||||
|
||||
describe("Linea Rollup contract: Finalization", () => {
|
||||
let lineaRollup: TestLineaRollup;
|
||||
let sepoliaFullVerifier: string;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
let securityCouncil: SignerWithAddress;
|
||||
let operator: SignerWithAddress;
|
||||
let nonAuthorizedAccount: SignerWithAddress;
|
||||
let roleAddresses: { addressWithRole: string; role: string }[];
|
||||
|
||||
before(async () => {
|
||||
({ securityCouncil, operator, nonAuthorizedAccount } = await loadFixture(getAccountsFixture));
|
||||
roleAddresses = await loadFixture(getRoleAddressesFixture);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
({ lineaRollup } = await loadFixture(deployLineaRollupFixture));
|
||||
});
|
||||
|
||||
describe("Blocks finalization with proof", () => {
|
||||
const messageHash = generateRandomBytes(32);
|
||||
|
||||
beforeEach(async () => {
|
||||
await lineaRollup.addRollingHash(10, messageHash);
|
||||
await lineaRollup.setLastFinalizedBlock(0);
|
||||
});
|
||||
|
||||
describe("With and without submission data", () => {
|
||||
it("Should revert if l1 message number == 0 and l1 rolling hash is not empty", async () => {
|
||||
const finalizationData = await generateFinalizationData({
|
||||
l1RollingHashMessageNumber: 0n,
|
||||
l1RollingHash: generateRandomBytes(32),
|
||||
});
|
||||
|
||||
const lastFinalizedBlockNumber = await lineaRollup.currentL2BlockNumber();
|
||||
const parentStateRootHash = await lineaRollup.stateRootHashes(lastFinalizedBlockNumber);
|
||||
finalizationData.parentStateRootHash = parentStateRootHash;
|
||||
|
||||
const proof = calldataAggregatedProof1To155.aggregatedProof;
|
||||
|
||||
const finalizeCall = lineaRollup
|
||||
.connect(operator)
|
||||
.finalizeBlocks(proof, TEST_PUBLIC_VERIFIER_INDEX, finalizationData);
|
||||
|
||||
await expectRevertWithCustomError(lineaRollup, finalizeCall, "MissingMessageNumberForRollingHash", [
|
||||
finalizationData.l1RollingHash,
|
||||
]);
|
||||
});
|
||||
|
||||
it("Should revert if l1 message number != 0 and l1 rolling hash is empty", async () => {
|
||||
const finalizationData = await generateFinalizationData({
|
||||
l1RollingHashMessageNumber: 1n,
|
||||
l1RollingHash: HASH_ZERO,
|
||||
});
|
||||
|
||||
const lastFinalizedBlockNumber = await lineaRollup.currentL2BlockNumber();
|
||||
const parentStateRootHash = await lineaRollup.stateRootHashes(lastFinalizedBlockNumber);
|
||||
finalizationData.parentStateRootHash = parentStateRootHash;
|
||||
|
||||
const proof = calldataAggregatedProof1To155.aggregatedProof;
|
||||
|
||||
const finalizeCall = lineaRollup
|
||||
.connect(operator)
|
||||
.finalizeBlocks(proof, TEST_PUBLIC_VERIFIER_INDEX, finalizationData);
|
||||
|
||||
await expectRevertWithCustomError(lineaRollup, finalizeCall, "MissingRollingHashForMessageNumber", [
|
||||
finalizationData.l1RollingHashMessageNumber,
|
||||
]);
|
||||
});
|
||||
|
||||
it("Should revert if l1RollingHash does not exist on L1", async () => {
|
||||
const finalizationData = await generateFinalizationData({
|
||||
l1RollingHashMessageNumber: 1n,
|
||||
l1RollingHash: generateRandomBytes(32),
|
||||
});
|
||||
|
||||
const lastFinalizedBlockNumber = await lineaRollup.currentL2BlockNumber();
|
||||
const parentStateRootHash = await lineaRollup.stateRootHashes(lastFinalizedBlockNumber);
|
||||
finalizationData.parentStateRootHash = parentStateRootHash;
|
||||
|
||||
const proof = calldataAggregatedProof1To155.aggregatedProof;
|
||||
|
||||
const finalizeCall = lineaRollup
|
||||
.connect(operator)
|
||||
.finalizeBlocks(proof, TEST_PUBLIC_VERIFIER_INDEX, finalizationData);
|
||||
|
||||
await expectRevertWithCustomError(lineaRollup, finalizeCall, "L1RollingHashDoesNotExistOnL1", [
|
||||
finalizationData.l1RollingHashMessageNumber,
|
||||
finalizationData.l1RollingHash,
|
||||
]);
|
||||
});
|
||||
|
||||
it("Should revert if timestamps are not in sequence", async () => {
|
||||
const submissionDataBeforeFinalization = generateCallDataSubmission(0, 4);
|
||||
let index = 0;
|
||||
for (const data of submissionDataBeforeFinalization) {
|
||||
const parentAndExpectedShnarf = generateParentAndExpectedShnarfForIndex(index);
|
||||
await lineaRollup
|
||||
.connect(operator)
|
||||
.submitDataAsCalldata(data, parentAndExpectedShnarf.parentShnarf, parentAndExpectedShnarf.expectedShnarf, {
|
||||
gasLimit: 30_000_000,
|
||||
});
|
||||
index++;
|
||||
}
|
||||
|
||||
const finalizationData = await generateFinalizationData({
|
||||
l1RollingHash: calculateRollingHash(HASH_ZERO, messageHash),
|
||||
l1RollingHashMessageNumber: 10n,
|
||||
lastFinalizedTimestamp: DEFAULT_LAST_FINALIZED_TIMESTAMP,
|
||||
endBlockNumber: BigInt(calldataAggregatedProof1To155.finalBlockNumber),
|
||||
parentStateRootHash: calldataAggregatedProof1To155.parentStateRootHash,
|
||||
finalTimestamp: BigInt(calldataAggregatedProof1To155.finalTimestamp),
|
||||
l2MerkleRoots: calldataAggregatedProof1To155.l2MerkleRoots,
|
||||
l2MerkleTreesDepth: BigInt(calldataAggregatedProof1To155.l2MerkleTreesDepth),
|
||||
l2MessagingBlocksOffsets: calldataAggregatedProof1To155.l2MessagingBlocksOffsets,
|
||||
aggregatedProof: calldataAggregatedProof1To155.aggregatedProof,
|
||||
shnarfData: generateParentShnarfData(index),
|
||||
});
|
||||
|
||||
await lineaRollup.setRollingHash(
|
||||
calldataAggregatedProof1To155.l1RollingHashMessageNumber,
|
||||
calldataAggregatedProof1To155.l1RollingHash,
|
||||
);
|
||||
|
||||
finalizationData.lastFinalizedTimestamp = finalizationData.finalTimestamp + 1n;
|
||||
|
||||
const expectedHashValue = generateKeccak256(
|
||||
["uint256", "bytes32", "uint256"],
|
||||
[
|
||||
finalizationData.lastFinalizedL1RollingHashMessageNumber,
|
||||
finalizationData.lastFinalizedL1RollingHash,
|
||||
finalizationData.lastFinalizedTimestamp,
|
||||
],
|
||||
);
|
||||
const actualHashValue = generateKeccak256(
|
||||
["uint256", "bytes32", "uint256"],
|
||||
[
|
||||
finalizationData.lastFinalizedL1RollingHashMessageNumber,
|
||||
finalizationData.lastFinalizedL1RollingHash,
|
||||
DEFAULT_LAST_FINALIZED_TIMESTAMP,
|
||||
],
|
||||
);
|
||||
|
||||
const finalizeCompressedCall = lineaRollup
|
||||
.connect(operator)
|
||||
.finalizeBlocks(calldataAggregatedProof1To155.aggregatedProof, TEST_PUBLIC_VERIFIER_INDEX, finalizationData);
|
||||
|
||||
await expectRevertWithCustomError(lineaRollup, finalizeCompressedCall, "FinalizationStateIncorrect", [
|
||||
expectedHashValue,
|
||||
actualHashValue,
|
||||
]);
|
||||
});
|
||||
|
||||
it("Should revert if the final shnarf does not exist", async () => {
|
||||
const submissionDataBeforeFinalization = generateCallDataSubmission(0, 4);
|
||||
let index = 0;
|
||||
for (const data of submissionDataBeforeFinalization) {
|
||||
const parentAndExpectedShnarf = generateParentAndExpectedShnarfForIndex(index);
|
||||
await lineaRollup
|
||||
.connect(operator)
|
||||
.submitDataAsCalldata(data, parentAndExpectedShnarf.parentShnarf, parentAndExpectedShnarf.expectedShnarf, {
|
||||
gasLimit: 30_000_000,
|
||||
});
|
||||
index++;
|
||||
}
|
||||
|
||||
const finalizationData = await generateFinalizationData({
|
||||
l1RollingHash: calculateRollingHash(HASH_ZERO, messageHash),
|
||||
l1RollingHashMessageNumber: 10n,
|
||||
lastFinalizedTimestamp: DEFAULT_LAST_FINALIZED_TIMESTAMP,
|
||||
endBlockNumber: BigInt(calldataAggregatedProof1To155.finalBlockNumber),
|
||||
parentStateRootHash: calldataAggregatedProof1To155.parentStateRootHash,
|
||||
finalTimestamp: BigInt(calldataAggregatedProof1To155.finalTimestamp),
|
||||
l2MerkleRoots: calldataAggregatedProof1To155.l2MerkleRoots,
|
||||
l2MerkleTreesDepth: BigInt(calldataAggregatedProof1To155.l2MerkleTreesDepth),
|
||||
l2MessagingBlocksOffsets: calldataAggregatedProof1To155.l2MessagingBlocksOffsets,
|
||||
aggregatedProof: calldataAggregatedProof1To155.aggregatedProof,
|
||||
shnarfData: generateParentShnarfData(index),
|
||||
});
|
||||
|
||||
await lineaRollup.setRollingHash(
|
||||
calldataAggregatedProof1To155.l1RollingHashMessageNumber,
|
||||
calldataAggregatedProof1To155.l1RollingHash,
|
||||
);
|
||||
|
||||
finalizationData.shnarfData.snarkHash = generateRandomBytes(32);
|
||||
|
||||
const { dataEvaluationClaim, dataEvaluationPoint, finalStateRootHash, parentShnarf, snarkHash } =
|
||||
finalizationData.shnarfData;
|
||||
const expectedMissingBlobShnarf = generateKeccak256(
|
||||
["bytes32", "bytes32", "bytes32", "bytes32", "bytes32"],
|
||||
[parentShnarf, snarkHash, finalStateRootHash, dataEvaluationPoint, dataEvaluationClaim],
|
||||
);
|
||||
|
||||
const finalizeCompressedCall = lineaRollup
|
||||
.connect(operator)
|
||||
.finalizeBlocks(calldataAggregatedProof1To155.aggregatedProof, TEST_PUBLIC_VERIFIER_INDEX, finalizationData);
|
||||
|
||||
await expectRevertWithCustomError(lineaRollup, finalizeCompressedCall, "FinalBlobNotSubmitted", [
|
||||
expectedMissingBlobShnarf,
|
||||
]);
|
||||
});
|
||||
|
||||
it("Should revert if finalizationData.finalTimestamp is greater than the block.timestamp", async () => {
|
||||
const submissionDataBeforeFinalization = generateCallDataSubmission(0, 4);
|
||||
let index = 0;
|
||||
for (const data of submissionDataBeforeFinalization) {
|
||||
const parentAndExpectedShnarf = generateParentAndExpectedShnarfForIndex(index);
|
||||
await lineaRollup
|
||||
.connect(operator)
|
||||
.submitDataAsCalldata(data, parentAndExpectedShnarf.parentShnarf, parentAndExpectedShnarf.expectedShnarf, {
|
||||
gasLimit: 30_000_000,
|
||||
});
|
||||
index++;
|
||||
}
|
||||
|
||||
const finalizationData = await generateFinalizationData({
|
||||
l1RollingHash: calculateRollingHash(HASH_ZERO, messageHash),
|
||||
l1RollingHashMessageNumber: 10n,
|
||||
lastFinalizedTimestamp: DEFAULT_LAST_FINALIZED_TIMESTAMP,
|
||||
endBlockNumber: BigInt(calldataAggregatedProof1To155.finalBlockNumber),
|
||||
parentStateRootHash: calldataAggregatedProof1To155.parentStateRootHash,
|
||||
finalTimestamp: BigInt(new Date(new Date().setHours(new Date().getHours() + 2)).getTime()), // Set to 2 hours in the future
|
||||
l2MerkleRoots: calldataAggregatedProof1To155.l2MerkleRoots,
|
||||
l2MerkleTreesDepth: BigInt(calldataAggregatedProof1To155.l2MerkleTreesDepth),
|
||||
l2MessagingBlocksOffsets: calldataAggregatedProof1To155.l2MessagingBlocksOffsets,
|
||||
aggregatedProof: calldataAggregatedProof1To155.aggregatedProof,
|
||||
shnarfData: generateParentShnarfData(index),
|
||||
});
|
||||
|
||||
await lineaRollup.setRollingHash(
|
||||
calldataAggregatedProof1To155.l1RollingHashMessageNumber,
|
||||
calldataAggregatedProof1To155.l1RollingHash,
|
||||
);
|
||||
|
||||
const finalizeCompressedCall = lineaRollup
|
||||
.connect(operator)
|
||||
.finalizeBlocks(calldataAggregatedProof1To155.aggregatedProof, TEST_PUBLIC_VERIFIER_INDEX, finalizationData);
|
||||
|
||||
await expectRevertWithCustomError(lineaRollup, finalizeCompressedCall, "FinalizationInTheFuture", [
|
||||
finalizationData.finalTimestamp,
|
||||
(await networkTime.latest()) + 1,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Without submission data", () => {
|
||||
it("Should revert if the final block state equals the zero hash", async () => {
|
||||
const submissionDataBeforeFinalization = generateCallDataSubmission(0, 4);
|
||||
let index = 0;
|
||||
for (const data of submissionDataBeforeFinalization) {
|
||||
const parentAndExpectedShnarf = generateParentAndExpectedShnarfForIndex(index);
|
||||
await lineaRollup
|
||||
.connect(operator)
|
||||
.submitDataAsCalldata(data, parentAndExpectedShnarf.parentShnarf, parentAndExpectedShnarf.expectedShnarf, {
|
||||
gasLimit: 30_000_000,
|
||||
});
|
||||
index++;
|
||||
}
|
||||
|
||||
const finalizationData = await generateFinalizationData({
|
||||
l1RollingHash: calculateRollingHash(HASH_ZERO, messageHash),
|
||||
l1RollingHashMessageNumber: 10n,
|
||||
lastFinalizedTimestamp: DEFAULT_LAST_FINALIZED_TIMESTAMP,
|
||||
endBlockNumber: BigInt(calldataAggregatedProof1To155.finalBlockNumber),
|
||||
parentStateRootHash: calldataAggregatedProof1To155.parentStateRootHash,
|
||||
finalTimestamp: BigInt(calldataAggregatedProof1To155.finalTimestamp),
|
||||
l2MerkleRoots: calldataAggregatedProof1To155.l2MerkleRoots,
|
||||
l2MerkleTreesDepth: BigInt(calldataAggregatedProof1To155.l2MerkleTreesDepth),
|
||||
l2MessagingBlocksOffsets: calldataAggregatedProof1To155.l2MessagingBlocksOffsets,
|
||||
aggregatedProof: calldataAggregatedProof1To155.aggregatedProof,
|
||||
shnarfData: generateParentShnarfData(index),
|
||||
});
|
||||
|
||||
await lineaRollup.setRollingHash(
|
||||
calldataAggregatedProof1To155.l1RollingHashMessageNumber,
|
||||
calldataAggregatedProof1To155.l1RollingHash,
|
||||
);
|
||||
|
||||
// Set the final state root hash to zero
|
||||
finalizationData.shnarfData.finalStateRootHash = HASH_ZERO;
|
||||
|
||||
const finalizeCall = lineaRollup
|
||||
.connect(operator)
|
||||
.finalizeBlocks(calldataAggregatedProof1To155.aggregatedProof, TEST_PUBLIC_VERIFIER_INDEX, finalizationData);
|
||||
|
||||
await expectRevertWithCustomError(lineaRollup, finalizeCall, "FinalBlockStateEqualsZeroHash");
|
||||
});
|
||||
});
|
||||
|
||||
it("Can submit blobs and finalize with Prover Beta V1", async () => {
|
||||
const blobFiles = getBetaV1BlobFiles();
|
||||
const finalBlobFile = JSON.parse(
|
||||
fs.readFileSync(`${__dirname}/../../_testData/betaV1/${blobFiles.slice(-1)[0]}`, "utf-8"),
|
||||
);
|
||||
sepoliaFullVerifier = await deployPlonkVerifierSepoliaFull();
|
||||
|
||||
const initializationData = {
|
||||
initialStateRootHash: betaV1FinalizationData.parentStateRootHash,
|
||||
initialL2BlockNumber: betaV1FinalizationData.lastFinalizedBlockNumber,
|
||||
genesisTimestamp: betaV1FinalizationData.parentAggregationLastBlockTimestamp,
|
||||
defaultVerifier: sepoliaFullVerifier,
|
||||
rateLimitPeriodInSeconds: ONE_DAY_IN_SECONDS,
|
||||
rateLimitAmountInWei: INITIAL_WITHDRAW_LIMIT,
|
||||
roleAddresses,
|
||||
pauseTypeRoles: LINEA_ROLLUP_PAUSE_TYPES_ROLES,
|
||||
unpauseTypeRoles: LINEA_ROLLUP_UNPAUSE_TYPES_ROLES,
|
||||
fallbackOperator: FALLBACK_OPERATOR_ADDRESS,
|
||||
defaultAdmin: securityCouncil.address,
|
||||
};
|
||||
|
||||
const betaV1LineaRollup = (await deployUpgradableFromFactory("TestLineaRollup", [initializationData], {
|
||||
initializer: LINEA_ROLLUP_INITIALIZE_SIGNATURE,
|
||||
unsafeAllow: ["constructor", "incorrect-initializer-order"],
|
||||
})) as unknown as TestLineaRollup;
|
||||
|
||||
await betaV1LineaRollup.setupParentShnarf(betaV1FinalizationData.parentAggregationFinalShnarf);
|
||||
await betaV1LineaRollup.setLastFinalizedShnarf(betaV1FinalizationData.parentAggregationFinalShnarf);
|
||||
|
||||
for (let i = 0; i < blobFiles.length; i++) {
|
||||
await sendBlobTransactionFromFile(lineaRollup, blobFiles[i], betaV1LineaRollup);
|
||||
}
|
||||
|
||||
const finalizationData = await generateFinalizationData({
|
||||
l1RollingHash: betaV1FinalizationData.l1RollingHash,
|
||||
l1RollingHashMessageNumber: BigInt(betaV1FinalizationData.l1RollingHashMessageNumber),
|
||||
lastFinalizedTimestamp: BigInt(betaV1FinalizationData.parentAggregationLastBlockTimestamp),
|
||||
endBlockNumber: BigInt(betaV1FinalizationData.finalBlockNumber),
|
||||
parentStateRootHash: betaV1FinalizationData.parentStateRootHash,
|
||||
finalTimestamp: BigInt(betaV1FinalizationData.finalTimestamp),
|
||||
l2MerkleRoots: betaV1FinalizationData.l2MerkleRoots,
|
||||
l2MerkleTreesDepth: BigInt(betaV1FinalizationData.l2MerkleTreesDepth),
|
||||
l2MessagingBlocksOffsets: betaV1FinalizationData.l2MessagingBlocksOffsets,
|
||||
aggregatedProof: betaV1FinalizationData.aggregatedProof,
|
||||
shnarfData: {
|
||||
parentShnarf: finalBlobFile.prevShnarf,
|
||||
snarkHash: finalBlobFile.snarkHash,
|
||||
finalStateRootHash: finalBlobFile.finalStateRootHash,
|
||||
dataEvaluationPoint: finalBlobFile.expectedX,
|
||||
dataEvaluationClaim: finalBlobFile.expectedY,
|
||||
},
|
||||
});
|
||||
|
||||
finalizationData.lastFinalizedL1RollingHash = betaV1FinalizationData.parentAggregationLastL1RollingHash;
|
||||
finalizationData.lastFinalizedL1RollingHashMessageNumber = BigInt(
|
||||
betaV1FinalizationData.parentAggregationLastL1RollingHashMessageNumber,
|
||||
);
|
||||
|
||||
await betaV1LineaRollup.setLastFinalizedState(
|
||||
betaV1FinalizationData.parentAggregationLastL1RollingHashMessageNumber,
|
||||
betaV1FinalizationData.parentAggregationLastL1RollingHash,
|
||||
betaV1FinalizationData.parentAggregationLastBlockTimestamp,
|
||||
);
|
||||
await betaV1LineaRollup.setRollingHash(
|
||||
betaV1FinalizationData.l1RollingHashMessageNumber,
|
||||
betaV1FinalizationData.l1RollingHash,
|
||||
);
|
||||
|
||||
const finalizeCompressedCall = betaV1LineaRollup
|
||||
.connect(operator)
|
||||
.finalizeBlocks(betaV1FinalizationData.aggregatedProof, TEST_PUBLIC_VERIFIER_INDEX, finalizationData);
|
||||
|
||||
const eventArgs = [
|
||||
BigInt(betaV1FinalizationData.lastFinalizedBlockNumber) + 1n,
|
||||
finalizationData.endBlockNumber,
|
||||
betaV1FinalizationData.finalShnarf,
|
||||
finalizationData.parentStateRootHash,
|
||||
finalBlobFile.finalStateRootHash,
|
||||
];
|
||||
|
||||
await expectEvent(betaV1LineaRollup, finalizeCompressedCall, "DataFinalizedV3", eventArgs);
|
||||
|
||||
const [expectedFinalStateRootHash, lastFinalizedBlockNumber, lastFinalizedState] = await Promise.all([
|
||||
betaV1LineaRollup.stateRootHashes(finalizationData.endBlockNumber),
|
||||
betaV1LineaRollup.currentL2BlockNumber(),
|
||||
betaV1LineaRollup.currentFinalizedState(),
|
||||
]);
|
||||
|
||||
expect(expectedFinalStateRootHash).to.equal(finalizationData.shnarfData.finalStateRootHash);
|
||||
expect(lastFinalizedBlockNumber).to.equal(finalizationData.endBlockNumber);
|
||||
expect(lastFinalizedState).to.equal(
|
||||
generateKeccak256(
|
||||
["uint256", "bytes32", "uint256"],
|
||||
[
|
||||
finalizationData.l1RollingHashMessageNumber,
|
||||
finalizationData.l1RollingHash,
|
||||
finalizationData.finalTimestamp,
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Compressed data finalization with proof", () => {
|
||||
beforeEach(async () => {
|
||||
await lineaRollup.setLastFinalizedBlock(0);
|
||||
});
|
||||
|
||||
it("Should revert if the caller does not have the OPERATOR_ROLE", async () => {
|
||||
const finalizationData = await generateFinalizationData();
|
||||
|
||||
const finalizeCall = lineaRollup
|
||||
.connect(nonAuthorizedAccount)
|
||||
.finalizeBlocks(calldataAggregatedProof1To155.aggregatedProof, TEST_PUBLIC_VERIFIER_INDEX, finalizationData);
|
||||
await expectRevertWithReason(finalizeCall, buildAccessErrorMessage(nonAuthorizedAccount, OPERATOR_ROLE));
|
||||
});
|
||||
|
||||
it("Should revert if GENERAL_PAUSE_TYPE is enabled", async () => {
|
||||
const finalizationData = await generateFinalizationData();
|
||||
|
||||
await lineaRollup.connect(securityCouncil).pauseByType(GENERAL_PAUSE_TYPE);
|
||||
|
||||
const finalizeCall = lineaRollup
|
||||
.connect(operator)
|
||||
.finalizeBlocks(EMPTY_CALLDATA, TEST_PUBLIC_VERIFIER_INDEX, finalizationData);
|
||||
await expectRevertWithCustomError(lineaRollup, finalizeCall, "IsPaused", [GENERAL_PAUSE_TYPE]);
|
||||
});
|
||||
|
||||
it("Should revert if FINALIZATION_PAUSE_TYPE is enabled", async () => {
|
||||
const finalizationData = await generateFinalizationData();
|
||||
|
||||
await lineaRollup.connect(securityCouncil).pauseByType(FINALIZATION_PAUSE_TYPE);
|
||||
|
||||
const finalizeCall = lineaRollup
|
||||
.connect(operator)
|
||||
.finalizeBlocks(EMPTY_CALLDATA, TEST_PUBLIC_VERIFIER_INDEX, finalizationData);
|
||||
await expectRevertWithCustomError(lineaRollup, finalizeCall, "IsPaused", [FINALIZATION_PAUSE_TYPE]);
|
||||
});
|
||||
|
||||
it("Should revert if the proof is empty", async () => {
|
||||
const finalizationData = await generateFinalizationData();
|
||||
|
||||
const finalizeCall = lineaRollup
|
||||
.connect(operator)
|
||||
.finalizeBlocks(EMPTY_CALLDATA, TEST_PUBLIC_VERIFIER_INDEX, finalizationData);
|
||||
await expectRevertWithCustomError(lineaRollup, finalizeCall, "ProofIsEmpty");
|
||||
});
|
||||
|
||||
it("Should revert when finalization parentStateRootHash is different than last finalized state root hash", async () => {
|
||||
// Submit 4 sets of compressed data setting the correct shnarf in storage
|
||||
const submissionDataBeforeFinalization = generateCallDataSubmission(0, 4);
|
||||
|
||||
let index = 0;
|
||||
for (const data of submissionDataBeforeFinalization) {
|
||||
const parentAndExpectedShnarf = generateParentAndExpectedShnarfForIndex(index);
|
||||
await lineaRollup
|
||||
.connect(operator)
|
||||
.submitDataAsCalldata(data, parentAndExpectedShnarf.parentShnarf, parentAndExpectedShnarf.expectedShnarf, {
|
||||
gasLimit: 30_000_000,
|
||||
});
|
||||
index++;
|
||||
}
|
||||
|
||||
const finalizationData = await generateFinalizationData({
|
||||
lastFinalizedTimestamp: DEFAULT_LAST_FINALIZED_TIMESTAMP,
|
||||
parentStateRootHash: generateRandomBytes(32),
|
||||
aggregatedProof: calldataAggregatedProof1To155.aggregatedProof,
|
||||
});
|
||||
|
||||
const finalizeCall = lineaRollup
|
||||
.connect(operator)
|
||||
.finalizeBlocks(calldataAggregatedProof1To155.aggregatedProof, TEST_PUBLIC_VERIFIER_INDEX, finalizationData, {
|
||||
gasLimit: 30_000_000,
|
||||
});
|
||||
await expectRevertWithCustomError(lineaRollup, finalizeCall, "StartingRootHashDoesNotMatch");
|
||||
});
|
||||
|
||||
it("Should successfully finalize with only previously submitted data", async () => {
|
||||
// Submit 4 sets of compressed data setting the correct shnarf in storage
|
||||
const submissionDataBeforeFinalization = generateCallDataSubmission(0, 4);
|
||||
let index = 0;
|
||||
for (const data of submissionDataBeforeFinalization) {
|
||||
const parentAndExpectedShnarf = generateParentAndExpectedShnarfForIndex(index);
|
||||
await lineaRollup
|
||||
.connect(operator)
|
||||
.submitDataAsCalldata(data, parentAndExpectedShnarf.parentShnarf, parentAndExpectedShnarf.expectedShnarf, {
|
||||
gasLimit: 30_000_000,
|
||||
});
|
||||
index++;
|
||||
}
|
||||
|
||||
await expectSuccessfulFinalize(
|
||||
lineaRollup,
|
||||
operator,
|
||||
calldataAggregatedProof1To155,
|
||||
index,
|
||||
fourthCompressedDataContent.finalStateRootHash,
|
||||
generateParentShnarfData,
|
||||
);
|
||||
});
|
||||
|
||||
it("Should revert when proofType is invalid", async () => {
|
||||
const submissionDataBeforeFinalization = generateCallDataSubmission(0, 4);
|
||||
let index = 0;
|
||||
for (const data of submissionDataBeforeFinalization) {
|
||||
const parentAndExpectedShnarf = generateParentAndExpectedShnarfForIndex(index);
|
||||
await lineaRollup
|
||||
.connect(operator)
|
||||
.submitDataAsCalldata(data, parentAndExpectedShnarf.parentShnarf, parentAndExpectedShnarf.expectedShnarf, {
|
||||
gasLimit: 30_000_000,
|
||||
});
|
||||
index++;
|
||||
}
|
||||
|
||||
const finalizationData = await generateFinalizationData({
|
||||
l1RollingHash: calldataAggregatedProof1To155.l1RollingHash,
|
||||
l1RollingHashMessageNumber: BigInt(calldataAggregatedProof1To155.l1RollingHashMessageNumber),
|
||||
lastFinalizedTimestamp: BigInt(calldataAggregatedProof1To155.parentAggregationLastBlockTimestamp),
|
||||
endBlockNumber: BigInt(calldataAggregatedProof1To155.finalBlockNumber),
|
||||
parentStateRootHash: calldataAggregatedProof1To155.parentStateRootHash,
|
||||
finalTimestamp: BigInt(calldataAggregatedProof1To155.finalTimestamp),
|
||||
l2MerkleRoots: calldataAggregatedProof1To155.l2MerkleRoots,
|
||||
l2MerkleTreesDepth: BigInt(calldataAggregatedProof1To155.l2MerkleTreesDepth),
|
||||
l2MessagingBlocksOffsets: calldataAggregatedProof1To155.l2MessagingBlocksOffsets,
|
||||
aggregatedProof: calldataAggregatedProof1To155.aggregatedProof,
|
||||
shnarfData: generateParentShnarfData(index),
|
||||
});
|
||||
|
||||
await lineaRollup.setRollingHash(
|
||||
calldataAggregatedProof1To155.l1RollingHashMessageNumber,
|
||||
calldataAggregatedProof1To155.l1RollingHash,
|
||||
);
|
||||
|
||||
const finalizeCall = lineaRollup
|
||||
.connect(operator)
|
||||
.finalizeBlocks(calldataAggregatedProof1To155.aggregatedProof, 99, finalizationData);
|
||||
await expectRevertWithCustomError(lineaRollup, finalizeCall, "InvalidProofType");
|
||||
});
|
||||
|
||||
it("Should revert when using a proofType index that was removed", async () => {
|
||||
const submissionDataBeforeFinalization = generateCallDataSubmission(0, 4);
|
||||
let index = 0;
|
||||
for (const data of submissionDataBeforeFinalization) {
|
||||
const parentAndExpectedShnarf = generateParentAndExpectedShnarfForIndex(index);
|
||||
await lineaRollup
|
||||
.connect(operator)
|
||||
.submitDataAsCalldata(data, parentAndExpectedShnarf.parentShnarf, parentAndExpectedShnarf.expectedShnarf, {
|
||||
gasLimit: 30_000_000,
|
||||
});
|
||||
index++;
|
||||
}
|
||||
|
||||
const finalizationData = await generateFinalizationData({
|
||||
l1RollingHash: calldataAggregatedProof1To155.l1RollingHash,
|
||||
l1RollingHashMessageNumber: BigInt(calldataAggregatedProof1To155.l1RollingHashMessageNumber),
|
||||
lastFinalizedTimestamp: BigInt(calldataAggregatedProof1To155.parentAggregationLastBlockTimestamp),
|
||||
endBlockNumber: BigInt(calldataAggregatedProof1To155.finalBlockNumber),
|
||||
parentStateRootHash: calldataAggregatedProof1To155.parentStateRootHash,
|
||||
finalTimestamp: BigInt(calldataAggregatedProof1To155.finalTimestamp),
|
||||
l2MerkleRoots: calldataAggregatedProof1To155.l2MerkleRoots,
|
||||
l2MerkleTreesDepth: BigInt(calldataAggregatedProof1To155.l2MerkleTreesDepth),
|
||||
l2MessagingBlocksOffsets: calldataAggregatedProof1To155.l2MessagingBlocksOffsets,
|
||||
aggregatedProof: calldataAggregatedProof1To155.aggregatedProof,
|
||||
shnarfData: generateParentShnarfData(index),
|
||||
});
|
||||
|
||||
await lineaRollup.setRollingHash(
|
||||
calldataAggregatedProof1To155.l1RollingHashMessageNumber,
|
||||
calldataAggregatedProof1To155.l1RollingHash,
|
||||
);
|
||||
|
||||
// removing the verifier index
|
||||
await lineaRollup.connect(securityCouncil).unsetVerifierAddress(TEST_PUBLIC_VERIFIER_INDEX);
|
||||
|
||||
const finalizeCall = lineaRollup
|
||||
.connect(operator)
|
||||
.finalizeBlocks(calldataAggregatedProof1To155.aggregatedProof, TEST_PUBLIC_VERIFIER_INDEX, finalizationData);
|
||||
await expectRevertWithCustomError(lineaRollup, finalizeCall, "InvalidProofType");
|
||||
});
|
||||
|
||||
it("Should fail when proof does not match", async () => {
|
||||
const submissionDataBeforeFinalization = generateCallDataSubmission(0, 4);
|
||||
let index = 0;
|
||||
for (const data of submissionDataBeforeFinalization) {
|
||||
const parentAndExpectedShnarf = generateParentAndExpectedShnarfForIndex(index);
|
||||
await lineaRollup
|
||||
.connect(operator)
|
||||
.submitDataAsCalldata(data, parentAndExpectedShnarf.parentShnarf, parentAndExpectedShnarf.expectedShnarf, {
|
||||
gasLimit: 30_000_000,
|
||||
});
|
||||
index++;
|
||||
}
|
||||
|
||||
const finalizationData = await generateFinalizationData({
|
||||
l1RollingHash: calldataAggregatedProof1To155.l1RollingHash,
|
||||
l1RollingHashMessageNumber: BigInt(calldataAggregatedProof1To155.l1RollingHashMessageNumber),
|
||||
lastFinalizedTimestamp: BigInt(calldataAggregatedProof1To155.parentAggregationLastBlockTimestamp),
|
||||
endBlockNumber: BigInt(calldataAggregatedProof1To155.finalBlockNumber),
|
||||
parentStateRootHash: calldataAggregatedProof1To155.parentStateRootHash,
|
||||
finalTimestamp: BigInt(calldataAggregatedProof1To155.finalTimestamp),
|
||||
l2MerkleRoots: calldataAggregatedProof1To155.l2MerkleRoots,
|
||||
l2MerkleTreesDepth: BigInt(calldataAggregatedProof1To155.l2MerkleTreesDepth),
|
||||
l2MessagingBlocksOffsets: calldataAggregatedProof1To155.l2MessagingBlocksOffsets,
|
||||
aggregatedProof: calldataAggregatedProof1To155.aggregatedProof,
|
||||
shnarfData: generateParentShnarfData(index),
|
||||
});
|
||||
|
||||
await lineaRollup.setRollingHash(
|
||||
calldataAggregatedProof1To155.l1RollingHashMessageNumber,
|
||||
calldataAggregatedProof1To155.l1RollingHash,
|
||||
);
|
||||
|
||||
// aggregatedProof1To81.aggregatedProof, wrong proof on purpose
|
||||
const finalizeCall = lineaRollup
|
||||
.connect(operator)
|
||||
.finalizeBlocks(aggregatedProof1To81.aggregatedProof, TEST_PUBLIC_VERIFIER_INDEX, finalizationData);
|
||||
await expectRevertWithCustomError(lineaRollup, finalizeCall, "InvalidProof");
|
||||
});
|
||||
|
||||
it("Should fail if shnarf does not exist when finalizing", async () => {
|
||||
const submissionDataBeforeFinalization = generateCallDataSubmission(0, 4);
|
||||
let index = 0;
|
||||
for (const data of submissionDataBeforeFinalization) {
|
||||
const parentAndExpectedShnarf = generateParentAndExpectedShnarfForIndex(index);
|
||||
await lineaRollup
|
||||
.connect(operator)
|
||||
.submitDataAsCalldata(data, parentAndExpectedShnarf.parentShnarf, parentAndExpectedShnarf.expectedShnarf, {
|
||||
gasLimit: 30_000_000,
|
||||
});
|
||||
index++;
|
||||
}
|
||||
|
||||
const finalizationData = await generateFinalizationData({
|
||||
l1RollingHash: calldataAggregatedProof1To155.l1RollingHash,
|
||||
l1RollingHashMessageNumber: BigInt(calldataAggregatedProof1To155.l1RollingHashMessageNumber),
|
||||
lastFinalizedTimestamp: BigInt(calldataAggregatedProof1To155.parentAggregationLastBlockTimestamp),
|
||||
endBlockNumber: BigInt(calldataAggregatedProof1To155.finalBlockNumber),
|
||||
parentStateRootHash: calldataAggregatedProof1To155.parentStateRootHash,
|
||||
finalTimestamp: BigInt(calldataAggregatedProof1To155.finalTimestamp),
|
||||
l2MerkleRoots: calldataAggregatedProof1To155.l2MerkleRoots,
|
||||
l2MerkleTreesDepth: BigInt(calldataAggregatedProof1To155.l2MerkleTreesDepth),
|
||||
l2MessagingBlocksOffsets: calldataAggregatedProof1To155.l2MessagingBlocksOffsets,
|
||||
aggregatedProof: calldataAggregatedProof1To155.aggregatedProof,
|
||||
shnarfData: generateParentShnarfData(1),
|
||||
});
|
||||
|
||||
await lineaRollup.setRollingHash(
|
||||
calldataAggregatedProof1To155.l1RollingHashMessageNumber,
|
||||
calldataAggregatedProof1To155.l1RollingHash,
|
||||
);
|
||||
|
||||
const finalizeCall = lineaRollup
|
||||
.connect(operator)
|
||||
.finalizeBlocks(calldataAggregatedProof1To155.aggregatedProof, TEST_PUBLIC_VERIFIER_INDEX, finalizationData);
|
||||
await expectRevertWithCustomError(lineaRollup, finalizeCall, "InvalidProof");
|
||||
});
|
||||
|
||||
it("Should successfully finalize 1-81 and then 82-153 in two separate finalizations", async () => {
|
||||
const submissionDataBeforeFinalization = generateCallDataSubmissionMultipleProofs(0, 4);
|
||||
let index = 0;
|
||||
for (const data of submissionDataBeforeFinalization) {
|
||||
const parentAndExpectedShnarf = generateParentAndExpectedShnarfForMulitpleIndex(index);
|
||||
await lineaRollup
|
||||
.connect(operator)
|
||||
.submitDataAsCalldata(data, parentAndExpectedShnarf.parentShnarf, parentAndExpectedShnarf.expectedShnarf, {
|
||||
gasLimit: 30_000_000,
|
||||
});
|
||||
index++;
|
||||
}
|
||||
|
||||
await expectSuccessfulFinalize(
|
||||
lineaRollup,
|
||||
operator,
|
||||
aggregatedProof1To81,
|
||||
2,
|
||||
secondCompressedDataContent.finalStateRootHash,
|
||||
generateParentShnarfData,
|
||||
true,
|
||||
);
|
||||
|
||||
await expectSuccessfulFinalize(
|
||||
lineaRollup,
|
||||
operator,
|
||||
aggregatedProof82To153,
|
||||
4,
|
||||
fourthMultipleCompressedDataContent.finalStateRootHash,
|
||||
generateParentShnarfData,
|
||||
true,
|
||||
aggregatedProof1To81.l1RollingHash,
|
||||
BigInt(aggregatedProof1To81.l1RollingHashMessageNumber),
|
||||
);
|
||||
});
|
||||
|
||||
it("Should succeed when sending with pure calldata", async () => {
|
||||
const submissionDataBeforeFinalization = generateCallDataSubmissionMultipleProofs(0, 4);
|
||||
let index = 0;
|
||||
for (const data of submissionDataBeforeFinalization) {
|
||||
const parentAndExpectedShnarf = generateParentAndExpectedShnarfForMulitpleIndex(index);
|
||||
await lineaRollup
|
||||
.connect(operator)
|
||||
.submitDataAsCalldata(data, parentAndExpectedShnarf.parentShnarf, parentAndExpectedShnarf.expectedShnarf, {
|
||||
gasLimit: 30_000_000,
|
||||
});
|
||||
index++;
|
||||
}
|
||||
await lineaRollup.setRollingHash(
|
||||
aggregatedProof1To81.l1RollingHashMessageNumber,
|
||||
aggregatedProof1To81.l1RollingHash,
|
||||
);
|
||||
|
||||
const lineaRollupAddress = await lineaRollup.getAddress();
|
||||
|
||||
const { maxFeePerGas, maxPriorityFeePerGas } = await ethers.provider.getFeeData();
|
||||
|
||||
const encodedCall =
|
||||
"0x5603c65f0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e00000000000000000000000000000000000000000000000000000000000000360008e483358dc0ac1d3f6a27b43f6332e88c4196151b2a1ce6c8575013cec2cbe2f666e5649165dfc5a88ede985203341f5910ec2f4e3d4e9fa2a0a2621e4b0a70a91e0f995a8d754a3ddec1698800be83da8815650488540d2ffb699eaf3518d14bff479974f1f5f8bef7687f89c6301247cd92a4ccfa76a868bd24090a670f20eb06513431e74411648ab8c02a270353146bc759451d00cf2a59477d6106b9d11df3e18bca0d3a36951e31699b21a73e22a77fb7fe43a33b717a23dd06a889a102e4143d1e024e053e4318f903c3dbd0ec0f6e49672dd0ccb10749ac00adaf311149a24bf2c2ee4cfe7ed1db713526c6f7c0893f2ec153e5f55a4b390ce135a0d3ebe372c1beea8142afebdc96fe2764b8b5a33efc0457b9ad65f678c8b9ede196eb1fe34a82e0f370fe1d53e5cfc34bc77f38a64eddbb74f6530a82feb0e0a148de4d309b179697ff8a3c97a5516a1bbffc36d8cf153e9f5ee8fdc98002fd1136a2a12603aa1c7a985b34c5dc6774a5efd6270eac180406b4f03c7ef08eee428bb45d04a3c6635a0225aae76c0522049df5080045629cd6a686c2d50d8b7fe10e6facded652c8d75d4a67d5f8f4c3c6b491f44cc23c307064ec94f7c3a44eb1d1dcd369464960e54a88ad3f9edbdd818f2ac711d6c8f49efe224fb2616f2ac0005ca0c753c801d9e2df1133cdd4de5e54554197ac3e2f42d0b140261a12796050d64ec14bb22355173d157a7a2a9a5c0a20d47430819b4c106fda54923f9bf273cbc4145962e07b853279e64e198d3da5919814935c4cf12ffc03128d0e4fc24e8f35180d257253c0ce33725cb665a81141f16418fb20cf2abb96fa65a03bc0fa8e1e842123e2a2590f423e34c1db2c51c9e872ea41942d44d94d735cf8b4b0ccc265c6bdc41dd6f1167da133450a5ea0c07ea4704c9ca377cd201d8a63f932f7d4c9edb88269008bfe08d91d576cbf1a46c2da88b13d2a38e68f66f330552114f37a226c38ed3a9acd35b75f5852e6879eb3c8746ed2676f9e3957d32bbee1493ac055f03a56e964c4ea278d323abde55b36439b5460897771bf5ef3be1992448a5479f3515d5605d403a51391c6c6db94a90b3353aa956a5ba6f7b951fee2b7657ab431f90220c84820c2db8e4b57eaa5c3ea13e8d1f4ed31b9434c5df5c083dbb24fdf9dc5a64ba7416e6d154e6b17727a3acaa9f59820e926196efd232072ead6777750dc20232d1cee8dc9a395c2d350df4bbaa5096c6f59b214dcecd00000000000000000000000000000000000000000000000000000000000000519562fa89830a0ba0063a636ca96e52ce2f032b855336c95dd788321f4e1934190eacb8ed649249b3ec6efd9fbd6ffebe1b325b53380a91f7d689bfc1aff3b6dcf0f26782f7afb93f926cacb145f55530714f20b1356725e3971dc99e0ef8b59101216d3e1700c3a0d5115686fa51caa982cb4e002a5bb9f9488c9c44e4d9a3042d2f290de42ce8ea03cf8ac09288166932f174cb069ff8147c95ed6374e4e6cb00000000000000000000000000000000000000000000000000000000645580d1000000000000000000000000000000000000000000000000000000006455a7e10000000000000000000000000000000000000000000000000000000000000000dc8e70637c1048e1e0406c4ed6fab51a7489ccb52f37ddd2c135cb1aa18ec6970000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000016de197db892fd7d3fe0a389452dae0c5d0520e23d18ad20327546e2189a7e3f1000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000fffff000000000000000000000000000000000000";
|
||||
const transaction = {
|
||||
data: encodedCall,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGas!,
|
||||
maxFeePerGas: maxFeePerGas!,
|
||||
to: lineaRollupAddress,
|
||||
chainId: 31337,
|
||||
value: 0,
|
||||
gasLimit: 5_000_000,
|
||||
};
|
||||
|
||||
await expect(operator.sendTransaction(transaction)).to.not.be.reverted;
|
||||
});
|
||||
|
||||
it("Should fail when sending with wrong merkle root location", async () => {
|
||||
const submissionDataBeforeFinalization = generateCallDataSubmissionMultipleProofs(0, 4);
|
||||
let index = 0;
|
||||
for (const data of submissionDataBeforeFinalization) {
|
||||
const parentAndExpectedShnarf = generateParentAndExpectedShnarfForMulitpleIndex(index);
|
||||
await lineaRollup
|
||||
.connect(operator)
|
||||
.submitDataAsCalldata(data, parentAndExpectedShnarf.parentShnarf, parentAndExpectedShnarf.expectedShnarf, {
|
||||
gasLimit: 30_000_000,
|
||||
});
|
||||
index++;
|
||||
}
|
||||
|
||||
await lineaRollup.setRollingHash(
|
||||
aggregatedProof1To81.l1RollingHashMessageNumber,
|
||||
aggregatedProof1To81.l1RollingHash,
|
||||
);
|
||||
|
||||
const lineaRollupAddress = await lineaRollup.getAddress();
|
||||
|
||||
const { maxFeePerGas, maxPriorityFeePerGas } = await ethers.provider.getFeeData();
|
||||
|
||||
// This contains the Merkle roots length for public input at 0x220 and a contrived pointer to an alternate location.
|
||||
const encodedCall =
|
||||
"0x5603c65f0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e00000000000000000000000000000000000000000000000000000000000000360008e483358dc0ac1d3f6a27b43f6332e88c4196151b2a1ce6c8575013cec2cbe2f666e5649165dfc5a88ede985203341f5910ec2f4e3d4e9fa2a0a2621e4b0a70a91e0f995a8d754a3ddec1698800be83da8815650488540d2ffb699eaf3518d14bff479974f1f5f8bef7687f89c6301247cd92a4ccfa76a868bd24090a670f20eb06513431e74411648ab8c02a270353146bc759451d00cf2a59477d6106b9d11df3e18bca0d3a36951e31699b21a73e22a77fb7fe43a33b717a23dd06a889a102e4143d1e024e053e4318f903c3dbd0ec0f6e49672dd0ccb10749ac00adaf311149a24bf2c2ee4cfe7ed1db713526c6f7c0893f2ec153e5f55a4b390ce135a0d3ebe372c1beea8142afebdc96fe2764b8b5a33efc0457b9ad65f678c8b9ede196eb1fe34a82e0f370fe1d53e5cfc34bc77f38a64eddbb74f6530a82feb0e0a148de4d309b179697ff8a3c97a5516a1bbffc36d8cf153e9f5ee8fdc98002fd1136a2a12603aa1c7a985b34c5dc6774a5efd6270eac180406b4f03c7ef08eee428bb45d04a3c6635a0225aae76c0522049df5080045629cd6a686c2d50d8b7fe10e6facded652c8d75d4a67d5f8f4c3c6b491f44cc23c307064ec94f7c3a44eb1d1dcd369464960e54a88ad3f9edbdd818f2ac711d6c8f49efe224fb2616f2ac0005ca0c753c801d9e2df1133cdd4de5e54554197ac3e2f42d0b140261a12796050d64ec14bb22355173d157a7a2a9a5c0a20d47430819b4c106fda54923f9bf273cbc4145962e07b853279e64e198d3da5919814935c4cf12ffc03128d0e4fc24e8f35180d257253c0ce33725cb665a81141f16418fb20cf2abb96fa65a03bc0fa8e1e842123e2a2590f423e34c1db2c51c9e872ea41942d44d94d735cf8b4b0ccc265c6bdc41dd6f1167da133450a5ea0c07ea4704c9ca377cd201d8a63f932f7d4c9edb88269008bfe08d91d576cbf1a46c2da88b13d2a38e68f66f330552114f37a226c38ed3a9acd35b75f5852e6879eb3c8746ed2676f9e3957d32bbee1493ac055f03a56e964c4ea278d323abde55b36439b5460897771bf5ef3be1992448a5479f3515d5605d403a51391c6c6db94a90b3353aa956a5ba6f7b951fee2b7657ab431f90220c84820c2db8e4b57eaa5c3ea13e8d1f4ed31b9434c5df5c083dbb24fdf9dc5a64ba7416e6d154e6b17727a3acaa9f59820e926196efd232072ead6777750dc20232d1cee8dc9a395c2d350df4bbaa5096c6f59b214dcecd00000000000000000000000000000000000000000000000000000000000000519562fa89830a0ba0063a636ca96e52ce2f032b855336c95dd788321f4e1934190eacb8ed649249b3ec6efd9fbd6ffebe1b325b53380a91f7d689bfc1aff3b6dcf0f26782f7afb93f926cacb145f55530714f20b1356725e3971dc99e0ef8b59101216d3e1700c3a0d5115686fa51caa982cb4e002a5bb9f9488c9c44e4d9a3042d2f290de42ce8ea03cf8ac09288166932f174cb069ff8147c95ed6374e4e6cb00000000000000000000000000000000000000000000000000000000645580d1000000000000000000000000000000000000000000000000000000006455a7e10000000000000000000000000000000000000000000000000000000000000000dc8e70637c1048e1e0406c4ed6fab51a7489ccb52f37ddd2c135cb1aa18ec6970000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000016de197db892fd7d3fe0a389452dae0c5d0520e23d18ad20327546e2189a7e3f1000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000fffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000001cafecafe";
|
||||
const transaction = {
|
||||
data: encodedCall,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGas!,
|
||||
maxFeePerGas: maxFeePerGas!,
|
||||
to: lineaRollupAddress,
|
||||
chainId: 31337,
|
||||
value: 0,
|
||||
gasLimit: 5_000_000,
|
||||
};
|
||||
|
||||
await expectRevertWithCustomError(lineaRollup, operator.sendTransaction(transaction), "InvalidProof");
|
||||
});
|
||||
|
||||
it("Should fail to finalize with extra merkle roots", async () => {
|
||||
const submissionDataBeforeFinalization = generateCallDataSubmissionMultipleProofs(0, 4);
|
||||
let index = 0;
|
||||
for (const data of submissionDataBeforeFinalization) {
|
||||
const parentAndExpectedShnarf = generateParentAndExpectedShnarfForMulitpleIndex(index);
|
||||
await lineaRollup
|
||||
.connect(operator)
|
||||
.submitDataAsCalldata(data, parentAndExpectedShnarf.parentShnarf, parentAndExpectedShnarf.expectedShnarf, {
|
||||
gasLimit: 30_000_000,
|
||||
});
|
||||
index++;
|
||||
}
|
||||
|
||||
const merkleRoots = aggregatedProof1To81.l2MerkleRoots;
|
||||
merkleRoots.push(generateRandomBytes(32));
|
||||
|
||||
const finalizationData = await generateFinalizationData({
|
||||
l1RollingHash: aggregatedProof1To81.l1RollingHash,
|
||||
l1RollingHashMessageNumber: BigInt(aggregatedProof1To81.l1RollingHashMessageNumber),
|
||||
lastFinalizedTimestamp: BigInt(aggregatedProof1To81.parentAggregationLastBlockTimestamp),
|
||||
parentStateRootHash: aggregatedProof1To81.parentStateRootHash,
|
||||
finalTimestamp: BigInt(aggregatedProof1To81.finalTimestamp),
|
||||
l2MerkleRoots: merkleRoots,
|
||||
l2MerkleTreesDepth: BigInt(aggregatedProof1To81.l2MerkleTreesDepth),
|
||||
l2MessagingBlocksOffsets: aggregatedProof1To81.l2MessagingBlocksOffsets,
|
||||
aggregatedProof: aggregatedProof1To81.aggregatedProof,
|
||||
shnarfData: generateParentShnarfData(2, true),
|
||||
});
|
||||
|
||||
finalizationData.lastFinalizedL1RollingHash = HASH_ZERO;
|
||||
finalizationData.lastFinalizedL1RollingHashMessageNumber = 0n;
|
||||
|
||||
await lineaRollup.setRollingHash(
|
||||
aggregatedProof1To81.l1RollingHashMessageNumber,
|
||||
aggregatedProof1To81.l1RollingHash,
|
||||
);
|
||||
|
||||
const finalizeCall = lineaRollup
|
||||
.connect(operator)
|
||||
.finalizeBlocks(aggregatedProof1To81.aggregatedProof, TEST_PUBLIC_VERIFIER_INDEX, finalizationData);
|
||||
|
||||
await expectRevertWithCustomError(lineaRollup, finalizeCall, "InvalidProof");
|
||||
});
|
||||
});
|
||||
});
|
||||
33
contracts/test/hardhat/rollup/helpers/before.ts
Normal file
33
contracts/test/hardhat/rollup/helpers/before.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Define fixtures to be loaded in the 'before' block using Hardhat 'loadFixture()' function, e.g.
|
||||
|
||||
before(async () => {
|
||||
({ admin, securityCouncil, operator, nonAuthorizedAccount } = await loadFixture(getAccountsFixture));
|
||||
roleAddresses = await loadFixture(getRoleAddressesFixture);
|
||||
});
|
||||
|
||||
*/
|
||||
|
||||
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
|
||||
import { ethers } from "hardhat";
|
||||
import { LINEA_ROLLUP_ROLES } from "contracts/common/constants";
|
||||
import { generateRoleAssignments } from "contracts/common/helpers";
|
||||
import { OPERATOR_ROLE } from "../../common/constants";
|
||||
|
||||
// Use in `loadFixture(getAccountsFixture))` and not as a standalone function.
|
||||
// This will ensure that the same return values will be retrieved across all invocations.
|
||||
export async function getAccountsFixture() {
|
||||
const [admin, securityCouncil, operator, nonAuthorizedAccount] = await ethers.getSigners();
|
||||
return { admin, securityCouncil, operator, nonAuthorizedAccount };
|
||||
}
|
||||
|
||||
export async function getRoleAddressesFixture() {
|
||||
const { securityCouncil, operator } = await loadFixture(getAccountsFixture);
|
||||
const roleAddresses = generateRoleAssignments(LINEA_ROLLUP_ROLES, await securityCouncil.getAddress(), [
|
||||
{
|
||||
role: OPERATOR_ROLE,
|
||||
addresses: [operator.address],
|
||||
},
|
||||
]);
|
||||
return roleAddresses;
|
||||
}
|
||||
183
contracts/test/hardhat/rollup/helpers/blob.ts
Normal file
183
contracts/test/hardhat/rollup/helpers/blob.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import * as kzg from "c-kzg";
|
||||
import { BaseContract, Contract, Transaction } from "ethers";
|
||||
import * as fs from "fs";
|
||||
import { ethers } from "hardhat";
|
||||
import path from "path";
|
||||
|
||||
import { TestLineaRollup } from "contracts/typechain-types";
|
||||
import { getWalletForIndex } from "./";
|
||||
import {
|
||||
expectEventDirectFromReceiptData,
|
||||
generateBlobDataSubmission,
|
||||
generateBlobDataSubmissionFromFile,
|
||||
} from "../../common/helpers";
|
||||
|
||||
export async function sendBlobTransaction(
|
||||
lineaRollup: TestLineaRollup,
|
||||
startIndex: number,
|
||||
finalIndex: number,
|
||||
isMultiple: boolean = false,
|
||||
) {
|
||||
const operatorHDSigner = getWalletForIndex(2);
|
||||
const lineaRollupAddress = await lineaRollup.getAddress();
|
||||
|
||||
const {
|
||||
blobDataSubmission: blobSubmission,
|
||||
compressedBlobs: compressedBlobs,
|
||||
parentShnarf: parentShnarf,
|
||||
finalShnarf: finalShnarf,
|
||||
} = generateBlobDataSubmission(startIndex, finalIndex, isMultiple);
|
||||
|
||||
const encodedCall = lineaRollup.interface.encodeFunctionData("submitBlobs", [
|
||||
blobSubmission,
|
||||
parentShnarf,
|
||||
finalShnarf,
|
||||
]);
|
||||
|
||||
const { maxFeePerGas, maxPriorityFeePerGas } = await ethers.provider.getFeeData();
|
||||
const nonce = await operatorHDSigner.getNonce();
|
||||
|
||||
const transaction = Transaction.from({
|
||||
data: encodedCall,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGas!,
|
||||
maxFeePerGas: maxFeePerGas!,
|
||||
to: lineaRollupAddress,
|
||||
chainId: (await ethers.provider.getNetwork()).chainId,
|
||||
type: 3,
|
||||
nonce: nonce,
|
||||
value: 0,
|
||||
gasLimit: 5_000_000,
|
||||
kzg,
|
||||
maxFeePerBlobGas: 1n,
|
||||
blobs: compressedBlobs,
|
||||
});
|
||||
|
||||
const signedTx = await operatorHDSigner.signTransaction(transaction);
|
||||
const txResponse = await ethers.provider.broadcastTransaction(signedTx);
|
||||
|
||||
const receipt = await ethers.provider.getTransactionReceipt(txResponse.hash);
|
||||
|
||||
const expectedEventArgs = [parentShnarf, finalShnarf, blobSubmission[blobSubmission.length - 1].finalStateRootHash];
|
||||
|
||||
expectEventDirectFromReceiptData(lineaRollup as BaseContract, receipt!, "DataSubmittedV3", expectedEventArgs);
|
||||
}
|
||||
|
||||
export async function sendBlobTransactionFromFile(
|
||||
lineaRollup: TestLineaRollup,
|
||||
filePath: string,
|
||||
betaV1LineaRollup: TestLineaRollup,
|
||||
) {
|
||||
const operatorHDSigner = getWalletForIndex(2);
|
||||
const lineaRollupAddress = await betaV1LineaRollup.getAddress();
|
||||
|
||||
const {
|
||||
blobDataSubmission: blobSubmission,
|
||||
compressedBlobs: compressedBlobs,
|
||||
parentShnarf: parentShnarf,
|
||||
finalShnarf: finalShnarf,
|
||||
} = generateBlobDataSubmissionFromFile(path.resolve(__dirname, "../../_testData/betaV1", filePath));
|
||||
|
||||
const encodedCall = lineaRollup.interface.encodeFunctionData("submitBlobs", [
|
||||
blobSubmission,
|
||||
parentShnarf,
|
||||
finalShnarf,
|
||||
]);
|
||||
|
||||
const { maxFeePerGas, maxPriorityFeePerGas } = await ethers.provider.getFeeData();
|
||||
const nonce = await operatorHDSigner.getNonce();
|
||||
|
||||
const transaction = Transaction.from({
|
||||
data: encodedCall,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGas!,
|
||||
maxFeePerGas: maxFeePerGas!,
|
||||
to: lineaRollupAddress,
|
||||
chainId: (await ethers.provider.getNetwork()).chainId,
|
||||
type: 3,
|
||||
nonce: nonce,
|
||||
value: 0,
|
||||
gasLimit: 5_000_000,
|
||||
kzg,
|
||||
maxFeePerBlobGas: 1n,
|
||||
blobs: compressedBlobs,
|
||||
});
|
||||
|
||||
const signedTx = await operatorHDSigner.signTransaction(transaction);
|
||||
const txResponse = await ethers.provider.broadcastTransaction(signedTx);
|
||||
const receipt = await ethers.provider.getTransactionReceipt(txResponse.hash);
|
||||
const expectedEventArgs = [parentShnarf, finalShnarf, blobSubmission[blobSubmission.length - 1].finalStateRootHash];
|
||||
|
||||
expectEventDirectFromReceiptData(lineaRollup as BaseContract, receipt!, "DataSubmittedV3", expectedEventArgs);
|
||||
}
|
||||
|
||||
export async function sendBlobTransactionViaCallForwarder(
|
||||
lineaRollupUpgraded: Contract,
|
||||
startIndex: number,
|
||||
finalIndex: number,
|
||||
callforwarderAddress: string,
|
||||
) {
|
||||
const operatorHDSigner = getWalletForIndex(2);
|
||||
|
||||
const {
|
||||
blobDataSubmission: blobSubmission,
|
||||
compressedBlobs: compressedBlobs,
|
||||
parentShnarf: parentShnarf,
|
||||
finalShnarf: finalShnarf,
|
||||
} = generateBlobDataSubmission(startIndex, finalIndex, false);
|
||||
|
||||
const encodedCall = lineaRollupUpgraded.interface.encodeFunctionData("submitBlobs", [
|
||||
blobSubmission,
|
||||
parentShnarf,
|
||||
finalShnarf,
|
||||
]);
|
||||
|
||||
const { maxFeePerGas, maxPriorityFeePerGas } = await ethers.provider.getFeeData();
|
||||
const nonce = await operatorHDSigner.getNonce();
|
||||
|
||||
const transaction = Transaction.from({
|
||||
data: encodedCall,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGas!,
|
||||
maxFeePerGas: maxFeePerGas!,
|
||||
to: callforwarderAddress,
|
||||
chainId: (await ethers.provider.getNetwork()).chainId,
|
||||
type: 3,
|
||||
nonce: nonce,
|
||||
value: 0,
|
||||
gasLimit: 5_000_000,
|
||||
kzg,
|
||||
maxFeePerBlobGas: 1n,
|
||||
blobs: compressedBlobs,
|
||||
});
|
||||
|
||||
const signedTx = await operatorHDSigner.signTransaction(transaction);
|
||||
const txResponse = await ethers.provider.broadcastTransaction(signedTx);
|
||||
const receipt = await ethers.provider.getTransactionReceipt(txResponse.hash);
|
||||
|
||||
const expectedEventArgs = [parentShnarf, finalShnarf, blobSubmission[blobSubmission.length - 1].finalStateRootHash];
|
||||
|
||||
expectEventDirectFromReceiptData(lineaRollupUpgraded as BaseContract, receipt!, "DataSubmittedV3", expectedEventArgs);
|
||||
}
|
||||
|
||||
export function getBetaV1BlobFiles(): string[] {
|
||||
// Read all files in the folder
|
||||
const files = fs.readdirSync(path.resolve(__dirname, "../../_testData/betaV1"));
|
||||
|
||||
// Map files to their ranges and filter invalid ones
|
||||
const filesWithRanges = files
|
||||
.map((fileName) => {
|
||||
const range = extractBlockRangeFromFileName(fileName);
|
||||
return range ? { fileName, range } : null;
|
||||
})
|
||||
.filter(Boolean) as { fileName: string; range: [number, number] }[];
|
||||
|
||||
return filesWithRanges.sort((a, b) => a.range[0] - b.range[0]).map((f) => f.fileName);
|
||||
}
|
||||
|
||||
// Function to extract range from the file name
|
||||
function extractBlockRangeFromFileName(fileName: string): [number, number] | null {
|
||||
const rangeRegex = /(\d+)-(\d+)-/;
|
||||
const match = fileName.match(rangeRegex);
|
||||
if (match && match.length >= 3) {
|
||||
return [parseInt(match[1], 10), parseInt(match[2], 10)];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
73
contracts/test/hardhat/rollup/helpers/deploy.ts
Normal file
73
contracts/test/hardhat/rollup/helpers/deploy.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
|
||||
import { ethers } from "hardhat";
|
||||
|
||||
import firstCompressedDataContent from "../../_testData/compressedData/blocks-1-46.json";
|
||||
|
||||
import { LINEA_ROLLUP_PAUSE_TYPES_ROLES, LINEA_ROLLUP_UNPAUSE_TYPES_ROLES } from "contracts/common/constants";
|
||||
import { CallForwardingProxy, TestLineaRollup } from "contracts/typechain-types";
|
||||
import { getAccountsFixture, getRoleAddressesFixture } from "./";
|
||||
import {
|
||||
DEFAULT_LAST_FINALIZED_TIMESTAMP,
|
||||
FALLBACK_OPERATOR_ADDRESS,
|
||||
INITIAL_WITHDRAW_LIMIT,
|
||||
LINEA_ROLLUP_INITIALIZE_SIGNATURE,
|
||||
ONE_DAY_IN_SECONDS,
|
||||
} from "../../common/constants";
|
||||
import { deployUpgradableFromFactory } from "../../common/deployment";
|
||||
|
||||
export async function deployRevertingVerifier(scenario: bigint): Promise<string> {
|
||||
const revertingVerifierFactory = await ethers.getContractFactory("RevertingVerifier");
|
||||
const verifier = await revertingVerifierFactory.deploy(scenario);
|
||||
await verifier.waitForDeployment();
|
||||
return await verifier.getAddress();
|
||||
}
|
||||
|
||||
export async function deployPlonkVerifierSepoliaFull(): Promise<string> {
|
||||
const plonkVerifierSepoliaFull = await ethers.getContractFactory("PlonkVerifierSepoliaFull");
|
||||
const verifier = await plonkVerifierSepoliaFull.deploy();
|
||||
await verifier.waitForDeployment();
|
||||
return await verifier.getAddress();
|
||||
}
|
||||
|
||||
export async function deployCallForwardingProxy(target: string): Promise<CallForwardingProxy> {
|
||||
const callForwardingProxyFactory = await ethers.getContractFactory("CallForwardingProxy");
|
||||
const callForwardingProxy = await callForwardingProxyFactory.deploy(target);
|
||||
await callForwardingProxy.waitForDeployment();
|
||||
return callForwardingProxy;
|
||||
}
|
||||
|
||||
export async function deployLineaRollupFixture() {
|
||||
const { securityCouncil } = await loadFixture(getAccountsFixture);
|
||||
const roleAddresses = await loadFixture(getRoleAddressesFixture);
|
||||
|
||||
const verifier = await deployTestPlonkVerifierForDataAggregation();
|
||||
const { parentStateRootHash } = firstCompressedDataContent;
|
||||
|
||||
const initializationData = {
|
||||
initialStateRootHash: parentStateRootHash,
|
||||
initialL2BlockNumber: 0,
|
||||
genesisTimestamp: DEFAULT_LAST_FINALIZED_TIMESTAMP,
|
||||
defaultVerifier: verifier,
|
||||
rateLimitPeriodInSeconds: ONE_DAY_IN_SECONDS,
|
||||
rateLimitAmountInWei: INITIAL_WITHDRAW_LIMIT,
|
||||
roleAddresses,
|
||||
pauseTypeRoles: LINEA_ROLLUP_PAUSE_TYPES_ROLES,
|
||||
unpauseTypeRoles: LINEA_ROLLUP_UNPAUSE_TYPES_ROLES,
|
||||
fallbackOperator: FALLBACK_OPERATOR_ADDRESS,
|
||||
defaultAdmin: securityCouncil.address,
|
||||
};
|
||||
|
||||
const lineaRollup = (await deployUpgradableFromFactory("TestLineaRollup", [initializationData], {
|
||||
initializer: LINEA_ROLLUP_INITIALIZE_SIGNATURE,
|
||||
unsafeAllow: ["constructor", "incorrect-initializer-order"],
|
||||
})) as unknown as TestLineaRollup;
|
||||
|
||||
return { verifier, lineaRollup };
|
||||
}
|
||||
|
||||
async function deployTestPlonkVerifierForDataAggregation(): Promise<string> {
|
||||
const plonkVerifierSepoliaFull = await ethers.getContractFactory("TestPlonkVerifierForDataAggregation");
|
||||
const verifier = await plonkVerifierSepoliaFull.deploy();
|
||||
await verifier.waitForDeployment();
|
||||
return await verifier.getAddress();
|
||||
}
|
||||
215
contracts/test/hardhat/rollup/helpers/expect.ts
Normal file
215
contracts/test/hardhat/rollup/helpers/expect.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
import { expect } from "chai";
|
||||
import { BaseContract, Contract, Transaction } from "ethers";
|
||||
import { ethers } from "hardhat";
|
||||
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
|
||||
|
||||
import { TestLineaRollup } from "contracts/typechain-types";
|
||||
import { getWalletForIndex } from "./";
|
||||
import { HASH_ZERO, TEST_PUBLIC_VERIFIER_INDEX } from "../../common/constants";
|
||||
import {
|
||||
expectEvent,
|
||||
expectEventDirectFromReceiptData,
|
||||
generateFinalizationData,
|
||||
generateKeccak256,
|
||||
} from "../../common/helpers";
|
||||
import { ShnarfDataGenerator } from "../../common/types";
|
||||
|
||||
export async function expectSuccessfulFinalize(
|
||||
lineaRollup: TestLineaRollup,
|
||||
operator: SignerWithAddress,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
proofData: any,
|
||||
blobParentShnarfIndex: number,
|
||||
finalStateRootHash: string,
|
||||
shnarfDataGenerator: ShnarfDataGenerator,
|
||||
isMultiple: boolean = false,
|
||||
lastFinalizedRollingHash: string = HASH_ZERO,
|
||||
lastFinalizedMessageNumber: bigint = 0n,
|
||||
) {
|
||||
const finalizationData = await generateFinalizationData({
|
||||
l1RollingHash: proofData.l1RollingHash,
|
||||
l1RollingHashMessageNumber: BigInt(proofData.l1RollingHashMessageNumber),
|
||||
lastFinalizedTimestamp: BigInt(proofData.parentAggregationLastBlockTimestamp),
|
||||
endBlockNumber: BigInt(proofData.finalBlockNumber),
|
||||
parentStateRootHash: proofData.parentStateRootHash,
|
||||
finalTimestamp: BigInt(proofData.finalTimestamp),
|
||||
l2MerkleRoots: proofData.l2MerkleRoots,
|
||||
l2MerkleTreesDepth: BigInt(proofData.l2MerkleTreesDepth),
|
||||
l2MessagingBlocksOffsets: proofData.l2MessagingBlocksOffsets,
|
||||
aggregatedProof: proofData.aggregatedProof,
|
||||
shnarfData: shnarfDataGenerator(blobParentShnarfIndex, isMultiple),
|
||||
});
|
||||
finalizationData.lastFinalizedL1RollingHash = lastFinalizedRollingHash;
|
||||
finalizationData.lastFinalizedL1RollingHashMessageNumber = lastFinalizedMessageNumber;
|
||||
|
||||
await lineaRollup.setRollingHash(proofData.l1RollingHashMessageNumber, proofData.l1RollingHash);
|
||||
|
||||
const finalizeCompressedCall = lineaRollup
|
||||
.connect(operator)
|
||||
.finalizeBlocks(proofData.aggregatedProof, TEST_PUBLIC_VERIFIER_INDEX, finalizationData);
|
||||
|
||||
const eventArgs = [
|
||||
BigInt(proofData.lastFinalizedBlockNumber) + 1n,
|
||||
finalizationData.endBlockNumber,
|
||||
proofData.finalShnarf,
|
||||
finalizationData.parentStateRootHash,
|
||||
finalStateRootHash,
|
||||
];
|
||||
|
||||
await expectEvent(lineaRollup, finalizeCompressedCall, "DataFinalizedV3", eventArgs);
|
||||
|
||||
const [expectedFinalStateRootHash, lastFinalizedBlockNumber, lastFinalizedState] = await Promise.all([
|
||||
lineaRollup.stateRootHashes(finalizationData.endBlockNumber),
|
||||
lineaRollup.currentL2BlockNumber(),
|
||||
lineaRollup.currentFinalizedState(),
|
||||
]);
|
||||
|
||||
expect(expectedFinalStateRootHash).to.equal(finalizationData.shnarfData.finalStateRootHash);
|
||||
expect(lastFinalizedBlockNumber).to.equal(finalizationData.endBlockNumber);
|
||||
expect(lastFinalizedState).to.equal(
|
||||
generateKeccak256(
|
||||
["uint256", "bytes32", "uint256"],
|
||||
[finalizationData.l1RollingHashMessageNumber, finalizationData.l1RollingHash, finalizationData.finalTimestamp],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export async function expectSuccessfulFinalizeViaCallForwarder(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
proofData: any,
|
||||
blobParentShnarfIndex: number,
|
||||
finalStateRootHash: string,
|
||||
shnarfDataGenerator: ShnarfDataGenerator,
|
||||
isMultiple: boolean = false,
|
||||
lastFinalizedRollingHash: string = HASH_ZERO,
|
||||
lastFinalizedMessageNumber: bigint = 0n,
|
||||
callforwarderAddress: string,
|
||||
upgradedContract: Contract,
|
||||
) {
|
||||
const finalizationData = await generateFinalizationData({
|
||||
l1RollingHash: proofData.l1RollingHash,
|
||||
l1RollingHashMessageNumber: BigInt(proofData.l1RollingHashMessageNumber),
|
||||
lastFinalizedTimestamp: BigInt(proofData.parentAggregationLastBlockTimestamp),
|
||||
endBlockNumber: BigInt(proofData.finalBlockNumber),
|
||||
parentStateRootHash: proofData.parentStateRootHash,
|
||||
finalTimestamp: BigInt(proofData.finalTimestamp),
|
||||
l2MerkleRoots: proofData.l2MerkleRoots,
|
||||
l2MerkleTreesDepth: BigInt(proofData.l2MerkleTreesDepth),
|
||||
l2MessagingBlocksOffsets: proofData.l2MessagingBlocksOffsets,
|
||||
aggregatedProof: proofData.aggregatedProof,
|
||||
shnarfData: shnarfDataGenerator(blobParentShnarfIndex, isMultiple),
|
||||
});
|
||||
finalizationData.lastFinalizedL1RollingHash = lastFinalizedRollingHash;
|
||||
finalizationData.lastFinalizedL1RollingHashMessageNumber = lastFinalizedMessageNumber;
|
||||
|
||||
await upgradedContract.setRollingHash(proofData.l1RollingHashMessageNumber, proofData.l1RollingHash);
|
||||
|
||||
const shnarfData = shnarfDataGenerator(blobParentShnarfIndex, isMultiple);
|
||||
|
||||
const finalShnarf = generateKeccak256(
|
||||
["bytes32", "bytes32", "bytes32", "bytes32", "bytes32"],
|
||||
[
|
||||
shnarfData.parentShnarf,
|
||||
shnarfData.snarkHash,
|
||||
shnarfData.finalStateRootHash,
|
||||
shnarfData.dataEvaluationPoint,
|
||||
shnarfData.dataEvaluationClaim,
|
||||
],
|
||||
);
|
||||
const blobShnarfExists = await upgradedContract.blobShnarfExists(finalShnarf);
|
||||
expect(blobShnarfExists).to.equal(1n);
|
||||
|
||||
await upgradedContract.setRollingHash(proofData.l1RollingHashMessageNumber, proofData.l1RollingHash);
|
||||
|
||||
const txData = [
|
||||
proofData.aggregatedProof,
|
||||
0,
|
||||
[
|
||||
proofData.parentStateRootHash,
|
||||
BigInt(proofData.finalBlockNumber),
|
||||
[
|
||||
shnarfData.parentShnarf,
|
||||
shnarfData.snarkHash,
|
||||
shnarfData.finalStateRootHash,
|
||||
shnarfData.dataEvaluationPoint,
|
||||
shnarfData.dataEvaluationClaim,
|
||||
],
|
||||
proofData.parentAggregationLastBlockTimestamp,
|
||||
proofData.finalTimestamp,
|
||||
lastFinalizedRollingHash,
|
||||
proofData.l1RollingHash,
|
||||
lastFinalizedMessageNumber,
|
||||
proofData.l1RollingHashMessageNumber,
|
||||
proofData.l2MerkleTreesDepth,
|
||||
proofData.l2MerkleRoots,
|
||||
proofData.l2MessagingBlocksOffsets,
|
||||
],
|
||||
];
|
||||
|
||||
const encodedCall = ethers.concat([
|
||||
"0x5603c65f",
|
||||
ethers.AbiCoder.defaultAbiCoder().encode(
|
||||
[
|
||||
"bytes",
|
||||
"uint256",
|
||||
"tuple(bytes32,uint256,tuple(bytes32,bytes32,bytes32,bytes32,bytes32),uint256,uint256,bytes32,bytes32,uint256,uint256,uint256,bytes32[],bytes)",
|
||||
],
|
||||
txData,
|
||||
),
|
||||
]);
|
||||
|
||||
const { maxFeePerGas, maxPriorityFeePerGas } = await ethers.provider.getFeeData();
|
||||
const operatorHDSigner = getWalletForIndex(2);
|
||||
const nonce = await operatorHDSigner.getNonce();
|
||||
|
||||
const transaction = Transaction.from({
|
||||
data: encodedCall,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGas!,
|
||||
maxFeePerGas: maxFeePerGas!,
|
||||
to: callforwarderAddress,
|
||||
chainId: (await ethers.provider.getNetwork()).chainId,
|
||||
type: 2,
|
||||
nonce: nonce,
|
||||
value: 0,
|
||||
gasLimit: 5_000_000,
|
||||
});
|
||||
|
||||
const signedTx = await operatorHDSigner.signTransaction(transaction);
|
||||
|
||||
const txResponse = await ethers.provider.broadcastTransaction(signedTx);
|
||||
const receipt = await ethers.provider.getTransactionReceipt(txResponse.hash);
|
||||
expect(receipt).is.not.null;
|
||||
|
||||
const eventArgs = [
|
||||
BigInt(proofData.lastFinalizedBlockNumber) + 1n,
|
||||
finalizationData.endBlockNumber,
|
||||
proofData.finalShnarf,
|
||||
finalizationData.parentStateRootHash,
|
||||
finalStateRootHash,
|
||||
];
|
||||
|
||||
const dataFinalizedLogIndex = 8;
|
||||
|
||||
expectEventDirectFromReceiptData(
|
||||
upgradedContract as BaseContract,
|
||||
receipt!,
|
||||
"DataFinalizedV3",
|
||||
eventArgs,
|
||||
dataFinalizedLogIndex,
|
||||
);
|
||||
|
||||
const [expectedFinalStateRootHash, lastFinalizedBlockNumber, lastFinalizedState] = await Promise.all([
|
||||
upgradedContract.stateRootHashes(finalizationData.endBlockNumber),
|
||||
upgradedContract.currentL2BlockNumber(),
|
||||
upgradedContract.currentFinalizedState(),
|
||||
]);
|
||||
|
||||
expect(expectedFinalStateRootHash).to.equal(finalizationData.shnarfData.finalStateRootHash);
|
||||
expect(lastFinalizedBlockNumber).to.equal(finalizationData.endBlockNumber);
|
||||
expect(lastFinalizedState).to.equal(
|
||||
generateKeccak256(
|
||||
["uint256", "bytes32", "uint256"],
|
||||
[finalizationData.l1RollingHashMessageNumber, finalizationData.l1RollingHash, finalizationData.finalTimestamp],
|
||||
),
|
||||
);
|
||||
}
|
||||
5
contracts/test/hardhat/rollup/helpers/index.ts
Normal file
5
contracts/test/hardhat/rollup/helpers/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from "./before";
|
||||
export * from "./blob";
|
||||
export * from "./deploy";
|
||||
export * from "./expect";
|
||||
export * from "./wallet";
|
||||
9
contracts/test/hardhat/rollup/helpers/wallet.ts
Normal file
9
contracts/test/hardhat/rollup/helpers/wallet.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { HDNodeWallet, Wallet } from "ethers";
|
||||
import { config, ethers } from "hardhat";
|
||||
import { HardhatNetworkHDAccountsConfig } from "hardhat/types";
|
||||
|
||||
export const getWalletForIndex = (index: number) => {
|
||||
const accounts = config.networks.hardhat.accounts as HardhatNetworkHDAccountsConfig;
|
||||
const signer = HDNodeWallet.fromPhrase(accounts.mnemonic, "", `m/44'/60'/0'/0/${index}`);
|
||||
return new Wallet(signer.privateKey, ethers.provider);
|
||||
};
|
||||
Reference in New Issue
Block a user