Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: OZ ZKChain: Governance Audit Fixes Combines #1044

Open
wants to merge 41 commits into
base: governance-fix-review
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
c764e73
remove gas constant
StanislavBreadless Oct 10, 2024
3a2e242
fix unit tests
StanislavBreadless Oct 10, 2024
1d2491a
separate function
StanislavBreadless Oct 10, 2024
f7e2eff
fix comments
StanislavBreadless Oct 10, 2024
d5e7667
add tests for the new methods
StanislavBreadless Oct 10, 2024
1c01590
additional checks for restrictions
StanislavBreadless Oct 10, 2024
5410fde
gas optimization
StanislavBreadless Oct 10, 2024
1bdf736
Update l1-contracts/contracts/governance/restriction/RestrictionValid…
StanislavBreadless Oct 14, 2024
cacd00f
Update l1-contracts/contracts/governance/PermanentRestriction.sol
StanislavBreadless Oct 14, 2024
7f3511b
Update l1-contracts/contracts/governance/PermanentRestriction.sol
StanislavBreadless Oct 14, 2024
57a3d97
Update l1-contracts/contracts/governance/L2AdminFactory.sol
StanislavBreadless Oct 14, 2024
dfcb9cd
Add ChainAdmin contract
vladbochok Oct 14, 2024
c832b06
Fix N-03
vladbochok Oct 14, 2024
f954a58
Emit event
vladbochok Oct 14, 2024
3a4f426
Fix N-05
vladbochok Oct 14, 2024
7ce1e00
Fix N-06
vladbochok Oct 14, 2024
04268f3
Fix N-07
vladbochok Oct 14, 2024
d0c822a
review comments
StanislavBreadless Oct 14, 2024
a418fb0
doc comment
StanislavBreadless Oct 14, 2024
12618e8
Update l1-contracts/contracts/governance/L2AdminFactory.sol
StanislavBreadless Oct 14, 2024
0fb6ebf
fix review
StanislavBreadless Oct 14, 2024
af8a60e
Apply suggestion
vladbochok Oct 15, 2024
1aed805
Update l1-contracts/contracts/governance/L2AdminFactory.sol
StanislavBreadless Oct 15, 2024
7209d99
Update l1-contracts/contracts/governance/L2AdminFactory.sol
StanislavBreadless Oct 15, 2024
bf9251a
respond to comments
StanislavBreadless Oct 23, 2024
64cceff
fix suggestion
StanislavBreadless Oct 23, 2024
210beef
Merge remote-tracking branch 'origin/sb-governance-m01' into ra/gover…
Oct 29, 2024
e9f969d
Merge remote-tracking branch 'origin/sb-governance-m02' into ra/gover…
Oct 29, 2024
2874526
Merge remote-tracking branch 'origin/sb-governance-l01' into ra/gover…
Oct 29, 2024
a264642
Merge remote-tracking branch 'origin/sb-governance-l02' into ra/gover…
Oct 29, 2024
9093c05
Merge remote-tracking branch 'origin/sb-governance-n01' into ra/gover…
Oct 29, 2024
f14c3e2
Merge remote-tracking branch 'origin/vb-governance-n02' into ra/gover…
Oct 29, 2024
cc2cd8b
Merge remote-tracking branch 'origin/vb-governance-n03' into ra/gover…
Oct 29, 2024
444cf6b
Merge remote-tracking branch 'origin/vb-governance-n04' into ra/gover…
Oct 29, 2024
befa74a
Merge remote-tracking branch 'origin/vb-governance-n05' into ra/gover…
Oct 29, 2024
2430404
Merge remote-tracking branch 'origin/vb-governance-n06' into ra/gover…
Oct 29, 2024
9bcd15b
Merge remote-tracking branch 'origin/vb-governance-n07' into ra/gover…
Oct 29, 2024
bf8e072
(fix): merge conflicts from all commits
Oct 29, 2024
59e0d42
(fix): add clarifying comment
Nov 11, 2024
7d28493
(fix): codespell
Nov 11, 2024
82fef7c
(fix): wrong function marked internal
Nov 11, 2024
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
14 changes: 2 additions & 12 deletions l1-contracts/contracts/common/L1ContractErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@ error RestrictionWasNotPresent(address restriction);
error RestrictionWasAlreadyPresent(address restriction);
// 0x3331e9c0
error CallNotAllowed(bytes call);
// 0x59e1b0d2
error ChainZeroAddress();
// 0xff4bbdf1
error NotAHyperchain(address chainAddress);
// 0xa3decdf3
error NotAnAdmin(address expected, address actual);
// 0xf6fd7071
error RemovingPermanentRestriction();
// 0xfcb9b2e1
Expand Down Expand Up @@ -242,8 +236,6 @@ error NonSequentialBatch();
error NonSequentialVersion();
// 0x4ef79e5a
error NonZeroAddress(address);
// 0xdd629f86
error NotEnoughGas();
// 0xdd7e3621
error NotInitializedReentrancyGuard();
// 0xdf17e316
Expand Down Expand Up @@ -407,12 +399,10 @@ error IncorrectBatchBounds(
);
// 0x64107968
error AssetHandlerNotRegistered(bytes32 assetId);
// 0x10f30e75
error NotBridgehub(address addr);
// 0x2554babc
error InvalidAddress(address expected, address actual);
// 0xfa5cd00f
error NotAllowed(address addr);
// 0x64846fe4
error NotARestriction(address addr);

