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

[WIP] Chain registrar #1064

Open
wants to merge 14 commits into
base: release-v25-protocol-defense
Choose a base branch
from
161 changes: 161 additions & 0 deletions l1-contracts/contracts/chain-registrar/ChainRegistrar.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.24;

import {IBridgehub} from "../bridgehub/IBridgehub.sol";
import {PubdataPricingMode} from "../state-transition/chain-deps/ZkSyncHyperchainStorage.sol";
import {IStateTransitionManager} from "../state-transition/IStateTransitionManager.sol";
import {ETH_TOKEN_ADDRESS} from "../common/Config.sol";
import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol";
import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol";
import {IGetters} from "../state-transition/chain-interfaces/IGetters.sol";
import {ReentrancyGuard} from "../common/ReentrancyGuard.sol";

/// @author Matter Labs
/// @custom:security-contact [email protected]
/// @dev ChainRegistrar serves as the main point for chain registration.
contract ChainRegistrar is Ownable2StepUpgradeable, ReentrancyGuard {
/// Address that will be used for deploying l2 contracts
address public l2Deployer;
/// ZKsync Bridgehub
IBridgehub public bridgehub;

/// Chains that has been successfully deployed
mapping(bytes32 => bool) public deployedChains;
/// Proposal for chain registration
mapping(bytes32 => ChainConfig) public proposedChains;

error ProposalNotFound();
error BaseTokenTransferFailed();
error ChainIsAlreadyDeployed();
error ChainIsNotYetDeployed();
error BridgeIsNotRegistered();

/// @notice new chain is deployed
event NewChainDeployed(uint256 indexed chainId, address author, address diamondProxy, address chainAdmin);

/// @notice new chain is proposed to register
event NewChainRegistrationProposal(uint256 indexed chainId, address author, bytes32 key);

/// @notice Shared bridge is registered on l2
event SharedBridgeRegistered(uint256 indexed chainId, address l2Address);

/// @notice L2 Deployer has changed
event L2DeployerChanged(address newDeployer);

struct BaseToken {
address tokenAddress;
address tokenMultiplierSetter;
uint128 gasPriceMultiplierNominator;
uint128 gasPriceMultiplierDenominator;
}

struct ChainConfig {

Check failure on line 53 in l1-contracts/contracts/chain-registrar/ChainRegistrar.sol

View workflow job for this annotation

GitHub Actions / lint

GC: For [ ChainConfig ] struct, packing seems inefficient. Try rearranging to achieve 32bytes slots

Check failure on line 53 in l1-contracts/contracts/chain-registrar/ChainRegistrar.sol

View workflow job for this annotation

GitHub Actions / lint

GC: For [ ChainConfig ] struct, packing seems inefficient. Try rearranging to achieve 32bytes slots

Check failure on line 53 in l1-contracts/contracts/chain-registrar/ChainRegistrar.sol

View workflow job for this annotation

GitHub Actions / lint

GC: For [ ChainConfig ] struct, packing seems inefficient. Try rearranging to achieve 32bytes slots
/// @param Chain id of the new chain should be unique for this bridgehub
uint256 chainId;
/// @param Operator for making commit txs.
address commitOperator;
/// @param Operator for making Prove and Execute transactions
address operator;
/// @param Governor of the chain. Ownership of the chain will be transferred to this operator
address governor;
/// @param baseToken of the chain
BaseToken baseToken;
/// @param pubdataPricingMode How the users will charged for pubdata.
PubdataPricingMode pubdataPricingMode;
}

constructor(address _bridgehub, address _l2Deployer, address _owner) reentrancyGuardInitializer {
bridgehub = IBridgehub(_bridgehub);
l2Deployer = _l2Deployer;
_transferOwnership(_owner);
}

function proposeChainRegistration(
uint256 chainId,
PubdataPricingMode pubdataPricingMode,
address commitOperator,
address operator,
Deniallugo marked this conversation as resolved.
Show resolved Hide resolved
address governor,
address tokenAddress,
address tokenMultiplierSetter,
uint128 gasPriceMultiplierNominator,
uint128 gasPriceMultiplierDenominator
) public {
ChainConfig memory config = ChainConfig({

Choose a reason for hiding this comment

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

should we also think about returning base token once the registration is done ? :D
idk, if it makes sense to keep author there...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, we need some token to operate on chain in the future

chainId: chainId,
pubdataPricingMode: pubdataPricingMode,
commitOperator: commitOperator,
operator: operator,
governor: governor,
baseToken: BaseToken({
tokenAddress: tokenAddress,
tokenMultiplierSetter: tokenMultiplierSetter,
gasPriceMultiplierNominator: gasPriceMultiplierNominator,

Choose a reason for hiding this comment

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

maybe adding more validations on gasPriceMultiplierNominator, I'd say at min check both values are 1, 1 if base token is ETH

gasPriceMultiplierDenominator: gasPriceMultiplierDenominator
})
});
bytes32 key = keccak256(abi.encode(msg.sender, config.chainId));
if (deployedChains[key] || bridgehub.stateTransitionManager(config.chainId) != address(0)) {
revert ChainIsAlreadyDeployed();
}
proposedChains[key] = config;
// For Deploying L2 contracts on for non ETH based networks, we as bridgehub owners required base token.
if (config.baseToken.tokenAddress != ETH_TOKEN_ADDRESS) {
uint256 amount = (1 ether * config.baseToken.gasPriceMultiplierNominator) /
config.baseToken.gasPriceMultiplierDenominator;
if (IERC20(config.baseToken.tokenAddress).balanceOf(address(this)) < amount) {
bool success = IERC20(config.baseToken.tokenAddress).transferFrom(msg.sender, l2Deployer, amount);
if (!success) {
revert BaseTokenTransferFailed();
}
}
}
emit NewChainRegistrationProposal(config.chainId, msg.sender, key);
}

function changeDeployer(address newDeployer) public onlyOwner {
l2Deployer = newDeployer;
emit L2DeployerChanged(l2Deployer);
}

function getChainConfig(address author, uint256 chainId) public view returns (ChainConfig memory) {
bytes32 key = keccak256(abi.encode(author, chainId));
return proposedChains[key];
}

function chainRegistered(address author, uint256 chainId) public onlyOwner nonReentrant {
Deniallugo marked this conversation as resolved.
Show resolved Hide resolved
bytes32 key = keccak256(abi.encode(author, chainId));
ChainConfig memory config = proposedChains[key];
if (config.chainId == 0) {
revert ProposalNotFound();
}

if (deployedChains[key]) {
revert ChainIsAlreadyDeployed();
}
address stm = bridgehub.stateTransitionManager(chainId);
if (stm == address(0)) {
revert ChainIsNotYetDeployed();
}
address diamondProxy = IStateTransitionManager(stm).getHyperchain(chainId);
// (bool success, bytes memory returnData) = diamondProxy.call(abi.encodeWithSelector(IGetters.getAdmin.selector));
// require(success);
// address chainAdmin = bytesToAddress(returnData);
address chainAdmin = IGetters(diamondProxy).getAdmin();

Choose a reason for hiding this comment

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

just as alternative:
address chainAdmin = IStateTransitionManager(stm).getChainAdmin(chainId);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we need pendingAdmin... and IGetter is the only way

address l2BridgeAddress = bridgehub.sharedBridge().l2BridgeAddress(chainId);

Choose a reason for hiding this comment

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

Hm, you can do that without specifying class?
IL1SharedBridge(bridgehub.sharedBridge()).l2BridgeAddress(chainId);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, we can. Because it's already specified.

if (l2BridgeAddress == address(0)) {
revert BridgeIsNotRegistered();
}

emit NewChainDeployed(chainId, diamondProxy, author, chainAdmin);
emit SharedBridgeRegistered(chainId, l2BridgeAddress);

Choose a reason for hiding this comment

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

If these are always emitted together - any point of not putting l2BridgeAddress into NewChainDeployed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was thinking about it, but for the future those events, will be most likely independent. because it's two different actions and for not breaking the future interface I think it's better to make it like this from the very beginning

deployedChains[key] = true;
}

function bytesToAddress(bytes memory bys) private pure returns (address addr) {
assembly {
addr := mload(add(bys, 32))
}
}
Deniallugo marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {MailboxFacet} from "../../state-transition/chain-deps/facets/Mailbox.sol
import {FeeParams, PubdataPricingMode} from "../../state-transition/chain-deps/ZkSyncHyperchainStorage.sol";

contract DummyHyperchain is MailboxFacet {
address public admin;
constructor(address bridgeHubAddress, uint256 _eraChainId) MailboxFacet(_eraChainId) {
s.bridgehub = bridgeHubAddress;
}
Expand Down Expand Up @@ -32,6 +33,14 @@ contract DummyHyperchain is MailboxFacet {
s.priorityTxMaxGasLimit = type(uint256).max;
}

function initialize(address _admin) external {
admin = _admin;
}

function getAdmin() external view returns (address) {
return admin;
}

function _randomFeeParams() internal pure returns (FeeParams memory) {
return
FeeParams({
Expand Down
16 changes: 16 additions & 0 deletions l1-contracts/deploy-scripts/DeployL1.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {L1SharedBridge} from "contracts/bridge/L1SharedBridge.sol";
import {L1ERC20Bridge} from "contracts/bridge/L1ERC20Bridge.sol";
import {DiamondProxy} from "contracts/state-transition/chain-deps/DiamondProxy.sol";
import {AddressHasNoCode} from "./ZkSyncScriptErrors.sol";
import {ChainRegistrar} from "../contracts/chain-registrar/ChainRegistrar.sol";

contract DeployL1Script is Script {
using stdToml for string;
Expand All @@ -52,6 +53,7 @@ contract DeployL1Script is Script {
address blobVersionedHashRetriever;
address validatorTimelock;
address create2Factory;
address chainRegistrar;
}

// solhint-disable-next-line gas-struct-packing
Expand Down Expand Up @@ -88,6 +90,7 @@ contract DeployL1Script is Script {
uint256 l1ChainId;
uint256 eraChainId;
address deployerAddress;
address l2Deployer;
address ownerAddress;
bool testnetVerifier;
ContractsConfig contracts;
Expand Down Expand Up @@ -157,6 +160,7 @@ contract DeployL1Script is Script {
deployErc20BridgeImplementation();
deployErc20BridgeProxy();
updateSharedBridge();
deployChainRegistrar();

updateOwners();

Expand All @@ -176,6 +180,7 @@ contract DeployL1Script is Script {
// https://book.getfoundry.sh/cheatcodes/parse-toml
config.eraChainId = toml.readUint("$.era_chain_id");
config.ownerAddress = toml.readAddress("$.owner_address");
config.l2Deployer = toml.readAddress("$.l2_deployer");
config.testnetVerifier = toml.readBool("$.testnet_verifier");

config.contracts.governanceSecurityCouncilAddress = toml.readAddress(
Expand Down Expand Up @@ -301,6 +306,16 @@ contract DeployL1Script is Script {
addresses.governance = contractAddress;
}

function deployChainRegistrar() internal {
bytes memory bytecode = abi.encodePacked(
type(ChainRegistrar).creationCode,
abi.encode(addresses.bridgehub.bridgehubProxy, config.l2Deployer, config.ownerAddress)
);
address contractAddress = deployViaCreate2(bytecode);
console.log("Chain Registrar deployed at:", contractAddress);
addresses.chainRegistrar = contractAddress;
}

function deployChainAdmin() internal {
bytes memory bytecode = abi.encodePacked(
type(ChainAdmin).creationCode,
Expand Down Expand Up @@ -715,6 +730,7 @@ contract DeployL1Script is Script {
vm.serializeAddress("deployed_addresses", "chain_admin", addresses.chainAdmin);
vm.serializeString("deployed_addresses", "bridgehub", bridgehub);
vm.serializeString("deployed_addresses", "state_transition", stateTransition);
vm.serializeAddress("deployed_addresses", "chain_registrar", addresses.chainRegistrar);
string memory deployedAddresses = vm.serializeString("deployed_addresses", "bridges", bridges);

vm.serializeAddress("root", "create2_factory_addr", addresses.create2Factory);
Expand Down
17 changes: 16 additions & 1 deletion l1-contracts/deploy-scripts/DeployL2Contracts.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

pragma solidity ^0.8.21;

import {Script} from "forge-std/Script.sol";
import {Script, console2 as console} from "forge-std/Script.sol";
import {stdToml} from "forge-std/StdToml.sol";

import {Utils} from "./Utils.sol";
import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol";
import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol";
import {L1SharedBridge} from "contracts/bridge/L1SharedBridge.sol";
import {ChainRegistrar} from "contracts/chain-registrar/ChainRegistrar.sol";

contract DeployL2Script is Script {
using stdToml for string;
Expand All @@ -22,6 +23,8 @@ contract DeployL2Script is Script {
address l1SharedBridgeProxy;
address governance;
address erc20BridgeProxy;
address chainRegistrar;
address proposalAuthor;
// The owner of the contract sets the validator/attester weights.
// Can be the developer multisig wallet on mainnet.
address consensusRegistryOwner;
Expand Down Expand Up @@ -65,6 +68,7 @@ contract DeployL2Script is Script {
deploySharedBridge();
deploySharedBridgeProxy(legacyBridge);
initializeChain();
finalizeRegistration();
deployForceDeployer();
deployConsensusRegistry();
deployConsensusRegistryProxy();
Expand All @@ -90,6 +94,7 @@ contract DeployL2Script is Script {
deploySharedBridge();
deploySharedBridgeProxy(legacyBridge);
initializeChain();
finalizeRegistration();

saveOutput();
}
Expand Down Expand Up @@ -184,6 +189,8 @@ contract DeployL2Script is Script {
config.l1SharedBridgeProxy = toml.readAddress("$.l1_shared_bridge");
config.erc20BridgeProxy = toml.readAddress("$.erc20_bridge");
config.consensusRegistryOwner = toml.readAddress("$.consensus_registry_owner");
config.chainRegistrar = toml.readAddress("$.chain_registrar");
config.proposalAuthor = toml.readAddress("$.proposal_author");
config.chainId = toml.readUint("$.chain_id");
config.eraChainId = toml.readUint("$.era_chain_id");
}
Expand Down Expand Up @@ -366,4 +373,12 @@ contract DeployL2Script is Script {
_value: 0
});
}

function finalizeRegistration() internal {
ChainRegistrar chainRegistrar = ChainRegistrar(config.chainRegistrar);
console.logAddress(msg.sender);
console.logAddress(chainRegistrar.owner());
Deniallugo marked this conversation as resolved.
Show resolved Hide resolved
vm.broadcast();
chainRegistrar.chainRegistered(config.proposalAuthor, config.chainId);
}
}
Loading
Loading