diff --git a/.github/workflows/l1-contracts-ci.yaml b/.github/workflows/l1-contracts-ci.yaml index c54944a27..431a4897b 100644 --- a/.github/workflows/l1-contracts-ci.yaml +++ b/.github/workflows/l1-contracts-ci.yaml @@ -58,6 +58,7 @@ jobs: l2-contracts/artifacts-zk l2-contracts/cache-zk l2-contracts/typechain + l1-contracts/lib lint: runs-on: ubuntu-latest @@ -117,6 +118,7 @@ jobs: l2-contracts/artifacts-zk l2-contracts/cache-zk l2-contracts/typechain + l1-contracts/lib - name: Run tests working-directory: ./l1-contracts @@ -158,6 +160,7 @@ jobs: l2-contracts/artifacts-zk l2-contracts/cache-zk l2-contracts/typechain + l1-contracts/lib - name: Install foundry zksync run: | @@ -208,6 +211,7 @@ jobs: l2-contracts/artifacts-zk l2-contracts/cache-zk l2-contracts/typechain + l1-contracts/lib - name: Build L2 contracts run: yarn l2 build @@ -275,6 +279,7 @@ jobs: l2-contracts/artifacts-zk l2-contracts/cache-zk l2-contracts/typechain + l1-contracts/lib - name: Run coverage run: FOUNDRY_PROFILE=default yarn test:foundry && FOUNDRY_PROFILE=default yarn coverage:foundry --report summary --report lcov diff --git a/README.md b/README.md index 8c776af4c..e4e7a2f56 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,7 @@ decentralization. Since it's EVM compatible (Solidity/Vyper), 99% of Ethereum pr or re-auditing a single line of code. ZKsync Era also uses an LLVM-based compiler that will eventually let developers write smart contracts in C++, Rust and other popular languages. -This repository contains both L1 and L2 ZKsync smart contracts. For their description see the -[system overview](docs/overview.md). +This repository contains both L1 and L2 ZKsync smart contracts. ## Disclaimer diff --git a/docs/README.md b/docs/README.md index b027c1801..334158148 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,7 +3,6 @@ The order of the files here only roughly represents the order of reading. A lot of topics are intertwined, so it is recommended to read everything first to have a complete picture and then refer to specific documents for more details. - [Glossary](./glossary.md) -- [Overview](./overview.md) - Contracts of an individual chain - [ZK Chain basics](./settlement_contracts/zkchain_basics.md) - Data availability diff --git a/docs/overview.md b/docs/overview.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/l1-contracts/contracts/common/libraries/UnsafeBytes.sol b/l1-contracts/contracts/common/libraries/UnsafeBytes.sol index e2680d9e0..4edf94004 100644 --- a/l1-contracts/contracts/common/libraries/UnsafeBytes.sol +++ b/l1-contracts/contracts/common/libraries/UnsafeBytes.sol @@ -30,6 +30,13 @@ library UnsafeBytes { } } + function readUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128 result, uint256 offset) { + assembly { + offset := add(_start, 16) + result := mload(add(_bytes, offset)) + } + } + function readUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256 result, uint256 offset) { assembly { offset := add(_start, 32) diff --git a/l1-contracts/contracts/state-transition/ChainTypeManager.sol b/l1-contracts/contracts/state-transition/ChainTypeManager.sol index d97f7b8f1..398a659ff 100644 --- a/l1-contracts/contracts/state-transition/ChainTypeManager.sol +++ b/l1-contracts/contracts/state-transition/ChainTypeManager.sol @@ -2,6 +2,8 @@ pragma solidity 0.8.24; +// solhint-disable gas-custom-errors, reason-string + import {EnumerableMap} from "@openzeppelin/contracts-v4/utils/structs/EnumerableMap.sol"; import {SafeCast} from "@openzeppelin/contracts-v4/utils/math/SafeCast.sol"; diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol index 7ea8c9ff9..82317e09d 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol @@ -2,6 +2,8 @@ pragma solidity 0.8.24; +// solhint-disable gas-custom-errors, reason-string + import {IAdmin} from "../../chain-interfaces/IAdmin.sol"; import {Diamond} from "../../libraries/Diamond.sol"; import {MAX_GAS_PER_TRANSACTION, ZKChainCommitment} from "../../../common/Config.sol"; diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol index 87f3496e7..0569442ab 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol @@ -2,6 +2,8 @@ pragma solidity 0.8.24; +// solhint-disable gas-custom-errors, reason-string + import {Math} from "@openzeppelin/contracts-v4/utils/math/Math.sol"; import {IMailbox} from "../../chain-interfaces/IMailbox.sol"; diff --git a/l1-contracts/deploy-scripts/DeployL2Contracts.sol b/l1-contracts/deploy-scripts/DeployL2Contracts.sol index 6e320639a..d249e086e 100644 --- a/l1-contracts/deploy-scripts/DeployL2Contracts.sol +++ b/l1-contracts/deploy-scripts/DeployL2Contracts.sol @@ -222,4 +222,65 @@ contract DeployL2Script is Script { l1SharedBridgeProxy: config.l1SharedBridgeProxy }); } + + // Deploy the ConsensusRegistry implementation and save its address into the config. + function deployConsensusRegistry() internal { + // ConsensusRegistry.sol doesn't have a constructor, just an initializer. + bytes memory constructorData = ""; + + config.consensusRegistryImplementation = Utils.deployThroughL1({ + bytecode: contracts.consensusRegistryBytecode, + constructorargs: constructorData, + create2salt: "", + l2GasLimit: Utils.MAX_PRIORITY_TX_GAS, + factoryDeps: new bytes[](0), + chainId: config.chainId, + bridgehubAddress: config.bridgehubAddress, + l1SharedBridgeProxy: config.l1SharedBridgeProxy + }); + } + + // Deploy a transparent upgradable proxy for the already deployed consensus registry + // implementation and save its address into the config. + function deployConsensusRegistryProxy() internal { + // Admin for the proxy + address l2GovernorAddress = AddressAliasHelper.applyL1ToL2Alias(config.governance); + + // Call ConsensusRegistry::initialize with the initial owner. + // solhint-disable-next-line func-named-parameters + bytes memory proxyInitializationParams = abi.encodeWithSignature( + "initialize(address)", + config.consensusRegistryOwner + ); + + bytes memory consensusRegistryProxyConstructorData = abi.encode( + config.consensusRegistryImplementation, // _logic + l2GovernorAddress, // admin_ + proxyInitializationParams // _data + ); + + config.consensusRegistryProxy = Utils.deployThroughL1({ + bytecode: contracts.consensusRegistryProxyBytecode, + constructorargs: consensusRegistryProxyConstructorData, + create2salt: "", + l2GasLimit: Utils.MAX_PRIORITY_TX_GAS, + factoryDeps: new bytes[](0), + chainId: config.chainId, + bridgehubAddress: config.bridgehubAddress, + l1SharedBridgeProxy: config.l1SharedBridgeProxy + }); + } + + function initializeChain() internal { + L1SharedBridge bridge = L1SharedBridge(config.l1SharedBridgeProxy); + + Utils.executeUpgrade({ + _governor: bridge.owner(), + _salt: bytes32(0), + _target: config.l1SharedBridgeProxy, + _data: abi.encodeCall(bridge.initializeChainGovernance, (config.chainId, config.l2SharedBridgeProxy)), + _value: 0, + _delay: 0 + }); + } } diff --git a/l1-contracts/deploy-scripts/GenerateForceDeploymentsData.s.sol b/l1-contracts/deploy-scripts/GenerateForceDeploymentsData.s.sol new file mode 100644 index 000000000..3d5a1c042 --- /dev/null +++ b/l1-contracts/deploy-scripts/GenerateForceDeploymentsData.s.sol @@ -0,0 +1,142 @@ +pragma solidity ^0.8.24; + +import {Script} from "forge-std/Script.sol"; +import {stdToml} from "forge-std/StdToml.sol"; + +import {Utils} from "./Utils.sol"; +import {L2_BRIDGEHUB_ADDR, L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR, L2_MESSAGE_ROOT_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {ForceDeployment} from "contracts/state-transition/l2-deps/IL2GenesisUpgrade.sol"; + +contract GenerateForceDeploymentsData is Script { + using stdToml for string; + + Config internal config; + ContractsBytecodes internal contracts; + + // solhint-disable-next-line gas-struct-packing + struct Config { + address l1AssetRouterProxy; + address governance; + uint256 chainId; + uint256 eraChainId; + bytes forceDeploymentsData; + address l2LegacySharedBridge; + address l2TokenBeacon; + bool contractsDeployedAlready; + } + + struct ContractsBytecodes { + bytes bridgehubBytecode; + bytes l2AssetRouterBytecode; + bytes l2NtvBytecode; + bytes l2StandardErc20FactoryBytecode; + bytes l2TokenProxyBytecode; + bytes l2StandardErc20Bytecode; + bytes messageRootBytecode; + } + + function run() public { + initializeConfig(); + loadContracts(); + + genesisForceDeploymentsData(); + + saveOutput(); + } + + function loadContracts() internal { + //HACK: Meanwhile we are not integrated foundry zksync we use contracts that has been built using hardhat + contracts.l2StandardErc20FactoryBytecode = Utils.readHardhatBytecode( + "/artifacts-zk/@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json" + ); + contracts.l2TokenProxyBytecode = Utils.readHardhatBytecode( + "/artifacts-zk/@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol/BeaconProxy.json" + ); + contracts.l2StandardErc20Bytecode = Utils.readHardhatBytecode( + "/artifacts-zk/contracts/bridge/BridgedStandardERC20.sol/BridgedStandardERC20.json" + ); + + contracts.l2AssetRouterBytecode = Utils.readHardhatBytecode( + "/artifacts-zk/contracts/bridge/asset-router/L2AssetRouter.sol/L2AssetRouter.json" + ); + contracts.bridgehubBytecode = Utils.readHardhatBytecode( + "/../l1-contracts/artifacts-zk/contracts/bridgehub/Bridgehub.sol/Bridgehub.json" + ); + contracts.messageRootBytecode = Utils.readHardhatBytecode( + "/../l1-contracts/artifacts-zk/contracts/bridgehub/MessageRoot.sol/MessageRoot.json" + ); + contracts.l2NtvBytecode = Utils.readHardhatBytecode( + "/artifacts-zk/contracts/bridge/ntv/L2NativeTokenVault.sol/L2NativeTokenVault.json" + ); + } + + function initializeConfig() internal { + string memory root = vm.projectRoot(); + string memory path = string.concat(root, vm.envString("FORCE_DEPLOYMENTS_CONFIG")); + string memory toml = vm.readFile(path); + config.governance = toml.readAddress("$.governance"); + config.l1AssetRouterProxy = toml.readAddress("$.l1_shared_bridge"); + config.chainId = toml.readUint("$.chain_id"); + config.eraChainId = toml.readUint("$.era_chain_id"); + config.l2LegacySharedBridge = toml.readAddress("$.l2_legacy_shared_bridge"); + config.l2TokenBeacon = toml.readAddress("$.l2_token_beacon"); + config.contractsDeployedAlready = toml.readBool("$.l2_contracts_deployed_already"); + } + + function saveOutput() internal { + string memory toml = vm.serializeBytes("root", "force_deployments_data", config.forceDeploymentsData); + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/script-out/output-force-deployments-data.toml"); + vm.writeToml(toml, path); + } + + function genesisForceDeploymentsData() internal { + address aliasedGovernance = AddressAliasHelper.applyL1ToL2Alias(config.governance); + ForceDeployment[] memory forceDeployments = new ForceDeployment[](4); + + forceDeployments[0] = ForceDeployment({ + bytecodeHash: keccak256(contracts.bridgehubBytecode), + newAddress: L2_BRIDGEHUB_ADDR, + callConstructor: true, + value: 0, + input: abi.encode(config.chainId, aliasedGovernance) + }); + + forceDeployments[1] = ForceDeployment({ + bytecodeHash: keccak256(contracts.l2AssetRouterBytecode), + newAddress: L2_ASSET_ROUTER_ADDR, + callConstructor: true, + value: 0, + // solhint-disable-next-line func-named-parameters + input: abi.encode(config.chainId, config.eraChainId, config.l1AssetRouterProxy, address(1)) + }); + + forceDeployments[2] = ForceDeployment({ + bytecodeHash: keccak256(contracts.l2NtvBytecode), + newAddress: L2_NATIVE_TOKEN_VAULT_ADDR, + callConstructor: true, + value: 0, + // solhint-disable-next-line func-named-parameters + input: abi.encode( + config.chainId, + aliasedGovernance, + keccak256(contracts.l2TokenProxyBytecode), + config.l2LegacySharedBridge, + config.l2TokenBeacon, + config.contractsDeployedAlready + ) + }); + + forceDeployments[3] = ForceDeployment({ + bytecodeHash: keccak256(contracts.messageRootBytecode), + newAddress: L2_MESSAGE_ROOT_ADDR, + callConstructor: true, + value: 0, + // solhint-disable-next-line func-named-parameters + input: abi.encode(L2_BRIDGEHUB_ADDR) + }); + + config.forceDeploymentsData = abi.encode(forceDeployments); + } +} diff --git a/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-zk-chain-10.toml b/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-zk-chain-10.toml new file mode 100644 index 000000000..8ce96fda5 --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-zk-chain-10.toml @@ -0,0 +1,13 @@ +owner_address = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" + +[chain] +base_token_addr = "0x0000000000000000000000000000000000000001" +base_token_gas_price_multiplier_denominator = 1 +base_token_gas_price_multiplier_nominator = 1 +bridgehub_create_new_chain_salt = 10 +chain_chain_id = 10 +governance_min_delay = 0 +governance_security_council_address = "0x0000000000000000000000000000000000000000" +validator_sender_operator_blobs_eth = "0x0000000000000000000000000000000000000001" +validator_sender_operator_commit_eth = "0x0000000000000000000000000000000000000000" +validium_mode = 0 diff --git a/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-zk-chain-11.toml b/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-zk-chain-11.toml new file mode 100644 index 000000000..5e4e1dce8 --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-zk-chain-11.toml @@ -0,0 +1,13 @@ +owner_address = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" + +[chain] +base_token_addr = "0x0000000000000000000000000000000000000001" +base_token_gas_price_multiplier_denominator = 1 +base_token_gas_price_multiplier_nominator = 1 +bridgehub_create_new_chain_salt = 11 +chain_chain_id = 11 +governance_min_delay = 0 +governance_security_council_address = "0x0000000000000000000000000000000000000000" +validator_sender_operator_blobs_eth = "0x0000000000000000000000000000000000000001" +validator_sender_operator_commit_eth = "0x0000000000000000000000000000000000000000" +validium_mode = 0 diff --git a/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-zk-chain-era.toml b/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-zk-chain-era.toml new file mode 100644 index 000000000..39ab26fe6 --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-zk-chain-era.toml @@ -0,0 +1,13 @@ +owner_address = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" + +[chain] +base_token_addr = "0x0000000000000000000000000000000000000001" +base_token_gas_price_multiplier_denominator = 1 +base_token_gas_price_multiplier_nominator = 1 +bridgehub_create_new_chain_salt = 9 +chain_chain_id = 9 +governance_min_delay = 0 +governance_security_council_address = "0x0000000000000000000000000000000000000000" +validator_sender_operator_blobs_eth = "0x0000000000000000000000000000000000000001" +validator_sender_operator_commit_eth = "0x0000000000000000000000000000000000000000" +validium_mode = 0 diff --git a/l2-contracts/contracts/ConsensusRegistry.sol b/l2-contracts/contracts/ConsensusRegistry.sol index 6d05a4909..de5af6340 100644 --- a/l2-contracts/contracts/ConsensusRegistry.sol +++ b/l2-contracts/contracts/ConsensusRegistry.sol @@ -5,7 +5,6 @@ pragma solidity 0.8.24; import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable-v4/proxy/utils/Initializable.sol"; import {IConsensusRegistry} from "./interfaces/IConsensusRegistry.sol"; -import {ZeroAddress} from "./errors/L2ContractErrors.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev @@ -22,14 +21,8 @@ contract ConsensusRegistry is IConsensusRegistry, Initializable, Ownable2StepUpg /// @dev A mapping of node owners => nodes. mapping(address => Node) public nodes; /// @dev A mapping for enabling efficient lookups when checking whether a given attester public key exists. - /// @dev Initially, the mappings mark the public keys used by the attesters in the current committee. However, - /// @dev after calling the changeAttesterKey functions, the mappings might also contain public keys of attesters - /// @dev that will only be part of the committee once the contract owner updates the attestersCommit state variable. mapping(bytes32 => bool) public attesterPubKeyHashes; /// @dev A mapping for enabling efficient lookups when checking whether a given validator public key exists. - /// @dev Initially, the mappings mark the public keys used by the validators in the current committee. However, - /// @dev after calling the changeValidatorKey functions, the mappings might also contain public keys of validators - /// @dev that will only be part of the committee once the contract owner updates the validatorsCommit state variable. mapping(bytes32 => bool) public validatorPubKeyHashes; /// @dev Counter that increments with each new commit to the attester committee. uint32 public attestersCommit; @@ -43,14 +36,9 @@ contract ConsensusRegistry is IConsensusRegistry, Initializable, Ownable2StepUpg _; } - /// @custom:oz-upgrades-unsafe-allow constructor - constructor() { - _disableInitializers(); - } - function initialize(address _initialOwner) external initializer { if (_initialOwner == address(0)) { - revert ZeroAddress(); + revert InvalidInputNodeOwnerAddress(); } _transferOwnership(_initialOwner); } @@ -59,20 +47,16 @@ contract ConsensusRegistry is IConsensusRegistry, Initializable, Ownable2StepUpg /// @dev Fails if node owner already exists. /// @dev Fails if a validator/attester with the same public key already exists. /// @param _nodeOwner The address of the new node's owner. - /// @param _isValidatorActive A flag stating if the validator starts activated. /// @param _validatorWeight The voting weight of the validator. /// @param _validatorPubKey The BLS12-381 public key of the validator. /// @param _validatorPoP The proof-of-possession (PoP) of the validator's public key. - /// @param _isAttesterActive A flag stating if the attester starts activated. /// @param _attesterWeight The voting weight of the attester. /// @param _attesterPubKey The ECDSA public key of the attester. function add( address _nodeOwner, - bool _isValidatorActive, uint32 _validatorWeight, BLS12_381PublicKey calldata _validatorPubKey, BLS12_381Signature calldata _validatorPoP, - bool _isAttesterActive, uint32 _attesterWeight, Secp256k1PublicKey calldata _attesterPubKey ) external onlyOwner { @@ -81,12 +65,6 @@ contract ConsensusRegistry is IConsensusRegistry, Initializable, Ownable2StepUpg _verifyInputBLS12_381PublicKey(_validatorPubKey); _verifyInputBLS12_381Signature(_validatorPoP); _verifyInputSecp256k1PublicKey(_attesterPubKey); - if (_attesterWeight == 0) { - revert ZeroAttesterWeight(); - } - if (_validatorWeight == 0) { - revert ZeroValidatorWeight(); - } // Verify storage. _verifyNodeOwnerDoesNotExist(_nodeOwner); @@ -99,8 +77,8 @@ contract ConsensusRegistry is IConsensusRegistry, Initializable, Ownable2StepUpg nodeOwners.push(_nodeOwner); nodes[_nodeOwner] = Node({ attesterLatest: AttesterAttr({ + active: true, removed: false, - active: _isAttesterActive, weight: _attesterWeight, pubKey: _attesterPubKey }), @@ -112,8 +90,8 @@ contract ConsensusRegistry is IConsensusRegistry, Initializable, Ownable2StepUpg }), attesterLastUpdateCommit: attestersCommit, validatorLatest: ValidatorAttr({ + active: true, removed: false, - active: _isValidatorActive, weight: _validatorWeight, pubKey: _validatorPubKey, proofOfPossession: _validatorPoP @@ -133,82 +111,50 @@ contract ConsensusRegistry is IConsensusRegistry, Initializable, Ownable2StepUpg emit NodeAdded({ nodeOwner: _nodeOwner, - isValidatorActive: _isValidatorActive, validatorWeight: _validatorWeight, validatorPubKey: _validatorPubKey, validatorPoP: _validatorPoP, - isAttesterActive: _isAttesterActive, attesterWeight: _attesterWeight, attesterPubKey: _attesterPubKey }); } - /// @notice Deactivates an attester, preventing it from participating in attester committees. + /// @notice Deactivates a node, preventing it from participating in committees. /// @dev Only callable by the contract owner or the node owner. /// @dev Verifies that the node owner exists in the registry. /// @param _nodeOwner The address of the node's owner to be inactivated. - function deactivateAttester(address _nodeOwner) external onlyOwnerOrNodeOwner(_nodeOwner) { + function deactivate(address _nodeOwner) external onlyOwnerOrNodeOwner(_nodeOwner) { _verifyNodeOwnerExists(_nodeOwner); (Node storage node, bool deleted) = _getNodeAndDeleteIfRequired(_nodeOwner); if (deleted) { return; } - _snapshotAttesterIfOutdated(node); + _ensureAttesterSnapshot(node); node.attesterLatest.active = false; - - emit AttesterDeactivated(_nodeOwner); - } - - /// @notice Deactivates a validator, preventing it from participating in validator committees. - /// @dev Only callable by the contract owner or the node owner. - /// @dev Verifies that the node owner exists in the registry. - /// @param _nodeOwner The address of the node's owner to be inactivated. - function deactivateValidator(address _nodeOwner) external onlyOwnerOrNodeOwner(_nodeOwner) { - _verifyNodeOwnerExists(_nodeOwner); - (Node storage node, bool deleted) = _getNodeAndDeleteIfRequired(_nodeOwner); - if (deleted) { - return; - } - - _snapshotValidatorIfOutdated(node); + _ensureValidatorSnapshot(node); node.validatorLatest.active = false; - emit ValidatorDeactivated(_nodeOwner); + emit NodeDeactivated(_nodeOwner); } - /// @notice Activates a previously inactive attester, allowing it to participate in attester committees. - /// @dev Only callable by the contract owner. + /// @notice Activates a previously inactive node, allowing it to participate in committees. + /// @dev Only callable by the contract owner or the node owner. /// @dev Verifies that the node owner exists in the registry. /// @param _nodeOwner The address of the node's owner to be activated. - function activateAttester(address _nodeOwner) external onlyOwner { + function activate(address _nodeOwner) external onlyOwnerOrNodeOwner(_nodeOwner) { _verifyNodeOwnerExists(_nodeOwner); (Node storage node, bool deleted) = _getNodeAndDeleteIfRequired(_nodeOwner); if (deleted) { return; } - _snapshotAttesterIfOutdated(node); + _ensureAttesterSnapshot(node); node.attesterLatest.active = true; - - emit AttesterActivated(_nodeOwner); - } - - /// @notice Activates a previously inactive validator, allowing it to participate in validator committees. - /// @dev Only callable by the contract owner. - /// @dev Verifies that the node owner exists in the registry. - /// @param _nodeOwner The address of the node's owner to be activated. - function activateValidator(address _nodeOwner) external onlyOwner { - _verifyNodeOwnerExists(_nodeOwner); - (Node storage node, bool deleted) = _getNodeAndDeleteIfRequired(_nodeOwner); - if (deleted) { - return; - } - - _snapshotValidatorIfOutdated(node); + _ensureValidatorSnapshot(node); node.validatorLatest.active = true; - emit ValidatorActivated(_nodeOwner); + emit NodeActivated(_nodeOwner); } /// @notice Removes a node from the registry. @@ -222,9 +168,9 @@ contract ConsensusRegistry is IConsensusRegistry, Initializable, Ownable2StepUpg return; } - _snapshotAttesterIfOutdated(node); + _ensureAttesterSnapshot(node); node.attesterLatest.removed = true; - _snapshotValidatorIfOutdated(node); + _ensureValidatorSnapshot(node); node.validatorLatest.removed = true; emit NodeRemoved(_nodeOwner); @@ -234,18 +180,15 @@ contract ConsensusRegistry is IConsensusRegistry, Initializable, Ownable2StepUpg /// @dev Only callable by the contract owner. /// @dev Verifies that the node owner exists in the registry. /// @param _nodeOwner The address of the node's owner whose validator weight will be changed. - /// @param _weight The new validator weight to assign to the node, must be greater than 0. + /// @param _weight The new validator weight to assign to the node. function changeValidatorWeight(address _nodeOwner, uint32 _weight) external onlyOwner { - if (_weight == 0) { - revert ZeroValidatorWeight(); - } _verifyNodeOwnerExists(_nodeOwner); (Node storage node, bool deleted) = _getNodeAndDeleteIfRequired(_nodeOwner); if (deleted) { return; } - _snapshotValidatorIfOutdated(node); + _ensureValidatorSnapshot(node); node.validatorLatest.weight = _weight; emit NodeValidatorWeightChanged(_nodeOwner, _weight); @@ -255,25 +198,22 @@ contract ConsensusRegistry is IConsensusRegistry, Initializable, Ownable2StepUpg /// @dev Only callable by the contract owner. /// @dev Verifies that the node owner exists in the registry. /// @param _nodeOwner The address of the node's owner whose attester weight will be changed. - /// @param _weight The new attester weight to assign to the node, must be greater than 0. + /// @param _weight The new attester weight to assign to the node. function changeAttesterWeight(address _nodeOwner, uint32 _weight) external onlyOwner { - if (_weight == 0) { - revert ZeroAttesterWeight(); - } _verifyNodeOwnerExists(_nodeOwner); (Node storage node, bool deleted) = _getNodeAndDeleteIfRequired(_nodeOwner); if (deleted) { return; } - _snapshotAttesterIfOutdated(node); + _ensureAttesterSnapshot(node); node.attesterLatest.weight = _weight; emit NodeAttesterWeightChanged(_nodeOwner, _weight); } /// @notice Changes the validator's public key and proof-of-possession in the registry. - /// @dev Only callable by the contract owner. + /// @dev Only callable by the contract owner or the node owner. /// @dev Verifies that the node owner exists in the registry. /// @param _nodeOwner The address of the node's owner whose validator key and PoP will be changed. /// @param _pubKey The new BLS12-381 public key to assign to the node's validator. @@ -282,7 +222,7 @@ contract ConsensusRegistry is IConsensusRegistry, Initializable, Ownable2StepUpg address _nodeOwner, BLS12_381PublicKey calldata _pubKey, BLS12_381Signature calldata _pop - ) external onlyOwner { + ) external onlyOwnerOrNodeOwner(_nodeOwner) { _verifyInputBLS12_381PublicKey(_pubKey); _verifyInputBLS12_381Signature(_pop); _verifyNodeOwnerExists(_nodeOwner); @@ -296,7 +236,7 @@ contract ConsensusRegistry is IConsensusRegistry, Initializable, Ownable2StepUpg bytes32 newHash = _hashValidatorPubKey(_pubKey); _verifyValidatorPubKeyDoesNotExist(newHash); validatorPubKeyHashes[newHash] = true; - _snapshotValidatorIfOutdated(node); + _ensureValidatorSnapshot(node); node.validatorLatest.pubKey = _pubKey; node.validatorLatest.proofOfPossession = _pop; @@ -304,11 +244,14 @@ contract ConsensusRegistry is IConsensusRegistry, Initializable, Ownable2StepUpg } /// @notice Changes the attester's public key of a node in the registry. - /// @dev Only callable by the contract owner. + /// @dev Only callable by the contract owner or the node owner. /// @dev Verifies that the node owner exists in the registry. /// @param _nodeOwner The address of the node's owner whose attester public key will be changed. /// @param _pubKey The new ECDSA public key to assign to the node's attester. - function changeAttesterKey(address _nodeOwner, Secp256k1PublicKey calldata _pubKey) external onlyOwner { + function changeAttesterKey( + address _nodeOwner, + Secp256k1PublicKey calldata _pubKey + ) external onlyOwnerOrNodeOwner(_nodeOwner) { _verifyInputSecp256k1PublicKey(_pubKey); _verifyNodeOwnerExists(_nodeOwner); (Node storage node, bool deleted) = _getNodeAndDeleteIfRequired(_nodeOwner); @@ -322,7 +265,7 @@ contract ConsensusRegistry is IConsensusRegistry, Initializable, Ownable2StepUpg _verifyAttesterPubKeyDoesNotExist(newHash); attesterPubKeyHashes[newHash] = true; - _snapshotAttesterIfOutdated(node); + _ensureAttesterSnapshot(node); node.attesterLatest.pubKey = _pubKey; emit NodeAttesterKeyChanged(_nodeOwner, _pubKey); @@ -350,9 +293,9 @@ contract ConsensusRegistry is IConsensusRegistry, Initializable, Ownable2StepUpg emit ValidatorsCommitted(validatorsCommit); } - /// @notice Returns an array of `CommitteeAttester` structs representing the current attester committee. + /// @notice Returns an array of `AttesterAttr` structs representing the current attester committee. /// @dev Collects active and non-removed attesters based on the latest commit to the committee. - function getAttesterCommittee() external view returns (CommitteeAttester[] memory) { + function getAttesterCommittee() public view returns (CommitteeAttester[] memory) { uint256 len = nodeOwners.length; CommitteeAttester[] memory committee = new CommitteeAttester[](len); uint256 count = 0; @@ -375,9 +318,9 @@ contract ConsensusRegistry is IConsensusRegistry, Initializable, Ownable2StepUpg return committee; } - /// @notice Returns an array of `CommitteeValidator` structs representing the current attester committee. + /// @notice Returns an array of `ValidatorAttr` structs representing the current attester committee. /// @dev Collects active and non-removed validators based on the latest commit to the committee. - function getValidatorCommittee() external view returns (CommitteeValidator[] memory) { + function getValidatorCommittee() public view returns (CommitteeValidator[] memory) { uint256 len = nodeOwners.length; CommitteeValidator[] memory committee = new CommitteeValidator[](len); uint256 count = 0; @@ -404,7 +347,7 @@ contract ConsensusRegistry is IConsensusRegistry, Initializable, Ownable2StepUpg return committee; } - function numNodes() external view returns (uint256) { + function numNodes() public view returns (uint256) { return nodeOwners.length; } @@ -443,21 +386,21 @@ contract ConsensusRegistry is IConsensusRegistry, Initializable, Ownable2StepUpg emit NodeDeleted(_nodeOwner); } - function _snapshotAttesterIfOutdated(Node storage _node) private { + function _ensureAttesterSnapshot(Node storage _node) private { if (_node.attesterLastUpdateCommit < attestersCommit) { _node.attesterSnapshot = _node.attesterLatest; _node.attesterLastUpdateCommit = attestersCommit; } } - function _snapshotValidatorIfOutdated(Node storage _node) private { + function _ensureValidatorSnapshot(Node storage _node) private { if (_node.validatorLastUpdateCommit < validatorsCommit) { _node.validatorSnapshot = _node.validatorLatest; _node.validatorLastUpdateCommit = validatorsCommit; } } - function _doesNodeOwnerExist(address _nodeOwner) private view returns (bool) { + function _isNodeOwnerExists(address _nodeOwner) private view returns (bool) { BLS12_381PublicKey storage pubKey = nodes[_nodeOwner].validatorLatest.pubKey; if (pubKey.a == bytes32(0) && pubKey.b == bytes32(0) && pubKey.c == bytes32(0)) { return false; @@ -466,13 +409,13 @@ contract ConsensusRegistry is IConsensusRegistry, Initializable, Ownable2StepUpg } function _verifyNodeOwnerExists(address _nodeOwner) private view { - if (!_doesNodeOwnerExist(_nodeOwner)) { + if (!_isNodeOwnerExists(_nodeOwner)) { revert NodeOwnerDoesNotExist(); } } function _verifyNodeOwnerDoesNotExist(address _nodeOwner) private view { - if (_doesNodeOwnerExist(_nodeOwner)) { + if (_isNodeOwnerExists(_nodeOwner)) { revert NodeOwnerExists(); } } @@ -495,7 +438,7 @@ contract ConsensusRegistry is IConsensusRegistry, Initializable, Ownable2StepUpg function _verifyInputAddress(address _nodeOwner) private pure { if (_nodeOwner == address(0)) { - revert ZeroAddress(); + revert InvalidInputNodeOwnerAddress(); } } diff --git a/l2-contracts/contracts/interfaces/IConsensusRegistry.sol b/l2-contracts/contracts/interfaces/IConsensusRegistry.sol index 632bdd60e..a5e017484 100644 --- a/l2-contracts/contracts/interfaces/IConsensusRegistry.sol +++ b/l2-contracts/contracts/interfaces/IConsensusRegistry.sol @@ -8,12 +8,12 @@ pragma solidity ^0.8.20; interface IConsensusRegistry { /// @dev Represents a consensus node. /// @param attesterLastUpdateCommit The latest `attestersCommit` where the node's attester attributes were updated. - /// @param validatorLastUpdateCommit The latest `validatorsCommit` where the node's validator attributes were updated. - /// @param nodeOwnerIdx Index of the node owner within the array of node owners. /// @param attesterLatest Attester attributes to read if `node.attesterLastUpdateCommit` < `attestersCommit`. /// @param attesterSnapshot Attester attributes to read if `node.attesterLastUpdateCommit` == `attestersCommit`. + /// @param validatorLastUpdateCommit The latest `validatorsCommit` where the node's validator attributes were updated. /// @param validatorLatest Validator attributes to read if `node.validatorLastUpdateCommit` < `validatorsCommit`. /// @param validatorSnapshot Validator attributes to read if `node.validatorLastUpdateCommit` == `validatorsCommit`. + /// @param nodeOwnerIdx Index of the node owner within the array of node owners. struct Node { uint32 attesterLastUpdateCommit; uint32 validatorLastUpdateCommit; @@ -97,28 +97,24 @@ interface IConsensusRegistry { error UnauthorizedOnlyOwnerOrNodeOwner(); error NodeOwnerExists(); error NodeOwnerDoesNotExist(); + error NodeOwnerNotFound(); error ValidatorPubKeyExists(); error AttesterPubKeyExists(); + error InvalidInputNodeOwnerAddress(); error InvalidInputBLS12_381PublicKey(); error InvalidInputBLS12_381Signature(); error InvalidInputSecp256k1PublicKey(); - error ZeroAttesterWeight(); - error ZeroValidatorWeight(); event NodeAdded( address indexed nodeOwner, - bool isValidatorActive, uint32 validatorWeight, BLS12_381PublicKey validatorPubKey, BLS12_381Signature validatorPoP, - bool isAttesterActive, uint32 attesterWeight, Secp256k1PublicKey attesterPubKey ); - event AttesterDeactivated(address indexed nodeOwner); - event ValidatorDeactivated(address indexed nodeOwner); - event AttesterActivated(address indexed nodeOwner); - event ValidatorActivated(address indexed nodeOwner); + event NodeDeactivated(address indexed nodeOwner); + event NodeActivated(address indexed nodeOwner); event NodeRemoved(address indexed nodeOwner); event NodeDeleted(address indexed nodeOwner); event NodeValidatorWeightChanged(address indexed nodeOwner, uint32 newWeight); @@ -130,22 +126,16 @@ interface IConsensusRegistry { function add( address _nodeOwner, - bool _isValidatorActive, uint32 _validatorWeight, BLS12_381PublicKey calldata _validatorPubKey, BLS12_381Signature calldata _validatorPoP, - bool _isAttesterActive, uint32 _attesterWeight, Secp256k1PublicKey calldata _attesterPubKey ) external; - function deactivateAttester(address _nodeOwner) external; + function deactivate(address _nodeOwner) external; - function deactivateValidator(address _nodeOwner) external; - - function activateAttester(address _nodeOwner) external; - - function activateValidator(address _nodeOwner) external; + function activate(address _nodeOwner) external; function remove(address _nodeOwner) external; @@ -168,6 +158,4 @@ interface IConsensusRegistry { function getAttesterCommittee() external view returns (CommitteeAttester[] memory); function getValidatorCommittee() external view returns (CommitteeValidator[] memory); - - function numNodes() external view returns (uint256); }