diff --git a/contracts/zksync/l1-contracts/zksync/interfaces/IAdmin.sol b/contracts/zksync/l1-contracts/zksync/interfaces/IAdmin.sol index 6e15386..570b09c 100644 --- a/contracts/zksync/l1-contracts/zksync/interfaces/IAdmin.sol +++ b/contracts/zksync/l1-contracts/zksync/interfaces/IAdmin.sol @@ -3,11 +3,16 @@ pragma solidity ^0.8.0; import {FeeParams} from "../Storage.sol"; +import {IL2Gateway} from "../../../../interfaces/IL2Gateway.sol"; /// @title The interface of the Admin Contract that controls access rights for contract management. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev interface IAdmin { + /// @notice Init gateway + /// @param _gateway The gateway on local chain + function setGateway(IL2Gateway _gateway) external; + /// @notice Change validator status (active or not active) /// @param _validator Validator address /// @param _active Active flag diff --git a/contracts/zksync/l1-contracts/zksync/interfaces/IGetters.sol b/contracts/zksync/l1-contracts/zksync/interfaces/IGetters.sol new file mode 100644 index 0000000..b676712 --- /dev/null +++ b/contracts/zksync/l1-contracts/zksync/interfaces/IGetters.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {IL2Gateway} from "../../../../interfaces/IL2Gateway.sol"; + +/// @title The interface of the Getters Contract that implements functions for getting contract state from outside the blockchain. +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +interface IGetters { + /*////////////////////////////////////////////////////////////// + CUSTOM GETTERS + //////////////////////////////////////////////////////////////*/ + + /// @return The gateway on local chain + function getGateway() external view returns (IL2Gateway); + + /// @return The total number of batches that were committed & verified & executed + function getTotalBatchesExecuted() external view returns (uint256); + + /// @return The total number of priority operations that were added to the priority queue, including all processed ones + function getTotalPriorityTxs() external view returns (uint256); + + /// @return Whether the address has a validator access + function isValidator(address _address) external view returns (bool); + + /// @return merkleRoot Merkle root of the tree with L2 logs for the selected batch + function l2LogsRootHash(uint256 _batchNumber) external view returns (bytes32 merkleRoot); +} diff --git a/examples/arbitrum/hardhat.config.js b/examples/arbitrum/hardhat.config.js index 9c0938d..3576123 100644 --- a/examples/arbitrum/hardhat.config.js +++ b/examples/arbitrum/hardhat.config.js @@ -1,6 +1,7 @@ require('@nomiclabs/hardhat-ethers'); require('./scripts/syncL2Requests'); require('./scripts/syncBatchRoot'); +require('./scripts/setSecondaryGateway'); const BaseConfig = require('../../hardhat.base.config'); diff --git a/examples/arbitrum/scripts/setSecondaryGateway.js b/examples/arbitrum/scripts/setSecondaryGateway.js new file mode 100644 index 0000000..0f79313 --- /dev/null +++ b/examples/arbitrum/scripts/setSecondaryGateway.js @@ -0,0 +1,156 @@ +const { providers, Wallet, utils } = require('ethers'); +const { readDeployContract, getLogName, readDeployLogField } = require('../../../script/utils'); +const logName = require('../../../script/deploy_log_name'); +const { L1TransactionReceipt, L1ToL2MessageStatus } = require('@arbitrum/sdk'); +const { L1ToL2MessageGasEstimator } = require('@arbitrum/sdk/dist/lib/message/L1ToL2MessageGasEstimator'); +const { getBaseFee } = require('@arbitrum/sdk/dist/lib/utils/lib'); +const { task, types } = require('hardhat/config'); + +require('dotenv').config(); + +task('setSecondaryGateway', 'Send secondary gateway') + .addOptionalParam( + 'arbitrator', + 'The arbitrator address (default get from arbitrator deploy log)', + undefined, + types.string, + ) + .addParam('targetNetwork', 'L2 network name', undefined, types.string, false) + .addOptionalParam('active', 'Enable the gateway?', true, types.boolean) + .setAction(async (taskArgs, hre) => { + const arbitrumName = process.env.ARBITRUM; + const ethereumName = process.env.ETHEREUM; + console.log(`Arbitrum net name: ${arbitrumName}`); + console.log(`Ethereum net name: ${ethereumName}`); + + let arbitratorAddr = taskArgs.arbitrator; + let targetNetwork = taskArgs.targetNetwork; + const active = taskArgs.active; + if (arbitratorAddr === undefined) { + arbitratorAddr = readDeployLogField( + logName.DEPLOY_ARBITRATOR_LOG_PREFIX, + logName.DEPLOY_LOG_ARBITRATOR, + ethereumName, + ); + } + if (targetNetwork === arbitrumName) { + console.log('Can not set for primary chain'); + return; + } + let l1GatewayAddr; + if (targetNetwork === ethereumName) { + l1GatewayAddr = readDeployContract(logName.DEPLOY_ETH_GATEWAY_LOG_PREFIX, logName.DEPLOY_GATEWAY, ethereumName); + } else { + const l1GatewayLogName = getLogName(logName.DEPLOY_L1_GATEWAY_LOG_PREFIX, targetNetwork); + l1GatewayAddr = readDeployContract(l1GatewayLogName, logName.DEPLOY_GATEWAY, ethereumName); + } + if (l1GatewayAddr === undefined) { + console.log('L1 gateway address not found'); + return; + } + console.log(`The arbitrator address: ${arbitratorAddr}`); + console.log(`The secondary chain l1 gateway address: ${l1GatewayAddr}`); + console.log(`Enable the gateway? ${active}`); + + const arbitrumL1GatewayLogName = getLogName(logName.DEPLOY_L1_GATEWAY_LOG_PREFIX, arbitrumName); + const arbitrumL1GatewayAddr = readDeployContract(arbitrumL1GatewayLogName, logName.DEPLOY_GATEWAY, ethereumName); + if (arbitrumL1GatewayAddr === undefined) { + console.log('Arbitrum l1 gateway address not exist'); + return; + } + console.log(`The arbitrum l1 gateway address: ${arbitrumL1GatewayAddr}`); + + const arbitrumL2GatewayAddr = readDeployContract( + logName.DEPLOY_L2_GATEWAY_LOG_PREFIX, + logName.DEPLOY_GATEWAY, + arbitrumName, + ); + if (arbitrumL2GatewayAddr === undefined) { + console.log('Arbitrum l2 gateway address not exist'); + return; + } + console.log(`The arbitrum l2 gateway address: ${arbitrumL2GatewayAddr}`); + + const walletPrivateKey = process.env.DEVNET_PRIVKEY; + const l1Provider = new providers.JsonRpcProvider(process.env.L1RPC); + const l2Provider = new providers.JsonRpcProvider(process.env.L2RPC); + const l1Wallet = new Wallet(walletPrivateKey, l1Provider); + const l2Wallet = new Wallet(walletPrivateKey, l2Provider); + + /** + * Now we can query the required gas params using the estimateAll method in Arbitrum SDK + */ + const l1ToL2MessageGasEstimate = new L1ToL2MessageGasEstimator(l2Provider); + + const l1WalletAddress = await l1Wallet.getAddress(); + const l1WalletBalance = utils.formatEther(await l1Wallet.getBalance()); + console.log(`${l1WalletAddress} balance on l1: ${l1WalletBalance} ether`); + + const arbitrator = await hre.ethers.getContractAt('Arbitrator', arbitratorAddr, l1Wallet); + const zkLinkFactory = await hre.ethers.getContractAt('IZkSync', hre.ethers.constants.AddressZero); + const zkLinkCallValue = 0; + const zkLinkCallData = zkLinkFactory.interface.encodeFunctionData('setSecondaryChainGateway', [ + l1GatewayAddr, + active, + ]); + const l2GatewayFactory = await hre.ethers.getContractFactory('ArbitrumL2Gateway'); + const l2GatewayCallData = l2GatewayFactory.interface.encodeFunctionData('claimMessageCallback', [ + zkLinkCallValue, + zkLinkCallData, + ]); + + /** + * The estimateAll method gives us the following values for sending an L1->L2 message + * (1) maxSubmissionCost: The maximum cost to be paid for submitting the transaction + * (2) gasLimit: The L2 gas limit + * (3) deposit: The total amount to deposit on L1 to cover L2 gas and L2 call value + */ + const l1BaseFee = await getBaseFee(l1Provider); + console.log(`Current base fee on L1 is: ${l1BaseFee}`); + const L1ToL2MessageGasParams = await l1ToL2MessageGasEstimate.estimateAll( + { + from: arbitrumL1GatewayAddr, + to: arbitrumL2GatewayAddr, + l2CallValue: zkLinkCallValue, + excessFeeRefundAddress: l1WalletAddress, + callValueRefundAddress: arbitrumL2GatewayAddr, + data: l2GatewayCallData, + }, + l1BaseFee, + l1Provider, + ); + console.log(`Current retryable base submission price is: ${L1ToL2MessageGasParams.maxSubmissionCost.toString()}`); + console.log(`Estimate gasLimit on L2 is: ${L1ToL2MessageGasParams.gasLimit.toString()}`); + console.log(`Estimate maxFeePerGas on L2 is: ${L1ToL2MessageGasParams.maxFeePerGas.toString()}`); + console.log(`Estimate fee to pay on L1 is: ${L1ToL2MessageGasParams.deposit.toString()}`); + + const adapterParams = utils.defaultAbiCoder.encode( + ['uint256', 'uint256', 'uint256'], + [L1ToL2MessageGasParams.maxSubmissionCost, L1ToL2MessageGasParams.gasLimit, L1ToL2MessageGasParams.maxFeePerGas], + ); + console.log(`Send a l1 message to l2...`); + const l1Tx = await arbitrator.setSecondaryChainGateway(l1GatewayAddr, active, adapterParams, { + value: L1ToL2MessageGasParams.deposit, + }); + const l1TxHash = l1Tx.hash; + console.log(`The l1 tx hash: ${l1TxHash}`); + const arbitratorReceipt = await l1Tx.wait(); + + const l1TxReceipt = new L1TransactionReceipt(arbitratorReceipt); + + /** + * In principle, a single L1 txn can trigger any number of L1-to-L2 messages (each with its own sequencer number). + * In this case, we know our txn triggered only one + * Here, We check if our L1 to L2 message is redeemed on L2 + */ + const messages = await l1TxReceipt.getL1ToL2Messages(l2Wallet); + const message = messages[0]; + console.log('Waiting for the L2 execution of the transaction. This may take up to 10-15 minutes ⏰'); + const messageResult = await message.waitForStatus(); + const status = messageResult.status; + if (status === L1ToL2MessageStatus.REDEEMED) { + console.log(`L2 retryable ticket is executed 🥳 ${messageResult.l2TxReceipt.transactionHash}`); + } else { + console.log(`L2 retryable ticket is failed with status ${L1ToL2MessageStatus[status]}`); + } + });