diff --git a/contracts/contracts/LineaRollup.sol b/contracts/contracts/LineaRollup.sol index b53ad3e70..4a3eb374c 100644 --- a/contracts/contracts/LineaRollup.sol +++ b/contracts/contracts/LineaRollup.sol @@ -48,6 +48,8 @@ contract LineaRollup is uint256 internal constant POINT_EVALUATION_RETURN_DATA_LENGTH = 64; uint256 internal constant POINT_EVALUATION_FIELD_ELEMENTS_LENGTH = 4096; + uint256 internal constant SIX_MONTHS_IN_SECONDS = 15768000; // 365 / 2 * 86400 + /// @dev DEPRECATED in favor of the single shnarfFinalBlockNumbers mapping. mapping(bytes32 dataHash => bytes32 finalStateRootHash) public dataFinalStateRootHashes; /// @dev DEPRECATED in favor of the single shnarfFinalBlockNumbers mapping. @@ -74,7 +76,11 @@ contract LineaRollup is /// @dev Hash of the L2 computed L1 message number, rolling hash and finalized timestamp. bytes32 public currentFinalizedState; - /// @dev Total contract storage is 10 slots. + /// @dev The address of the gateway operator. + /// @dev This address is granted the OPERATOR_ROLE after six months of finalization inactivity by the current operators. + address public gatewayOperator; + + /// @dev Total contract storage is 11 slots. /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -101,6 +107,7 @@ contract LineaRollup is verifiers[0] = _initializationData.defaultVerifier; + gatewayOperator = _initializationData.gatewayOperator; currentL2BlockNumber = _initializationData.initialL2BlockNumber; stateRootHashes[_initializationData.initialL2BlockNumber] = _initializationData.initialStateRootHash; @@ -111,19 +118,22 @@ contract LineaRollup is } /** - * @notice Sets permissions for a list of addresses and their roles as well as initialises the PauseManager pauseType:role mappings. + * @notice Sets permissions for a list of addresses and their roles as well as initialises the PauseManager pauseType:role mappings and gateway operator. * @dev This function is a reinitializer and can only be called once per version. Should be called using an upgradeAndCall transaction to the ProxyAdmin. * @param _roleAddresses The list of addresses and their roles. * @param _pauseTypeRoles The list of pause type roles. * @param _unpauseTypeRoles The list of unpause type roles. + * @param _gatewayOperator The address of the gateway operator. */ function reinitializePauseTypesAndPermissions( RoleAddress[] calldata _roleAddresses, PauseTypeRole[] calldata _pauseTypeRoles, - PauseTypeRole[] calldata _unpauseTypeRoles + PauseTypeRole[] calldata _unpauseTypeRoles, + address _gatewayOperator ) external reinitializer(6) { __Permissions_init(_roleAddresses); __PauseManager_init(_pauseTypeRoles, _unpauseTypeRoles); + gatewayOperator = _gatewayOperator; } /** @@ -142,6 +152,30 @@ contract LineaRollup is verifiers[_proofType] = _newVerifierAddress; } + /** + * @notice Sets the gateway operator role to the specified address if six months have passed since the last finalization. + * @dev Reverts if six months have not passed since the last finalization. + * @param _messageNumber Last finalized L1 message number as part of the feedback loop. + * @param _rollingHash Last finalized L1 rolling hash as part of the feedback loop. + * @param _lastFinalizedTimestamp Last finalized L2 block timestamp. + */ + function setGatewayOperator(uint256 _messageNumber, bytes32 _rollingHash, uint256 _lastFinalizedTimestamp) external { + if (block.timestamp < _lastFinalizedTimestamp + SIX_MONTHS_IN_SECONDS) { + revert LastFinalizationTimeNotLapsed(); + } + if (currentFinalizedState != _computeLastFinalizedState(_messageNumber, _rollingHash, _lastFinalizedTimestamp)) { + revert FinalizationStateIncorrect( + currentFinalizedState, + _computeLastFinalizedState(_messageNumber, _rollingHash, _lastFinalizedTimestamp) + ); + } + + address gatewayOperatorAddress = gatewayOperator; + + _grantRole(OPERATOR_ROLE, gatewayOperatorAddress); + emit GatewayOperatorRoleGranted(msg.sender, gatewayOperatorAddress); + } + /** * @notice Unset the verifier contract address for a proof type. * @dev VERIFIER_UNSETTER_ROLE is required to execute. diff --git a/contracts/contracts/interfaces/l1/ILineaRollup.sol b/contracts/contracts/interfaces/l1/ILineaRollup.sol index 301e8eff8..1c002e9a3 100644 --- a/contracts/contracts/interfaces/l1/ILineaRollup.sol +++ b/contracts/contracts/interfaces/l1/ILineaRollup.sol @@ -21,6 +21,7 @@ interface ILineaRollup { * @param roleAddresses The list of role addresses. * @param pauseTypeRoles The list of pause type roles. * @param unpauseTypeRoles The list of unpause type roles. + * @param gatewayOperator The account to be given OPERATOR_ROLE when. */ struct InitializationData { bytes32 initialStateRootHash; @@ -32,6 +33,7 @@ interface ILineaRollup { IPermissionsManager.RoleAddress[] roleAddresses; IPauseManager.PauseTypeRole[] pauseTypeRoles; IPauseManager.PauseTypeRole[] unpauseTypeRoles; + address gatewayOperator; } /** @@ -129,6 +131,13 @@ interface ILineaRollup { bytes l2MessagingBlocksOffsets; } + /** + * @notice Emitted when the gateway operator role is granted. + * @param caller The address that granted the role. + * @param gatewayOperatorAddress The gateway operator address that received the operator role. + */ + event GatewayOperatorRoleGranted(address indexed caller, address indexed gatewayOperatorAddress); + /** * @notice Emitted when a verifier is set for a particular proof type. * @param verifierAddress The indexed new verifier address being set. @@ -168,6 +177,11 @@ interface ILineaRollup { bool withProof ); + /** + * @dev Thrown when the last finalization time has not lapsed when trying to grant the OPERATOR_ROLE to the gateway operator address. + */ + error LastFinalizationTimeNotLapsed(); + /** * @dev Thrown when the point evaluation precompile call return data field(s) are wrong. */ @@ -307,6 +321,15 @@ interface ILineaRollup { */ function setVerifierAddress(address _newVerifierAddress, uint256 _proofType) external; + /** + * @notice Sets the gateway operator role to the specified address if six months have passed since the last finalization. + * @dev Reverts if six months have not passed since the last finalization. + * @param _messageNumber Last finalized L1 message number as part of the feedback loop. + * @param _rollingHash Last finalized L1 rolling hash as part of the feedback loop. + * @param _lastFinalizedTimestamp Last finalized L2 block timestamp. + */ + function setGatewayOperator(uint256 _messageNumber, bytes32 _rollingHash, uint256 _lastFinalizedTimestamp) external; + /** * @notice Unset the verifier contract address for a proof type. * @dev VERIFIER_SETTER_ROLE is required to execute. diff --git a/contracts/contracts/messageService/l2/L2MessageService.sol b/contracts/contracts/messageService/l2/L2MessageService.sol index a98fddfa9..22fec5be6 100644 --- a/contracts/contracts/messageService/l2/L2MessageService.sol +++ b/contracts/contracts/messageService/l2/L2MessageService.sol @@ -16,6 +16,11 @@ contract L2MessageService is AccessControlUpgradeable, L2MessageServiceV1, L2Mes /// @dev Keep 50 free storage slots for future implementation updates to avoid storage collision. uint256[50] private __gap_L2MessageService; + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + /** * @notice Initializes underlying message service dependencies. * @param _rateLimitPeriod The period to rate limit against. diff --git a/contracts/deploy/03_deploy_LineaRollup.ts b/contracts/deploy/03_deploy_LineaRollup.ts index df619c54b..d0dfa62f8 100644 --- a/contracts/deploy/03_deploy_LineaRollup.ts +++ b/contracts/deploy/03_deploy_LineaRollup.ts @@ -56,6 +56,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const LineaRollup_rateLimitPeriodInSeconds = requireEnv("LINEA_ROLLUP_RATE_LIMIT_PERIOD"); const LineaRollup_rateLimitAmountInWei = requireEnv("LINEA_ROLLUP_RATE_LIMIT_AMOUNT"); const LineaRollup_genesisTimestamp = requireEnv("LINEA_ROLLUP_GENESIS_TIMESTAMP"); + const MultiCallAddress = "0xcA11bde05977b3631167028862bE2a173976CA11"; const LineaRollup_roleAddresses = process.env["LINEA_ROLLUP_ROLE_ADDRESSES"]; const LineaRollup_pauseTypeRoles = process.env["LINEA_ROLLUP_PAUSE_TYPE_ROLES"]; const LineaRollup_unpauseTypeRoles = process.env["LINEA_ROLLUP_UNPAUSE_TYPE_ROLES"]; @@ -140,6 +141,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { roleAddresses: roleAddresses, pauseTypeRoles: pauseTypeRoles, unpauseTypeRoles: unpauseTypeRoles, + gatewayOperator: MultiCallAddress, }, ], { diff --git a/contracts/test/LineaRollup.ts b/contracts/test/LineaRollup.ts index d700e2244..876e8b894 100644 --- a/contracts/test/LineaRollup.ts +++ b/contracts/test/LineaRollup.ts @@ -83,6 +83,8 @@ describe("Linea Rollup contract", () => { let operator: SignerWithAddress; let nonAuthorizedAccount: SignerWithAddress; + const multiCallAddress = "0xcA11bde05977b3631167028862bE2a173976CA11"; + const { compressedData, prevShnarf, expectedShnarf, expectedX, expectedY, parentDataHash, parentStateRootHash } = firstCompressedDataContent; const { expectedShnarf: secondExpectedShnarf } = secondCompressedDataContent; @@ -125,6 +127,7 @@ describe("Linea Rollup contract", () => { roleAddresses: roleAddresses, pauseTypeRoles: pauseTypeRoles, unpauseTypeRoles: unpauseTypeRoles, + gatewayOperator: multiCallAddress, }; const lineaRollup = (await deployUpgradableFromFactory("TestLineaRollup", [initializationData], { @@ -178,6 +181,7 @@ describe("Linea Rollup contract", () => { ], pauseTypeRoles: pauseTypeRoles, unpauseTypeRoles: unpauseTypeRoles, + gatewayOperator: multiCallAddress, }; const deployCall = deployUpgradableFromFactory("contracts/LineaRollup.sol:LineaRollup", [initializationData], { @@ -203,6 +207,7 @@ describe("Linea Rollup contract", () => { ], pauseTypeRoles: pauseTypeRoles, unpauseTypeRoles: unpauseTypeRoles, + gatewayOperator: multiCallAddress, }; const deployCall = deployUpgradableFromFactory("TestLineaRollup", [initializationData], { @@ -247,6 +252,7 @@ describe("Linea Rollup contract", () => { ], pauseTypeRoles: pauseTypeRoles, unpauseTypeRoles: unpauseTypeRoles, + gatewayOperator: multiCallAddress, }; const lineaRollup = await deployUpgradableFromFactory( @@ -276,6 +282,7 @@ describe("Linea Rollup contract", () => { ], pauseTypeRoles: pauseTypeRoles, unpauseTypeRoles: unpauseTypeRoles, + gatewayOperator: multiCallAddress, }; const lineaRollup = await deployUpgradableFromFactory( @@ -306,6 +313,7 @@ describe("Linea Rollup contract", () => { ], pauseTypeRoles: pauseTypeRoles, unpauseTypeRoles: unpauseTypeRoles, + gatewayOperator: multiCallAddress, }); await expectRevertWithReason(initializeCall, INITIALIZED_ALREADY_MESSAGE); @@ -2243,6 +2251,7 @@ describe("Linea Rollup contract", () => { ], pauseTypeRoles, unpauseTypeRoles, + multiCallAddress, ); expect(await newLineaRollup.currentL2BlockNumber()).to.equal(0); @@ -2261,7 +2270,12 @@ describe("Linea Rollup contract", () => { await expectRevertWithCustomError( newLineaRollup, - newLineaRollup.reinitializePauseTypesAndPermissions(roleAddresses, pauseTypeRoles, unpauseTypeRoles), + newLineaRollup.reinitializePauseTypesAndPermissions( + roleAddresses, + pauseTypeRoles, + unpauseTypeRoles, + multiCallAddress, + ), "ZeroAddressNotAllowed", ); }); diff --git a/contracts/test/LineaRollupInit.ts b/contracts/test/LineaRollupInit.ts index 60c9770ca..a1d7c2699 100644 --- a/contracts/test/LineaRollupInit.ts +++ b/contracts/test/LineaRollupInit.ts @@ -27,6 +27,8 @@ describe("LineaRollup Init contract", () => { let securityCouncil: SignerWithAddress; let operator: SignerWithAddress; + const multiCallAddress = "0xcA11bde05977b3631167028862bE2a173976CA11"; + const parentStateRootHash = generateRandomBytes(32); const firstBlockNumber = 199; @@ -52,6 +54,7 @@ describe("LineaRollup Init contract", () => { ], pauseTypeRoles: pauseTypeRoles, unpauseTypeRoles: unpauseTypeRoles, + gatewayOperator: multiCallAddress, }; const LineaRollup = (await deployUpgradableFromFactory("TestLineaRollup", [genesisData], { diff --git a/contracts/test/LineaRollupNew.ts b/contracts/test/LineaRollupNew.ts index a19d5e3e2..d98228966 100644 --- a/contracts/test/LineaRollupNew.ts +++ b/contracts/test/LineaRollupNew.ts @@ -53,6 +53,8 @@ describe("Linea Rollup contract", () => { let securityCouncil: SignerWithAddress; let operator: SignerWithAddress; + const multiCallAddress = "0xcA11bde05977b3631167028862bE2a173976CA11"; + async function deployLineaRollupFixture() { const PlonkVerifierFactory = await ethers.getContractFactory("TestPlonkVerifierForDataAggregation"); const plonkVerifier = await PlonkVerifierFactory.deploy(); @@ -71,9 +73,10 @@ describe("Linea Rollup contract", () => { ONE_DAY_IN_SECONDS, INITIAL_WITHDRAW_LIMIT, GENESIS_L2_TIMESTAMP, + multiCallAddress, ], { - initializer: "initialize(bytes32,uint256,address,address,address[],uint256,uint256,uint256)", + initializer: "initialize(bytes32,uint256,address,address,address[],uint256,uint256,uint256,address)", unsafeAllow: ["constructor"], }, )) as unknown as TestLineaRollup; diff --git a/contracts/test/utils/constants.ts b/contracts/test/utils/constants.ts index a63943f52..21a2d68a3 100644 --- a/contracts/test/utils/constants.ts +++ b/contracts/test/utils/constants.ts @@ -6,7 +6,7 @@ export const ADDRESS_ZERO = ethers.ZeroAddress; export const HASH_WITHOUT_ZERO_FIRST_BYTE = "0xf887bbc07b0e849fb625aafadf4cb6b65b98e492fbb689705312bf1db98ead7f"; export const LINEA_ROLLUP_INITIALIZE_SIGNATURE = - "initialize((bytes32,uint256,uint256,address,uint256,uint256,(address,bytes32)[],(uint8,bytes32)[],(uint8,bytes32)[]))"; + "initialize((bytes32,uint256,uint256,address,uint256,uint256,(address,bytes32)[],(uint8,bytes32)[],(uint8,bytes32)[],address))"; // Linea XP Token roles export const MINTER_ROLE = generateKeccak256(["string"], ["MINTER_ROLE"], true);