Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 22 additions & 12 deletions AllContractsHashes.json
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,16 @@
"evmDeployedBytecodeBlakeHash": "0xc5f77bf2ff95e8683646dc2184454af663309c32dbaa6df3f6f777350b809694",
"evmDeployedBytecodeLength": 1836
},
{
"contractName": "l1-contracts/EraMultisigValidator",
"zkBytecodeHash": "0x01000b8b690cd52bce5518d5c3c6da26ee9e3bad7f206d6b74fa96c827669f66",
"zkBytecodePath": "/l1-contracts/zkout/EraMultisigValidator.sol/EraMultisigValidator.json",
"evmBytecodeHash": "0x4390b81c6b4173ede7168882ba102d0b871035272a6a843be1ff24b2a7f2045d",
"evmBytecodePath": "/l1-contracts/out/EraMultisigValidator.sol/EraMultisigValidator.json",
"evmDeployedBytecodeHash": "0xe2c31b13dae45a819f4ff743b40847d31f64728fe518f74e046a1c26e1991808",
"evmDeployedBytecodeBlakeHash": "0x5b0ba0904620e2218468140530ac3232682cf28f6181a6d61413165e80d4ea74",
"evmDeployedBytecodeLength": 12479
},
{
"contractName": "l1-contracts/EraTestnetVerifier",
"zkBytecodeHash": "0x010000c7fa5b643d684d2a6da6744576fd1efc0bf19da766168e32a71d4cd052",
Expand Down Expand Up @@ -1211,12 +1221,12 @@
},
{
"contractName": "l1-contracts/GatewayCTMDeployerValidatorTimelock",
"zkBytecodeHash": "0x01000071df6e2212e57f17a974ff2a0ac74417de457440b3300fc45fcfb5b110",
"zkBytecodeHash": "0x01000071bd06468e15a797849b639986217f327a0ded3f7fcf273ee8b72f897a",
"zkBytecodePath": "/l1-contracts/zkout/GatewayCTMDeployerValidatorTimelock.sol/GatewayCTMDeployerValidatorTimelock.json",
"evmBytecodeHash": "0x4f69d01a31492ad94e41aa222e09d8a2c882ee1642d2b4fc4a5335c73c3d1190",
"evmBytecodeHash": "0x198a99a98a0524d4eac1eb2cf22831c2e73498b3192acbfea50a146109d2c6ab",
"evmBytecodePath": "/l1-contracts/out/GatewayCTMDeployerValidatorTimelock.sol/GatewayCTMDeployerValidatorTimelock.json",
"evmDeployedBytecodeHash": "0xe01c02df3785784f5fc3f3d1be1f1851b24225bef4cf5afe5e12204761287427",
"evmDeployedBytecodeBlakeHash": "0x95c2c4750925df67955be6e984ff26262d98bf5e30e1a09cd9143efad98cbc98",
"evmDeployedBytecodeHash": "0xeafc9e57767e46e20f2b5db388ee7f2b2e683117faada8713bc8755a5a73097c",
"evmDeployedBytecodeBlakeHash": "0x6e8029a55d5563a800185be53fa692abca6cfe94dbc3cc1ab9ea09a1ae9d4e6d",
"evmDeployedBytecodeLength": 191
},
{
Expand Down Expand Up @@ -1711,12 +1721,12 @@
},
{
"contractName": "l1-contracts/MultisigCommitter",
"zkBytecodeHash": "0x01000d9117a6e04d7305fdab2e0eca295f329cbfe195e2d885aa24d86881f661",
"zkBytecodeHash": "0x01000d9165a93a381c0b895ddaaecb61ac74955193dbd4ddcb3c5e50d658f385",
"zkBytecodePath": "/l1-contracts/zkout/MultisigCommitter.sol/MultisigCommitter.json",
"evmBytecodeHash": "0x27c66cc72bf4f417980e9fe85179892a1f7f0afa1a9f8ac35986411c1beb4734",
"evmBytecodeHash": "0x61ad37d682538c58470a39976e5380786c9179c450f29042473fe7433a131ee7",
"evmBytecodePath": "/l1-contracts/out/MultisigCommitter.sol/MultisigCommitter.json",
"evmDeployedBytecodeHash": "0xa90c0e2ecb2264c40a64ce790ed71e0c190dd72fc34373dd5fcbedaaf5d494f6",
"evmDeployedBytecodeBlakeHash": "0x04f091fc91aa4b648b905dca5d4a7d3c14b788ac3cb90a07363b5901481e557d",
"evmDeployedBytecodeHash": "0x70063e9ad368941e49411fbf440b2d38ecf924aa41e5c741f2956a3169e36836",
"evmDeployedBytecodeBlakeHash": "0xf3cb49c3bde9426cd11dc44ef1dc25d931b1e4e723058e3ff2505c2b5de93ba7",
"evmDeployedBytecodeLength": 16597
},
{
Expand Down Expand Up @@ -2161,12 +2171,12 @@
},
{
"contractName": "l1-contracts/ValidatorTimelock",
"zkBytecodeHash": "0x010009b9630d7da78defeaf32a79d98f736de467b0e2d6d5bb0dca004b3229e1",
"zkBytecodeHash": "0x010009b99cc0338a9703df60302676eab8370e5038151ef01acce0473773ee0c",
"zkBytecodePath": "/l1-contracts/zkout/ValidatorTimelock.sol/ValidatorTimelock.json",
"evmBytecodeHash": "0x92d4fd7dacc168024719dee77b908a31f9dd23a945b126991f5d9ead169d59f9",
"evmBytecodeHash": "0x4644eb73b6340899a792675d36f05b536ca640c474c6a4df9446707f54483201",
"evmBytecodePath": "/l1-contracts/out/ValidatorTimelock.sol/ValidatorTimelock.json",
"evmDeployedBytecodeHash": "0x657316287c5e499f4eb5486ad42aa34c720559f573c93e1fbfb68170fccc7d6d",
"evmDeployedBytecodeBlakeHash": "0xd29e04c978e7a0487669a01dd52ed8416a0756b4450ddefdd10e9eecb1cd55fa",
"evmDeployedBytecodeHash": "0x3043c009a2a74e3664035cc5dcb065f96450599abe1a6557513e2e206ce585da",
"evmDeployedBytecodeBlakeHash": "0x5e703bf3c35c79157fcea56fee8b35e8ed5c447721c7708c0c4356db487914f4",
"evmDeployedBytecodeLength": 8631
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable-v4/utils/cryptography/EIP712Upgradeable.sol";
import {AddressHasNoCode} from "../../common/L1ContractErrors.sol";
import {ValidatorTimelock} from "./ValidatorTimelock.sol";
import {IValidatorTimelock} from "./interfaces/IValidatorTimelock.sol";
import {IEraMultisigValidator} from "./interfaces/IEraMultisigValidator.sol";

/// @author Matter Labs
/// @custom:security-contact security@matterlabs.dev
/// @notice A multisig wrapper around `ValidatorTimelock` that requires a threshold of approvals
/// before batch execution can proceed. Designed for Era chains (not ZKsync OS chains) that want
/// additional security through 2FA: independent nodes verify the execution and sign off on the
/// state transition before it can be finalized on L1.
/// @dev This contract sits between the executor EOA and the `ValidatorTimelock`. Commit and prove
/// calls are forwarded directly, while execute calls require that enough multisig members have
/// pre-approved the exact execution parameters via `approveHash`.
/// @dev Expected to be deployed as a TransparentUpgradeableProxy.
contract EraMultisigValidator is IEraMultisigValidator, ValidatorTimelock, EIP712Upgradeable {
/// @dev EIP-712 typehash for the ExecuteBatches struct.
bytes32 internal constant EXECUTE_BATCHES_TYPEHASH =
keccak256(
"ExecuteBatches(address chainAddress,uint256 processBatchFrom,uint256 processBatchTo,bytes batchData)"
);

/// @inheritdoc IEraMultisigValidator
address public override validatorTimelock;

/// @inheritdoc IEraMultisigValidator
mapping(address => bool) public override executionMultisigMember;

/// @inheritdoc IEraMultisigValidator
mapping(address => mapping(bytes32 => bool)) public override individualApprovals;

/// @dev Addresses that have approved a given hash. Iterated at execution time
/// to count only current members.
mapping(bytes32 => address[]) internal hashApprovers;

/// @inheritdoc IEraMultisigValidator
uint256 public override threshold;

/// @dev Reserved storage space to allow for layout changes in future upgrades.
uint256[44] private __gap;

constructor(address _bridgeHub) ValidatorTimelock(_bridgeHub) {
_disableInitializers();
}

/// @dev Disable the inherited 2-param `initialize` from `ValidatorTimelock` / `IValidatorTimelock`.
function initialize(address, uint32) external pure override(ValidatorTimelock, IValidatorTimelock) {
revert InitializeNotAvailable();
}

/// @inheritdoc IEraMultisigValidator
function initialize(
address _initialOwner,
uint32 _initialExecutionDelay,
address _validatorTimelock
) external initializer {
_validatorTimelockInit(_initialOwner, _initialExecutionDelay);
_initializeEraMultisig(_validatorTimelock);
}

/// @dev Shared initialization logic for EIP-712 and the validator timelock address.
function _initializeEraMultisig(address _validatorTimelock) internal {
// Initialize the required EIP-712 domain to generate signatures for batch hashes
__EIP712_init("EraMultisigValidator", "1");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment

if (_validatorTimelock.code.length == 0) {
revert AddressHasNoCode(_validatorTimelock);
}
validatorTimelock = _validatorTimelock;
}

/// @inheritdoc IEraMultisigValidator
function approveHash(bytes32 _hash) external {
if (!executionMultisigMember[msg.sender]) {
revert NotSigner();
}
if (individualApprovals[msg.sender][_hash]) {
revert AlreadySigned();
}
individualApprovals[msg.sender][_hash] = true;
hashApprovers[_hash].push(msg.sender);
emit HashApproved(msg.sender, _hash);
}

/// @inheritdoc IEraMultisigValidator
function getApprovals(bytes32 _hash) public view returns (uint256) {
uint256 count = 0;
address[] storage approvers = hashApprovers[_hash];
uint256 length = approvers.length;
for (uint256 i = 0; i < length; ++i) {
if (executionMultisigMember[approvers[i]]) {
++count;
}
}
return count;
}

/// @inheritdoc IEraMultisigValidator
function changeThreshold(uint256 _newThreshold) external onlyOwner {
threshold = _newThreshold;
emit ThresholdChanged(_newThreshold);
}

/// @inheritdoc IEraMultisigValidator
function changeExecutionMultisigMember(
address[] calldata _addressesToAdd,
address[] calldata _addressesToRemove
) external onlyOwner {
uint256 addLength = _addressesToAdd.length;
for (uint256 i = 0; i < addLength; ++i) {
executionMultisigMember[_addressesToAdd[i]] = true;
emit MultisigMemberChanged(_addressesToAdd[i], true);
}
uint256 removeLength = _addressesToRemove.length;
for (uint256 i = 0; i < removeLength; ++i) {
executionMultisigMember[_addressesToRemove[i]] = false;
emit MultisigMemberChanged(_addressesToRemove[i], false);
}
}

/// @inheritdoc IValidatorTimelock
function precommitSharedBridge(
address _chainAddress,
uint256,
bytes calldata
) public override(ValidatorTimelock, IValidatorTimelock) onlyRole(_chainAddress, PRECOMMITTER_ROLE) {
_propagateToValidatorTimelock();
}

/// @inheritdoc IValidatorTimelock
function revertBatchesSharedBridge(
address _chainAddress,
uint256
) public override(ValidatorTimelock, IValidatorTimelock) onlyRole(_chainAddress, REVERTER_ROLE) {
_propagateToValidatorTimelock();
}

/// @inheritdoc IValidatorTimelock
function commitBatchesSharedBridge(
address _chainAddress,
uint256,
uint256,
bytes calldata
) public override(ValidatorTimelock, IValidatorTimelock) onlyRole(_chainAddress, COMMITTER_ROLE) {
_propagateToValidatorTimelock();
}

/// @inheritdoc IValidatorTimelock
function proveBatchesSharedBridge(
address _chainAddress,
uint256,
uint256,
bytes calldata
) public override(ValidatorTimelock, IValidatorTimelock) onlyRole(_chainAddress, PROVER_ROLE) {
_propagateToValidatorTimelock();
}

/// @inheritdoc IValidatorTimelock
/// @dev In addition to the base role check, this override requires that the execution parameters
/// have been approved by at least `threshold` multisig members before forwarding.
function executeBatchesSharedBridge(
address _chainAddress,
uint256 _processBatchFrom,
uint256 _processBatchTo,
bytes calldata _batchData
) public override(ValidatorTimelock, IValidatorTimelock) onlyRole(_chainAddress, EXECUTOR_ROLE) {
bytes32 approvedHash = calculateHash(_chainAddress, _processBatchFrom, _processBatchTo, _batchData);
if (getApprovals(approvedHash) < threshold) {
revert NotEnoughSignatures();
}
_propagateToValidatorTimelock();
}

/// @inheritdoc IEraMultisigValidator
function calculateHash(
address _chainAddress,
uint256 _processBatchFrom,
uint256 _processBatchTo,
bytes calldata _batchData
) public view returns (bytes32) {
return
_hashTypedDataV4(
keccak256(
abi.encode(
EXECUTE_BATCHES_TYPEHASH,
_chainAddress,
_processBatchFrom,
_processBatchTo,
keccak256(_batchData)
)
)
);
}

/// @dev Forwards the current calldata to the downstream `ValidatorTimelock`.
function _propagateToValidatorTimelock() internal {
address validatorTimelock_ = validatorTimelock;
assembly {
// Copy function signature and arguments from calldata at zero position into memory at pointer position
calldatacopy(0, 0, calldatasize())
// Call the ValidatorTimelock contract, returns 0 on error
let result := call(gas(), validatorTimelock_, 0, 0, calldatasize(), 0, 0)
// Get the size of the last return data
let size := returndatasize()
// Copy the size length of bytes from return data at zero position to pointer position
returndatacopy(0, 0, size)
// Depending on the result value
switch result
case 0 {
// End execution and revert state changes
revert(0, size)
}
default {
// Return data with length of size at pointers position
return(0, size)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ contract ValidatorTimelock is
address _chainAddress,
uint256, // _l2BlockNumber (unused in this specific implementation)
bytes calldata // _l2Block (unused in this specific implementation)
) public onlyRole(_chainAddress, PRECOMMITTER_ROLE) {
) public virtual onlyRole(_chainAddress, PRECOMMITTER_ROLE) {
_propagateToZKChain(_chainAddress);
}

Expand Down Expand Up @@ -233,7 +233,7 @@ contract ValidatorTimelock is
function revertBatchesSharedBridge(
address _chainAddress,
uint256 /*_newLastBatch*/
) external onlyRole(_chainAddress, REVERTER_ROLE) {
) public virtual onlyRole(_chainAddress, REVERTER_ROLE) {
_propagateToZKChain(_chainAddress);
}

Expand All @@ -243,7 +243,7 @@ contract ValidatorTimelock is
uint256, // _processBatchFrom (unused in this specific implementation)
uint256, // _processBatchTo (unused in this specific implementation)
bytes calldata // _proofData (unused in this specific implementation)
) external onlyRole(_chainAddress, PROVER_ROLE) {
) public virtual onlyRole(_chainAddress, PROVER_ROLE) {
_propagateToZKChain(_chainAddress);
}

Expand All @@ -253,7 +253,7 @@ contract ValidatorTimelock is
uint256 _processBatchFrom,
uint256 _processBatchTo,
bytes calldata // _batchData (unused in this specific implementation)
) external onlyRole(_chainAddress, EXECUTOR_ROLE) {
) public virtual onlyRole(_chainAddress, EXECUTOR_ROLE) {
uint256 delay = executionDelay; // uint32
unchecked {
for (uint256 i = _processBatchFrom; i <= _processBatchTo; ++i) {
Expand Down
Loading
Loading