mirror of
https://github.com/vacp2p/linea-monorepo.git
synced 2026-01-09 04:08:01 -05:00
* allow tokenbridge overrides * add L1MessageService overrides * refactor L2 MessageService * refactor L2 MessageService V1 * use correct modifier * refactor LineaRollup for overriding * allow other overrides * reinstate general in pause on tokenbridge * add missing NatSpec * sample overrides * add generic bridge and document placeholder * documentation and folder placement * documentation cleanup * use imported references * use variable pragma for inherited contracts * reset pragmas for some * use base abstract contracts with version overrides * use TokenBridgeBase as abstract * Update contracts/src/bridging/token/TokenBridgeBase.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/bridging/token/TokenBridgeBase.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/bridging/token/TokenBridgeBase.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/bridging/token/interfaces/ITokenBridge.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/messaging/l2/L2MessageServiceBase.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/messaging/l2/v1/interfaces/IL2MessageServiceV1.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/rollup/interfaces/ILineaRollup.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/rollup/LineaRollupBase.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/_testing/unit/bridging/InheritingTokenBridge.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/verifiers/PlonkVerifierDev.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/verifiers/PlonkVerifierForDataAggregation.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/verifiers/PlonkVerifierForMultiTypeDataAggregation.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/verifiers/PlonkVerifierMainnetFull.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/verifiers/PlonkVerifierSepoliaFull.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * linting * allow submitDataAsCalldata overriding * address missing test coverage * adjust gap name for storage clarity --------- Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com>
765 lines
31 KiB
TypeScript
765 lines
31 KiB
TypeScript
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
|
|
import { loadFixture, time as networkTime } from "@nomicfoundation/hardhat-network-helpers";
|
|
import * as kzg from "c-kzg";
|
|
import { expect } from "chai";
|
|
import { ethers, upgrades } from "hardhat";
|
|
|
|
import blobAggregatedProof1To155 from "../_testData/compressedDataEip4844/aggregatedProof-1-155.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 { LINEA_ROLLUP_PAUSE_TYPES_ROLES, LINEA_ROLLUP_UNPAUSE_TYPES_ROLES } from "contracts/common/constants";
|
|
import { CallForwardingProxy, TestLineaRollup } from "contracts/typechain-types";
|
|
import {
|
|
deployCallForwardingProxy,
|
|
deployLineaRollupFixture,
|
|
expectSuccessfulFinalizeViaCallForwarder,
|
|
getAccountsFixture,
|
|
getRoleAddressesFixture,
|
|
sendBlobTransactionViaCallForwarder,
|
|
} from "./helpers";
|
|
import {
|
|
ADDRESS_ZERO,
|
|
FALLBACK_OPERATOR_ADDRESS,
|
|
GENERAL_PAUSE_TYPE,
|
|
HASH_WITHOUT_ZERO_FIRST_BYTE,
|
|
HASH_ZERO,
|
|
INITIAL_MIGRATION_BLOCK,
|
|
INITIAL_WITHDRAW_LIMIT,
|
|
ONE_DAY_IN_SECONDS,
|
|
OPERATOR_ROLE,
|
|
VERIFIER_SETTER_ROLE,
|
|
VERIFIER_UNSETTER_ROLE,
|
|
GENESIS_L2_TIMESTAMP,
|
|
EMPTY_CALLDATA,
|
|
INITIALIZED_ALREADY_MESSAGE,
|
|
CALLDATA_SUBMISSION_PAUSE_TYPE,
|
|
DEFAULT_ADMIN_ROLE,
|
|
DEFAULT_LAST_FINALIZED_TIMESTAMP,
|
|
SIX_MONTHS_IN_SECONDS,
|
|
LINEA_ROLLUP_INITIALIZE_SIGNATURE,
|
|
} from "../common/constants";
|
|
import { deployUpgradableFromFactory } from "../common/deployment";
|
|
import {
|
|
calculateRollingHash,
|
|
encodeData,
|
|
generateRandomBytes,
|
|
generateCallDataSubmission,
|
|
expectEvent,
|
|
buildAccessErrorMessage,
|
|
expectRevertWithCustomError,
|
|
expectRevertWithReason,
|
|
generateBlobParentShnarfData,
|
|
calculateLastFinalizedState,
|
|
} from "../common/helpers";
|
|
import { CalldataSubmissionData } from "../common/types";
|
|
|
|
kzg.loadTrustedSetup(`${__dirname}/../_testData/trusted_setup.txt`);
|
|
|
|
describe("Linea Rollup contract", () => {
|
|
let lineaRollup: TestLineaRollup;
|
|
let verifier: string;
|
|
let callForwardingProxy: CallForwardingProxy;
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
let admin: SignerWithAddress;
|
|
let securityCouncil: SignerWithAddress;
|
|
let operator: SignerWithAddress;
|
|
let nonAuthorizedAccount: SignerWithAddress;
|
|
let roleAddresses: { addressWithRole: string; role: string }[];
|
|
|
|
const { compressedData, prevShnarf, expectedShnarf, expectedX, expectedY, parentStateRootHash } =
|
|
firstCompressedDataContent;
|
|
const { expectedShnarf: secondExpectedShnarf } = secondCompressedDataContent;
|
|
|
|
before(async () => {
|
|
({ admin, securityCouncil, operator, nonAuthorizedAccount } = await loadFixture(getAccountsFixture));
|
|
roleAddresses = await loadFixture(getRoleAddressesFixture);
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
({ verifier, lineaRollup } = await loadFixture(deployLineaRollupFixture));
|
|
});
|
|
|
|
describe("Fallback/Receive tests", () => {
|
|
const sendEthToContract = async (data: string) => {
|
|
return admin.sendTransaction({ to: await lineaRollup.getAddress(), value: INITIAL_WITHDRAW_LIMIT, data });
|
|
};
|
|
|
|
it("Should fail to send eth to the lineaRollup contract through the fallback", async () => {
|
|
await expect(sendEthToContract(EMPTY_CALLDATA)).to.be.reverted;
|
|
});
|
|
|
|
it("Should fail to send eth to the lineaRollup contract through the receive function", async () => {
|
|
await expect(sendEthToContract("0x1234")).to.be.reverted;
|
|
});
|
|
});
|
|
|
|
describe("Initialisation", () => {
|
|
it("Should revert if verifier address is zero address", async () => {
|
|
const initializationData = {
|
|
initialStateRootHash: parentStateRootHash,
|
|
initialL2BlockNumber: INITIAL_MIGRATION_BLOCK,
|
|
genesisTimestamp: GENESIS_L2_TIMESTAMP,
|
|
defaultVerifier: ADDRESS_ZERO,
|
|
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 deployCall = deployUpgradableFromFactory("src/rollup/LineaRollup.sol:LineaRollup", [initializationData], {
|
|
initializer: LINEA_ROLLUP_INITIALIZE_SIGNATURE,
|
|
unsafeAllow: ["constructor", "incorrect-initializer-order"],
|
|
});
|
|
|
|
await expectRevertWithCustomError(lineaRollup, deployCall, "ZeroAddressNotAllowed");
|
|
});
|
|
|
|
it("Should revert if the fallback operator address is zero address", async () => {
|
|
const initializationData = {
|
|
initialStateRootHash: parentStateRootHash,
|
|
initialL2BlockNumber: INITIAL_MIGRATION_BLOCK,
|
|
genesisTimestamp: GENESIS_L2_TIMESTAMP,
|
|
defaultVerifier: verifier,
|
|
rateLimitPeriodInSeconds: ONE_DAY_IN_SECONDS,
|
|
rateLimitAmountInWei: INITIAL_WITHDRAW_LIMIT,
|
|
roleAddresses: [...roleAddresses.slice(1)],
|
|
pauseTypeRoles: LINEA_ROLLUP_PAUSE_TYPES_ROLES,
|
|
unpauseTypeRoles: LINEA_ROLLUP_UNPAUSE_TYPES_ROLES,
|
|
fallbackOperator: ADDRESS_ZERO,
|
|
defaultAdmin: securityCouncil.address,
|
|
};
|
|
|
|
const deployCall = deployUpgradableFromFactory("TestLineaRollup", [initializationData], {
|
|
initializer: LINEA_ROLLUP_INITIALIZE_SIGNATURE,
|
|
unsafeAllow: ["constructor", "incorrect-initializer-order"],
|
|
});
|
|
|
|
await expectRevertWithCustomError(lineaRollup, deployCall, "ZeroAddressNotAllowed");
|
|
});
|
|
|
|
it("Should revert if the default admin address is zero address", async () => {
|
|
const initializationData = {
|
|
initialStateRootHash: parentStateRootHash,
|
|
initialL2BlockNumber: INITIAL_MIGRATION_BLOCK,
|
|
genesisTimestamp: GENESIS_L2_TIMESTAMP,
|
|
defaultVerifier: verifier,
|
|
rateLimitPeriodInSeconds: ONE_DAY_IN_SECONDS,
|
|
rateLimitAmountInWei: INITIAL_WITHDRAW_LIMIT,
|
|
roleAddresses: [...roleAddresses.slice(1)],
|
|
pauseTypeRoles: LINEA_ROLLUP_PAUSE_TYPES_ROLES,
|
|
unpauseTypeRoles: LINEA_ROLLUP_UNPAUSE_TYPES_ROLES,
|
|
fallbackOperator: FALLBACK_OPERATOR_ADDRESS,
|
|
defaultAdmin: ADDRESS_ZERO,
|
|
};
|
|
|
|
const deployCall = deployUpgradableFromFactory("TestLineaRollup", [initializationData], {
|
|
initializer: LINEA_ROLLUP_INITIALIZE_SIGNATURE,
|
|
unsafeAllow: ["constructor", "incorrect-initializer-order"],
|
|
});
|
|
|
|
await expectRevertWithCustomError(lineaRollup, deployCall, "ZeroAddressNotAllowed");
|
|
});
|
|
|
|
it("Should revert if an operator address is zero address", async () => {
|
|
const initializationData = {
|
|
initialStateRootHash: parentStateRootHash,
|
|
initialL2BlockNumber: INITIAL_MIGRATION_BLOCK,
|
|
genesisTimestamp: GENESIS_L2_TIMESTAMP,
|
|
defaultVerifier: verifier,
|
|
rateLimitPeriodInSeconds: ONE_DAY_IN_SECONDS,
|
|
rateLimitAmountInWei: INITIAL_WITHDRAW_LIMIT,
|
|
roleAddresses: [{ addressWithRole: ADDRESS_ZERO, role: DEFAULT_ADMIN_ROLE }, ...roleAddresses.slice(1)],
|
|
pauseTypeRoles: LINEA_ROLLUP_PAUSE_TYPES_ROLES,
|
|
unpauseTypeRoles: LINEA_ROLLUP_UNPAUSE_TYPES_ROLES,
|
|
fallbackOperator: FALLBACK_OPERATOR_ADDRESS,
|
|
defaultAdmin: securityCouncil.address,
|
|
};
|
|
|
|
const deployCall = deployUpgradableFromFactory("TestLineaRollup", [initializationData], {
|
|
initializer: LINEA_ROLLUP_INITIALIZE_SIGNATURE,
|
|
unsafeAllow: ["constructor", "incorrect-initializer-order"],
|
|
});
|
|
|
|
await expectRevertWithCustomError(lineaRollup, deployCall, "ZeroAddressNotAllowed");
|
|
});
|
|
|
|
it("Should store verifier address in storage", async () => {
|
|
({ verifier, lineaRollup } = await loadFixture(deployLineaRollupFixture));
|
|
expect(await lineaRollup.verifiers(0)).to.be.equal(verifier);
|
|
});
|
|
|
|
it("Should assign the OPERATOR_ROLE to operator addresses", async () => {
|
|
({ verifier, lineaRollup } = await loadFixture(deployLineaRollupFixture));
|
|
expect(await lineaRollup.hasRole(OPERATOR_ROLE, operator.address)).to.be.true;
|
|
});
|
|
|
|
it("Should assign the VERIFIER_SETTER_ROLE to securityCouncil addresses", async () => {
|
|
({ verifier, lineaRollup } = await loadFixture(deployLineaRollupFixture));
|
|
expect(await lineaRollup.hasRole(VERIFIER_SETTER_ROLE, securityCouncil.address)).to.be.true;
|
|
});
|
|
|
|
it("Should assign the VERIFIER_UNSETTER_ROLE to securityCouncil addresses", async () => {
|
|
({ verifier, lineaRollup } = await loadFixture(deployLineaRollupFixture));
|
|
expect(await lineaRollup.hasRole(VERIFIER_UNSETTER_ROLE, securityCouncil.address)).to.be.true;
|
|
});
|
|
|
|
it("Should store the startingRootHash in storage for the first block number", async () => {
|
|
const initializationData = {
|
|
initialStateRootHash: parentStateRootHash,
|
|
initialL2BlockNumber: INITIAL_MIGRATION_BLOCK,
|
|
genesisTimestamp: GENESIS_L2_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(
|
|
"src/rollup/LineaRollup.sol:LineaRollup",
|
|
[initializationData],
|
|
{
|
|
initializer: LINEA_ROLLUP_INITIALIZE_SIGNATURE,
|
|
unsafeAllow: ["constructor", "incorrect-initializer-order"],
|
|
},
|
|
);
|
|
|
|
expect(await lineaRollup.stateRootHashes(INITIAL_MIGRATION_BLOCK)).to.be.equal(parentStateRootHash);
|
|
});
|
|
|
|
it("Should assign the VERIFIER_SETTER_ROLE to both SecurityCouncil and Operator", async () => {
|
|
const initializationData = {
|
|
initialStateRootHash: parentStateRootHash,
|
|
initialL2BlockNumber: INITIAL_MIGRATION_BLOCK,
|
|
genesisTimestamp: GENESIS_L2_TIMESTAMP,
|
|
defaultVerifier: verifier,
|
|
rateLimitPeriodInSeconds: ONE_DAY_IN_SECONDS,
|
|
rateLimitAmountInWei: INITIAL_WITHDRAW_LIMIT,
|
|
roleAddresses: [...roleAddresses, { addressWithRole: operator.address, role: VERIFIER_SETTER_ROLE }],
|
|
pauseTypeRoles: LINEA_ROLLUP_PAUSE_TYPES_ROLES,
|
|
unpauseTypeRoles: LINEA_ROLLUP_UNPAUSE_TYPES_ROLES,
|
|
fallbackOperator: FALLBACK_OPERATOR_ADDRESS,
|
|
defaultAdmin: securityCouncil.address,
|
|
};
|
|
|
|
const lineaRollup = await deployUpgradableFromFactory(
|
|
"src/rollup/LineaRollup.sol:LineaRollup",
|
|
[initializationData],
|
|
{
|
|
initializer: LINEA_ROLLUP_INITIALIZE_SIGNATURE,
|
|
unsafeAllow: ["constructor", "incorrect-initializer-order"],
|
|
},
|
|
);
|
|
|
|
expect(await lineaRollup.hasRole(VERIFIER_SETTER_ROLE, securityCouncil.address)).to.be.true;
|
|
expect(await lineaRollup.hasRole(VERIFIER_SETTER_ROLE, operator.address)).to.be.true;
|
|
});
|
|
|
|
it("Should have the correct contract version", async () => {
|
|
({ verifier, lineaRollup } = await loadFixture(deployLineaRollupFixture));
|
|
expect(await lineaRollup.CONTRACT_VERSION()).to.equal("7.0");
|
|
});
|
|
|
|
it("Should revert if the initialize function is called a second time", async () => {
|
|
({ verifier, lineaRollup } = await loadFixture(deployLineaRollupFixture));
|
|
const initializeCall = lineaRollup.initialize({
|
|
initialStateRootHash: parentStateRootHash,
|
|
initialL2BlockNumber: INITIAL_MIGRATION_BLOCK,
|
|
genesisTimestamp: GENESIS_L2_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,
|
|
});
|
|
|
|
await expectRevertWithReason(initializeCall, INITIALIZED_ALREADY_MESSAGE);
|
|
});
|
|
});
|
|
|
|
describe("Change verifier address", () => {
|
|
it("Should revert if the caller has not the VERIFIER_SETTER_ROLE", async () => {
|
|
const setVerifierCall = lineaRollup.connect(nonAuthorizedAccount).setVerifierAddress(verifier, 2);
|
|
|
|
await expectRevertWithReason(
|
|
setVerifierCall,
|
|
buildAccessErrorMessage(nonAuthorizedAccount, VERIFIER_SETTER_ROLE),
|
|
);
|
|
});
|
|
|
|
it("Should revert if the address being set is the zero address", async () => {
|
|
await lineaRollup.connect(securityCouncil).grantRole(VERIFIER_SETTER_ROLE, securityCouncil.address);
|
|
|
|
const setVerifierCall = lineaRollup.connect(securityCouncil).setVerifierAddress(ADDRESS_ZERO, 2);
|
|
await expectRevertWithCustomError(lineaRollup, setVerifierCall, "ZeroAddressNotAllowed");
|
|
});
|
|
|
|
it("Should set the new verifier address", async () => {
|
|
await lineaRollup.connect(securityCouncil).grantRole(VERIFIER_SETTER_ROLE, securityCouncil.address);
|
|
|
|
await lineaRollup.connect(securityCouncil).setVerifierAddress(verifier, 2);
|
|
expect(await lineaRollup.verifiers(2)).to.be.equal(verifier);
|
|
});
|
|
|
|
it("Should remove verifier address in storage ", async () => {
|
|
({ verifier, lineaRollup } = await loadFixture(deployLineaRollupFixture));
|
|
await lineaRollup.connect(securityCouncil).unsetVerifierAddress(0);
|
|
|
|
expect(await lineaRollup.verifiers(0)).to.be.equal(ADDRESS_ZERO);
|
|
});
|
|
|
|
it("Should revert when removing verifier address if the caller has not the VERIFIER_UNSETTER_ROLE ", async () => {
|
|
({ verifier, lineaRollup } = await loadFixture(deployLineaRollupFixture));
|
|
|
|
await expect(lineaRollup.connect(nonAuthorizedAccount).unsetVerifierAddress(0)).to.be.revertedWith(
|
|
buildAccessErrorMessage(nonAuthorizedAccount, VERIFIER_UNSETTER_ROLE),
|
|
);
|
|
});
|
|
|
|
it("Should emit the correct event", async () => {
|
|
await lineaRollup.connect(securityCouncil).grantRole(VERIFIER_SETTER_ROLE, securityCouncil.address);
|
|
|
|
const oldVerifierAddress = await lineaRollup.verifiers(2);
|
|
|
|
const setVerifierCall = lineaRollup.connect(securityCouncil).setVerifierAddress(verifier, 2);
|
|
let expectedArgs = [verifier, 2, securityCouncil.address, oldVerifierAddress];
|
|
|
|
await expectEvent(lineaRollup, setVerifierCall, "VerifierAddressChanged", expectedArgs);
|
|
|
|
await lineaRollup.connect(securityCouncil).unsetVerifierAddress(2);
|
|
|
|
const unsetVerifierCall = lineaRollup.connect(securityCouncil).unsetVerifierAddress(2);
|
|
expectedArgs = [ADDRESS_ZERO, 2, securityCouncil.address, oldVerifierAddress];
|
|
|
|
await expectEvent(lineaRollup, unsetVerifierCall, "VerifierAddressChanged", expectedArgs);
|
|
});
|
|
});
|
|
|
|
describe("Data submission tests", () => {
|
|
beforeEach(async () => {
|
|
await lineaRollup.setLastFinalizedBlock(0);
|
|
});
|
|
|
|
const [DATA_ONE] = generateCallDataSubmission(0, 1);
|
|
|
|
it("Fails when the compressed data is empty", async () => {
|
|
const [submissionData] = generateCallDataSubmission(0, 1);
|
|
submissionData.compressedData = EMPTY_CALLDATA;
|
|
|
|
const submitDataCall = lineaRollup
|
|
.connect(operator)
|
|
.submitDataAsCalldata(submissionData, prevShnarf, secondExpectedShnarf, { gasLimit: 30_000_000 });
|
|
await expectRevertWithCustomError(lineaRollup, submitDataCall, "EmptySubmissionData");
|
|
});
|
|
|
|
it("Should fail when the parent shnarf does not exist", async () => {
|
|
const [submissionData] = generateCallDataSubmission(0, 1);
|
|
const nonExistingParentShnarf = generateRandomBytes(32);
|
|
const asyncCall = lineaRollup
|
|
.connect(operator)
|
|
.submitDataAsCalldata(submissionData, nonExistingParentShnarf, expectedShnarf, { gasLimit: 30_000_000 });
|
|
|
|
await expectRevertWithCustomError(lineaRollup, asyncCall, "ParentBlobNotSubmitted", [nonExistingParentShnarf]);
|
|
});
|
|
|
|
it("Should succesfully submit 1 compressed data chunk setting values", async () => {
|
|
const [submissionData] = generateCallDataSubmission(0, 1);
|
|
|
|
await expect(
|
|
lineaRollup
|
|
.connect(operator)
|
|
.submitDataAsCalldata(submissionData, prevShnarf, expectedShnarf, { gasLimit: 30_000_000 }),
|
|
).to.not.be.reverted;
|
|
|
|
const blobShnarfExists = await lineaRollup.blobShnarfExists(expectedShnarf);
|
|
expect(blobShnarfExists).to.equal(1n);
|
|
});
|
|
|
|
it("Should successfully submit 2 compressed data chunks in two transactions", async () => {
|
|
const [firstSubmissionData, secondSubmissionData] = generateCallDataSubmission(0, 2);
|
|
|
|
await expect(
|
|
lineaRollup
|
|
.connect(operator)
|
|
.submitDataAsCalldata(firstSubmissionData, prevShnarf, expectedShnarf, { gasLimit: 30_000_000 }),
|
|
).to.not.be.reverted;
|
|
|
|
await expect(
|
|
lineaRollup.connect(operator).submitDataAsCalldata(secondSubmissionData, expectedShnarf, secondExpectedShnarf, {
|
|
gasLimit: 30_000_000,
|
|
}),
|
|
).to.not.be.reverted;
|
|
|
|
const blobShnarfExists = await lineaRollup.blobShnarfExists(expectedShnarf);
|
|
expect(blobShnarfExists).to.equal(1n);
|
|
});
|
|
|
|
it("Should emit an event while submitting 1 compressed data chunk", async () => {
|
|
const [submissionData] = generateCallDataSubmission(0, 1);
|
|
|
|
const submitDataCall = lineaRollup
|
|
.connect(operator)
|
|
.submitDataAsCalldata(submissionData, prevShnarf, expectedShnarf, { gasLimit: 30_000_000 });
|
|
const eventArgs = [prevShnarf, expectedShnarf, submissionData.finalStateRootHash];
|
|
|
|
await expectEvent(lineaRollup, submitDataCall, "DataSubmittedV3", eventArgs);
|
|
});
|
|
|
|
it("Should fail if the final state root hash is empty", async () => {
|
|
const [submissionData] = generateCallDataSubmission(0, 1);
|
|
|
|
submissionData.finalStateRootHash = HASH_ZERO;
|
|
|
|
const submitDataCall = lineaRollup
|
|
.connect(operator)
|
|
.submitDataAsCalldata(submissionData, prevShnarf, expectedShnarf, { gasLimit: 30_000_000 });
|
|
|
|
// TODO: Make the failure shnarf dynamic and computed
|
|
await expectRevertWithCustomError(lineaRollup, submitDataCall, "FinalShnarfWrong", [
|
|
expectedShnarf,
|
|
"0xf53c28b2287f506b4df1b9de48cf3601392d54a73afe400a6f8f4ded2e0929ad",
|
|
]);
|
|
});
|
|
|
|
it("Should fail to submit where expected shnarf is wrong", async () => {
|
|
const [firstSubmissionData, secondSubmissionData] = generateCallDataSubmission(0, 2);
|
|
|
|
await expect(
|
|
lineaRollup
|
|
.connect(operator)
|
|
.submitDataAsCalldata(firstSubmissionData, prevShnarf, expectedShnarf, { gasLimit: 30_000_000 }),
|
|
).to.not.be.reverted;
|
|
|
|
const wrongComputedShnarf = generateRandomBytes(32);
|
|
|
|
const submitDataCall = lineaRollup
|
|
.connect(operator)
|
|
.submitDataAsCalldata(secondSubmissionData, expectedShnarf, wrongComputedShnarf, { gasLimit: 30_000_000 });
|
|
|
|
const eventArgs = [wrongComputedShnarf, secondExpectedShnarf];
|
|
|
|
await expectRevertWithCustomError(lineaRollup, submitDataCall, "FinalShnarfWrong", eventArgs);
|
|
});
|
|
|
|
it("Should revert if the caller does not have the OPERATOR_ROLE", async () => {
|
|
const submitDataCall = lineaRollup
|
|
.connect(nonAuthorizedAccount)
|
|
.submitDataAsCalldata(DATA_ONE, prevShnarf, expectedShnarf, { gasLimit: 30_000_000 });
|
|
|
|
await expectRevertWithReason(submitDataCall, buildAccessErrorMessage(nonAuthorizedAccount, OPERATOR_ROLE));
|
|
});
|
|
|
|
it("Should revert if GENERAL_PAUSE_TYPE is enabled", async () => {
|
|
await lineaRollup.connect(securityCouncil).pauseByType(GENERAL_PAUSE_TYPE);
|
|
|
|
const submitDataCall = lineaRollup
|
|
.connect(operator)
|
|
.submitDataAsCalldata(DATA_ONE, prevShnarf, expectedShnarf, { gasLimit: 30_000_000 });
|
|
|
|
await expectRevertWithCustomError(lineaRollup, submitDataCall, "IsPaused", [GENERAL_PAUSE_TYPE]);
|
|
});
|
|
|
|
it("Should revert if CALLDATA_SUBMISSION_PAUSE_TYPE is enabled", async () => {
|
|
await lineaRollup.connect(securityCouncil).pauseByType(CALLDATA_SUBMISSION_PAUSE_TYPE);
|
|
|
|
const submitDataCall = lineaRollup
|
|
.connect(operator)
|
|
.submitDataAsCalldata(DATA_ONE, prevShnarf, expectedShnarf, { gasLimit: 30_000_000 });
|
|
|
|
await expectRevertWithCustomError(lineaRollup, submitDataCall, "IsPaused", [CALLDATA_SUBMISSION_PAUSE_TYPE]);
|
|
});
|
|
|
|
it("Should revert with DataAlreadySubmitted when submitting same compressed data twice in 2 separate transactions", async () => {
|
|
await lineaRollup
|
|
.connect(operator)
|
|
.submitDataAsCalldata(DATA_ONE, prevShnarf, expectedShnarf, { gasLimit: 30_000_000 });
|
|
|
|
const submitDataCall = lineaRollup
|
|
.connect(operator)
|
|
.submitDataAsCalldata(DATA_ONE, prevShnarf, expectedShnarf, { gasLimit: 30_000_000 });
|
|
|
|
await expectRevertWithCustomError(lineaRollup, submitDataCall, "DataAlreadySubmitted", [expectedShnarf]);
|
|
});
|
|
|
|
it("Should revert with DataAlreadySubmitted when submitting same data, differing block numbers", async () => {
|
|
await lineaRollup
|
|
.connect(operator)
|
|
.submitDataAsCalldata(DATA_ONE, prevShnarf, expectedShnarf, { gasLimit: 30_000_000 });
|
|
|
|
const [dataOneCopy] = generateCallDataSubmission(0, 1);
|
|
dataOneCopy.endBlockNumber = 234253242n;
|
|
|
|
const submitDataCall = lineaRollup
|
|
.connect(operator)
|
|
.submitDataAsCalldata(dataOneCopy, prevShnarf, expectedShnarf, { gasLimit: 30_000_000 });
|
|
|
|
await expectRevertWithCustomError(lineaRollup, submitDataCall, "DataAlreadySubmitted", [expectedShnarf]);
|
|
});
|
|
|
|
it("Should revert when snarkHash is zero hash", async () => {
|
|
const submissionData: CalldataSubmissionData = {
|
|
...DATA_ONE,
|
|
snarkHash: HASH_ZERO,
|
|
};
|
|
|
|
const submitDataCall = lineaRollup
|
|
.connect(operator)
|
|
.submitDataAsCalldata(submissionData, prevShnarf, expectedShnarf, { gasLimit: 30_000_000 });
|
|
|
|
// TODO: Make the failure shnarf dynamic and computed
|
|
await expectRevertWithCustomError(lineaRollup, submitDataCall, "FinalShnarfWrong", [
|
|
expectedShnarf,
|
|
"0xa6b52564082728b51bb81a4fa92cfb4ec3af8de3f18b5d68ec27b89eead93293",
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("Validate L2 computed rolling hash", () => {
|
|
it("Should revert if l1 message number == 0 and l1 rolling hash is not empty", async () => {
|
|
const l1MessageNumber = 0;
|
|
const l1RollingHash = generateRandomBytes(32);
|
|
|
|
await expectRevertWithCustomError(
|
|
lineaRollup,
|
|
lineaRollup.validateL2ComputedRollingHash(l1MessageNumber, l1RollingHash),
|
|
"MissingMessageNumberForRollingHash",
|
|
[l1RollingHash],
|
|
);
|
|
});
|
|
|
|
it("Should revert if l1 message number != 0 and l1 rolling hash is empty", async () => {
|
|
const l1MessageNumber = 1n;
|
|
const l1RollingHash = HASH_ZERO;
|
|
|
|
await expectRevertWithCustomError(
|
|
lineaRollup,
|
|
lineaRollup.validateL2ComputedRollingHash(l1MessageNumber, l1RollingHash),
|
|
"MissingRollingHashForMessageNumber",
|
|
[l1MessageNumber],
|
|
);
|
|
});
|
|
|
|
it("Should revert if l1RollingHash does not exist on L1", async () => {
|
|
const l1MessageNumber = 1n;
|
|
const l1RollingHash = generateRandomBytes(32);
|
|
|
|
await expectRevertWithCustomError(
|
|
lineaRollup,
|
|
lineaRollup.validateL2ComputedRollingHash(l1MessageNumber, l1RollingHash),
|
|
"L1RollingHashDoesNotExistOnL1",
|
|
[l1MessageNumber, l1RollingHash],
|
|
);
|
|
});
|
|
|
|
it("Should succeed if l1 message number == 0 and l1 rolling hash is empty", async () => {
|
|
const l1MessageNumber = 0;
|
|
const l1RollingHash = HASH_ZERO;
|
|
await expect(lineaRollup.validateL2ComputedRollingHash(l1MessageNumber, l1RollingHash)).to.not.be.reverted;
|
|
});
|
|
|
|
it("Should succeed if l1 message number != 0, l1 rolling hash is not empty and exists on L1", async () => {
|
|
const l1MessageNumber = 1n;
|
|
const messageHash = generateRandomBytes(32);
|
|
|
|
await lineaRollup.addRollingHash(l1MessageNumber, messageHash);
|
|
|
|
const l1RollingHash = calculateRollingHash(HASH_ZERO, messageHash);
|
|
|
|
await expect(lineaRollup.validateL2ComputedRollingHash(l1MessageNumber, l1RollingHash)).to.not.be.reverted;
|
|
});
|
|
});
|
|
|
|
describe("Calculate Y value for Compressed Data", () => {
|
|
it("Should successfully calculate y", async () => {
|
|
const compressedDataBytes = ethers.decodeBase64(compressedData);
|
|
|
|
expect(await lineaRollup.calculateY(compressedDataBytes, expectedX, { gasLimit: 30_000_000 })).to.equal(
|
|
expectedY,
|
|
);
|
|
});
|
|
|
|
it("Should revert if first byte is no zero", async () => {
|
|
const compressedDataBytes = encodeData(
|
|
["bytes32", "bytes32", "bytes32"],
|
|
[generateRandomBytes(32), HASH_WITHOUT_ZERO_FIRST_BYTE, generateRandomBytes(32)],
|
|
);
|
|
|
|
await expectRevertWithCustomError(
|
|
lineaRollup,
|
|
lineaRollup.calculateY(compressedDataBytes, expectedX, { gasLimit: 30_000_000 }),
|
|
"FirstByteIsNotZero",
|
|
);
|
|
});
|
|
|
|
it("Should revert if bytes length is not a multiple of 32", async () => {
|
|
const compressedDataBytes = generateRandomBytes(56);
|
|
|
|
await expectRevertWithCustomError(
|
|
lineaRollup,
|
|
lineaRollup.calculateY(compressedDataBytes, expectedX, { gasLimit: 30_000_000 }),
|
|
"BytesLengthNotMultipleOf32",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("fallback operator Role", () => {
|
|
const expectedLastFinalizedState = calculateLastFinalizedState(0n, HASH_ZERO, DEFAULT_LAST_FINALIZED_TIMESTAMP);
|
|
|
|
it("Should revert if trying to set fallback operator role before six months have passed", async () => {
|
|
const initialBlock = await ethers.provider.getBlock("latest");
|
|
|
|
await expectRevertWithCustomError(
|
|
lineaRollup,
|
|
lineaRollup.setFallbackOperator(0n, HASH_ZERO, BigInt(initialBlock!.timestamp)),
|
|
"LastFinalizationTimeNotLapsed",
|
|
);
|
|
});
|
|
|
|
it("Should revert if the time has passed and the last finalized timestamp does not match", async () => {
|
|
await networkTime.increase(SIX_MONTHS_IN_SECONDS);
|
|
const actualSentState = calculateLastFinalizedState(0n, HASH_ZERO, 123456789n);
|
|
|
|
await expectRevertWithCustomError(
|
|
lineaRollup,
|
|
lineaRollup.setFallbackOperator(0n, HASH_ZERO, 123456789n),
|
|
"FinalizationStateIncorrect",
|
|
[expectedLastFinalizedState, actualSentState],
|
|
);
|
|
});
|
|
|
|
it("Should revert if the time has passed and the last finalized L1 message number does not match", async () => {
|
|
await networkTime.increase(SIX_MONTHS_IN_SECONDS);
|
|
const actualSentState = calculateLastFinalizedState(1n, HASH_ZERO, DEFAULT_LAST_FINALIZED_TIMESTAMP);
|
|
|
|
await expectRevertWithCustomError(
|
|
lineaRollup,
|
|
lineaRollup.setFallbackOperator(1n, HASH_ZERO, DEFAULT_LAST_FINALIZED_TIMESTAMP),
|
|
"FinalizationStateIncorrect",
|
|
[expectedLastFinalizedState, actualSentState],
|
|
);
|
|
});
|
|
|
|
it("Should revert if the time has passed and the last finalized L1 rolling hash does not match", async () => {
|
|
await networkTime.increase(SIX_MONTHS_IN_SECONDS);
|
|
const random32Bytes = generateRandomBytes(32);
|
|
const actualSentState = calculateLastFinalizedState(0n, random32Bytes, DEFAULT_LAST_FINALIZED_TIMESTAMP);
|
|
|
|
await expectRevertWithCustomError(
|
|
lineaRollup,
|
|
lineaRollup.setFallbackOperator(0n, random32Bytes, DEFAULT_LAST_FINALIZED_TIMESTAMP),
|
|
"FinalizationStateIncorrect",
|
|
[expectedLastFinalizedState, actualSentState],
|
|
);
|
|
});
|
|
|
|
it("Should set the fallback operator role after six months have passed", async () => {
|
|
await networkTime.increase(SIX_MONTHS_IN_SECONDS);
|
|
|
|
await expectEvent(
|
|
lineaRollup,
|
|
lineaRollup.setFallbackOperator(0n, HASH_ZERO, DEFAULT_LAST_FINALIZED_TIMESTAMP),
|
|
"FallbackOperatorRoleGranted",
|
|
[admin.address, FALLBACK_OPERATOR_ADDRESS],
|
|
);
|
|
|
|
expect(await lineaRollup.hasRole(OPERATOR_ROLE, FALLBACK_OPERATOR_ADDRESS)).to.be.true;
|
|
});
|
|
|
|
it("Should revert if trying to renounce role as fallback operator", async () => {
|
|
await networkTime.increase(SIX_MONTHS_IN_SECONDS);
|
|
|
|
await expectEvent(
|
|
lineaRollup,
|
|
lineaRollup.setFallbackOperator(0n, HASH_ZERO, DEFAULT_LAST_FINALIZED_TIMESTAMP),
|
|
"FallbackOperatorRoleGranted",
|
|
[admin.address, FALLBACK_OPERATOR_ADDRESS],
|
|
);
|
|
|
|
expect(await lineaRollup.hasRole(OPERATOR_ROLE, FALLBACK_OPERATOR_ADDRESS)).to.be.true;
|
|
|
|
const renounceCall = lineaRollup.renounceRole(OPERATOR_ROLE, FALLBACK_OPERATOR_ADDRESS);
|
|
|
|
expectRevertWithCustomError(lineaRollup, renounceCall, "OnlyNonFallbackOperator");
|
|
});
|
|
|
|
it("Should renounce role if not fallback operator", async () => {
|
|
expect(await lineaRollup.hasRole(OPERATOR_ROLE, operator.address)).to.be.true;
|
|
|
|
const renounceCall = lineaRollup.connect(operator).renounceRole(OPERATOR_ROLE, operator.address);
|
|
const args = [OPERATOR_ROLE, operator.address, operator.address];
|
|
expectEvent(lineaRollup, renounceCall, "RoleRevoked", args);
|
|
});
|
|
|
|
it("Should fail to accept ETH on the CallForwardingProxy receive function", async () => {
|
|
callForwardingProxy = await deployCallForwardingProxy(await lineaRollup.getAddress());
|
|
const forwardingProxyAddress = await callForwardingProxy.getAddress();
|
|
|
|
const tx = {
|
|
to: forwardingProxyAddress,
|
|
value: ethers.parseEther("0.1"),
|
|
};
|
|
|
|
await expectRevertWithReason(admin.sendTransaction(tx), "ETH not accepted");
|
|
});
|
|
|
|
it("Should be able to submit blobs and finalize via callforwarding proxy", async () => {
|
|
callForwardingProxy = await deployCallForwardingProxy(await lineaRollup.getAddress());
|
|
const forwardingProxyAddress = await callForwardingProxy.getAddress();
|
|
|
|
expect(await lineaRollup.currentL2BlockNumber()).to.equal(0);
|
|
|
|
// Deploy new LineaRollup implementation
|
|
const newLineaRollupFactory = await ethers.getContractFactory(
|
|
"src/_testing/unit/rollup/TestLineaRollup.sol:TestLineaRollup",
|
|
);
|
|
const newLineaRollup = await upgrades.upgradeProxy(lineaRollup, newLineaRollupFactory, {
|
|
unsafeAllowRenames: true,
|
|
unsafeAllow: ["incorrect-initializer-order"],
|
|
});
|
|
|
|
const upgradedContract = await newLineaRollup.waitForDeployment();
|
|
|
|
await upgradedContract.setFallbackOperatorAddress(forwardingProxyAddress);
|
|
|
|
// Grants deployed callforwarding proxy as operator
|
|
await networkTime.increase(SIX_MONTHS_IN_SECONDS);
|
|
await expectEvent(
|
|
upgradedContract,
|
|
upgradedContract.setFallbackOperator(0n, HASH_ZERO, DEFAULT_LAST_FINALIZED_TIMESTAMP),
|
|
"FallbackOperatorRoleGranted",
|
|
[admin.address, forwardingProxyAddress],
|
|
);
|
|
|
|
// Submit 2 blobs
|
|
await sendBlobTransactionViaCallForwarder(upgradedContract, 0, 2, forwardingProxyAddress);
|
|
// Submit another 2 blobs
|
|
await sendBlobTransactionViaCallForwarder(upgradedContract, 2, 4, forwardingProxyAddress);
|
|
|
|
// Finalize 4 blobs
|
|
await expectSuccessfulFinalizeViaCallForwarder(
|
|
blobAggregatedProof1To155,
|
|
4,
|
|
fourthCompressedDataContent.finalStateRootHash,
|
|
generateBlobParentShnarfData,
|
|
false,
|
|
HASH_ZERO,
|
|
0n,
|
|
forwardingProxyAddress,
|
|
upgradedContract,
|
|
);
|
|
});
|
|
});
|
|
});
|