enum SharedBridgeKey {
PostUpgradeFirstBatch,
Expand Down
35 changes: 35 additions & 0 deletions l1-contracts/contracts/dev-contracts/DummyRestriction.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.24;

import {Call} from "../governance/Common.sol";
import {IRestriction, RESTRICTION_MAGIC} from "../governance/restriction/IRestriction.sol";

/// @title Restriction contract interface
/// @author Matter Labs
/// @custom:security-contact [email protected]
contract DummyRestriction is IRestriction {
bool immutable correctMagic;

constructor(bool useCorrectMagic) {
correctMagic = useCorrectMagic;
}

/// @notice A method used to check that the contract supports this interface.
/// @return Returns the `RESTRICTION_MAGIC`
function getSupportsRestrictionMagic() external view returns (bytes32) {
if (correctMagic) {
return RESTRICTION_MAGIC;
} else {
// Invalid magic
return bytes32(0);
}
}

/// @notice Ensures that the invoker has the required role to call the function.
/// @param _call The call data.
/// @param _invoker The address of the invoker.
function validateCall(Call calldata _call, address _invoker) external view virtual {
// nothing
}
}
22 changes: 14 additions & 8 deletions l1-contracts/contracts/governance/AccessControlRestriction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@

pragma solidity 0.8.24;

import {AccessToFallbackDenied, AccessToFunctionDenied} from "../common/L1ContractErrors.sol";
import {AccessToFallbackDenied, AccessToFunctionDenied, ZeroAddress} from "../common/L1ContractErrors.sol";
import {IAccessControlRestriction} from "./IAccessControlRestriction.sol";
import {AccessControlDefaultAdminRules} from "@openzeppelin/contracts-v4/access/AccessControlDefaultAdminRules.sol";
import {IRestriction} from "./IRestriction.sol";
import {Restriction} from "./restriction/Restriction.sol";
import {Call} from "./Common.sol";

/// @author Matter Labs
/// @custom:security-contact [email protected]
/// @notice The Restriction that is designed to provide the access control logic for the `ChainAdmin` contract.
/// @dev It inherits from `AccessControlDefaultAdminRules` without overriding `_setRoleAdmin` functionaity. In other
/// @dev It inherits from `AccessControlDefaultAdminRules` without overriding `_setRoleAdmin` functionality. In other
/// words, the `DEFAULT_ADMIN_ROLE` is the only role that can manage roles. This is done for simplicity.
/// @dev An instance of this restriction should be deployed separately for each `ChainAdmin` contract.
/// @dev IMPORTANT: this function does not validate the ability of the invoker to use `msg.value`. Thus,
/// either all callers with access to functions should be trusted to not steal ETH from the `ChainAdmin` account
/// or not ETH should be passively stored in `ChainAdmin` account.
contract AccessControlRestriction is IRestriction, IAccessControlRestriction, AccessControlDefaultAdminRules {
/// @notice Required roles to call a specific functions.
/// or no ETH should be passively stored in `ChainAdmin` account.
contract AccessControlRestriction is Restriction, IAccessControlRestriction, AccessControlDefaultAdminRules {
/// @notice Required roles to call a specific function.
/// @dev Note, that the role 0 means the `DEFAULT_ADMIN_ROLE` from the `AccessControlDefaultAdminRules` contract.
mapping(address target => mapping(bytes4 selector => bytes32 requiredRole)) public requiredRoles;

Expand All @@ -39,6 +39,9 @@ contract AccessControlRestriction is IRestriction, IAccessControlRestriction, Ac
bytes4 _selector,
bytes32 _requiredRole
) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (_target == address(0)) {
revert ZeroAddress();
}
requiredRoles[_target][_selector] = _requiredRole;

emit RoleSet(_target, _selector, _requiredRole);
Expand All @@ -48,13 +51,16 @@ contract AccessControlRestriction is IRestriction, IAccessControlRestriction, Ac
/// @param _target The address of the contract.
/// @param _requiredRole The required role.
function setRequiredRoleForFallback(address _target, bytes32 _requiredRole) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (_target == address(0)) {
revert ZeroAddress();
}
requiredRolesForFallback[_target] = _requiredRole;

emit FallbackRoleSet(_target, _requiredRole);
}

/// @inheritdoc IRestriction
function validateCall(Call calldata _call, address _invoker) external view {
/// @inheritdoc Restriction
function validateCall(Call calldata _call, address _invoker) external view override {
// Note, that since `DEFAULT_ADMIN_ROLE` is 0 and the default storage value for the
// `requiredRoles` and `requiredRolesForFallback` is 0, the default admin is by default a required
// role for all the functions.
Expand Down
39 changes: 23 additions & 16 deletions l1-contracts/contracts/governance/ChainAdmin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ pragma solidity 0.8.24;

// solhint-disable gas-length-in-loops

import {NoCallsProvided, OnlySelfAllowed, RestrictionWasNotPresent, RestrictionWasAlreadyPresent} from "../common/L1ContractErrors.sol";
import {ZeroAddress, NoCallsProvided, OnlySelfAllowed, RestrictionWasNotPresent, RestrictionWasAlreadyPresent} from "../common/L1ContractErrors.sol";
import {IChainAdmin} from "./IChainAdmin.sol";
import {IRestriction} from "./IRestriction.sol";
import {Restriction} from "./restriction/Restriction.sol";
import {RestrictionValidator} from "./restriction/RestrictionValidator.sol";
import {Call} from "./Common.sol";

import {EnumerableSet} from "@openzeppelin/contracts-v4/utils/structs/EnumerableSet.sol";
Expand All @@ -15,11 +16,19 @@ import {ReentrancyGuard} from "../common/ReentrancyGuard.sol";
/// @author Matter Labs
/// @custom:security-contact [email protected]
/// @notice The contract is designed to hold the `admin` role in ZKSync Chain (State Transition) contracts.
/// The owner of the contract can perform any external calls and also save the information needed for
/// the blockchain node to accept the protocol upgrade.
/// @dev Note, that it does not implement any form of access control by default, but instead utilizes
/// so called "restrictions": contracts that implement the `IRestriction` interface and ensure that
/// particular restrictions are ensured for the contract, including access control, security invariants, etc.
contract ChainAdmin is IChainAdmin, ReentrancyGuard {
using EnumerableSet for EnumerableSet.AddressSet;

/// @notice Mapping of protocol versions to their expected upgrade timestamps.
/// @dev Needed for the offchain node administration to know when to start building batches with the new protocol version.
mapping(uint256 protocolVersion => uint256 upgradeTimestamp) public protocolVersionToUpgradeTimestamp;

/// @notice The set of active restrictions.
EnumerableSet.AddressSet internal activeRestrictions;

/// @notice Ensures that only the `ChainAdmin` contract itself can call the function.
/// @dev All functions that require access-control should use `onlySelf` modifier, while the access control logic
/// should be implemented in the restriction contracts.
Expand All @@ -38,13 +47,6 @@ contract ChainAdmin is IChainAdmin, ReentrancyGuard {
}
}

/// @notice Mapping of protocol versions to their expected upgrade timestamps.
/// @dev Needed for the offchain node administration to know when to start building batches with the new protocol version.
mapping(uint256 protocolVersion => uint256 upgradeTimestamp) public protocolVersionToUpgradeTimestamp;

/// @notice The set of active restrictions.
EnumerableSet.AddressSet internal activeRestrictions;

/// @notice Returns the list of active restrictions.
function getRestrictions() public view returns (address[] memory) {
return activeRestrictions.values();
Expand Down Expand Up @@ -105,21 +107,26 @@ contract ChainAdmin is IChainAdmin, ReentrancyGuard {
/// @dev Contract might receive/hold ETH as part of the maintenance process.
receive() external payable {}

/// @notice Function that returns the current admin can perform the call.
/// @dev By default it always returns true, but can be overridden in derived contracts.
function _validateCall(Call calldata _call) internal view {
/// @notice Function that ensures that the current admin can perform the call.
/// @dev Reverts in case the call can not be performed. Successfully executes otherwise.
function _validateCall(Call calldata _call) private view {
address[] memory restrictions = getRestrictions();

unchecked {
for (uint256 i = 0; i < restrictions.length; ++i) {
IRestriction(restrictions[i]).validateCall(_call, msg.sender);
Restriction(restrictions[i]).validateCall(_call, msg.sender);
}
}
}

/// @notice Adds a new restriction to the active restrictions set.
/// @param _restriction The address of the restriction contract to be added.
function _addRestriction(address _restriction) internal {
function _addRestriction(address _restriction) private {
if (_restriction == address(0)) {
revert ZeroAddress();
}
RestrictionValidator.validateRestriction(_restriction);

if (!activeRestrictions.add(_restriction)) {
revert RestrictionWasAlreadyPresent(_restriction);
}
Expand Down
59 changes: 52 additions & 7 deletions l1-contracts/contracts/governance/L2AdminFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
pragma solidity 0.8.24;

import {ChainAdmin} from "./ChainAdmin.sol";
import {RestrictionValidator} from "./restriction/RestrictionValidator.sol";
import {ZeroAddress} from "../common/L1ContractErrors.sol";

/// @author Matter Labs
/// @custom:security-contact [email protected]
Expand All @@ -14,29 +16,72 @@ import {ChainAdmin} from "./ChainAdmin.sol";
/// @dev The contract is immutable, in case the restrictions need to be changed,
/// a new contract should be deployed.
contract L2AdminFactory {
/// @notice Emitted when an admin is deployed on the L2.
/// @param admin The address of the newly deployed admin.
event AdminDeployed(address admin);

/// @dev We use storage instead of immutable variables due to the
/// specifics of the zkEVM environment, where storage is actually cheaper.
address[] public requiredRestrictions;

constructor(address[] memory _requiredRestrictions) {
_validateZeroAddress(_requiredRestrictions);
_validateRestrctions(_requiredRestrictions);
requiredRestrictions = _requiredRestrictions;
}

/// @notice Deploys a new L2 admin contract.
/// @return admin The address of the deployed admin contract.
function deployAdmin(address[] calldata _additionalRestrictions, bytes32 _salt) external returns (address admin) {
address[] memory restrictions = new address[](requiredRestrictions.length + _additionalRestrictions.length);
// solhint-disable-next-line gas-calldata-parameters
function deployAdmin(address[] memory _additionalRestrictions, bytes32 _salt) external returns (address admin) {
// Even though the chain admin will likely perform similar checks,
// we keep those here just in case, since it is not expensive, while allowing to fail fast.
_validateZeroAddress(_additionalRestrictions);
_validateRestrctions(_additionalRestrictions);

uint256 cachedRequired = requiredRestrictions.length;
for (uint256 i = 0; i < cachedRequired; ++i) {
restrictions[i] = requiredRestrictions[i];
}
uint256 cachedAdditional = _additionalRestrictions.length;
for (uint256 i = 0; i < cachedAdditional; ++i) {
restrictions[requiredRestrictions.length + i] = _additionalRestrictions[i];

address[] memory restrictions = new address[](cachedRequired + cachedAdditional);

unchecked {
for (uint256 i = 0; i < cachedRequired; ++i) {
restrictions[i] = requiredRestrictions[i];
}
for (uint256 i = 0; i < cachedAdditional; ++i) {
restrictions[cachedRequired + i] = _additionalRestrictions[i];
}
}

admin = address(new ChainAdmin{salt: _salt}(restrictions));

emit AdminDeployed(admin);
}

/// @notice Checks that the provided list of restrictions does not contain
/// any zero addresses.
/// @param _restrictions List of the restrictions to check.
/// @dev In case either of the restrictions is zero address, the function reverts.
function _validateZeroAddress(address[] memory _restrictions) private pure {
unchecked {
uint256 length = _restrictions.length;
for (uint256 i = 0; i < length; ++i) {
if (_restrictions[i] == address(0)) {
revert ZeroAddress();
}
}
}
}

/// @notice Checks that the provided list of restrictions is correct.
/// @param _restrictions List of the restrictions to check.
/// @dev In case either of the restrictions is not correct, the function reverts.
function _validateRestrctions(address[] memory _restrictions) private view {
unchecked {
uint256 length = _restrictions.length;
for (uint256 i = 0; i < length; ++i) {
RestrictionValidator.validateRestriction(_restrictions[i]);
}
}
}
}
Loading
Loading