diff --git a/.github/scripts/gateway.sh b/.github/scripts/gateway.sh new file mode 100755 index 00000000000..ed9e8a32dc8 --- /dev/null +++ b/.github/scripts/gateway.sh @@ -0,0 +1,50 @@ +sudo rm -rf ./volumes && zk_supervisor clean containers && zk_inception up -o false + +zk_inception ecosystem init --deploy-paymaster --deploy-erc20 \ + --deploy-ecosystem --l1-rpc-url=http://localhost:8545 \ + --server-db-url=postgres://postgres:notsecurepassword@localhost:5432 \ + --server-db-name=zksync_server_localhost_era \ + --prover-db-url=postgres://postgres:notsecurepassword@localhost:5432 \ + --prover-db-name=zksync_prover_localhost_era \ + --ignore-prerequisites --observability=false --skip-submodules-checkout \ + --chain era \ + # --skip-contract-compilation-override \ + +zk_inception chain create \ + --chain-name gateway \ + --chain-id 505 \ + --prover-mode no-proofs \ + --wallet-creation localhost \ + --l1-batch-commit-data-generator-mode rollup \ + --base-token-address 0x0000000000000000000000000000000000000001 \ + --base-token-price-nominator 1 \ + --base-token-price-denominator 1 \ + --set-as-default false \ + --ignore-prerequisites --skip-submodules-checkout --skip-contract-compilation-override + +zk_inception chain init \ + --deploy-paymaster \ + --l1-rpc-url=http://localhost:8545 \ + --server-db-url=postgres://postgres:notsecurepassword@localhost:5432 \ + --server-db-name=zksync_server_localhost_gateway \ + --prover-db-url=postgres://postgres:notsecurepassword@localhost:5432 \ + --prover-db-name=zksync_prover_localhost_gateway \ + --chain gateway --skip-submodules-checkout + +zk_inception chain convert-to-gateway --chain gateway --ignore-prerequisites + +zk_inception server --ignore-prerequisites --chain gateway &> ./gateway.log & + +sleep 20 + +zk_inception chain migrate-to-gateway --chain era --gateway-chain-name gateway + +zk_inception chain migrate-from-gateway --chain era --gateway-chain-name gateway + +zk_inception chain migrate-to-gateway --chain era --gateway-chain-name gateway + +zk_inception server --ignore-prerequisites --chain era &> ./rollup.log & + +sleep 20 + +zk_supervisor test integration --no-deps --ignore-prerequisites --chain era diff --git a/.github/workflows/ci-core-reusable.yml b/.github/workflows/ci-core-reusable.yml index ba6548dd143..fed26bbbb3b 100644 --- a/.github/workflows/ci-core-reusable.yml +++ b/.github/workflows/ci-core-reusable.yml @@ -313,6 +313,14 @@ jobs: ci_run zk_inception chain migrate-to-gateway --chain era --gateway-chain-name gateway ci_run zk_inception chain migrate-to-gateway --chain validium --gateway-chain-name gateway ci_run zk_inception chain migrate-to-gateway --chain custom_token --gateway-chain-name gateway + + - name: Migrate back era + run: | + ci_run zk_inception chain migrate-from-gateway --chain era --gateway-chain-name gateway + + - name: Migrate to gateway again + run: | + ci_run zk_inception chain migrate-to-gateway --chain era --gateway-chain-name gateway - name: Build test dependencies run: | diff --git a/contracts b/contracts index c3030f58e6c..53b0283f82f 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit c3030f58e6c13fb523cdc2fa5c095fa2f49c0666 +Subproject commit 53b0283f82f4262c973eb3faed56ee8f6cda47b9 diff --git a/core/lib/config/src/configs/contracts.rs b/core/lib/config/src/configs/contracts.rs index 2a17f74265d..24f50243758 100644 --- a/core/lib/config/src/configs/contracts.rs +++ b/core/lib/config/src/configs/contracts.rs @@ -53,6 +53,8 @@ pub struct ContractsConfig { pub l2_da_validator_addr: Option
, pub chain_admin_addr: Option
, + + pub settlement_layer: Option, } impl ContractsConfig { @@ -78,6 +80,7 @@ impl ContractsConfig { user_facing_diamond_proxy_addr: Some(Address::repeat_byte(0x16)), chain_admin_addr: Some(Address::repeat_byte(0x18)), l2_da_validator_addr: Some(Address::repeat_byte(0x19)), + settlement_layer: Some(0), } } } diff --git a/core/lib/config/src/configs/gateway.rs b/core/lib/config/src/configs/gateway.rs index bf2f362ae26..cc0cdcc1d6a 100644 --- a/core/lib/config/src/configs/gateway.rs +++ b/core/lib/config/src/configs/gateway.rs @@ -30,6 +30,7 @@ pub struct GatewayChainConfig { pub diamond_proxy_addr: Address, pub chain_admin_addr: Option
, pub governance_addr: Address, + pub settlement_layer: u64, } impl GatewayChainConfig { @@ -37,6 +38,7 @@ impl GatewayChainConfig { gateway_config: &GatewayConfig, diamond_proxy_addr: Address, chain_admin_addr: Address, + settlement_layer: u64, ) -> Self { // FIXME: there is no "governnace" for a chain, only an admin, we // need to figure out what we mean here @@ -48,6 +50,7 @@ impl GatewayChainConfig { diamond_proxy_addr, chain_admin_addr: Some(chain_admin_addr), governance_addr: chain_admin_addr, + settlement_layer, } } } @@ -64,6 +67,7 @@ impl From for GatewayChainConfig { diamond_proxy_addr: value.diamond_proxy_addr, chain_admin_addr: value.chain_admin_addr, governance_addr: value.governance_addr, + settlement_layer: value.settlement_layer.unwrap(), } } } diff --git a/core/lib/config/src/testonly.rs b/core/lib/config/src/testonly.rs index f16f5fa2375..0dca7335d1b 100644 --- a/core/lib/config/src/testonly.rs +++ b/core/lib/config/src/testonly.rs @@ -266,6 +266,7 @@ impl Distribution for EncodeDist { l2_da_validator_addr: rng.gen(), base_token_addr: self.sample_opt(|| rng.gen()), chain_admin_addr: self.sample_opt(|| rng.gen()), + settlement_layer: self.sample_opt(|| rng.gen()), } } } diff --git a/core/lib/env_config/src/contracts.rs b/core/lib/env_config/src/contracts.rs index b9ca4e88d07..a386adad1df 100644 --- a/core/lib/env_config/src/contracts.rs +++ b/core/lib/env_config/src/contracts.rs @@ -98,6 +98,7 @@ mod tests { )), chain_admin_addr: Some(addr("0xdd6fa5c14e7550b4caf2aa2818d24c69cbc347ff")), l2_da_validator_addr: Some(addr("0xed6fa5c14e7550b4caf2aa2818d24c69cbc347ff")), + settlement_layer: Some(0), } } @@ -129,6 +130,7 @@ CONTRACTS_USER_FACING_DIAMOND_PROXY_ADDR="0xF00B988a98Ca742e7958DeF9F7823b590871 CONTRACTS_L2_NATIVE_TOKEN_VAULT_PROXY_ADDR="0xfc073319977e314f251eae6ae6be76b0b3baeecf" CONTRACTS_L2_DA_VALIDATOR_ADDR="0xed6fa5c14e7550b4caf2aa2818d24c69cbc347ff" CONTRACTS_CHAIN_ADMIN_ADDR="0xdd6fa5c14e7550b4caf2aa2818d24c69cbc347ff" +CONTRACTS_SETTLEMENT_LAYER="0" "#; lock.set_env(config); diff --git a/core/lib/protobuf_config/src/contracts.rs b/core/lib/protobuf_config/src/contracts.rs index f9670ead838..3141c7149ec 100644 --- a/core/lib/protobuf_config/src/contracts.rs +++ b/core/lib/protobuf_config/src/contracts.rs @@ -131,6 +131,7 @@ impl ProtoRepr for proto::Contracts { .map(|x| parse_h160(x)) .transpose() .context("chain_admin_addr")?, + settlement_layer: self.settlement_layer, }) } @@ -191,6 +192,7 @@ impl ProtoRepr for proto::Contracts { user_facing_diamond_proxy: this .user_facing_diamond_proxy_addr .map(|a| format!("{:?}", a)), + settlement_layer: this.settlement_layer, } } } diff --git a/core/lib/protobuf_config/src/gateway.rs b/core/lib/protobuf_config/src/gateway.rs index 3e536a9edb6..b0d4cec7d49 100644 --- a/core/lib/protobuf_config/src/gateway.rs +++ b/core/lib/protobuf_config/src/gateway.rs @@ -34,6 +34,7 @@ impl ProtoRepr for proto::GatewayChainConfig { governance_addr: required(&self.governance_addr) .and_then(|x| parse_h160(x)) .context("governance_addr")?, + settlement_layer: *required(&self.settlement_layer)?, }) } @@ -45,6 +46,7 @@ impl ProtoRepr for proto::GatewayChainConfig { diamond_proxy_addr: Some(format!("{:?}", this.diamond_proxy_addr)), chain_admin_addr: this.chain_admin_addr.map(|x| format!("{:?}", x)), governance_addr: Some(format!("{:?}", this.governance_addr)), + settlement_layer: Some(this.settlement_layer), } } } diff --git a/core/lib/protobuf_config/src/proto/config/contracts.proto b/core/lib/protobuf_config/src/proto/config/contracts.proto index 54dda78afe5..11fbdcacdce 100644 --- a/core/lib/protobuf_config/src/proto/config/contracts.proto +++ b/core/lib/protobuf_config/src/proto/config/contracts.proto @@ -43,4 +43,5 @@ message Contracts { optional EcosystemContracts ecosystem_contracts = 4; optional string user_facing_bridgehub = 5; optional string user_facing_diamond_proxy = 6; + optional uint64 settlement_layer = 7; } diff --git a/core/lib/protobuf_config/src/proto/config/gateway.proto b/core/lib/protobuf_config/src/proto/config/gateway.proto index f39e0ab506e..2acb53677b8 100644 --- a/core/lib/protobuf_config/src/proto/config/gateway.proto +++ b/core/lib/protobuf_config/src/proto/config/gateway.proto @@ -9,4 +9,5 @@ message GatewayChainConfig { optional string diamond_proxy_addr = 4; optional string chain_admin_addr = 5; optional string governance_addr = 6; + optional uint64 settlement_layer = 7; } diff --git a/core/node/api_server/src/web3/namespaces/zks.rs b/core/node/api_server/src/web3/namespaces/zks.rs index 0d64d26b478..34010785c52 100644 --- a/core/node/api_server/src/web3/namespaces/zks.rs +++ b/core/node/api_server/src/web3/namespaces/zks.rs @@ -939,7 +939,6 @@ impl ZksNamespace { batch_number: L1BatchNumber, ) -> Result, Web3Error> { let mut storage = self.state.acquire_connection().await?; - println!("\n\nHey1\n\n"); self.state .start_info .ensure_not_pruned(batch_number, &mut storage) diff --git a/core/node/genesis/src/lib.rs b/core/node/genesis/src/lib.rs index 029c065a659..708297b08aa 100644 --- a/core/node/genesis/src/lib.rs +++ b/core/node/genesis/src/lib.rs @@ -99,13 +99,14 @@ impl GenesisParams { ) -> Result { println!( " - bootloader_hash = \"{:?}\" - default_aa_hash = \"{:?}\" - GENESIS_PROTOCOL_SEMANTIC_VERSION = \"{:?}\" + bootloader_hash: {:?} + default_aa_hash: {:?} + genesis_protocol_semantic_version: 0.{:?}.{:?} ", base_system_contracts.hashes().bootloader, base_system_contracts.hashes().default_aa, - config.protocol_version.unwrap(), + config.protocol_version.unwrap().minor, + config.protocol_version.unwrap().patch, ); let base_system_contracts_hashes = BaseSystemContractsHashes { bootloader: config @@ -339,9 +340,9 @@ pub async fn ensure_genesis_state( } = insert_genesis_batch(&mut transaction, genesis_params).await?; println!( " - GENESIS_ROOT = \"{:?}\" - GENESIS_BATCH_COMMITMENT = \"{:?}\" - GENESIS_ROLLUP_LEAF_INDEX = \"{:?}\" + genesis_root: {:?} + genesis_batch_commitment: {:?} + genesis_rollup_leaf_index: {:?} ", root_hash, commitment, rollup_last_leaf_index ); diff --git a/core/node/node_framework/src/implementations/layers/pk_signing_eth_client.rs b/core/node/node_framework/src/implementations/layers/pk_signing_eth_client.rs index 528601ca91e..6dda91907e1 100644 --- a/core/node/node_framework/src/implementations/layers/pk_signing_eth_client.rs +++ b/core/node/node_framework/src/implementations/layers/pk_signing_eth_client.rs @@ -98,18 +98,26 @@ impl WiringLayer for PKSigningEthClientLayer { BoundEthInterfaceForBlobsResource(Box::new(signing_client_for_blobs)) }); let signing_client_for_gateway = if input.gateway_client.is_some() { - let private_key = self.wallets.operator.private_key(); - let GatewayEthInterfaceResource(gateway_client) = input.gateway_client.unwrap(); - let signing_client_for_blobs = PKSigningClient::new_raw( - private_key.clone(), - self.gateway_contracts_config.unwrap().diamond_proxy_addr, - gas_adjuster_config.default_priority_fee_per_gas, - self.sl_chain_id, - gateway_client, - ); - Some(BoundEthInterfaceForL2Resource(Box::new( - signing_client_for_blobs, - ))) + if self + .gateway_contracts_config + .clone() + .is_some_and(|v| v.settlement_layer != 0_u64) + { + let private_key = self.wallets.operator.private_key(); + let GatewayEthInterfaceResource(gateway_client) = input.gateway_client.unwrap(); + let signing_client_for_blobs = PKSigningClient::new_raw( + private_key.clone(), + self.gateway_contracts_config.unwrap().diamond_proxy_addr, + gas_adjuster_config.default_priority_fee_per_gas, + self.sl_chain_id, + gateway_client, + ); + Some(BoundEthInterfaceForL2Resource(Box::new( + signing_client_for_blobs, + ))) + } else { + None + } } else { None }; diff --git a/core/tests/ts-integration/package.json b/core/tests/ts-integration/package.json index 78e02dce957..24e564504f4 100644 --- a/core/tests/ts-integration/package.json +++ b/core/tests/ts-integration/package.json @@ -31,7 +31,8 @@ "ts-jest": "^29.0.1", "ts-node": "^10.1.0", "typescript": "^4.3.5", - "zksync-ethers": "^6.9.0", + "zksync-ethers": "git+https://github.com/zksync-sdk/zksync-ethers#ra/fix-l2-l1-bridging", + "zksync-ethers-gw": "https://github.com/zksync-sdk/zksync-ethers#kl/gateway-support", "elliptic": "^6.5.5", "yaml": "^2.4.2" } diff --git a/core/tests/ts-integration/src/helpers.ts b/core/tests/ts-integration/src/helpers.ts index 1850d1fe5e4..354dfe64fdf 100644 --- a/core/tests/ts-integration/src/helpers.ts +++ b/core/tests/ts-integration/src/helpers.ts @@ -29,6 +29,10 @@ export function getContractSource(relativePath: string): string { return source; } +export function readContract(path: string, fileName: string) { + return JSON.parse(fs.readFileSync(`${path}/${fileName}.sol/${fileName}.json`, { encoding: 'utf-8' })); +} + /** * Performs a contract deployment * diff --git a/core/tests/ts-integration/tests/contracts.test.ts b/core/tests/ts-integration/tests/contracts.test.ts index 4a11973f3bf..b6b9672750b 100644 --- a/core/tests/ts-integration/tests/contracts.test.ts +++ b/core/tests/ts-integration/tests/contracts.test.ts @@ -116,7 +116,7 @@ describe('Smart contract behavior checks', () => { // We manually provide a constant, since otherwise the exception would be thrown // while estimating gas - await expect(counterContract.incrementWithRevert(5, true, { gasLimit: 5000000, gasPrice })).toBeReverted([]); + await expect(counterContract.incrementWithRevert(5, true, { gasLimit: 5000000, gasPrice })).toBeReverted(); // The tx has been reverted, so the value Should not have been changed: const newValue = await counterContract.get(); diff --git a/core/tests/ts-integration/tests/erc20.test.ts b/core/tests/ts-integration/tests/erc20.test.ts index 68beb1f4632..382c625ac70 100644 --- a/core/tests/ts-integration/tests/erc20.test.ts +++ b/core/tests/ts-integration/tests/erc20.test.ts @@ -11,7 +11,7 @@ import * as ethers from 'ethers'; import { scaledGasPrice, waitForBlockToBeFinalizedOnL1 } from '../src/helpers'; import { L2_DEFAULT_ETH_PER_ACCOUNT } from '../src/context-owner'; -describe('ERC20 contract checks', () => { +describe('L1 ERC20 contract checks', () => { let testMaster: TestMaster; let alice: zksync.Wallet; let bob: zksync.Wallet; diff --git a/core/tests/ts-integration/tests/l2-erc20.test.ts b/core/tests/ts-integration/tests/l2-erc20.test.ts new file mode 100644 index 00000000000..cc07ebb9c47 --- /dev/null +++ b/core/tests/ts-integration/tests/l2-erc20.test.ts @@ -0,0 +1,227 @@ +/** + * This suite contains tests checking default ERC-20 contract behavior. + */ + +import { TestMaster } from '../src'; +import { Token } from '../src/types'; +import { shouldChangeTokenBalances, shouldOnlyTakeFee } from '../src/modifiers/balance-checker'; + +import * as zksync from 'zksync-ethers'; +import * as ethers from 'ethers'; +import { Provider, Wallet } from 'ethers'; +import { scaledGasPrice, deployContract, readContract, waitForBlockToBeFinalizedOnL1 } from '../src/helpers'; + +describe('L2 native ERC20 contract checks', () => { + let testMaster: TestMaster; + let alice: zksync.Wallet; + let isETHBasedChain: boolean; + let baseTokenAddress: string; + let zkTokenAssetId: string; + let tokenDetails: Token; + let aliceErc20: zksync.Contract; + let l1NativeTokenVault: ethers.Contract; + let l1Wallet: Wallet; + let l2Wallet: Wallet; + let l1Provider: Provider; + let l2Provider: Provider; + + beforeAll(async () => { + testMaster = TestMaster.getInstance(__filename); + alice = testMaster.mainAccount(); + const bridgeContracts = await alice.getL1BridgeContracts(); + const assetRouter = bridgeContracts.shared; + l2Provider = alice._providerL2(); + l1Provider = alice._providerL1(); + l2Wallet = new Wallet(alice.privateKey, l2Provider); + l1Wallet = new Wallet(alice.privateKey, l1Provider); + const L2_NATIVE_TOKEN_VAULT_ADDRESS = '0x0000000000000000000000000000000000010004'; + const ARTIFACTS_PATH = '../../../contracts/l1-contracts/artifacts/contracts/'; + const l2NtvInterface = readContract(`${ARTIFACTS_PATH}/bridge/ntv`, 'L2NativeTokenVault').abi; + const l2NativeTokenVault = new ethers.Contract(L2_NATIVE_TOKEN_VAULT_ADDRESS, l2NtvInterface, l2Wallet); + const l1AssetRouterInterface = readContract(`${ARTIFACTS_PATH}/bridge/asset-router`, 'L1AssetRouter').abi; + const l1NativeTokenVaultInterface = readContract(`${ARTIFACTS_PATH}/bridge/ntv`, 'L1NativeTokenVault').abi; + const l1AssetRouter = new ethers.Contract(await assetRouter.getAddress(), l1AssetRouterInterface, l1Wallet); + l1NativeTokenVault = new ethers.Contract( + await l1AssetRouter.nativeTokenVault(), + l1NativeTokenVaultInterface, + l1Wallet + ); + + // Get the information about base token address directly from the L2. + baseTokenAddress = await alice._providerL2().getBaseTokenContractAddress(); + isETHBasedChain = baseTokenAddress == zksync.utils.ETH_ADDRESS_IN_CONTRACTS; + + const ZkSyncERC20 = await readContract( + '../../../contracts/l1-contracts/artifacts-zk/contracts/dev-contracts', + 'TestnetERC20Token' + ); + + aliceErc20 = await deployContract(alice, ZkSyncERC20, ['ZKsync', 'ZK', 18]); + const l2TokenAddress = await aliceErc20.getAddress(); + tokenDetails = { + name: 'ZKsync', + symbol: 'ZK', + decimals: 18n, + l1Address: ethers.ZeroAddress, + l2Address: l2TokenAddress + }; + const mintTx = await aliceErc20.mint(alice.address, 1000n); + await mintTx.wait(); + // const mintTx2 = await aliceErc20.mint('0x36615Cf349d7F6344891B1e7CA7C72883F5dc049', 1000n); + // await mintTx2.wait(); + const registerZKTx = await l2NativeTokenVault.registerToken(tokenDetails.l2Address); + await registerZKTx.wait(); + zkTokenAssetId = await l2NativeTokenVault.assetId(l2TokenAddress); + const tokenApprovalTx = await aliceErc20.approve(L2_NATIVE_TOKEN_VAULT_ADDRESS, 100n); + await tokenApprovalTx.wait(); + }); + + test('Token properties are correct', async () => { + await expect(aliceErc20.name()).resolves.toBe(tokenDetails.name); + await expect(aliceErc20.decimals()).resolves.toBe(tokenDetails.decimals); + await expect(aliceErc20.symbol()).resolves.toBe(tokenDetails.symbol); + await expect(aliceErc20.balanceOf(alice.address)).resolves.toBeGreaterThan(0n); // 'Alice should have non-zero balance' + }); + + test('Can perform a withdrawal', async () => { + if (testMaster.isFastMode()) { + return; + } + const amount = 10n; + + const l2BalanceChange = await shouldChangeTokenBalances(tokenDetails.l2Address, [ + { wallet: alice, change: -amount } + ]); + const feeCheck = await shouldOnlyTakeFee(alice); + const withdrawalPromise = alice.withdraw({ + token: tokenDetails.l2Address, + amount + }); + await expect(withdrawalPromise).toBeAccepted([l2BalanceChange, feeCheck]); + const withdrawalTx = await withdrawalPromise; + const l2TxReceipt = await alice.provider.getTransactionReceipt(withdrawalTx.hash); + await withdrawalTx.waitFinalize(); + await waitForBlockToBeFinalizedOnL1(alice, l2TxReceipt!.blockNumber); + + await alice.finalizeWithdrawalParams(withdrawalTx.hash); // kl todo finalize the Withdrawals with the params here. Alternatively do in the SDK. + await expect(alice.finalizeWithdrawal(withdrawalTx.hash)).toBeAccepted(); + + tokenDetails.l1Address = await l1NativeTokenVault.tokenAddress(zkTokenAssetId); + const balanceAfterBridging = await alice.getBalanceL1(tokenDetails.l1Address); + expect(balanceAfterBridging).toEqual(10n); + }); + + test('Can perform a deposit', async () => { + const amount = 1n; // 1 wei is enough. + const gasPrice = await scaledGasPrice(alice); + + // Note: for L1 we should use L1 token address. + const l1BalanceChange = await shouldChangeTokenBalances( + tokenDetails.l1Address, + [{ wallet: alice, change: -amount }], + { + l1: true + } + ); + const l2BalanceChange = await shouldChangeTokenBalances(tokenDetails.l2Address, [ + { wallet: alice, change: amount } + ]); + const feeCheck = await shouldOnlyTakeFee(alice, true); + + await expect( + alice.deposit({ + token: tokenDetails.l1Address, + amount, + approveERC20: true, + approveBaseERC20: true, + approveOverrides: { + gasPrice + }, + overrides: { + gasPrice + } + }) + ).toBeAccepted([l1BalanceChange, l2BalanceChange, feeCheck]); + }); + + test('Should claim failed deposit', async () => { + if (testMaster.isFastMode()) { + return; + } + + const amount = 1n; + const initialBalance = await alice.getBalanceL1(tokenDetails.l1Address); + // Deposit to the zero address is forbidden and should fail with the current implementation. + const depositHandle = await alice.deposit({ + token: tokenDetails.l1Address, + to: ethers.ZeroAddress, + amount, + approveERC20: true, + approveBaseERC20: true, + l2GasLimit: 5_000_000 // Setting the limit manually to avoid estimation for L1->L2 transaction + }); + const l1Receipt = await depositHandle.waitL1Commit(); + + // L1 balance should change, but tx should fail in L2. + await expect(alice.getBalanceL1(tokenDetails.l1Address)).resolves.toEqual(initialBalance - amount); + await expect(depositHandle).toBeReverted(); + + // Wait for tx to be finalized. + // `waitFinalize` is not used because it doesn't work as expected for failed transactions. + // It throws once it gets status == 0 in the receipt and doesn't wait for the finalization. + const l2Hash = zksync.utils.getL2HashFromPriorityOp(l1Receipt, await alice.provider.getMainContractAddress()); + const l2TxReceipt = await alice.provider.getTransactionReceipt(l2Hash); + await waitForBlockToBeFinalizedOnL1(alice, l2TxReceipt!.blockNumber); + // Claim failed deposit. + await expect(alice.claimFailedDeposit(l2Hash)).toBeAccepted(); + await expect(alice.getBalanceL1(tokenDetails.l1Address)).resolves.toEqual(initialBalance); + }); + + test('Can perform a deposit with precalculated max value', async () => { + if (!isETHBasedChain) { + // approving whole base token balance + const baseTokenDetails = testMaster.environment().baseToken; + const baseTokenMaxAmount = await alice.getBalanceL1(baseTokenDetails.l1Address); + await (await alice.approveERC20(baseTokenDetails.l1Address, baseTokenMaxAmount)).wait(); + } + + // depositing the max amount: the whole balance of the token + const tokenDepositAmount = await alice.getBalanceL1(tokenDetails.l1Address); + + // approving the needed allowance for the deposit + await (await alice.approveERC20(tokenDetails.l1Address, tokenDepositAmount)).wait(); + + // fee of the deposit in ether + const depositFee = await alice.getFullRequiredDepositFee({ + token: tokenDetails.l1Address + }); + + // checking if alice has enough funds to pay the fee + const l1Fee = depositFee.l1GasLimit * (depositFee.maxFeePerGas! || depositFee.gasPrice!); + const l2Fee = depositFee.baseCost; + const aliceBalance = await alice.getBalanceL1(); + if (aliceBalance < l1Fee + l2Fee) { + throw new Error('Not enough balance to pay the fee'); + } + + // deposit handle with the precalculated max amount + const depositHandle = await alice.deposit({ + token: tokenDetails.l1Address, + amount: tokenDepositAmount, + l2GasLimit: depositFee.l2GasLimit, + approveBaseERC20: true, + approveERC20: true, + overrides: depositFee + }); + + // checking the l2 balance change + const l2TokenBalanceChange = await shouldChangeTokenBalances(tokenDetails.l2Address, [ + { wallet: alice, change: tokenDepositAmount } + ]); + await expect(depositHandle).toBeAccepted([l2TokenBalanceChange]); + }); + + afterAll(async () => { + await testMaster.deinitialize(); + }); +}); diff --git a/etc/env/file_based/genesis.yaml b/etc/env/file_based/genesis.yaml index c9797dccc18..33634c253ba 100644 --- a/etc/env/file_based/genesis.yaml +++ b/etc/env/file_based/genesis.yaml @@ -1,6 +1,6 @@ -genesis_root: 0x3b4912a5bc33df084fa514ff3b68b84c6f0786cb416fbf0f7c5e27655f760a65 +genesis_root: 0x526a5d3e384ff95a976283c79a976e0a2fb749e4631233f29d3765201efd937d +genesis_batch_commitment: 0xb9794246425fd654cf6a4c2e9adfdd48aaaf97bf3b8ba6bdc88e1d141bcfa5b3 genesis_rollup_leaf_index: 64 -genesis_batch_commitment: 0x91743a280e8015098e95c4c40c4ecca77da16c80a3db9ad3229624deef8ddafb genesis_protocol_version: 25 default_aa_hash: 0x0100055d3993e14104994ca4d8cfa91beb9b544ee86894b45708b4824d832ff2 bootloader_hash: 0x010008c753336bc8d1ddca235602b9f31d346412b2d463cd342899f7bfb73baf diff --git a/zk_toolbox/Cargo.lock b/zk_toolbox/Cargo.lock index 9f52c1157de..10ef9085041 100644 --- a/zk_toolbox/Cargo.lock +++ b/zk_toolbox/Cargo.lock @@ -703,6 +703,7 @@ name = "common" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", "clap", "cliclack", "console", @@ -721,6 +722,8 @@ dependencies = [ "types", "url", "xshell", + "zksync_system_constants", + "zksync_types", ] [[package]] @@ -6712,6 +6715,7 @@ dependencies = [ "zksync_consensus_crypto", "zksync_consensus_roles", "zksync_system_constants", + "zksync_types", ] [[package]] diff --git a/zk_toolbox/Cargo.toml b/zk_toolbox/Cargo.toml index 1fde1d6570e..0c447f18f07 100644 --- a/zk_toolbox/Cargo.toml +++ b/zk_toolbox/Cargo.toml @@ -36,6 +36,8 @@ zksync_system_constants = { path = "../core/lib/constants" } zksync_consensus_roles = "=0.3.0" zksync_consensus_crypto = "=0.3.0" zksync_protobuf = "=0.3.0" +zksync_types = { path = "../core/lib/types" } +zksync_web3_decl = { path = "../core/lib/web3_decl" } # External dependencies anyhow = "1.0.82" diff --git a/zk_toolbox/crates/common/Cargo.toml b/zk_toolbox/crates/common/Cargo.toml index 5fdf481bea6..6021e866e8e 100644 --- a/zk_toolbox/crates/common/Cargo.toml +++ b/zk_toolbox/crates/common/Cargo.toml @@ -30,3 +30,10 @@ xshell.workspace = true thiserror.workspace = true strum.workspace = true git_version_macro.workspace = true + +# Async +async-trait = "0.1.68" + +zksync_system_constants.workspace = true +zksync_types.workspace = true +zksync_web3_decl.workspace = true diff --git a/zk_toolbox/crates/common/src/forge.rs b/zk_toolbox/crates/common/src/forge.rs index 4421edca663..846685ab29a 100644 --- a/zk_toolbox/crates/common/src/forge.rs +++ b/zk_toolbox/crates/common/src/forge.rs @@ -132,6 +132,11 @@ impl ForgeScript { self } + pub fn with_zksync(mut self) -> Self { + self.args.add_arg(ForgeScriptArg::Zksync); + self + } + pub fn with_calldata(mut self, calldata: &Bytes) -> Self { self.args.add_arg(ForgeScriptArg::Sig { sig: hex::encode(calldata), @@ -266,6 +271,7 @@ pub enum ForgeScriptArg { Sender { address: String, }, + Zksync, } /// ForgeScriptArgs is a set of arguments that can be passed to the forge script command. @@ -289,6 +295,8 @@ pub struct ForgeScriptArgs { pub verifier_api_key: Option, #[clap(long)] pub resume: bool, + #[clap(long)] + pub zksync: bool, /// List of additional arguments that can be passed through the CLI. /// /// e.g.: `zk_inception init -a --private-key=` @@ -302,6 +310,9 @@ impl ForgeScriptArgs { pub fn build(&mut self) -> Vec { self.add_verify_args(); self.cleanup_contract_args(); + if self.zksync { + self.add_arg(ForgeScriptArg::Zksync); + } self.args .iter() .map(|arg| arg.to_string()) @@ -397,6 +408,10 @@ impl ForgeScriptArgs { .iter() .any(|arg| WALLET_ARGS.contains(&arg.as_ref())) } + + pub fn with_zksync(&mut self) { + self.zksync = true; + } } #[derive(Debug, Clone, ValueEnum, Display, Serialize, Deserialize, Default)] diff --git a/zk_toolbox/crates/common/src/lib.rs b/zk_toolbox/crates/common/src/lib.rs index 436d44fef87..6dc26bbba9f 100644 --- a/zk_toolbox/crates/common/src/lib.rs +++ b/zk_toolbox/crates/common/src/lib.rs @@ -15,6 +15,7 @@ pub mod hardhat; pub mod server; pub mod version; pub mod wallets; +pub mod withdraw; pub mod yaml; pub use prerequisites::{ diff --git a/zk_toolbox/crates/common/src/withdraw.rs b/zk_toolbox/crates/common/src/withdraw.rs new file mode 100644 index 00000000000..bdd1b426cc4 --- /dev/null +++ b/zk_toolbox/crates/common/src/withdraw.rs @@ -0,0 +1,164 @@ +use async_trait::async_trait; +use ethers::{ + providers::ProviderError, + types::{Address, Bytes, H160, H256, U64}, +}; +use serde::{Deserialize, Serialize}; +use zksync_system_constants::L1_MESSENGER_ADDRESS; +use zksync_types::{ + api::{L2ToL1Log, L2ToL1LogProof, Log}, + ethabi, +}; +use zksync_web3_decl::{ + client::{Client, L2}, + namespaces::{EthNamespaceClient, ZksNamespaceClient}, +}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FinalizeWithdrawalParams { + pub l2_batch_number: U64, + pub l2_message_index: U64, + pub l2_tx_number_in_block: U64, + pub message: Bytes, + pub sender: Address, + pub proof: L2ToL1LogProof, +} + +#[async_trait] +pub trait ZKSProvider { + async fn get_withdrawal_log( + &self, + withdrawal_hash: H256, + index: usize, + ) -> Result<(Log, U64), ProviderError>; + + async fn get_withdrawal_l2_to_l1_log( + &self, + withdrawal_hash: H256, + index: usize, + ) -> Result<(u64, L2ToL1Log), ProviderError>; + + async fn get_finalize_withdrawal_params( + &self, + withdrawal_hash: H256, + index: usize, + ) -> Result; +} + +#[async_trait] +impl ZKSProvider for Client +where + Client: ZksNamespaceClient + EthNamespaceClient, +{ + async fn get_withdrawal_log( + &self, + withdrawal_hash: H256, + index: usize, + ) -> Result<(Log, U64), ProviderError> { + let receipt = ::get_transaction_receipt(self, withdrawal_hash) + .await + .map_err(|e| { + ProviderError::CustomError(format!("Failed to get transaction receipt: {}", e)) + })?; + + let receipt = receipt + .ok_or_else(|| ProviderError::CustomError("Transaction is not mined!".into()))?; + + let l1_message_event_signature: H256 = ethabi::long_signature( + "L1MessageSent", + &[ + ethabi::ParamType::Address, + ethabi::ParamType::FixedBytes(32), + ethabi::ParamType::Bytes, + ], + ); + + let log = receipt + .logs + .clone() + .into_iter() + .filter(|log| { + log.address == L1_MESSENGER_ADDRESS && log.topics[0] == l1_message_event_signature + }) + .nth(index) + .ok_or_else(|| ProviderError::CustomError("Log not found".into()))?; + + Ok(( + Log { + l1_batch_number: receipt.l1_batch_number, + ..log + }, + receipt.l1_batch_tx_index.unwrap_or_default(), + )) + } + + async fn get_withdrawal_l2_to_l1_log( + &self, + withdrawal_hash: H256, + index: usize, + ) -> Result<(u64, L2ToL1Log), ProviderError> { + let receipt = ::get_transaction_receipt(self, withdrawal_hash) + .await + .map_err(|e| { + ProviderError::CustomError(format!("Failed to get withdrawal log: {}", e)) + })?; + + if receipt.is_none() { + return Err(ProviderError::CustomError( + "Transaction is not mined!".into(), + )); + } + + let receipt = receipt.unwrap(); + let messages: Vec<(u64, L2ToL1Log)> = receipt + .l2_to_l1_logs + .into_iter() + .enumerate() + .filter(|(_, log)| log.sender == L1_MESSENGER_ADDRESS) + .map(|(i, log)| (i as u64, log)) + .collect(); + + messages.get(index).cloned().ok_or_else(|| { + ProviderError::CustomError("L2ToL1Log not found at specified index".into()) + }) + } + + async fn get_finalize_withdrawal_params( + &self, + withdrawal_hash: H256, + index: usize, + ) -> Result { + let (log, l1_batch_tx_id) = self.get_withdrawal_log(withdrawal_hash, index).await?; + let (l2_to_l1_log_index, _) = self + .get_withdrawal_l2_to_l1_log(withdrawal_hash, index) + .await?; + let sender = H160::from_slice(&log.topics[1][12..]); + let proof = ::get_l2_to_l1_log_proof( + self, + withdrawal_hash, + Some(l2_to_l1_log_index as usize), + ) + .await + .map_err(|e| { + ProviderError::CustomError(format!("Failed to get withdrawal log proof: {}", e)) + })? + .ok_or_else(|| ProviderError::CustomError("Log proof not found!".into()))?; + + let message = ethers::abi::decode(&[ethers::abi::ParamType::Bytes], &log.data.0) + .map_err(|e| ProviderError::CustomError(format!("Failed to decode log data: {}", e)))? + .remove(0) + .into_bytes() + .ok_or_else(|| { + ProviderError::CustomError("Failed to extract message from decoded data".into()) + })?; + + Ok(FinalizeWithdrawalParams { + l2_batch_number: log.l1_batch_number.unwrap_or_default(), + l2_message_index: proof.id.into(), + l2_tx_number_in_block: l1_batch_tx_id, + message: message.into(), + sender, + proof, + }) + } +} diff --git a/zk_toolbox/crates/config/src/contracts.rs b/zk_toolbox/crates/config/src/contracts.rs index c2b69b7df8c..3210e4f1926 100644 --- a/zk_toolbox/crates/config/src/contracts.rs +++ b/zk_toolbox/crates/config/src/contracts.rs @@ -19,7 +19,7 @@ use crate::{ pub struct ContractsConfig { pub create2_factory_addr: Address, pub create2_factory_salt: H256, - pub ecosystem_contracts: EcosystemContracts, + pub ecosystem_contracts: ToolboxEcosystemContracts, pub bridges: BridgesContracts, pub l1: L1Contracts, pub l2: L2Contracts, @@ -162,7 +162,7 @@ impl FileConfigWithDefaultName for ContractsConfig { impl ZkToolboxConfig for ContractsConfig {} #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] -pub struct EcosystemContracts { +pub struct ToolboxEcosystemContracts { pub bridgehub_proxy_addr: Address, pub state_transition_proxy_addr: Address, pub transparent_proxy_admin_addr: Address, @@ -173,7 +173,7 @@ pub struct EcosystemContracts { pub native_token_vault_addr: Address, } -impl ZkToolboxConfig for EcosystemContracts {} +impl ZkToolboxConfig for ToolboxEcosystemContracts {} #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct BridgesContracts { diff --git a/zk_toolbox/crates/config/src/forge_interface/deploy_gateway_ctm/output.rs b/zk_toolbox/crates/config/src/forge_interface/deploy_gateway_ctm/output.rs index 7d2a2cadb2c..9cbec63f0b9 100644 --- a/zk_toolbox/crates/config/src/forge_interface/deploy_gateway_ctm/output.rs +++ b/zk_toolbox/crates/config/src/forge_interface/deploy_gateway_ctm/output.rs @@ -1,6 +1,5 @@ use ethers::abi::Address; use serde::{Deserialize, Serialize}; -use zksync_basic_types::web3::Bytes; use crate::traits::ZkToolboxConfig; @@ -10,7 +9,7 @@ pub struct DeployGatewayCTMOutput { pub multicall3_addr: Address, pub validium_da_validator: Address, pub relayed_sl_da_validator: Address, - pub diamond_cut_data: Bytes, + pub diamond_cut_data: String, } impl ZkToolboxConfig for DeployGatewayCTMOutput {} diff --git a/zk_toolbox/crates/config/src/forge_interface/gateway_preparation/input.rs b/zk_toolbox/crates/config/src/forge_interface/gateway_preparation/input.rs index 99465b07d73..3689c64f0ff 100644 --- a/zk_toolbox/crates/config/src/forge_interface/gateway_preparation/input.rs +++ b/zk_toolbox/crates/config/src/forge_interface/gateway_preparation/input.rs @@ -1,3 +1,4 @@ +use ethers::utils::hex; use serde::{Deserialize, Serialize}; use zksync_basic_types::{web3::Bytes, Address}; use zksync_config::configs::GatewayConfig; @@ -13,9 +14,11 @@ pub struct GatewayPreparationConfig { pub governance: Address, pub chain_chain_id: u64, // Assuming uint256 can be represented as u64 for chain ID, use U256 for full uint256 support pub gateway_diamond_cut_data: Bytes, + pub l1_diamond_cut_data: Bytes, pub chain_proxy_admin: Address, pub chain_admin: Address, pub access_control_restriction: Address, + pub l1_nullifier_proxy_addr: Address, } impl ZkToolboxConfig for GatewayPreparationConfig {} @@ -43,6 +46,15 @@ impl GatewayPreparationConfig { chain_proxy_admin: chain_contracts_config.l1.chain_proxy_admin_addr, chain_admin: chain_contracts_config.l1.chain_admin_addr, access_control_restriction: chain_contracts_config.l1.access_control_restriction_addr, + l1_nullifier_proxy_addr: chain_contracts_config.bridges.l1_nullifier_addr, + l1_diamond_cut_data: hex::decode( + ecosystem_contracts_config + .ecosystem_contracts + .diamond_cut_data + .clone(), + ) + .unwrap() + .into(), }) } } diff --git a/zk_toolbox/crates/config/src/gateway.rs b/zk_toolbox/crates/config/src/gateway.rs index 2e21589cb9b..37410b9a5e6 100644 --- a/zk_toolbox/crates/config/src/gateway.rs +++ b/zk_toolbox/crates/config/src/gateway.rs @@ -1,3 +1,4 @@ +use ethers::utils::hex; use zksync_config::configs::{gateway::GatewayChainConfig, GatewayConfig}; use crate::{ @@ -30,7 +31,7 @@ impl From for GatewayConfig { genesis_upgrade_addr: output.gateway_state_transition.genesis_upgrade_addr, default_upgrade_addr: output.gateway_state_transition.default_upgrade_addr, multicall3_addr: output.multicall3_addr, - diamond_cut_data: output.diamond_cut_data, + diamond_cut_data: hex::decode(output.diamond_cut_data.clone()).unwrap().into(), validator_timelock_addr: output.gateway_state_transition.validator_timelock_addr, relayed_sl_da_validator: output.relayed_sl_da_validator, validium_da_validator: output.validium_da_validator, diff --git a/zk_toolbox/crates/zk_inception/Cargo.toml b/zk_toolbox/crates/zk_inception/Cargo.toml index c8ef73d7d1f..c95b2256f58 100644 --- a/zk_toolbox/crates/zk_inception/Cargo.toml +++ b/zk_toolbox/crates/zk_inception/Cargo.toml @@ -36,6 +36,8 @@ zksync_basic_types.workspace = true clap-markdown.workspace = true zksync_consensus_roles.workspace = true zksync_consensus_crypto.workspace = true +zksync_types.workspace = true +zksync_web3_decl.workspace = true secrecy.workspace = true [build-dependencies] diff --git a/zk_toolbox/crates/zk_inception/src/commands/chain/args/create.rs b/zk_toolbox/crates/zk_inception/src/commands/chain/args/create.rs index 3ea15d10f8b..255fe05de59 100644 --- a/zk_toolbox/crates/zk_inception/src/commands/chain/args/create.rs +++ b/zk_toolbox/crates/zk_inception/src/commands/chain/args/create.rs @@ -67,6 +67,18 @@ pub struct ChainCreateArgs { pub(crate) set_as_default: Option, #[clap(long, default_value = "false")] pub(crate) legacy_bridge: bool, + #[clap( + long, + help = "Skip submodules checkout", + default_missing_value = "true" + )] + pub skip_submodules_checkout: bool, + #[clap( + long, + help = "Skip contract compilation override", + default_missing_value = "true" + )] + pub skip_contract_compilation_override: bool, } impl ChainCreateArgs { @@ -227,6 +239,8 @@ impl ChainCreateArgs { base_token, set_as_default, legacy_bridge: self.legacy_bridge, + skip_submodules_checkout: self.skip_submodules_checkout, + skip_contract_compilation_override: self.skip_contract_compilation_override, }) } } @@ -242,6 +256,8 @@ pub struct ChainCreateArgsFinal { pub base_token: BaseToken, pub set_as_default: bool, pub legacy_bridge: bool, + pub skip_submodules_checkout: bool, + pub skip_contract_compilation_override: bool, } #[derive(Debug, Clone, EnumIter, Display, PartialEq, Eq)] diff --git a/zk_toolbox/crates/zk_inception/src/commands/chain/args/init.rs b/zk_toolbox/crates/zk_inception/src/commands/chain/args/init.rs index 24a0539f27d..fbdd71a7724 100644 --- a/zk_toolbox/crates/zk_inception/src/commands/chain/args/init.rs +++ b/zk_toolbox/crates/zk_inception/src/commands/chain/args/init.rs @@ -30,6 +30,12 @@ pub struct InitArgs { pub l1_rpc_url: Option, #[clap(long, help = MSG_NO_PORT_REALLOCATION_HELP, default_value = "false", default_missing_value = "true", num_args = 0..=1)] pub no_port_reallocation: bool, + #[clap( + long, + help = "Skip submodules checkout", + default_missing_value = "true" + )] + pub skip_submodules_checkout: bool, } impl InitArgs { @@ -60,6 +66,7 @@ impl InitArgs { deploy_paymaster, l1_rpc_url, no_port_reallocation: self.no_port_reallocation, + skip_submodules_checkout: self.skip_submodules_checkout, } } } @@ -71,4 +78,5 @@ pub struct InitArgsFinal { pub deploy_paymaster: bool, pub l1_rpc_url: String, pub no_port_reallocation: bool, + pub skip_submodules_checkout: bool, } diff --git a/zk_toolbox/crates/zk_inception/src/commands/chain/init.rs b/zk_toolbox/crates/zk_inception/src/commands/chain/init.rs index ae117cf602a..53e49955f5e 100644 --- a/zk_toolbox/crates/zk_inception/src/commands/chain/init.rs +++ b/zk_toolbox/crates/zk_inception/src/commands/chain/init.rs @@ -43,7 +43,9 @@ pub(crate) async fn run(args: InitArgs, shell: &Shell) -> anyhow::Result<()> { logger::note(MSG_SELECTED_CONFIG, logger::object_to_string(&chain_config)); logger::info(msg_initializing_chain("")); - git::submodule_update(shell, config.link_to_code.clone())?; + if !args.skip_submodules_checkout { + git::submodule_update(shell, config.link_to_code.clone())?; + } init(&mut args, shell, &config, &chain_config).await?; diff --git a/zk_toolbox/crates/zk_inception/src/commands/chain/migrate_from_gateway.rs b/zk_toolbox/crates/zk_inception/src/commands/chain/migrate_from_gateway.rs new file mode 100644 index 00000000000..19b0042037d --- /dev/null +++ b/zk_toolbox/crates/zk_inception/src/commands/chain/migrate_from_gateway.rs @@ -0,0 +1,291 @@ +use anyhow::Context; +use clap::Parser; +use common::{ + config::global_config, + forge::{Forge, ForgeScriptArgs}, + withdraw::ZKSProvider, +}; +use config::{ + forge_interface::{ + gateway_preparation::{input::GatewayPreparationConfig, output::GatewayPreparationOutput}, + script_params::GATEWAY_PREPARATION, + }, + traits::{ReadConfig, SaveConfig, SaveConfigWithBasePath}, + EcosystemConfig, +}; +use ethers::{ + abi::parse_abi, + contract::BaseContract, + providers::{Http, Middleware, Provider}, + types::Bytes, + utils::hex, +}; +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; +use types::L1BatchCommitmentMode; +use xshell::Shell; +use zksync_basic_types::{settlement::SettlementMode, H256, U256, U64}; +use zksync_config::configs::eth_sender::PubdataSendingMode; +use zksync_types::L2ChainId; +use zksync_web3_decl::client::{Client, L2}; + +use crate::{ + messages::{MSG_CHAIN_NOT_INITIALIZED, MSG_L1_SECRETS_MUST_BE_PRESENTED}, + utils::forge::{check_the_balance, fill_forge_private_key}, +}; + +#[derive(Debug, Serialize, Deserialize, Parser)] +pub struct MigrateFromGatewayArgs { + /// All ethereum environment related arguments + #[clap(flatten)] + #[serde(flatten)] + pub forge_args: ForgeScriptArgs, + + #[clap(long)] + pub gateway_chain_name: String, +} + +// TODO: use a different script here (i.e. make it have a different file) +lazy_static! { + static ref GATEWAY_PREPARATION_INTERFACE: BaseContract = BaseContract::from( + parse_abi(&[ + "function startMigrateChainFromGateway(address chainAdmin,address accessControlRestriction,uint256 chainId) public", + "function finishMigrateChainFromGateway(uint256 migratingChainId,uint256 gatewayChainId,uint256 l2BatchNumber,uint256 l2MessageIndex,uint16 l2TxNumberInBatch,bytes memory message,bytes32[] memory merkleProof) public", + ]) + .unwrap(), + ); +} + +pub async fn run(args: MigrateFromGatewayArgs, shell: &Shell) -> anyhow::Result<()> { + let ecosystem_config = EcosystemConfig::from_file(shell)?; + + let chain_name = global_config().chain_name.clone(); + let chain_config = ecosystem_config + .load_chain(chain_name) + .context(MSG_CHAIN_NOT_INITIALIZED)?; + + let gateway_chain_config = ecosystem_config + .load_chain(Some(args.gateway_chain_name.clone())) + .context("Gateway not present")?; + let gateway_chain_id = gateway_chain_config.chain_id.0; + let gateway_gateway_config = gateway_chain_config + .get_gateway_config() + .context("Gateway config not present")?; + + let l1_url = chain_config + .get_secrets_config()? + .l1 + .context(MSG_L1_SECRETS_MUST_BE_PRESENTED)? + .l1_rpc_url + .expose_str() + .to_string(); + + let genesis_config = chain_config.get_genesis_config()?; + + let is_rollup = matches!( + genesis_config.l1_batch_commit_data_generator_mode, + L1BatchCommitmentMode::Rollup + ); + + let preparation_config_path = GATEWAY_PREPARATION.input(&ecosystem_config.link_to_code); + let preparation_config = GatewayPreparationConfig::new( + &gateway_chain_config, + &gateway_chain_config.get_contracts_config()?, + &ecosystem_config.get_contracts_config()?, + &gateway_gateway_config, + )?; + preparation_config.save(shell, preparation_config_path)?; + + let chain_contracts_config = chain_config.get_contracts_config().unwrap(); + let mut gateway_chain_chain_config = chain_config.get_gateway_chain_config().unwrap(); + let chain_admin_addr = chain_contracts_config.l1.chain_admin_addr; + let chain_access_control_restriction = + chain_contracts_config.l1.access_control_restriction_addr; + + println!("Migrating the chain to L1..."); + let hash = call_script( + shell, + args.forge_args.clone(), + &GATEWAY_PREPARATION_INTERFACE + .encode( + "startMigrateChainFromGateway", + ( + chain_admin_addr, + chain_access_control_restriction, + U256::from(chain_config.chain_id.0), + ), + ) + .unwrap(), + &ecosystem_config, + chain_config.get_wallets_config()?.governor_private_key(), + l1_url.clone(), + ) + .await?; + + let gateway_provider = Provider::::try_from( + gateway_chain_config + .get_general_config() + .unwrap() + .api_config + .unwrap() + .web3_json_rpc + .http_url, + )?; + + let client: Client = Client::http( + gateway_chain_config + .get_general_config() + .unwrap() + .api_config + .unwrap() + .web3_json_rpc + .http_url + .parse() + .unwrap(), + )? + .for_network(L2::from(L2ChainId(gateway_chain_id))) + .build(); + + if hash == H256::zero() { + println!("Chain already migrated!"); + } else { + println!("Migration started! Migration hash: {}", hex::encode(hash)); + await_for_tx_to_complete(&gateway_provider, hash).await?; + await_for_withdrawal_to_finalize(&client, hash).await?; + } + // FIXME: this is a temporary hack to make sure that the withdrawal is processed. + tokio::time::sleep(tokio::time::Duration::from_millis(60000)).await; + + let params = client.get_finalize_withdrawal_params(hash, 0).await?; + + call_script( + shell, + args.forge_args, + &GATEWAY_PREPARATION_INTERFACE + .encode( + "finishMigrateChainFromGateway", + ( + U256::from(chain_config.chain_id.0), + U256::from(gateway_chain_id), + U256::from(params.l2_batch_number.0[0]), + U256::from(params.l2_message_index.0[0]), + U256::from(params.l2_tx_number_in_block.0[0]), + params.message, + params.proof.proof, + ), + ) + .unwrap(), + &ecosystem_config, + chain_config.get_wallets_config()?.governor_private_key(), + l1_url.clone(), + ) + .await?; + + gateway_chain_chain_config.settlement_layer = 0; + gateway_chain_chain_config.save_with_base_path(shell, chain_config.configs.clone())?; + + let mut general_config = chain_config.get_general_config().unwrap(); + + let eth_config = general_config.eth.as_mut().context("eth")?; + let api_config = general_config.api_config.as_mut().context("api config")?; + let state_keeper = general_config + .state_keeper_config + .as_mut() + .context("state_keeper")?; + + eth_config + .gas_adjuster + .as_mut() + .expect("gas_adjuster") + .settlement_mode = SettlementMode::SettlesToL1; + if is_rollup { + // For rollups, new type of commitment should be used, but + // not for validium. + eth_config + .sender + .as_mut() + .expect("sender") + .pubdata_sending_mode = PubdataSendingMode::Blobs; + } + eth_config + .sender + .as_mut() + .context("sender")? + .wait_confirmations = Some(0); + // FIXME: do we need to move the following to be u64? + eth_config + .sender + .as_mut() + .expect("sender") + .max_aggregated_tx_gas = 15000000; + // we need to ensure that this value is lower than in blob + state_keeper.max_pubdata_per_batch = 500000; + api_config.web3_json_rpc.settlement_layer_url = Some(l1_url); + + general_config.save_with_base_path(shell, chain_config.configs.clone())?; + Ok(()) +} + +async fn await_for_tx_to_complete( + gateway_provider: &Provider, + hash: H256, +) -> anyhow::Result<()> { + println!("Waiting for transaction to complete..."); + while Middleware::get_transaction_receipt(gateway_provider, hash) + .await? + .is_none() + { + tokio::time::sleep(tokio::time::Duration::from_millis(200)).await; + } + + // We do not handle network errors + let receipt = Middleware::get_transaction_receipt(gateway_provider, hash) + .await? + .unwrap(); + + if receipt.status == Some(U64::from(1)) { + println!("Transaction completed successfully!"); + } else { + panic!("Transaction failed!"); + } + + Ok(()) +} + +async fn await_for_withdrawal_to_finalize( + gateway_provider: &Client, + hash: H256, +) -> anyhow::Result<()> { + println!("Waiting for withdrawal to finalize..."); + while gateway_provider.get_withdrawal_log(hash, 0).await.is_err() { + println!("Waiting for withdrawal to finalize..."); + tokio::time::sleep(tokio::time::Duration::from_millis(200)).await; + } + Ok(()) +} + +async fn call_script( + shell: &Shell, + forge_args: ForgeScriptArgs, + data: &Bytes, + config: &EcosystemConfig, + private_key: Option, + rpc_url: String, +) -> anyhow::Result { + let mut forge = Forge::new(&config.path_to_l1_foundry()) + .script(&GATEWAY_PREPARATION.script(), forge_args.clone()) + .with_ffi() + .with_rpc_url(rpc_url) + .with_broadcast() + .with_calldata(data); + + // Governor private key is required for this script + forge = fill_forge_private_key(forge, private_key)?; + check_the_balance(&forge).await?; + forge.run(shell)?; + + let gateway_preparation_script_output = + GatewayPreparationOutput::read(shell, GATEWAY_PREPARATION.output(&config.link_to_code))?; + + Ok(gateway_preparation_script_output.governance_l2_tx_hash) +} diff --git a/zk_toolbox/crates/zk_inception/src/commands/chain/migrate_to_gateway.rs b/zk_toolbox/crates/zk_inception/src/commands/chain/migrate_to_gateway.rs index 8ec54c82339..7cfb041862a 100644 --- a/zk_toolbox/crates/zk_inception/src/commands/chain/migrate_to_gateway.rs +++ b/zk_toolbox/crates/zk_inception/src/commands/chain/migrate_to_gateway.rs @@ -56,7 +56,7 @@ lazy_static! { .unwrap(), ); - static ref BRDIGEHUB_INTERFACE: BaseContract = BaseContract::from( + static ref BRIDGEHUB_INTERFACE: BaseContract = BaseContract::from( parse_abi(&[ "function getHyperchain(uint256 chainId) public returns (address)" ]) @@ -90,8 +90,6 @@ pub async fn run(args: MigrateToGatewayArgs, shell: &Shell) -> anyhow::Result<() let genesis_config = chain_config.get_genesis_config()?; - // Firstly, deploying gateway contracts - let preparation_config_path = GATEWAY_PREPARATION.input(&ecosystem_config.link_to_code); let preparation_config = GatewayPreparationConfig::new( &gateway_chain_config, @@ -133,7 +131,7 @@ pub async fn run(args: MigrateToGatewayArgs, shell: &Shell) -> anyhow::Result<() ) .await?; - println!("Migrating the chain..."); + println!("Migrating the chain to the Gateway..."); let hash = call_script( shell, @@ -176,7 +174,7 @@ pub async fn run(args: MigrateToGatewayArgs, shell: &Shell) -> anyhow::Result<() // TODO: maybe move to using a precalculated address, just like for EN let chain_id = U256::from(chain_config.chain_id.0); - let contract = BRDIGEHUB_INTERFACE + let contract = BRIDGEHUB_INTERFACE .clone() .into_contract(L2_BRIDGEHUB_ADDRESS, gateway_provider); @@ -342,6 +340,7 @@ pub async fn run(args: MigrateToGatewayArgs, shell: &Shell) -> anyhow::Result<() new_diamond_proxy_address, // TODO: for now we do not use a noraml chain admin Address::zero(), + gateway_chain_id, ); gateway_chain_config.save_with_base_path(shell, chain_config.configs.clone())?; diff --git a/zk_toolbox/crates/zk_inception/src/commands/chain/mod.rs b/zk_toolbox/crates/zk_inception/src/commands/chain/mod.rs index e56fb5cf612..877580d19a8 100644 --- a/zk_toolbox/crates/zk_inception/src/commands/chain/mod.rs +++ b/zk_toolbox/crates/zk_inception/src/commands/chain/mod.rs @@ -3,6 +3,7 @@ use args::build_transactions::BuildTransactionsArgs; pub(crate) use args::create::ChainCreateArgsFinal; use clap::Subcommand; pub(crate) use create::create_chain_inner; +use migrate_from_gateway::MigrateFromGatewayArgs; use migrate_to_gateway::MigrateToGatewayArgs; use xshell::Shell; @@ -20,6 +21,7 @@ pub mod deploy_l2_contracts; pub mod deploy_paymaster; pub mod genesis; pub(crate) mod init; +mod migrate_from_gateway; mod migrate_to_gateway; mod set_token_multiplier_setter; mod setup_legacy_bridge; @@ -54,6 +56,8 @@ pub enum ChainCommands { ConvertToGateway(ForgeScriptArgs), /// Migrate chain to gateway MigrateToGateway(MigrateToGatewayArgs), + /// Migrate chain from gateway + MigrateFromGateway(MigrateFromGatewayArgs), } pub(crate) async fn run(shell: &Shell, args: ChainCommands) -> anyhow::Result<()> { @@ -80,5 +84,6 @@ pub(crate) async fn run(shell: &Shell, args: ChainCommands) -> anyhow::Result<() } ChainCommands::ConvertToGateway(args) => convert_to_gateway::run(args, shell).await, ChainCommands::MigrateToGateway(args) => migrate_to_gateway::run(args, shell).await, + ChainCommands::MigrateFromGateway(args) => migrate_from_gateway::run(args, shell).await, } } diff --git a/zk_toolbox/crates/zk_inception/src/commands/ecosystem/args/init.rs b/zk_toolbox/crates/zk_inception/src/commands/ecosystem/args/init.rs index 7898f8d254a..6eb3780755f 100644 --- a/zk_toolbox/crates/zk_inception/src/commands/ecosystem/args/init.rs +++ b/zk_toolbox/crates/zk_inception/src/commands/ecosystem/args/init.rs @@ -95,6 +95,18 @@ pub struct EcosystemInitArgs { pub observability: Option, #[clap(long, help = MSG_NO_PORT_REALLOCATION_HELP, default_value = "false", default_missing_value = "true", num_args = 0..=1)] pub no_port_reallocation: bool, + #[clap( + long, + help = "Skip submodules checkout", + default_missing_value = "true" + )] + pub skip_submodules_checkout: bool, + #[clap( + long, + help = "Skip contract compilation override", + default_missing_value = "true" + )] + pub skip_contract_compilation_override: bool, } impl EcosystemInitArgs { @@ -133,6 +145,8 @@ impl EcosystemInitArgs { dev: self.dev, observability, no_port_reallocation: self.no_port_reallocation, + skip_submodules_checkout: self.skip_submodules_checkout, + skip_contract_compilation_override: self.skip_contract_compilation_override, } } } @@ -146,4 +160,6 @@ pub struct EcosystemInitArgsFinal { pub dev: bool, pub observability: bool, pub no_port_reallocation: bool, + pub skip_submodules_checkout: bool, + pub skip_contract_compilation_override: bool, } diff --git a/zk_toolbox/crates/zk_inception/src/commands/ecosystem/init.rs b/zk_toolbox/crates/zk_inception/src/commands/ecosystem/init.rs index 8669d356845..fc4dc3ccf57 100644 --- a/zk_toolbox/crates/zk_inception/src/commands/ecosystem/init.rs +++ b/zk_toolbox/crates/zk_inception/src/commands/ecosystem/init.rs @@ -52,7 +52,10 @@ use crate::{ pub async fn run(args: EcosystemInitArgs, shell: &Shell) -> anyhow::Result<()> { let ecosystem_config = EcosystemConfig::from_file(shell)?; - git::submodule_update(shell, ecosystem_config.link_to_code.clone())?; + if !args.skip_submodules_checkout { + println!("Checking out submodules"); + git::submodule_update(shell, ecosystem_config.link_to_code.clone())?; + } let initial_deployment_config = match ecosystem_config.get_initial_deployment_config() { Ok(config) => config, @@ -115,6 +118,7 @@ pub async fn run(args: EcosystemInitArgs, shell: &Shell) -> anyhow::Result<()> { deploy_paymaster: final_ecosystem_args.deploy_paymaster, l1_rpc_url: final_ecosystem_args.ecosystem.l1_rpc_url.clone(), no_port_reallocation: final_ecosystem_args.no_port_reallocation, + skip_submodules_checkout: final_ecosystem_args.skip_submodules_checkout, }; chain::init::init( @@ -139,10 +143,12 @@ async fn init( ) -> anyhow::Result { let spinner = Spinner::new(MSG_INTALLING_DEPS_SPINNER); install_yarn_dependencies(shell, &ecosystem_config.link_to_code)?; - build_da_contracts(shell, &ecosystem_config.link_to_code)?; - build_l1_contracts(shell, &ecosystem_config.link_to_code)?; - build_system_contracts(shell, &ecosystem_config.link_to_code)?; - build_l2_contracts(shell, &ecosystem_config.link_to_code)?; + if !init_args.skip_contract_compilation_override { + build_da_contracts(shell, &ecosystem_config.link_to_code)?; + build_l1_contracts(shell, &ecosystem_config.link_to_code)?; + build_system_contracts(shell, &ecosystem_config.link_to_code)?; + build_l2_contracts(shell, &ecosystem_config.link_to_code)?; + } spinner.finish(); let contracts = deploy_ecosystem( diff --git a/zk_toolbox/crates/zk_inception/src/commands/server.rs b/zk_toolbox/crates/zk_inception/src/commands/server.rs index 3b737726989..60fad1c6513 100644 --- a/zk_toolbox/crates/zk_inception/src/commands/server.rs +++ b/zk_toolbox/crates/zk_inception/src/commands/server.rs @@ -51,10 +51,17 @@ fn run_server( ServerMode::Normal }; - let gateway_contracts = chain_config - .get_gateway_chain_config() - .ok() - .map(|_| GatewayChainConfig::get_path_with_base_path(&chain_config.configs)); + let gateway_config = chain_config.get_gateway_chain_config().ok(); + let mut gateway_contracts = None; + if let Some(gateway_config) = gateway_config { + gateway_contracts = if gateway_config.settlement_layer != 0_u64 { + Some(GatewayChainConfig::get_path_with_base_path( + &chain_config.configs, + )) + } else { + None + }; + } server .run(