From 13282269741f869cddd0f8ba5cb451ac56f879cc Mon Sep 17 00:00:00 2001 From: kelemeno <34402761+kelemeno@users.noreply.github.com> Date: Thu, 7 Mar 2024 10:26:45 +0000 Subject: [PATCH] feat(): v0 shared bridge (#29) Co-authored-by: Bence Haromi Co-authored-by: vladbochok Co-authored-by: Vlad Bochok <41153528+vladbochok@users.noreply.github.com> Co-authored-by: ilitteri Co-authored-by: Santiago Pittella Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: Igor Aleksanov Co-authored-by: Dima Zhornyk <55756184+dimazhornyk@users.noreply.github.com> Co-authored-by: Raid Ateir Co-authored-by: Raid Ateir Co-authored-by: dimazhornyk Co-authored-by: Raid5594 <52794079+Raid5594@users.noreply.github.com> Co-authored-by: Rahul Saxena Co-authored-by: Stanislav Breadless --- .github/workflows/l1-contracts-ci.yaml | 11 +- .github/workflows/l2-contracts-ci.yaml | 18 +- .gitignore | 4 + docs/Overview.md | 69 +- l1-contracts/.env | 23 +- .../contracts/bridge/L1ERC20Bridge.sol | 274 ++--- .../contracts/bridge/L1SharedBridge.sol | 667 +++++++++++ .../contracts/bridge/L1WethBridge.sol | 313 ----- .../bridge/interfaces/IL1BridgeLegacy.sol | 16 - .../{IL1Bridge.sol => IL1ERC20Bridge.sol} | 27 +- .../bridge/interfaces/IL1SharedBridge.sol | 145 +++ .../bridge/interfaces/IL2ERC20Bridge.sol | 8 - .../bridge/interfaces/IL2WethBridge.sol | 7 - .../libraries/BridgeInitializationHelper.sol | 59 - .../contracts/bridgehub/Bridgehub.sol | 336 ++++++ .../contracts/bridgehub/IBridgehub.sol | 134 +++ .../contracts/{zksync => common}/Config.sol | 16 + .../contracts/common/Dependencies.sol | 1 + .../contracts/common/L2ContractAddresses.sol | 2 +- l1-contracts/contracts/common/Messaging.sol | 137 +++ .../common/interfaces/IL2ContractDeployer.sol | 2 +- .../dev-contracts/ConstructorForwarder.sol | 3 + .../dev-contracts/EventOnFallback.sol | 3 + .../dev-contracts/FeeOnTransferToken.sol | 21 + .../contracts/dev-contracts/Forwarder.sol | 3 + .../contracts/dev-contracts/Multicall.sol | 5 +- .../contracts/dev-contracts/Multicall3.sol | 3 + .../dev-contracts/ReturnSomething.sol | 3 + .../dev-contracts/RevertFallback.sol | 3 + .../dev-contracts/RevertReceiveAccount.sol | 3 + .../dev-contracts/RevertTransferERC20.sol | 3 + .../dev-contracts/SingletonFactory.sol | 3 + .../dev-contracts/TestnetERC20Token.sol | 3 + .../contracts/dev-contracts/WETH9.sol | 9 +- .../interfaces/ITestnetERC20Token.sol | 9 + .../test/AddressAliasHelperTest.sol | 15 + .../dev-contracts/test/AdminFacetTest.sol | 20 +- .../dev-contracts/test/CustomUpgradeTest.sol | 5 +- .../test/DiamondCutTestContract.sol | 4 +- .../dev-contracts/test/DiamondProxyTest.sol | 9 +- .../dev-contracts/test/DummyAdminFacet.sol | 23 + .../dev-contracts/test/DummyAdminFacet2.sol | 23 + .../DummyERC20BytesTransferReturnValue.sol | 3 + .../test/DummyERC20NoTransferReturnValue.sol | 3 + .../test/DummyEraBaseTokenBridge.sol | 15 + .../dev-contracts/test/DummyExecutor.sol | 57 +- .../dev-contracts/test/DummySharedBridge.sol | 130 ++ .../test/DummyStateTransition.sol | 42 + .../test/DummyStateTransitionManager.sol | 19 + ...eTransitionManagerForValidatorTimelock.sol | 26 + ...eTransitionManagerWithBridgeHubAddress.sol | 16 + .../test/ExecutorProvingTest.sol | 13 +- .../dev-contracts/test/L1ERC20BridgeTest.sol | 14 +- .../dev-contracts/test/L1SharedBridgeTest.sol | 57 + .../dev-contracts/test/MailboxFacetTest.sol | 10 +- .../dev-contracts/test/MerkleTest.sol | 2 +- .../dev-contracts/test/MockExecutor.sol | 7 +- .../dev-contracts/test/PriorityQueueTest.sol | 18 +- .../dev-contracts/test/ReenterGovernance.sol | 3 + .../test/ReenterL1ERC20Bridge.sol | 50 + .../dev-contracts/test/UncheckedMathTest.sol | 15 + .../dev-contracts/test/UnsafeBytesTest.sol | 3 + .../test/VerifierRecursiveTest.sol | 5 +- .../dev-contracts/test/VerifierTest.sol | 5 +- .../contracts/governance/Governance.sol | 2 +- .../IStateTransitionManager.sol | 95 ++ .../StateTransitionManager.sol | 289 +++++ .../state-transition/ValidatorTimelock.sol | 248 ++++ .../{zksync => state-transition}/Verifier.sol | 4 +- .../chain-deps/DiamondInit.sol | 55 + .../chain-deps}/DiamondProxy.sol | 2 +- .../ZkSyncStateTransitionStorage.sol} | 68 +- .../chain-deps}/facets/Admin.sol | 95 +- .../chain-deps}/facets/Executor.sol | 100 +- .../chain-deps}/facets/Getters.sol | 64 +- .../chain-deps}/facets/Mailbox.sol | 256 ++-- .../facets/ZkSyncStateTransitionBase.sol | 56 + .../chain-interfaces}/IAdmin.sol | 47 +- .../chain-interfaces/IDiamondInit.sol | 63 + .../chain-interfaces}/IExecutor.sol | 25 +- .../chain-interfaces}/IGetters.sol | 40 +- .../chain-interfaces}/ILegacyGetters.sol | 4 +- .../chain-interfaces}/IMailbox.sol | 105 +- .../chain-interfaces}/IVerifier.sol | 7 + .../IZkSyncStateTransition.sol | 22 + .../IZkSyncStateTransitionBase.sol} | 2 +- .../l2-deps/ISystemContext.sol | 6 + .../libraries/Diamond.sol | 0 .../libraries/LibMap.sol | 0 .../libraries/Merkle.sol | 0 .../libraries/PriorityQueue.sol | 0 .../libraries/TransactionValidator.sol | 10 +- .../utils/BlobVersionedHashRetriever.yul | 0 .../contracts/upgrades/BaseZkSyncUpgrade.sol | 84 +- .../upgrades/BaseZkSyncUpgradeGenesis.sol | 69 ++ .../contracts/upgrades/DefaultUpgrade.sol | 4 +- .../contracts/upgrades/GenesisUpgrade.sol | 18 + .../contracts/upgrades/IDefaultUpgrade.sol | 9 + .../contracts/upgrades/Upgrade_4844.sol | 4 +- .../contracts/upgrades/Upgrade_v1_4_1.sol | 6 +- l1-contracts/contracts/zksync/DiamondInit.sol | 96 -- .../contracts/zksync/ValidatorTimelock.sol | 178 --- l1-contracts/contracts/zksync/facets/Base.sol | 31 - .../contracts/zksync/interfaces/IZkSync.sol | 14 - l1-contracts/hardhat.config.ts | 28 +- l1-contracts/package.json | 20 +- l1-contracts/remappings.txt | 1 + l1-contracts/scripts/deploy-erc20.ts | 114 +- l1-contracts/scripts/deploy-testkit.ts | 8 +- l1-contracts/scripts/deploy-testnet-token.ts | 96 -- l1-contracts/scripts/deploy.ts | 53 +- l1-contracts/scripts/display-governance.ts | 39 +- l1-contracts/scripts/initialize-bridges.ts | 189 --- ...-bridges.ts => initialize-erc20-bridge.ts} | 30 +- l1-contracts/scripts/initialize-governance.ts | 60 +- .../scripts/initialize-l2-weth-token.ts | 87 +- l1-contracts/scripts/initialize-validator.ts | 18 +- .../scripts/initialize-weth-bridges.ts | 106 -- l1-contracts/scripts/migrate-governance.ts | 57 +- l1-contracts/scripts/register-hyperchain.ts | 117 ++ l1-contracts/scripts/utils.ts | 157 +-- l1-contracts/scripts/verify.ts | 17 +- l1-contracts/src.ts/deploy-process.ts | 107 ++ l1-contracts/src.ts/deploy-test-process.ts | 196 +++ l1-contracts/src.ts/deploy-token.ts | 133 +++ l1-contracts/src.ts/deploy-utils.ts | 72 +- l1-contracts/src.ts/deploy.ts | 596 ++++++++-- l1-contracts/src.ts/diamondCut.ts | 35 +- l1-contracts/src.ts/hyperchain-upgrade.ts | 225 ++++ l1-contracts/src.ts/utils.ts | 175 +++ .../_AddressAliasHelper_Shared.t.sol | 16 + .../AddressAliasHelper/applyL1ToL2Alias.t.sol | 23 + .../AddressAliasHelper/undoL1ToL2Alias.t.sol | 23 + .../unit/concrete/Admin/Authorization.t.sol | 47 - .../unit/concrete/Admin/_Admin_Shared.t.sol | 116 -- .../L1WethBridge/ClaimFailedDeposit.t.sol | 12 - .../Bridge/L1WethBridge/Deposit.t.sol | 78 -- .../L1WethBridge/FinalizeWithdrawal.t.sol | 184 --- .../Bridge/L1WethBridge/L2TokenAddress.t.sol | 27 - .../Bridge/L1WethBridge/Receive.t.sol | 52 - .../L1WethBridge/_L1WethBridge_Shared.t.sol | 122 -- .../Bridgehub/BridgehubMailbox/Deposit.t.sol | 19 + .../FinalizeEthWithdrawal.t.sol | 98 ++ .../IsEthWithdrawalFinalized.t.sol | 42 + .../L2TransactionBaseCost.t.sol | 66 ++ .../ProveL1ToL2TransactionStatus.t.sol | 100 ++ .../ProveL2LogInclusion.t.sol | 53 + .../ProveL2MessageInclusion.t.sol | 50 + .../RequestL2Transaction.t.sol | 123 ++ .../BridgehubMailbox/WithdrawFunds.t.sol | 36 + .../_BridgehubMailbox_Shared.t.sol | 27 + .../unit/concrete/Bridgehub/Initialize.t.sol | 43 + .../Bridgehub/Registry/NewChain.t.sol | 251 ++++ .../Bridgehub/Registry/NewProofSystem.t.sol | 37 + .../Bridgehub/Registry/_Registry_Shared.t.sol | 9 + .../Bridgehub/_Bridgehub_Shared.t.sol | 40 + .../Bridgehub/experimental_bridge.t.sol | 1047 +++++++++++++++++ .../L1Erc20Bridge/ClaimFailedDeposit.t.sol | 44 + .../Bridges/L1Erc20Bridge/Deposit.t.sol | 94 ++ .../L1Erc20Bridge/FinalizeWithdrawal.sol | 49 + .../Bridges/L1Erc20Bridge/Getters.t.sol | 30 + .../L1Erc20Bridge/Initialization.t.sol | 12 + .../Bridges/L1Erc20Bridge/Reentrancy.t.sol | 137 +++ .../L1Erc20Bridge/_L1Erc20Bridge_Shared.t.sol | 49 + .../L1SharedBridge/L1SharedBridge.t.sol | 639 ++++++++++ .../L1SharedBridge/L1SharedBridgeFails.t.sol | 832 +++++++++++++ .../L1SharedBridgeHyperEnabled.t.sol | 608 ++++++++++ .../L1SharedBridge/L1SharedBridgeLegacy.t.sol | 321 +++++ .../L1SharedBridgeLegacyHyperEnabled.t.sol | 0 .../unit/concrete/DiamondCut/FacetCut.t.sol | 15 +- .../concrete/DiamondCut/Initialization.t.sol | 11 +- .../concrete/DiamondCut/UpgradeLogic.t.sol | 87 +- .../DiamondCut/_DiamondCut_Shared.t.sol | 7 +- .../concrete/Executor/Authorization.t.sol | 12 +- .../unit/concrete/Executor/Committing.t.sol | 9 +- .../unit/concrete/Executor/Executing.t.sol | 13 +- .../concrete/Executor/ExecutorProof.t.sol | 136 +++ .../unit/concrete/Executor/Proving.t.sol | 8 +- .../unit/concrete/Executor/Reverting.t.sol | 8 +- .../concrete/Executor/_Executor_Shared.t.sol | 104 +- .../concrete/Governance/Authorization.t.sol | 2 +- .../unit/concrete/Governance/Executing.t.sol | 6 +- .../concrete/Governance/OperationStatus.t.sol | 6 +- .../unit/concrete/Governance/Reentrancy.t.sol | 6 +- .../concrete/Governance/SelfUpgrades.t.sol | 6 +- .../Governance/_Governance_Shared.t.sol | 14 +- .../UncheckedMath/_UncheckedMath_Shared.t.sol | 6 - .../foundry/unit/concrete/Utils/Utils.sol | 246 +++- .../foundry/unit/concrete/Utils/Utils.t.sol | 7 +- .../unit/concrete/Utils/UtilsFacet.sol | 159 +++ .../unit/concrete/ValidatorTimelock.t.sol | 123 -- .../ValidatorTimelock/ValidatorTimelock.t.sol | 434 +++++++ .../unit/concrete/Verifier/Verifier.t.sol | 3 +- .../UncheckedMath/UncheckedAdd.t.sol | 11 +- .../UncheckedMath/UncheckedInc.t.sol | 11 +- .../UncheckedMath/_UncheckedMath_Shared.t.sol | 16 + .../libraries}/UnsafeBytes/UnsafeBytes.t.sol | 3 +- .../CreateNewChain.t.sol | 34 + .../StateTransitionManager/FreezeChain.t.sol | 31 + .../RevertBatches.t.sol | 142 +++ .../SetInitialCutHash.t.sol | 21 + .../SetNewVersionUpgrade.t.sol | 20 + .../SetUpgradeDiamondCut.t.sol | 20 + .../SetValidatorTimelock.t.sol | 23 + .../StateTransitionGovernorZero.t.sol | 30 + .../_StateTransitionManager_Shared.t.sol | 138 +++ .../chain-deps/DiamondInit/Initialize.t.sol | 108 ++ .../DiamondInit/_DiamondInit_Shared.t.sol | 27 + .../DiamondProxy/DiamondProxy.t.sol | 175 +++ .../chain-deps/facets/Admin/AcceptAdmin.t.sol | 35 + .../facets/Admin/ChangeFeeParams.t.sol | 86 ++ .../facets/Admin/ExecuteUpgrade.t.sol | 45 + .../facets/Admin/FreezeDiamond.t.sol | 44 + .../facets/Admin/SetPendingGovernor.t.sol | 34 + .../facets/Admin/SetPorterAvailability.t.sol | 50 + .../Admin/SetPriorityTxMaxGasLimit.t.sol | 46 + .../facets/Admin/SetValidator.t.sol | 53 + .../facets/Admin/UnfreezeDiamond.t.sol | 44 + .../Admin/UpgradeChainFromVersion.t.sol | 114 ++ .../facets/Admin/_Admin_Shared.t.sol | 58 + .../facets/Base/OnlyBridgehub.t.sol | 23 + .../chain-deps/facets/Base/OnlyGovernor.t.sol | 23 + ...OnlyGovernorOrStateTransitionManager.t.sol | 39 + .../Base/OnlyStateTransitionManager.t.sol | 23 + .../facets/Base/OnlyValidator.t.sol | 26 + .../chain-deps/facets/Base/_Base_Shared.t.sol | 75 ++ .../facets/Getters/FacetAddress.t.sol | 19 + .../facets/Getters/FacetAddresses.t.sol | 22 + .../Getters/FacetFunctionSelectors.t.sol | 23 + .../chain-deps/facets/Getters/Facets.t.sol | 22 + .../chain-deps/facets/Getters/GetAdmin.t.sol | 16 + .../facets/Getters/GetBaseToken.t.sol | 16 + .../facets/Getters/GetBaseTokenBridge.t.sol | 16 + .../facets/Getters/GetBridgehub.t.sol | 16 + .../GetFirstUnprocessedPriorityTx.t.sol | 16 + .../Getters/GetL2BootloaderBytecodeHash.t.sol | 16 + .../GetL2DefaultAccountBytecodeHash.t.sol | 16 + ...tL2SystemContractsUpgradeBatchNumber.t.sol | 16 + ...tL2SystemContractsUpgradeBlockNumber.t.sol | 16 + .../GetL2SystemContractsUpgradeTxHash.t.sol | 16 + .../facets/Getters/GetPendingAdmin.t.sol | 16 + .../facets/Getters/GetPriorityQueueSize.t.sol | 16 + .../Getters/GetPriorityTxMaxGasLimit.t.sol | 16 + .../facets/Getters/GetProtocolVersion.t.sol | 16 + .../Getters/GetStateTransitionManager.t.sol | 16 + .../Getters/GetTotalBatchesCommitted.t.sol | 16 + .../Getters/GetTotalBatchesExecuted.t.sol | 16 + .../Getters/GetTotalBatchesVerified.t.sol | 16 + .../Getters/GetTotalBlocksCommitted.t.sol | 16 + .../Getters/GetTotalBlocksExecuted.t.sol | 16 + .../Getters/GetTotalBlocksVerified.t.sol | 16 + .../facets/Getters/GetTotalPriorityTxs.t.sol | 16 + .../facets/Getters/GetVerifier.t.sol | 16 + .../facets/Getters/GetVerifierParams.t.sol | 23 + .../Getters/IsDiamondStorageFrozen.t.sol | 15 + .../Getters/IsEthWithdrawalFinalized.t.sol | 17 + .../facets/Getters/IsFacetFreezable.t.sol | 23 + .../facets/Getters/IsFunctionFreezable.t.sol | 28 + .../facets/Getters/IsValidator.t.sol | 25 + .../facets/Getters/L2LogsRootHash.t.sol | 17 + .../Getters/PriorityQueueFrontOperation.t.sol | 29 + .../facets/Getters/StoredBatchHash.t.sol | 17 + .../facets/Getters/StoredBlockHash.t.sol | 17 + .../facets/Getters/_Getters_Shared.t.sol | 188 +++ .../libraries}/Merkle/Merkle.t.sol | 0 .../libraries}/Merkle/MerkleTreeNoSort.sol | 4 + .../libraries}/PriorityQueue/OnEmptyQueue.sol | 2 + .../PriorityQueue/PopOperations.sol | 4 +- .../PriorityQueue/PushOperations.sol | 5 +- .../PriorityQueue/_PriorityQueue_Shared.t.sol | 4 + .../TransactionValidator/ValidateL1L2Tx.t.sol | 21 +- .../ValidateUpgradeTransaction.t.sol | 32 +- .../_TransactionValidator_Shared.t.sol | 18 +- .../test/test_config/constant/addresses.json | 29 + .../test/test_config/constant/eth.json | 14 + .../test/test_config/constant/hardhat.json | 98 ++ l1-contracts/test/unit_tests/README.md | 4 + .../test/unit_tests/custom_base_token.spec.ts | 159 +++ .../unit_tests/erc20-bridge-upgrade.fork.ts | 3 +- .../test/unit_tests/executor_proof.spec.ts | 142 ++- .../test/unit_tests/governance_test.spec.ts | 60 +- .../hyperchain_migration_test.spec.ts | 94 ++ .../initial_deployment_test.spec.ts | 68 ++ .../unit_tests/l1_erc20_bridge_test.spec.ts | 214 ---- .../unit_tests/l1_shared_bridge_test.spec.ts | 241 ++++ .../unit_tests/l1_weth_bridge_test.spec.ts | 212 ---- .../test/unit_tests/l2-upgrade.test.spec.ts | 529 ++++----- .../test/unit_tests/legacy_era_test.spec.ts | 321 +++++ .../test/unit_tests/mailbox_test.spec.ts | 217 ++-- .../unit_tests/new_hyperchain_test.spec.ts | 0 .../test/unit_tests/proxy_test.spec.ts | 58 +- l1-contracts/test/unit_tests/utils.ts | 291 ++++- .../validator_timelock_test.spec.ts | 96 +- .../test/unit_tests/zksync-upgrade.fork.ts | 14 +- l1-contracts/upgrade-system/facets.ts | 8 +- l1-contracts/upgrade-system/index.ts | 3 + l1-contracts/upgrade-system/verifier.ts | 3 + l2-contracts/.env | 4 +- l2-contracts/contracts/Config.sol | 8 + l2-contracts/contracts/Dependencies.sol | 1 + l2-contracts/contracts/L2ContractHelper.sol | 4 +- .../{L2ERC20Bridge.sol => L2SharedBridge.sol} | 40 +- .../contracts/bridge/L2WethBridge.sol | 115 -- .../{L2Weth.sol => L2WrappedBaseToken.sol} | 36 +- .../bridge/interfaces/IL1ERC20Bridge.sol | 16 + .../{IL1Bridge.sol => IL1SharedBridge.sol} | 3 +- .../{IL2Bridge.sol => IL2SharedBridge.sol} | 5 +- .../{IL2Weth.sol => IL2WrappedBaseToken.sol} | 2 +- l2-contracts/hardhat.config.ts | 41 + l2-contracts/package.json | 14 +- ...eploy-force-deploy-upgrader-through-l1.ts} | 24 +- .../src/deploy-force-deploy-upgrader.ts | 59 + .../deploy-shared-bridge-on-l2-through-l1.ts | 226 ++++ .../src/deploy-shared-bridge-on-l2.ts | 54 + .../deploy-testnet-paymaster-through-l1.ts | 57 + l2-contracts/src/deployL2Weth.ts | 138 --- l2-contracts/src/deployTestnetPaymaster.ts | 54 - l2-contracts/src/publish-bridge-preimages.ts | 36 +- ...etadata.ts => update-l2-erc20-metadata.ts} | 9 +- ...deBridgeImpl.ts => upgrade-bridge-impl.ts} | 50 +- l2-contracts/src/utils.ts | 80 +- l2-contracts/test/erc20.test.ts | 14 +- l2-contracts/test/test-utils.ts | 11 + l2-contracts/test/weth.test.ts | 69 +- system-contracts/SystemContractsHashes.json | 64 +- system-contracts/bootloader/bootloader.yul | 2 +- .../bootloader/test_infra/Cargo.lock | 846 ++----------- .../bootloader/test_infra/Cargo.toml | 12 +- .../bootloader/test_infra/src/hook.rs | 2 +- .../test_infra/src/test_count_tracer.rs | 2 +- .../bootloader/test_infra/src/tracer.rs | 2 +- system-contracts/contracts/Compressor.sol | 9 +- system-contracts/contracts/Constants.sol | 4 +- .../contracts/ContractDeployer.sol | 4 +- .../{L2EthToken.sol => L2BaseToken.sol} | 4 +- .../contracts/MsgValueSimulator.sol | 6 +- system-contracts/contracts/SystemContext.sol | 6 + .../{IEthToken.sol => IBaseToken.sol} | 2 +- .../contracts/interfaces/ISystemContract.sol | 9 +- .../contracts/libraries/TransactionHelper.sol | 6 +- system-contracts/package.json | 6 +- system-contracts/scripts/constants.ts | 4 +- system-contracts/scripts/deploy-preimages.ts | 3 +- .../scripts/preprocess-bootloader.ts | 6 +- .../test/AccountCodeStorage.spec.ts | 2 +- .../test/BootloaderUtilities.spec.ts | 4 +- system-contracts/test/Compressor.spec.ts | 109 +- .../test/ContractDeployer.spec.ts | 4 +- system-contracts/test/DefaultAccount.spec.ts | 4 +- system-contracts/test/EmptyContract.spec.ts | 2 +- system-contracts/test/EventWriter.spec.ts | 4 +- .../test/KnownCodesStorage.spec.ts | 2 +- ...L2EthToken.spec.ts => L2BaseToken.spec.ts} | 104 +- .../test/precompiles/EcAdd.spec.ts | 2 +- .../test/precompiles/EcMul.spec.ts | 2 +- system-contracts/test/shared/constants.ts | 2 +- system-contracts/test/shared/mocks.ts | 4 +- system-contracts/test/shared/transactions.ts | 2 +- system-contracts/test/shared/utils.ts | 6 +- tools/README.md | 2 +- tools/data/verifier_contract_template.txt | 4 +- yarn.lock | 164 ++- 362 files changed, 17125 insertions(+), 5769 deletions(-) create mode 100644 l1-contracts/contracts/bridge/L1SharedBridge.sol delete mode 100644 l1-contracts/contracts/bridge/L1WethBridge.sol delete mode 100644 l1-contracts/contracts/bridge/interfaces/IL1BridgeLegacy.sol rename l1-contracts/contracts/bridge/interfaces/{IL1Bridge.sol => IL1ERC20Bridge.sol} (65%) create mode 100644 l1-contracts/contracts/bridge/interfaces/IL1SharedBridge.sol delete mode 100644 l1-contracts/contracts/bridge/interfaces/IL2ERC20Bridge.sol delete mode 100644 l1-contracts/contracts/bridge/interfaces/IL2WethBridge.sol delete mode 100644 l1-contracts/contracts/bridge/libraries/BridgeInitializationHelper.sol create mode 100644 l1-contracts/contracts/bridgehub/Bridgehub.sol create mode 100644 l1-contracts/contracts/bridgehub/IBridgehub.sol rename l1-contracts/contracts/{zksync => common}/Config.sol (89%) create mode 100644 l1-contracts/contracts/common/Messaging.sol create mode 100644 l1-contracts/contracts/dev-contracts/FeeOnTransferToken.sol create mode 100644 l1-contracts/contracts/dev-contracts/interfaces/ITestnetERC20Token.sol create mode 100644 l1-contracts/contracts/dev-contracts/test/AddressAliasHelperTest.sol create mode 100644 l1-contracts/contracts/dev-contracts/test/DummyAdminFacet.sol create mode 100644 l1-contracts/contracts/dev-contracts/test/DummyAdminFacet2.sol create mode 100644 l1-contracts/contracts/dev-contracts/test/DummyEraBaseTokenBridge.sol create mode 100644 l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol create mode 100644 l1-contracts/contracts/dev-contracts/test/DummyStateTransition.sol create mode 100644 l1-contracts/contracts/dev-contracts/test/DummyStateTransitionManager.sol create mode 100644 l1-contracts/contracts/dev-contracts/test/DummyStateTransitionManagerForValidatorTimelock.sol create mode 100644 l1-contracts/contracts/dev-contracts/test/DummyStateTransitionManagerWithBridgeHubAddress.sol create mode 100644 l1-contracts/contracts/dev-contracts/test/L1SharedBridgeTest.sol create mode 100644 l1-contracts/contracts/dev-contracts/test/ReenterL1ERC20Bridge.sol create mode 100644 l1-contracts/contracts/dev-contracts/test/UncheckedMathTest.sol create mode 100644 l1-contracts/contracts/state-transition/IStateTransitionManager.sol create mode 100644 l1-contracts/contracts/state-transition/StateTransitionManager.sol create mode 100644 l1-contracts/contracts/state-transition/ValidatorTimelock.sol rename l1-contracts/contracts/{zksync => state-transition}/Verifier.sol (99%) create mode 100644 l1-contracts/contracts/state-transition/chain-deps/DiamondInit.sol rename l1-contracts/contracts/{zksync => state-transition/chain-deps}/DiamondProxy.sol (98%) rename l1-contracts/contracts/{zksync/Storage.sol => state-transition/chain-deps/ZkSyncStateTransitionStorage.sol} (79%) rename l1-contracts/contracts/{zksync => state-transition/chain-deps}/facets/Admin.sol (57%) rename l1-contracts/contracts/{zksync => state-transition/chain-deps}/facets/Executor.sol (87%) rename l1-contracts/contracts/{zksync => state-transition/chain-deps}/facets/Getters.sol (77%) rename l1-contracts/contracts/{zksync => state-transition/chain-deps}/facets/Mailbox.sol (61%) create mode 100644 l1-contracts/contracts/state-transition/chain-deps/facets/ZkSyncStateTransitionBase.sol rename l1-contracts/contracts/{zksync/interfaces => state-transition/chain-interfaces}/IAdmin.sol (73%) create mode 100644 l1-contracts/contracts/state-transition/chain-interfaces/IDiamondInit.sol rename l1-contracts/contracts/{zksync/interfaces => state-transition/chain-interfaces}/IExecutor.sol (89%) rename l1-contracts/contracts/{zksync/interfaces => state-transition/chain-interfaces}/IGetters.sol (78%) rename l1-contracts/contracts/{zksync/interfaces => state-transition/chain-interfaces}/ILegacyGetters.sol (93%) rename l1-contracts/contracts/{zksync/interfaces => state-transition/chain-interfaces}/IMailbox.sol (57%) rename l1-contracts/contracts/{zksync/interfaces => state-transition/chain-interfaces}/IVerifier.sol (81%) create mode 100644 l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncStateTransition.sol rename l1-contracts/contracts/{zksync/interfaces/IBase.sol => state-transition/chain-interfaces/IZkSyncStateTransitionBase.sol} (89%) create mode 100644 l1-contracts/contracts/state-transition/l2-deps/ISystemContext.sol rename l1-contracts/contracts/{zksync => state-transition}/libraries/Diamond.sol (100%) rename l1-contracts/contracts/{zksync => state-transition}/libraries/LibMap.sol (100%) rename l1-contracts/contracts/{zksync => state-transition}/libraries/Merkle.sol (100%) rename l1-contracts/contracts/{zksync => state-transition}/libraries/PriorityQueue.sol (100%) rename l1-contracts/contracts/{zksync => state-transition}/libraries/TransactionValidator.sol (95%) rename l1-contracts/contracts/{zksync => state-transition}/utils/BlobVersionedHashRetriever.yul (100%) create mode 100644 l1-contracts/contracts/upgrades/BaseZkSyncUpgradeGenesis.sol create mode 100644 l1-contracts/contracts/upgrades/GenesisUpgrade.sol create mode 100644 l1-contracts/contracts/upgrades/IDefaultUpgrade.sol delete mode 100644 l1-contracts/contracts/zksync/DiamondInit.sol delete mode 100644 l1-contracts/contracts/zksync/ValidatorTimelock.sol delete mode 100644 l1-contracts/contracts/zksync/facets/Base.sol delete mode 100644 l1-contracts/contracts/zksync/interfaces/IZkSync.sol delete mode 100644 l1-contracts/scripts/deploy-testnet-token.ts delete mode 100644 l1-contracts/scripts/initialize-bridges.ts rename l1-contracts/scripts/{deploy-weth-bridges.ts => initialize-erc20-bridge.ts} (60%) delete mode 100644 l1-contracts/scripts/initialize-weth-bridges.ts create mode 100644 l1-contracts/scripts/register-hyperchain.ts create mode 100644 l1-contracts/src.ts/deploy-process.ts create mode 100644 l1-contracts/src.ts/deploy-test-process.ts create mode 100644 l1-contracts/src.ts/deploy-token.ts create mode 100644 l1-contracts/src.ts/hyperchain-upgrade.ts create mode 100644 l1-contracts/src.ts/utils.ts create mode 100644 l1-contracts/test/foundry/unit/concrete/AddressAliasHelper/_AddressAliasHelper_Shared.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/AddressAliasHelper/applyL1ToL2Alias.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/AddressAliasHelper/undoL1ToL2Alias.t.sol delete mode 100644 l1-contracts/test/foundry/unit/concrete/Admin/Authorization.t.sol delete mode 100644 l1-contracts/test/foundry/unit/concrete/Admin/_Admin_Shared.t.sol delete mode 100644 l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/ClaimFailedDeposit.t.sol delete mode 100644 l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/Deposit.t.sol delete mode 100644 l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/FinalizeWithdrawal.t.sol delete mode 100644 l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/L2TokenAddress.t.sol delete mode 100644 l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/Receive.t.sol delete mode 100644 l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/_L1WethBridge_Shared.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/Deposit.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/FinalizeEthWithdrawal.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/IsEthWithdrawalFinalized.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/L2TransactionBaseCost.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/ProveL1ToL2TransactionStatus.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/ProveL2LogInclusion.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/ProveL2MessageInclusion.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/RequestL2Transaction.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/WithdrawFunds.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/_BridgehubMailbox_Shared.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridgehub/Initialize.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridgehub/Registry/NewChain.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridgehub/Registry/NewProofSystem.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridgehub/Registry/_Registry_Shared.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridgehub/_Bridgehub_Shared.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/ClaimFailedDeposit.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Deposit.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/FinalizeWithdrawal.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Getters.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Initialization.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Reentrancy.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/_L1Erc20Bridge_Shared.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridge.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacyHyperEnabled.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Executor/ExecutorProof.t.sol delete mode 100644 l1-contracts/test/foundry/unit/concrete/UncheckedMath/_UncheckedMath_Shared.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/Utils/UtilsFacet.sol delete mode 100644 l1-contracts/test/foundry/unit/concrete/ValidatorTimelock.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/ValidatorTimelock/ValidatorTimelock.t.sol rename l1-contracts/test/foundry/unit/concrete/{ => common/libraries}/UncheckedMath/UncheckedAdd.t.sol (59%) rename l1-contracts/test/foundry/unit/concrete/{ => common/libraries}/UncheckedMath/UncheckedInc.t.sol (56%) create mode 100644 l1-contracts/test/foundry/unit/concrete/common/libraries/UncheckedMath/_UncheckedMath_Shared.t.sol rename l1-contracts/test/foundry/unit/concrete/{ => common/libraries}/UnsafeBytes/UnsafeBytes.t.sol (95%) create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/CreateNewChain.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/FreezeChain.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/RevertBatches.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetInitialCutHash.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetNewVersionUpgrade.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetUpgradeDiamondCut.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetValidatorTimelock.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/StateTransitionGovernorZero.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/_StateTransitionManager_Shared.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondInit/Initialize.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondInit/_DiamondInit_Shared.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondProxy/DiamondProxy.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/AcceptAdmin.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/ChangeFeeParams.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/ExecuteUpgrade.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/FreezeDiamond.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetPendingGovernor.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetPorterAvailability.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetPriorityTxMaxGasLimit.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetValidator.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/UnfreezeDiamond.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/UpgradeChainFromVersion.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/_Admin_Shared.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyBridgehub.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyGovernor.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyGovernorOrStateTransitionManager.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyStateTransitionManager.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyValidator.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/_Base_Shared.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/FacetAddress.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/FacetAddresses.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/FacetFunctionSelectors.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/Facets.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetAdmin.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetBaseToken.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetBaseTokenBridge.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetBridgehub.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetFirstUnprocessedPriorityTx.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2BootloaderBytecodeHash.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2DefaultAccountBytecodeHash.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeBatchNumber.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeBlockNumber.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeTxHash.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetPendingAdmin.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetPriorityQueueSize.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetPriorityTxMaxGasLimit.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetProtocolVersion.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetStateTransitionManager.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesCommitted.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesExecuted.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesVerified.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksCommitted.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksExecuted.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksVerified.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalPriorityTxs.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetVerifier.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetVerifierParams.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsDiamondStorageFrozen.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsEthWithdrawalFinalized.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsFacetFreezable.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsFunctionFreezable.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsValidator.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/L2LogsRootHash.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/PriorityQueueFrontOperation.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/StoredBatchHash.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/StoredBlockHash.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/_Getters_Shared.t.sol rename l1-contracts/test/foundry/unit/concrete/{ => state-transition/libraries}/Merkle/Merkle.t.sol (100%) rename l1-contracts/test/foundry/unit/concrete/{ => state-transition/libraries}/Merkle/MerkleTreeNoSort.sol (85%) rename l1-contracts/test/foundry/unit/concrete/{ => state-transition/libraries}/PriorityQueue/OnEmptyQueue.sol (95%) rename l1-contracts/test/foundry/unit/concrete/{ => state-transition/libraries}/PriorityQueue/PopOperations.sol (95%) rename l1-contracts/test/foundry/unit/concrete/{ => state-transition/libraries}/PriorityQueue/PushOperations.sol (88%) rename l1-contracts/test/foundry/unit/concrete/{ => state-transition/libraries}/PriorityQueue/_PriorityQueue_Shared.t.sol (89%) rename l1-contracts/test/foundry/unit/concrete/{ => state-transition/libraries}/TransactionValidator/ValidateL1L2Tx.t.sol (82%) rename l1-contracts/test/foundry/unit/concrete/{ => state-transition/libraries}/TransactionValidator/ValidateUpgradeTransaction.t.sol (72%) rename l1-contracts/test/foundry/unit/concrete/{ => state-transition/libraries}/TransactionValidator/_TransactionValidator_Shared.t.sol (71%) create mode 100644 l1-contracts/test/test_config/constant/addresses.json create mode 100644 l1-contracts/test/test_config/constant/eth.json create mode 100644 l1-contracts/test/test_config/constant/hardhat.json create mode 100644 l1-contracts/test/unit_tests/README.md create mode 100644 l1-contracts/test/unit_tests/custom_base_token.spec.ts create mode 100644 l1-contracts/test/unit_tests/hyperchain_migration_test.spec.ts create mode 100644 l1-contracts/test/unit_tests/initial_deployment_test.spec.ts delete mode 100644 l1-contracts/test/unit_tests/l1_erc20_bridge_test.spec.ts create mode 100644 l1-contracts/test/unit_tests/l1_shared_bridge_test.spec.ts delete mode 100644 l1-contracts/test/unit_tests/l1_weth_bridge_test.spec.ts create mode 100644 l1-contracts/test/unit_tests/legacy_era_test.spec.ts create mode 100644 l1-contracts/test/unit_tests/new_hyperchain_test.spec.ts create mode 100644 l2-contracts/contracts/Config.sol rename l2-contracts/contracts/bridge/{L2ERC20Bridge.sol => L2SharedBridge.sol} (81%) delete mode 100644 l2-contracts/contracts/bridge/L2WethBridge.sol rename l2-contracts/contracts/bridge/{L2Weth.sol => L2WrappedBaseToken.sol} (90%) create mode 100644 l2-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol rename l2-contracts/contracts/bridge/interfaces/{IL1Bridge.sol => IL1SharedBridge.sol} (88%) rename l2-contracts/contracts/bridge/interfaces/{IL2Bridge.sol => IL2SharedBridge.sol} (86%) rename l2-contracts/contracts/bridge/interfaces/{IL2Weth.sol => IL2WrappedBaseToken.sol} (91%) rename l2-contracts/src/{deployForceDeployUpgrader.ts => deploy-force-deploy-upgrader-through-l1.ts} (69%) create mode 100644 l2-contracts/src/deploy-force-deploy-upgrader.ts create mode 100644 l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts create mode 100644 l2-contracts/src/deploy-shared-bridge-on-l2.ts create mode 100644 l2-contracts/src/deploy-testnet-paymaster-through-l1.ts delete mode 100644 l2-contracts/src/deployL2Weth.ts delete mode 100644 l2-contracts/src/deployTestnetPaymaster.ts rename l2-contracts/src/{updateL2ERC20Metadata.ts => update-l2-erc20-metadata.ts} (94%) rename l2-contracts/src/{upgradeBridgeImpl.ts => upgrade-bridge-impl.ts} (86%) create mode 100644 l2-contracts/test/test-utils.ts rename system-contracts/contracts/{L2EthToken.sol => L2BaseToken.sol} (98%) rename system-contracts/contracts/interfaces/{IEthToken.sol => IBaseToken.sol} (97%) rename system-contracts/test/{L2EthToken.spec.ts => L2BaseToken.spec.ts} (60%) diff --git a/.github/workflows/l1-contracts-ci.yaml b/.github/workflows/l1-contracts-ci.yaml index 394eabaef17e..39ab7b83c5d3 100644 --- a/.github/workflows/l1-contracts-ci.yaml +++ b/.github/workflows/l1-contracts-ci.yaml @@ -23,6 +23,9 @@ jobs: - name: Build artifacts run: yarn l1 build + - name: Build L2 artifacts + run: yarn l2 build + - name: Create cache uses: actions/cache/save@v3 with: @@ -31,6 +34,9 @@ jobs: l1-contracts/artifacts l1-contracts/cache l1-contracts/typechain + l2-contracts/artifacts-zk + l2-contracts/cache-zk + l2-contracts/typechain lint: runs-on: ubuntu-latest @@ -112,6 +118,9 @@ jobs: l1-contracts/artifacts l1-contracts/cache l1-contracts/typechain + l2-contracts/artifacts-zk + l2-contracts/cache-zk + l2-contracts/typechain - name: Run tests run: yarn l1 test --no-compile @@ -135,4 +144,4 @@ jobs: run: cargo run - name: Compare - run: diff tools/data/Verifier.sol l1-contracts/contracts/zksync/Verifier.sol + run: diff tools/data/Verifier.sol l1-contracts/contracts/state-transition/Verifier.sol diff --git a/.github/workflows/l2-contracts-ci.yaml b/.github/workflows/l2-contracts-ci.yaml index f3e02ec10ce4..d1dc509af6db 100644 --- a/.github/workflows/l2-contracts-ci.yaml +++ b/.github/workflows/l2-contracts-ci.yaml @@ -20,23 +20,23 @@ jobs: - name: Install dependencies run: yarn - - name: Build L2 artifacts - run: yarn l2 build - - name: Build L1 artifacts run: yarn l1 build + - name: Build L2 artifacts + run: yarn l2 build + - name: Create cache uses: actions/cache/save@v3 with: key: artifacts-l2-${{ github.sha }} path: | - l2-contracts/artifacts-zk - l2-contracts/cache-zk - l2-contracts/typechain l1-contracts/artifacts l1-contracts/cache l1-contracts/typechain + l2-contracts/artifacts-zk + l2-contracts/cache-zk + l2-contracts/typechain lint: runs-on: ubuntu-latest @@ -82,12 +82,12 @@ jobs: fail-on-cache-miss: true key: artifacts-l2-${{ github.sha }} path: | - l2-contracts/artifacts-zk - l2-contracts/cache-zk - l2-contracts/typechain l1-contracts/artifacts l1-contracts/cache l1-contracts/typechain + l2-contracts/artifacts-zk + l2-contracts/cache-zk + l2-contracts/typechain - name: Run Era test node uses: dutterbutter/era-test-node-action@v0.1.3 diff --git a/.gitignore b/.gitignore index 0f3f8b7183cf..023be12a4e35 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,7 @@ tools/data/Verifier.sol typechain/ yarn-debug.log* yarn-error.log* +l1-contracts/yarn-error.log* +l1-contracts/lcov.info +l1-contracts/report/* +l1-contracts/coverage/* \ No newline at end of file diff --git a/docs/Overview.md b/docs/Overview.md index 872d4ef5981b..0c57e2891af2 100644 --- a/docs/Overview.md +++ b/docs/Overview.md @@ -20,7 +20,9 @@ See the [documentation](https://era.zksync.io/docs/dev/fundamentals/rollups.html addresses. - **Security council** - an address of the Gnosis multisig with the trusted owners that can decrease upgrade timelock. - **Validator/Operator** - a privileged address that can commit/verify/execute L2 batches. -- **L2 batch (or just batch)** - An aggregation of multiple L2 blocks. Note, that while the API operates on L2 blocks, the prove system operates on batches, which represent a single proved VM execution, which typically contains multiple L2 blocks. +- **L2 batch (or just batch)** - An aggregation of multiple L2 blocks. Note, that while the API operates on L2 blocks, + the prove system operates on batches, which represent a single proved VM execution, which typically contains multiple + L2 blocks. - **Facet** - implementation contract. The word comes from the EIP-2535. - **Gas** - a unit that measures the amount of computational effort required to execute specific operations on the zkSync Era network. @@ -44,9 +46,8 @@ even an upgrade system is a separate facet that can be replaced. One of the differences from the reference implementation is access freezability. Each of the facets has an associated parameter that indicates if it is possible to freeze access to the facet. Privileged actors can freeze the **diamond** -(not a specific facet!) and all facets with the marker `isFreezable` should be inaccessible until the governor or its owner -unfreezes the diamond. Note that it is a very dangerous thing since the diamond proxy can freeze the upgrade system and then -the diamond will be frozen forever. +(not a specific facet!) and all facets with the marker `isFreezable` should be inaccessible until the admin or the state transition manager unfreezes the diamond. Note that it is a very dangerous thing since the diamond proxy can freeze the upgrade +system and then the diamond will be frozen forever. #### DiamondInit @@ -59,20 +60,20 @@ Implementation detail - function returns a magic value just like it is designed #### GettersFacet Separate facet, whose only function is providing `view` and `pure` methods. It also implements -[diamond loupe](https://eips.ethereum.org/EIPS/eip-2535#diamond-loupe) which makes managing facets easier. -This contract must never be frozen. +[diamond loupe](https://eips.ethereum.org/EIPS/eip-2535#diamond-loupe) which makes managing facets easier. This contract +must never be frozen. #### AdminFacet -Controls changing the privileged addresses such as governor and validators or one of the system parameters (L2 -bootloader bytecode hash, verifier address, verifier parameters, etc), and it also manages the freezing/unfreezing and execution of -upgrades in the diamond proxy. +Controls changing the privileged addresses such as admin and validators or one of the system parameters (L2 +bootloader bytecode hash, verifier address, verifier parameters, etc), and it also manages the freezing/unfreezing and +execution of upgrades in the diamond proxy. #### Governance -This contract manages operations (calls with preconditions) for governance tasks. The contract allows for operations to be scheduled, -executed, and canceled with appropriate permissions and delays. It is used for managing and coordinating upgrades and changes in all -zkSync Era governed contracts. +This contract manages operations (calls with preconditions) for governance tasks. The contract allows for operations to +be scheduled, executed, and canceled with appropriate permissions and delays. It is used for managing and coordinating +upgrades and changes in all zkSync Era governed contracts. Each upgrade consists of two steps: @@ -206,23 +207,23 @@ L1 <-> L2 communication. ##### L1ERC20Bridge -The "standard" implementation of the ERC20 token bridge. Works only with regular ERC20 tokens, i.e. not with -fee-on-transfer tokens or other custom logic for handling user balances. +The legacy implementation of the ERC20 token bridge. Works only with regular ERC20 tokens, i.e. not with +fee-on-transfer tokens or other custom logic for handling user balances. Only works for Era. - `deposit` - lock funds inside the contract and send a request to mint bridged assets on L2. - `claimFailedDeposit` - unlock funds if the deposit was initiated but then failed on L2. - `finalizeWithdrawal` - unlock funds for the valid withdrawal request from L2. -##### L2ERC20Bridge - -The L2 counterpart of the L1 ERC20 bridge. +##### L1SharedBridge -- `withdraw` - initiate a withdrawal by burning funds on the contract and sending a corresponding message to L1. -- `finalizeDeposit` - finalize the deposit and mint funds on L2. +The "standard" implementation of the ERC20 and WETH token bridge. Works only with regular ERC20 tokens, i.e. not with +fee-on-transfer tokens or other custom logic for handling user balances. -##### L1WethBridge +- `deposit` - lock funds inside the contract and send a request to mint bridged assets on L2. +- `claimFailedDeposit` - unlock funds if the deposit was initiated but then failed on L2. +- `finalizeWithdrawal` - unlock funds for the valid withdrawal request from L2. -The custom bridge exclusively handles transfers of WETH tokens between the two domains. It is designed to streamline and +The bridge also handles WETH token deposits between the two domains. It is designed to streamline and enhance the user experience for bridging WETH tokens by minimizing the number of transactions required and reducing liquidity fragmentation thus improving efficiency and user experience. @@ -231,11 +232,14 @@ it is wrapped back into WETH and delivered to the L2 recipient. Thus, the deposit is made in one transaction, and the user receives L2 WETH that can be unwrapped to ETH. -##### L2WethBridge +##### L2SharedBridge + +The L2 counterpart of the L1 Shared bridge. -The L2 counterpart of the L1 WETH bridge. +- `withdraw` - initiate a withdrawal by burning funds on the contract and sending a corresponding message to L1. +- `finalizeDeposit` - finalize the deposit and mint funds on L2. -For withdrawals, the contract receives ETH from the L2 WETH bridge contract, wraps it into WETH, and sends the WETH to +For WETH withdrawals, the contract receives ETH from the L2 WETH bridge contract, wraps it into WETH, and sends the WETH to the L1 recipient. #### ValidatorTimelock @@ -248,15 +252,16 @@ investigation and mitigation before resuming normal operations. It is a temporary solution to prevent any significant impact of the validator hot key leakage, while the network is in the Alpha stage. -This contract consists of four main functions `commitBatches`, `proveBatches`, `executeBatches`, and `revertBatches`, that -can be called only by the validator. +This contract consists of four main functions `commitBatches`, `proveBatches`, `executeBatches`, and `revertBatches`, +that can be called only by the validator. -When the validator calls `commitBatches`, the same calldata will be propogated to the zkSync contract (`DiamondProxy` through -`call` where it invokes the `ExecutorFacet` through `delegatecall`), and also a timestamp is assigned to these batches to track -the time these batches are commited by the validator to enforce a delay between committing and execution of batches. Then, the -validator can prove the already commited batches regardless of the mentioned timestamp, and again the same calldata (related -to the `proveBatches` function) will be propogated to the zkSync contract. After, the `delay` is elapsed, the validator -is allowed to call `executeBatches` to propogate the same calldata to zkSync contract. +When the validator calls `commitBatches`, the same calldata will be propogated to the zkSync contract (`DiamondProxy` +through `call` where it invokes the `ExecutorFacet` through `delegatecall`), and also a timestamp is assigned to these +batches to track the time these batches are commited by the validator to enforce a delay between committing and +execution of batches. Then, the validator can prove the already commited batches regardless of the mentioned timestamp, +and again the same calldata (related to the `proveBatches` function) will be propogated to the zkSync contract. After, +the `delay` is elapsed, the validator is allowed to call `executeBatches` to propogate the same calldata to zkSync +contract. ### L2 specifics diff --git a/l1-contracts/.env b/l1-contracts/.env index d02962c27965..829a5212fc6f 100644 --- a/l1-contracts/.env +++ b/l1-contracts/.env @@ -1,19 +1,34 @@ -CHAIN_ETH_NETWORK=localhost +CHAIN_ETH_NETWORK=hardhat CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT=72000000 CONTRACTS_DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT=10000000 ETH_CLIENT_WEB3_URL=http://127.0.0.1:8545 -CONTRACTS_MAILBOX_FACET_ADDR=0x0000000000000000000000000000000000000000 -CONTRACTS_GOVERNANCE_FACET_ADDR=0x0000000000000000000000000000000000000000 +CHAIN_ETH_ZKSYNC_NETWORK_ID=270 +CONTRACTS_BRIDGEHUB_PROXY_ADDR=0x0000000000000000000000000000000000000000 +CONTRACTS_BRIDGEHUB_IMPL_ADDR=0x0000000000000000000000000000000000000000 +CONTRACTS_STATE_TRANSITION_PROXY_ADDR=0x0000000000000000000000000000000000000000 +CONTRACTS_STATE_TRANSITION_IMPL_ADDR=0x0000000000000000000000000000000000000000 +CONTRACTS_VERIFIER_ADDR=0x0000000000000000000000000000000000000000 +CONTRACTS_BLOB_VERSIONED_HASH_RETRIEVER_ADDR=0x0000000000000000000000000000000000000000 +CONTRACTS_ADMIN_FACET_ADDR=0x0000000000000000000000000000000000000000 CONTRACTS_DIAMOND_CUT_FACET_ADDR=0x0000000000000000000000000000000000000000 CONTRACTS_EXECUTOR_FACET_ADDR=0x0000000000000000000000000000000000000000 +CONTRACTS_MAILBOX_FACET_ADDR=0x0000000000000000000000000000000000000000 CONTRACTS_GETTERS_FACET_ADDR=0x0000000000000000000000000000000000000000 CONTRACTS_DIAMOND_INIT_ADDR=0x0000000000000000000000000000000000000000 CONTRACTS_DIAMOND_UPGRADE_INIT_ADDR=0x0000000000000000000000000000000000000000 CONTRACTS_DIAMOND_PROXY_ADDR=0x0000000000000000000000000000000000000000 -CONTRACTS_VERIFIER_ADDR=0x0000000000000000000000000000000000000000 +CONTRACTS_BASE_TOKEN_ADDR=0x0000000000000000000000000000000000000000 +CONTRACTS_DEFAULT_UPGRADE_ADDR=0x0000000000000000000000000000000000000000 +CONTRACTS_GENESIS_UPGRADE_ADDR=0x0000000000000000000000000000000000000000 +CONTRACTS_TRANSPARENT_PROXY_ADMIN_ADDR=0x0000000000000000000000000000000000000000 +CONTRACTS_GOVERNANCE_ADDR=0x0000000000000000000000000000000000000000 CONTRACTS_L1_ERC20_BRIDGE_IMPL_ADDR=0x0000000000000000000000000000000000000000 CONTRACTS_L1_ERC20_BRIDGE_PROXY_ADDR=0x0000000000000000000000000000000000000000 +CONTRACTS_L1_SHARED_BRIDGE_IMPL_ADDR=0x0000000000000000000000000000000000000000 +CONTRACTS_L1_SHARED_BRIDGE_PROXY_ADDR=0x0000000000000000000000000000000000000000 CONTRACTS_L1_ALLOW_LIST_ADDR=0x0000000000000000000000000000000000000000 CONTRACTS_CREATE2_FACTORY_ADDR=0x0000000000000000000000000000000000000000 CONTRACTS_VALIDATOR_TIMELOCK_ADDR=0x0000000000000000000000000000000000000000 CONTRACTS_VALIDATOR_TIMELOCK_EXECUTION_DELAY=0 +ETH_SENDER_SENDER_OPERATOR_COMMIT_ETH_ADDR=0x0000000000000000000000000000000000000000 +CONTRACTS_SHARED_BRIDGE_UPGRADE_STORAGE_SWITCH=0 \ No newline at end of file diff --git a/l1-contracts/contracts/bridge/L1ERC20Bridge.sol b/l1-contracts/contracts/bridge/L1ERC20Bridge.sol index bd32a19408b0..f69e87b2a795 100644 --- a/l1-contracts/contracts/bridge/L1ERC20Bridge.sol +++ b/l1-contracts/contracts/bridge/L1ERC20Bridge.sol @@ -2,133 +2,87 @@ pragma solidity 0.8.20; -import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {IL1BridgeLegacy} from "./interfaces/IL1BridgeLegacy.sol"; -import {IL1Bridge} from "./interfaces/IL1Bridge.sol"; -import {IL2Bridge} from "./interfaces/IL2Bridge.sol"; -import {IL2ERC20Bridge} from "./interfaces/IL2ERC20Bridge.sol"; +import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; +import {IL1SharedBridge} from "./interfaces/IL1SharedBridge.sol"; -import {BridgeInitializationHelper} from "./libraries/BridgeInitializationHelper.sol"; - -import {IZkSync} from "../zksync/interfaces/IZkSync.sol"; -import {TxStatus} from "../zksync/interfaces/IMailbox.sol"; -import {L2Message} from "../zksync/Storage.sol"; -import {UnsafeBytes} from "../common/libraries/UnsafeBytes.sol"; import {L2ContractHelper} from "../common/libraries/L2ContractHelper.sol"; import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; -import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -/// @notice Smart contract that allows depositing ERC20 tokens from Ethereum to zkSync Era -/// @dev It is standard implementation of ERC20 Bridge that can be used as a reference -/// for any other custom token bridges. -contract L1ERC20Bridge is IL1Bridge, IL1BridgeLegacy, ReentrancyGuard { +/// @notice Smart contract that allows depositing ERC20 tokens from Ethereum to hyperchains +/// @dev It is a legacy bridge from zkSync Era, that was deprecated in favour of shared bridge. +/// It is needed for backward compatibility with already integrated projects. +contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { using SafeERC20 for IERC20; - /// @dev zkSync smart contract that is used to operate with L2 via asynchronous L2 <-> L1 communication - IZkSync internal immutable zkSync; + /// @dev The shared bridge that is now used for all bridging, replacing the legacy contract. + IL1SharedBridge public immutable override sharedBridge; - /// @dev A mapping L2 batch number => message number => flag - /// @dev Used to indicate that zkSync L2 -> L1 message was already processed + /// @dev A mapping L2 batch number => message number => flag. + /// @dev Used to indicate that L2 -> L1 message was already processed for zkSync Era withdrawals. mapping(uint256 l2BatchNumber => mapping(uint256 l2ToL1MessageNumber => bool isFinalized)) public isWithdrawalFinalized; - /// @dev A mapping account => L1 token address => L2 deposit transaction hash => amount - /// @dev Used for saving the number of deposited funds, to claim them in case the deposit transaction will fail + /// @dev A mapping account => L1 token address => L2 deposit transaction hash => amount. + /// @dev Used for saving the number of deposited funds, to claim them in case the deposit transaction will fail in zkSync Era. mapping(address account => mapping(address l1Token => mapping(bytes32 depositL2TxHash => uint256 amount))) - internal depositAmount; + public depositAmount; - /// @dev The address of deployed L2 bridge counterpart + /// @dev The address that is used as a L2 bridge counterpart in zkSync Era. address public l2Bridge; - /// @dev The address that acts as a beacon for L2 tokens + /// @dev The address that is used as a beacon for L2 tokens in zkSync Era. address public l2TokenBeacon; - /// @dev The bytecode hash of the L2 token contract + /// @dev Stores the hash of the L2 token proxy contract's bytecode on zkSync Era. bytes32 public l2TokenProxyBytecodeHash; + /// @dev Deprecated storage variable related to withdrawal limitations. mapping(address => uint256) private __DEPRECATED_lastWithdrawalLimitReset; - /// @dev A mapping L1 token address => the accumulated withdrawn amount during the withdrawal limit window + /// @dev Deprecated storage variable related to withdrawal limitations. mapping(address => uint256) private __DEPRECATED_withdrawnAmountInWindow; - /// @dev The accumulated deposited amount per user. - /// @dev A mapping L1 token address => user address => the total deposited amount by the user + /// @dev Deprecated storage variable related to deposit limitations. mapping(address => mapping(address => uint256)) private __DEPRECATED_totalDepositedAmountPerUser; /// @dev Contract is expected to be used as proxy implementation. /// @dev Initialize the implementation to prevent Parity hack. - constructor(IZkSync _zkSync) reentrancyGuardInitializer { - zkSync = _zkSync; + constructor(IL1SharedBridge _sharedBridge) reentrancyGuardInitializer { + sharedBridge = _sharedBridge; } - /// @dev Initializes a contract bridge for later use. Expected to be used in the proxy - /// @dev During initialization deploys L2 bridge counterpart as well as provides some factory deps for it - /// @param _factoryDeps A list of raw bytecodes that are needed for deployment of the L2 bridge - /// @notice _factoryDeps[0] == a raw bytecode of L2 bridge implementation - /// @notice _factoryDeps[1] == a raw bytecode of proxy that is used as L2 bridge - /// @notice _factoryDeps[2] == a raw bytecode of token proxy - /// @param _l2TokenBeacon Pre-calculated address of the L2 token upgradeable beacon - /// @notice At the time of the function call, it is not yet deployed in L2, but knowledge of its address - /// @notice is necessary for determining L2 token address by L1 address, see `l2TokenAddress(address)` function - /// @param _governor Address which can change L2 token implementation and upgrade the bridge - /// @param _deployBridgeImplementationFee How much of the sent value should be allocated to deploying the L2 bridge - /// implementation - /// @param _deployBridgeProxyFee How much of the sent value should be allocated to deploying the L2 bridge proxy - function initialize( - bytes[] calldata _factoryDeps, - address _l2TokenBeacon, - address _governor, - uint256 _deployBridgeImplementationFee, - uint256 _deployBridgeProxyFee - ) external payable reentrancyGuardInitializer { - require(_l2TokenBeacon != address(0), "nf"); - require(_governor != address(0), "nh"); - // We are expecting to see the exact three bytecodes that are needed to initialize the bridge - require(_factoryDeps.length == 3, "mk"); - // The caller miscalculated deploy transactions fees - require(msg.value == _deployBridgeImplementationFee + _deployBridgeProxyFee, "fee"); - l2TokenProxyBytecodeHash = L2ContractHelper.hashL2Bytecode(_factoryDeps[2]); - l2TokenBeacon = _l2TokenBeacon; + /// @dev Initializes the reentrancy guard. Expected to be used in the proxy. + function initialize() external reentrancyGuardInitializer {} - bytes32 l2BridgeImplementationBytecodeHash = L2ContractHelper.hashL2Bytecode(_factoryDeps[0]); - bytes32 l2BridgeProxyBytecodeHash = L2ContractHelper.hashL2Bytecode(_factoryDeps[1]); + /// @dev transfer token to shared bridge as part of upgrade + function tranferTokenToSharedBridge(address _token, uint256 _amount) external { + require(msg.sender == address(sharedBridge), "Not shared bridge"); + uint256 amount = IERC20(_token).balanceOf(address(this)); + require(amount == _amount, "Incorrect amount"); + IERC20(_token).safeTransfer(address(sharedBridge), amount); + } - // Deploy L2 bridge implementation contract - address bridgeImplementationAddr = BridgeInitializationHelper.requestDeployTransaction( - zkSync, - _deployBridgeImplementationFee, - l2BridgeImplementationBytecodeHash, - "", // Empty constructor data - _factoryDeps // All factory deps are needed for L2 bridge - ); + /*////////////////////////////////////////////////////////////// + ERA LEGACY GETTERS + //////////////////////////////////////////////////////////////*/ - // Prepare the proxy constructor data - bytes memory l2BridgeProxyConstructorData; - { - // Data to be used in delegate call to initialize the proxy - bytes memory proxyInitializationParams = abi.encodeCall( - IL2ERC20Bridge.initialize, - (address(this), l2TokenProxyBytecodeHash, _governor) - ); - l2BridgeProxyConstructorData = abi.encode(bridgeImplementationAddr, _governor, proxyInitializationParams); - } + /// @return The L2 token address that would be minted for deposit of the given L1 token on zkSync Era. + function l2TokenAddress(address _l1Token) external view returns (address) { + bytes32 constructorInputHash = keccak256(abi.encode(l2TokenBeacon, "")); + bytes32 salt = bytes32(uint256(uint160(_l1Token))); - // Deploy L2 bridge proxy contract - l2Bridge = BridgeInitializationHelper.requestDeployTransaction( - zkSync, - _deployBridgeProxyFee, - l2BridgeProxyBytecodeHash, - l2BridgeProxyConstructorData, - // No factory deps are needed for the L2 bridge proxy, because it is already passed in previous step - new bytes[](0) - ); + return L2ContractHelper.computeCreate2Address(l2Bridge, salt, l2TokenProxyBytecodeHash, constructorInputHash); } + /*////////////////////////////////////////////////////////////// + ERA LEGACY FUNCTIONS + //////////////////////////////////////////////////////////////*/ + /// @notice Legacy deposit method with refunding the fee to the caller, use another `deposit` method instead. /// @dev Initiates a deposit by locking funds on the contract and sending the request /// of processing an L2 transaction where tokens would be minted. @@ -152,6 +106,7 @@ contract L1ERC20Bridge is IL1Bridge, IL1BridgeLegacy, ReentrancyGuard { } /// @notice Initiates a deposit by locking funds on the contract and sending the request + /// @dev Initiates a deposit by locking funds on the contract and sending the request /// of processing an L2 transaction where tokens would be minted /// @dev If the token is bridged for the first time, the L2 token contract will be deployed. Note however, that the /// newly-deployed token does not support any custom logic, i.e. rebase tokens' functionality is not supported. @@ -183,68 +138,34 @@ contract L1ERC20Bridge is IL1Bridge, IL1BridgeLegacy, ReentrancyGuard { uint256 _l2TxGasPerPubdataByte, address _refundRecipient ) public payable nonReentrant returns (bytes32 l2TxHash) { - require(_amount != 0, "2T"); // empty deposit amount - uint256 amount = _depositFunds(msg.sender, IERC20(_l1Token), _amount); - require(amount == _amount, "1T"); // The token has non-standard transfer logic - - bytes memory l2TxCalldata = _getDepositL2Calldata(msg.sender, _l2Receiver, _l1Token, amount); - // If the refund recipient is not specified, the refund will be sent to the sender of the transaction. - // Otherwise, the refund will be sent to the specified address. - // If the recipient is a contract on L1, the address alias will be applied. - address refundRecipient = _refundRecipient; - if (_refundRecipient == address(0)) { - refundRecipient = msg.sender != tx.origin ? AddressAliasHelper.applyL1ToL2Alias(msg.sender) : msg.sender; - } - l2TxHash = zkSync.requestL2Transaction{value: msg.value}( - l2Bridge, - 0, // L2 msg.value - l2TxCalldata, + require(_amount != 0, "0T"); // empty deposit + uint256 amount = _depositFundsToSharedBridge(msg.sender, IERC20(_l1Token), _amount); + require(amount == _amount, "3T"); // The token has non-standard transfer logic + + l2TxHash = sharedBridge.depositLegacyErc20Bridge{value: msg.value}( + msg.sender, + _l2Receiver, + _l1Token, + _amount, _l2TxGasLimit, _l2TxGasPerPubdataByte, - new bytes[](0), - refundRecipient + _refundRecipient ); - - // Save the deposited amount to claim funds on L1 if the deposit failed on L2 - depositAmount[msg.sender][_l1Token][l2TxHash] = amount; - - emit DepositInitiated(l2TxHash, msg.sender, _l2Receiver, _l1Token, amount); + depositAmount[msg.sender][_l1Token][l2TxHash] = _amount; + emit DepositInitiated(l2TxHash, msg.sender, _l2Receiver, _l1Token, _amount); } - /// @dev Transfers tokens from the depositor address to the smart contract address - /// @return The difference between the contract balance before and after the transferring of funds - function _depositFunds(address _from, IERC20 _token, uint256 _amount) internal returns (uint256) { - uint256 balanceBefore = _token.balanceOf(address(this)); - _token.safeTransferFrom(_from, address(this), _amount); - uint256 balanceAfter = _token.balanceOf(address(this)); + /// @dev Transfers tokens from the depositor address to the shared bridge address. + /// @return The difference between the contract balance before and after the transferring of funds. + function _depositFundsToSharedBridge(address _from, IERC20 _token, uint256 _amount) internal returns (uint256) { + uint256 balanceBefore = _token.balanceOf(address(sharedBridge)); + _token.safeTransferFrom(_from, address(sharedBridge), _amount); + uint256 balanceAfter = _token.balanceOf(address(sharedBridge)); return balanceAfter - balanceBefore; } - /// @dev Generate a calldata for calling the deposit finalization on the L2 bridge contract - function _getDepositL2Calldata( - address _l1Sender, - address _l2Receiver, - address _l1Token, - uint256 _amount - ) internal view returns (bytes memory txCalldata) { - bytes memory gettersData = _getERC20Getters(_l1Token); - - txCalldata = abi.encodeCall( - IL2Bridge.finalizeDeposit, - (_l1Sender, _l2Receiver, _l1Token, _amount, gettersData) - ); - } - - /// @dev Receives and parses (name, symbol, decimals) from the token contract - function _getERC20Getters(address _token) internal view returns (bytes memory data) { - (, bytes memory data1) = _token.staticcall(abi.encodeCall(IERC20Metadata.name, ())); - (, bytes memory data2) = _token.staticcall(abi.encodeCall(IERC20Metadata.symbol, ())); - (, bytes memory data3) = _token.staticcall(abi.encodeCall(IERC20Metadata.decimals, ())); - data = abi.encode(data1, data2, data3); - } - - /// @dev Withdraw funds from the initiated deposit, that failed when finalizing on L2 + /// @dev Withdraw funds from the initiated deposit, that failed when finalizing on L2. /// @param _depositSender The address of the deposit initiator /// @param _l1Token The address of the deposited L1 ERC20 token /// @param _l2TxHash The L2 transaction hash of the failed deposit finalization @@ -261,23 +182,20 @@ contract L1ERC20Bridge is IL1Bridge, IL1BridgeLegacy, ReentrancyGuard { uint16 _l2TxNumberInBatch, bytes32[] calldata _merkleProof ) external nonReentrant { - bool proofValid = zkSync.proveL1ToL2TransactionStatus( + uint256 amount = depositAmount[_depositSender][_l1Token][_l2TxHash]; + require(amount != 0, "2T"); // empty deposit + delete depositAmount[_depositSender][_l1Token][_l2TxHash]; + + sharedBridge.claimFailedDepositLegacyErc20Bridge( + _depositSender, + _l1Token, + amount, _l2TxHash, _l2BatchNumber, _l2MessageIndex, _l2TxNumberInBatch, - _merkleProof, - TxStatus.Failure + _merkleProof ); - require(proofValid, "yn"); - - uint256 amount = depositAmount[_depositSender][_l1Token][_l2TxHash]; - require(amount > 0, "y1"); - - delete depositAmount[_depositSender][_l1Token][_l2TxHash]; - // Withdraw funds - IERC20(_l1Token).safeTransfer(_depositSender, amount); - emit ClaimedFailedDeposit(_depositSender, _l1Token, amount); } @@ -295,49 +213,15 @@ contract L1ERC20Bridge is IL1Bridge, IL1BridgeLegacy, ReentrancyGuard { bytes32[] calldata _merkleProof ) external nonReentrant { require(!isWithdrawalFinalized[_l2BatchNumber][_l2MessageIndex], "pw"); + // We don't need to set finalizeWithdrawal here, as we set it in the shared bridge - L2Message memory l2ToL1Message = L2Message({ - txNumberInBatch: _l2TxNumberInBatch, - sender: l2Bridge, - data: _message - }); - - (address l1Receiver, address l1Token, uint256 amount) = _parseL2WithdrawalMessage(l2ToL1Message.data); - // Preventing the stack too deep error - { - bool success = zkSync.proveL2MessageInclusion(_l2BatchNumber, _l2MessageIndex, l2ToL1Message, _merkleProof); - require(success, "nq"); - } - - isWithdrawalFinalized[_l2BatchNumber][_l2MessageIndex] = true; - // Withdraw funds - IERC20(l1Token).safeTransfer(l1Receiver, amount); - + (address l1Receiver, address l1Token, uint256 amount) = sharedBridge.finalizeWithdrawalLegacyErc20Bridge( + _l2BatchNumber, + _l2MessageIndex, + _l2TxNumberInBatch, + _message, + _merkleProof + ); emit WithdrawalFinalized(l1Receiver, l1Token, amount); } - - /// @dev Decode the withdraw message that came from L2 - function _parseL2WithdrawalMessage( - bytes memory _l2ToL1message - ) internal pure returns (address l1Receiver, address l1Token, uint256 amount) { - // Check that the message length is correct. - // It should be equal to the length of the function signature + address + address + uint256 = 4 + 20 + 20 + 32 = - // 76 (bytes). - require(_l2ToL1message.length == 76, "kk"); - - (uint32 functionSignature, uint256 offset) = UnsafeBytes.readUint32(_l2ToL1message, 0); - require(bytes4(functionSignature) == this.finalizeWithdrawal.selector, "nt"); - - (l1Receiver, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); - (l1Token, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); - (amount, offset) = UnsafeBytes.readUint256(_l2ToL1message, offset); - } - - /// @return The L2 token address that would be minted for deposit of the given L1 token - function l2TokenAddress(address _l1Token) public view returns (address) { - bytes32 constructorInputHash = keccak256(abi.encode(l2TokenBeacon, "")); - bytes32 salt = bytes32(uint256(uint160(_l1Token))); - - return L2ContractHelper.computeCreate2Address(l2Bridge, salt, l2TokenProxyBytecodeHash, constructorInputHash); - } } diff --git a/l1-contracts/contracts/bridge/L1SharedBridge.sol b/l1-contracts/contracts/bridge/L1SharedBridge.sol new file mode 100644 index 000000000000..791e1dc4a806 --- /dev/null +++ b/l1-contracts/contracts/bridge/L1SharedBridge.sol @@ -0,0 +1,667 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; + +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; +import {IL1SharedBridge} from "./interfaces/IL1SharedBridge.sol"; +import {IL2Bridge} from "./interfaces/IL2Bridge.sol"; + +import {IMailbox} from "../state-transition/chain-interfaces/IMailbox.sol"; +import {L2Message, TxStatus} from "../common/Messaging.sol"; +import {UnsafeBytes} from "../common/libraries/UnsafeBytes.sol"; +import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; +import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; +import {ERA_CHAIN_ID, ERA_ERC20_BRIDGE_ADDRESS, ETH_TOKEN_ADDRESS, ERA_DIAMOND_PROXY, TWO_BRIDGES_MAGIC_VALUE} from "../common/Config.sol"; +import {IBridgehub, L2TransactionRequestTwoBridgesInner, L2TransactionRequestDirect} from "../bridgehub/IBridgehub.sol"; +import {IGetters} from "../state-transition/chain-interfaces/IGetters.sol"; +import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR} from "../common/L2ContractAddresses.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @dev Bridges assets between L1 and hyperchains, supporting both ETH and ERC20 tokens. +/// @dev Designed for use with a proxy for upgradability. +contract L1SharedBridge is IL1SharedBridge, ReentrancyGuard, Initializable, Ownable2Step { + using SafeERC20 for IERC20; + + /// @dev The address of the WETH token on L1. + address public immutable override l1WethAddress; + + /// @dev Bridgehub smart contract that is used to operate with L2 via asynchronous L2 <-> L1 communication. + IBridgehub public immutable override bridgehub; + + /// @dev Legacy bridge smart contract that used to hold ERC20 tokens. + IL1ERC20Bridge public immutable override legacyBridge; + + /// @dev Stores the first batch number on the zkSync Era Diamond Proxy that was settled after Shared Bridge upgrade. + /// This variable is used to differentiate between pre-upgrade and post-upgrade withdrawals. Withdrawals from batches older + /// than this value are considered to have been finalized prior to the upgrade and handled separately. + uint256 internal eraFirstPostUpgradeBatch; + + /// @dev A mapping chainId => bridgeProxy. Used to store the bridge proxy's address, and to see if it has been deployed yet. + mapping(uint256 chainId => address l2Bridge) public override l2BridgeAddress; + + /// @dev A mapping chainId => L2 deposit transaction hash => keccak256(abi.encode(account, tokenAddress, amount)) + /// @dev Tracks deposit transactions from L2 to enable users to claim their funds if a deposit fails. + mapping(uint256 chainId => mapping(bytes32 l2DepositTxHash => bytes32 depositDataHash)) + public + override depositHappened; + + /// @dev Tracks the processing status of L2 to L1 messages, indicating whether a message has already been finalized. + mapping(uint256 chainId => mapping(uint256 l2BatchNumber => mapping(uint256 l2ToL1MessageNumber => bool isFinalized))) + public isWithdrawalFinalized; + + /// @dev Indicates whether the hyperbridging is enabled for a given chain. + mapping(uint256 chainId => bool enabled) internal hyperbridgingEnabled; + + /// @dev Maps token balances for each chain to prevent unauthorized spending across hyperchains. + /// This serves as a security measure until hyperbridging is implemented. + mapping(uint256 chainId => mapping(address l1Token => uint256 balance)) internal chainBalance; + + /// @notice Checks that the message sender is the bridgehub. + modifier onlyBridgehub() { + require(msg.sender == address(bridgehub), "ShB not BH"); + _; + } + + /// @notice Checks that the message sender is the bridgehub or zkSync Era Diamond Proxy. + modifier onlyBridgehubOrEra(uint256 _chainId) { + require( + msg.sender == address(bridgehub) || (_chainId == ERA_CHAIN_ID && msg.sender == ERA_DIAMOND_PROXY), + "L1SharedBridge: not bridgehub or era chain" + ); + _; + } + + /// @notice Checks that the message sender is the legacy bridge. + modifier onlyLegacyBridge() { + require(msg.sender == address(legacyBridge), "ShB not legacy bridge"); + _; + } + + /// @dev Contract is expected to be used as proxy implementation. + /// @dev Initialize the implementation to prevent Parity hack. + constructor( + address _l1WethAddress, + IBridgehub _bridgehub, + IL1ERC20Bridge _legacyBridge + ) reentrancyGuardInitializer { + _disableInitializers(); + l1WethAddress = _l1WethAddress; + bridgehub = _bridgehub; + legacyBridge = _legacyBridge; + } + + /// @dev Initializes a contract bridge for later use. Expected to be used in the proxy + /// @param _owner Address which can change L2 token implementation and upgrade the bridge + /// implementation. The owner is the Governor and separate from the ProxyAdmin from now on, so that the Governor can call the bridge. + function initialize( + address _owner, + uint256 _eraFirstPostUpgradeBatch + ) external reentrancyGuardInitializer initializer { + require(_owner != address(0), "ShB owner 0"); + _transferOwnership(_owner); + + eraFirstPostUpgradeBatch = _eraFirstPostUpgradeBatch; + l2BridgeAddress[ERA_CHAIN_ID] = ERA_ERC20_BRIDGE_ADDRESS; + } + + /// @dev tranfer tokens from legacy erc20 bridge or mailbox and set chainBalance as part of migration process + function transferFundsFromLegacy(address _token, address _target, uint256 _targetChainId) external onlyOwner { + if (_token == ETH_TOKEN_ADDRESS) { + uint256 balanceBefore = address(this).balance; + IMailbox(_target).transferEthToSharedBridge(); + uint256 balanceAfter = address(this).balance; + require(balanceAfter > balanceBefore, "ShB: 0 eth transferred"); + chainBalance[_targetChainId][ETH_TOKEN_ADDRESS] = + chainBalance[_targetChainId][ETH_TOKEN_ADDRESS] + + balanceAfter - + balanceBefore; + } else { + uint256 balanceBefore = IERC20(_token).balanceOf(address(this)); + uint256 amount = IERC20(_token).balanceOf(address(legacyBridge)); + require(amount > 0, "ShB: 0 amount to transfer"); + IL1ERC20Bridge(_target).tranferTokenToSharedBridge(_token, amount); + uint256 balanceAfter = IERC20(_token).balanceOf(address(this)); + require(balanceAfter - balanceBefore == amount, "ShB: wrong amount transferred"); + chainBalance[_targetChainId][_token] = chainBalance[_targetChainId][_token] + amount; + } + } + + function receiveEth(uint256 _chainId) external payable { + require(bridgehub.getStateTransition(_chainId) == msg.sender, "receiveEth not state transition"); + } + + /// @dev Initializes the l2Bridge address by governance for a specific chain. + function initializeChainGovernance(uint256 _chainId, address _l2BridgeAddress) external onlyOwner { + l2BridgeAddress[_chainId] = _l2BridgeAddress; + } + + /// @notice Allows bridgehub to acquire mintValue for L1->L2 transactions. + /// @dev If the corresponding L2 transaction fails, refunds are issued to a refund recipient on L2. + function bridgehubDepositBaseToken( + uint256 _chainId, + address _prevMsgSender, + address _l1Token, + uint256 _amount + ) external payable virtual onlyBridgehubOrEra(_chainId) { + if (_l1Token == ETH_TOKEN_ADDRESS) { + require(msg.value == _amount, "L1SharedBridge: msg.value not equal to amount"); + } else { + // The Bridgehub also checks this, but we want to be sure + require(msg.value == 0, "ShB m.v > 0 b d.it"); + + uint256 amount = _depositFunds(_prevMsgSender, IERC20(_l1Token), _amount); // note if _prevMsgSender is this contract, this will return 0. This does not happen. + require(amount == _amount, "3T"); // The token has non-standard transfer logic + } + + if (!hyperbridgingEnabled[_chainId]) { + chainBalance[_chainId][_l1Token] += _amount; + } + // Note that we don't save the deposited amount, as this is for the base token, which gets sent to the refundRecipient if the tx fails + emit BridgehubDepositBaseTokenInitiated(_chainId, _prevMsgSender, _l1Token, _amount); + } + + /// @dev Transfers tokens from the depositor address to the smart contract address. + /// @return The difference between the contract balance before and after the transferring of funds. + function _depositFunds(address _from, IERC20 _token, uint256 _amount) internal returns (uint256) { + uint256 balanceBefore = _token.balanceOf(address(this)); + _token.safeTransferFrom(_from, address(this), _amount); + uint256 balanceAfter = _token.balanceOf(address(this)); + + return balanceAfter - balanceBefore; + } + + /// @notice Initiates a deposit transaction within Bridgehub, used by `requestL2TransactionTwoBridges`. + function bridgehubDeposit( + uint256 _chainId, + address _prevMsgSender, + uint256, // l2Value, needed for Weth deposits in the future + bytes calldata _data + ) external payable override onlyBridgehub returns (L2TransactionRequestTwoBridgesInner memory request) { + require(l2BridgeAddress[_chainId] != address(0), "ShB l2 bridge not deployed"); + + (address _l1Token, uint256 _depositAmount, address _l2Receiver) = abi.decode( + _data, + (address, uint256, address) + ); + require(_l1Token != l1WethAddress, "ShB: WETH deposit not supported"); + require(bridgehub.baseToken(_chainId) != _l1Token, "ShB: baseToken deposit not supported"); + + uint256 amount; + if (_l1Token == ETH_TOKEN_ADDRESS) { + amount = msg.value; + require(_depositAmount == 0, "ShB wrong withdraw amount"); + } else { + require(msg.value == 0, "ShB m.v > 0 for BH d.it 2"); + amount = _depositAmount; + + uint256 withdrawAmount = _depositFunds(_prevMsgSender, IERC20(_l1Token), _depositAmount); + require(withdrawAmount == _depositAmount, "5T"); // The token has non-standard transfer logic + } + require(amount != 0, "6T"); // empty deposit amount + + bytes32 txDataHash = keccak256(abi.encode(_prevMsgSender, _l1Token, amount)); + if (!hyperbridgingEnabled[_chainId]) { + chainBalance[_chainId][_l1Token] += amount; + } + + { + // Request the finalization of the deposit on the L2 side + bytes memory l2TxCalldata = _getDepositL2Calldata(_prevMsgSender, _l2Receiver, _l1Token, amount); + + request = L2TransactionRequestTwoBridgesInner({ + magicValue: TWO_BRIDGES_MAGIC_VALUE, + l2Contract: l2BridgeAddress[_chainId], + l2Calldata: l2TxCalldata, + factoryDeps: new bytes[](0), + txDataHash: txDataHash + }); + } + emit BridgehubDepositInitiated(_chainId, txDataHash, _prevMsgSender, _l2Receiver, _l1Token, amount); + } + + /// @notice Confirms the acceptance of a transaction by the Mailbox, as part of the L2 transaction process within Bridgehub. + /// This function is utilized by `requestL2TransactionTwoBridges` to validate the execution of a transaction. + function bridgehubConfirmL2Transaction( + uint256 _chainId, + bytes32 _txDataHash, + bytes32 _txHash + ) external override onlyBridgehub { + require(depositHappened[_chainId][_txHash] == 0x00, "ShB tx hap"); + depositHappened[_chainId][_txHash] = _txDataHash; + emit BridgehubDepositFinalized(_chainId, _txDataHash, _txHash); + } + + /// @dev Generate a calldata for calling the deposit finalization on the L2 bridge contract + function _getDepositL2Calldata( + address _l1Sender, + address _l2Receiver, + address _l1Token, + uint256 _amount + ) internal view returns (bytes memory) { + bytes memory gettersData = _getERC20Getters(_l1Token); + return abi.encodeCall(IL2Bridge.finalizeDeposit, (_l1Sender, _l2Receiver, _l1Token, _amount, gettersData)); + } + + /// @dev Receives and parses (name, symbol, decimals) from the token contract + function _getERC20Getters(address _token) internal view returns (bytes memory) { + if (_token == ETH_TOKEN_ADDRESS) { + bytes memory name = bytes("Ether"); + bytes memory symbol = bytes("ETH"); + bytes memory decimals = abi.encode(uint8(18)); + return abi.encode(name, symbol, decimals); // when depositing eth to a non-eth based chain it is an ERC20 + } + + (, bytes memory data1) = _token.staticcall(abi.encodeCall(IERC20Metadata.name, ())); + (, bytes memory data2) = _token.staticcall(abi.encodeCall(IERC20Metadata.symbol, ())); + (, bytes memory data3) = _token.staticcall(abi.encodeCall(IERC20Metadata.decimals, ())); + return abi.encode(data1, data2, data3); + } + + /// @dev Withdraw funds from the initiated deposit, that failed when finalizing on L2 + /// @param _depositSender The address of the deposit initiator + /// @param _l1Token The address of the deposited L1 ERC20 token + /// @param _amount The amount of the deposit that failed. + /// @param _l2TxHash The L2 transaction hash of the failed deposit finalization + /// @param _l2BatchNumber The L2 batch number where the deposit finalization was processed + /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message + /// @param _l2TxNumberInBatch The L2 transaction number in a batch, in which the log was sent + /// @param _merkleProof The Merkle proof of the processing L1 -> L2 transaction with deposit finalization + function claimFailedDeposit( + uint256 _chainId, + address _depositSender, + address _l1Token, + uint256 _amount, + bytes32 _l2TxHash, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes32[] calldata _merkleProof + ) external override { + _claimFailedDeposit( + false, + _chainId, + _depositSender, + _l1Token, + _amount, + _l2TxHash, + _l2BatchNumber, + _l2MessageIndex, + _l2TxNumberInBatch, + _merkleProof + ); + } + + /// @dev Processes claims of failed deposit, whether they originated from the legacy bridge or the current system. + function _claimFailedDeposit( + bool _checkedInLegacyBridge, + uint256 _chainId, + address _depositSender, + address _l1Token, + uint256 _amount, + bytes32 _l2TxHash, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes32[] calldata _merkleProof + ) internal nonReentrant { + { + bool proofValid = bridgehub.proveL1ToL2TransactionStatus( + _chainId, + _l2TxHash, + _l2BatchNumber, + _l2MessageIndex, + _l2TxNumberInBatch, + _merkleProof, + TxStatus.Failure + ); + require(proofValid, "yn"); + } + require(_amount > 0, "y1"); + + { + bool notCheckedInLegacyBridgeOrWeCanCheckDeposit; + { + // Deposits that happened before the upgrade cannot be checked here, they have to be claimed and checked in the legacyBridge + bool weCanCheckDepositHere = !_isEraLegacyWithdrawal(_chainId, _l2BatchNumber); + // Double claims are not possible, as we this check except for legacy bridge withdrawals + // Funds claimed before the update will still be recorded in the legacy bridge + // Note we double check NEW deposits if they are called from the legacy bridge + notCheckedInLegacyBridgeOrWeCanCheckDeposit = (!_checkedInLegacyBridge) || weCanCheckDepositHere; + } + if (notCheckedInLegacyBridgeOrWeCanCheckDeposit) { + bytes32 dataHash = depositHappened[_chainId][_l2TxHash]; + bytes32 txDataHash = keccak256(abi.encode(_depositSender, _l1Token, _amount)); + require(dataHash == txDataHash, "ShB: d.it not hap"); + delete depositHappened[_chainId][_l2TxHash]; + } + } + + if (!hyperbridgingEnabled[_chainId]) { + // check that the chain has sufficient balance + require(chainBalance[_chainId][_l1Token] >= _amount, "ShB n funds"); + chainBalance[_chainId][_l1Token] -= _amount; + } + + // Withdraw funds + if (_l1Token == ETH_TOKEN_ADDRESS) { + bool callSuccess; + // Low-level assembly call, to avoid any memory copying (save gas) + assembly { + callSuccess := call(gas(), _depositSender, _amount, 0, 0, 0, 0) + } + require(callSuccess, "ShB: claimFailedDeposit failed"); + } else { + IERC20(_l1Token).safeTransfer(_depositSender, _amount); + // Note we don't allow weth deposits anymore, but there might be legacy weth deposits. + // until we add Weth bridging capabilities, we don't wrap/unwrap weth to ether. + } + + emit ClaimedFailedDepositSharedBridge(_chainId, _depositSender, _l1Token, _amount); + } + + /// @dev Determines if a withdrawal was initiated on zkSync Era before the upgrade to the Shared Bridge. + /// @param _chainId The chain ID of the transaction to check. + /// @param _l2BatchNumber The L2 batch number for the withdrawal. + /// @return Whether withdrawal was initiated on zkSync Era before Shared Bridge upgrade. + function _isEraLegacyWithdrawal(uint256 _chainId, uint256 _l2BatchNumber) internal view returns (bool) { + return (_chainId == ERA_CHAIN_ID) && (_l2BatchNumber < eraFirstPostUpgradeBatch); + } + + /// @notice Finalize the withdrawal and release funds + /// @param _chainId The chain ID of the transaction to check + /// @param _l2BatchNumber The L2 batch number where the withdrawal was processed + /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message + /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the log was sent + /// @param _message The L2 withdraw data, stored in an L2 -> L1 message + /// @param _merkleProof The Merkle proof of the inclusion L2 -> L1 message about withdrawal initialization + function finalizeWithdrawal( + uint256 _chainId, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes calldata _message, + bytes32[] calldata _merkleProof + ) external override { + // To avoid rewithdrawing txs that have already happened on the legacy bridge. + // Note: new withdraws are all recorded here, so double withdrawing them is not possible. + if (_isEraLegacyWithdrawal(_chainId, _l2BatchNumber)) { + require(!legacyBridge.isWithdrawalFinalized(_l2BatchNumber, _l2MessageIndex), "ShB: legacy withdrawal"); + } + _finalizeWithdrawal(_chainId, _l2BatchNumber, _l2MessageIndex, _l2TxNumberInBatch, _message, _merkleProof); + } + + struct MessageParams { + uint256 l2BatchNumber; + uint256 l2MessageIndex; + uint16 l2TxNumberInBatch; + } + + /// @dev Internal function that handles the logic for finalizing withdrawals, + /// serving both the current bridge system and the legacy ERC20 bridge. + function _finalizeWithdrawal( + uint256 _chainId, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes calldata _message, + bytes32[] calldata _merkleProof + ) internal nonReentrant returns (address l1Receiver, address l1Token, uint256 amount) { + require(!isWithdrawalFinalized[_chainId][_l2BatchNumber][_l2MessageIndex], "Withdrawal is already finalized"); + isWithdrawalFinalized[_chainId][_l2BatchNumber][_l2MessageIndex] = true; + + // Handling special case for withdrawal from zkSync Era initiated before Shared Bridge. + if (_isEraLegacyWithdrawal(_chainId, _l2BatchNumber)) { + // Checks that the withdrawal wasn't finalized already. + bool alreadyFinalized = IGetters(ERA_DIAMOND_PROXY).isEthWithdrawalFinalized( + _l2BatchNumber, + _l2MessageIndex + ); + require(!alreadyFinalized, "Withdrawal is already finalized 2"); + } + + MessageParams memory messageParams = MessageParams({ + l2BatchNumber: _l2BatchNumber, + l2MessageIndex: _l2MessageIndex, + l2TxNumberInBatch: _l2TxNumberInBatch + }); + (l1Receiver, l1Token, amount) = _checkWithdrawal(_chainId, messageParams, _message, _merkleProof); + + if (!hyperbridgingEnabled[_chainId]) { + // Check that the chain has sufficient balance + require(chainBalance[_chainId][l1Token] >= amount, "ShB not enough funds 2"); // not enought funds + chainBalance[_chainId][l1Token] -= amount; + } + + if (l1Token == ETH_TOKEN_ADDRESS) { + bool callSuccess; + // Low-level assembly call, to avoid any memory copying (save gas) + assembly { + callSuccess := call(gas(), l1Receiver, amount, 0, 0, 0, 0) + } + require(callSuccess, "ShB: withdraw failed"); + } else { + // Withdraw funds + IERC20(l1Token).safeTransfer(l1Receiver, amount); + } + emit WithdrawalFinalizedSharedBridge(_chainId, l1Receiver, l1Token, amount); + } + + /// @dev Verifies the validity of a withdrawal message from L2 and returns details of the withdrawal. + function _checkWithdrawal( + uint256 _chainId, + MessageParams memory _messageParams, + bytes calldata _message, + bytes32[] calldata _merkleProof + ) internal view returns (address l1Receiver, address l1Token, uint256 amount) { + (l1Receiver, l1Token, amount) = _parseL2WithdrawalMessage(_chainId, _message); + L2Message memory l2ToL1Message; + { + bool baseTokenWithdrawal = (l1Token == bridgehub.baseToken(_chainId)); + address l2Sender = baseTokenWithdrawal ? L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR : l2BridgeAddress[_chainId]; + + l2ToL1Message = L2Message({ + txNumberInBatch: _messageParams.l2TxNumberInBatch, + sender: l2Sender, + data: _message + }); + } + + bool success = bridgehub.proveL2MessageInclusion( + _chainId, + _messageParams.l2BatchNumber, + _messageParams.l2MessageIndex, + l2ToL1Message, + _merkleProof + ); + require(success, "ShB withd w proof"); // withdrawal wrong proof + } + + function _parseL2WithdrawalMessage( + uint256 _chainId, + bytes memory _l2ToL1message + ) internal view returns (address l1Receiver, address l1Token, uint256 amount) { + // We check that the message is long enough to read the data. + // Please note that there are two versions of the message: + // 1. The message that is sent by `withdraw(address _l1Receiver)` + // It should be equal to the length of the bytes4 function signature + address l1Receiver + uint256 amount = 4 + 20 + 32 = 56 (bytes). + // 2. The message that is sent by `withdrawWithMessage(address _l1Receiver, bytes calldata _additionalData)` + // It should be equal to the length of the following: + // bytes4 function signature + address l1Receiver + uint256 amount + address l2Sender + bytes _additionalData = + // = 4 + 20 + 32 + 32 + _additionalData.length >= 68 (bytes). + + // So the data is expected to be at least 56 bytes long. + require(_l2ToL1message.length >= 56, "ShB wrong msg len"); // wrong messsage length + + (uint32 functionSignature, uint256 offset) = UnsafeBytes.readUint32(_l2ToL1message, 0); + if (bytes4(functionSignature) == IMailbox.finalizeEthWithdrawal.selector) { + // this message is a base token withdrawal + (l1Receiver, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); + (amount, offset) = UnsafeBytes.readUint256(_l2ToL1message, offset); + l1Token = bridgehub.baseToken(_chainId); + } else if (bytes4(functionSignature) == IL1ERC20Bridge.finalizeWithdrawal.selector) { + // We use the IL1ERC20Bridge for backward compatibility with old withdrawals. + + // this message is a token withdrawal + + // Check that the message length is correct. + // It should be equal to the length of the function signature + address + address + uint256 = 4 + 20 + 20 + 32 = + // 76 (bytes). + require(_l2ToL1message.length == 76, "ShB wrong msg len 2"); + (l1Receiver, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); + (l1Token, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); + (amount, offset) = UnsafeBytes.readUint256(_l2ToL1message, offset); + } else { + revert("ShB Incorrect message function selector"); + } + } + + /*////////////////////////////////////////////////////////////// + ERA LEGACY FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @notice Initiates a deposit by locking funds on the contract and sending the request + /// of processing an L2 transaction where tokens would be minted. + /// @dev If the token is bridged for the first time, the L2 token contract will be deployed. Note however, that the + /// newly-deployed token does not support any custom logic, i.e. rebase tokens' functionality is not supported. + /// @param _l2Receiver The account address that should receive funds on L2 + /// @param _l1Token The L1 token address which is deposited + /// @param _amount The total amount of tokens to be bridged + /// @param _l2TxGasLimit The L2 gas limit to be used in the corresponding L2 transaction + /// @param _l2TxGasPerPubdataByte The gasPerPubdataByteLimit to be used in the corresponding L2 transaction + /// @param _refundRecipient The address on L2 that will receive the refund for the transaction. + /// @dev If the L2 deposit finalization transaction fails, the `_refundRecipient` will receive the `_l2Value`. + /// Please note, the contract may change the refund recipient's address to eliminate sending funds to addresses + /// out of control. + /// - If `_refundRecipient` is a contract on L1, the refund will be sent to the aliased `_refundRecipient`. + /// - If `_refundRecipient` is set to `address(0)` and the sender has NO deployed bytecode on L1, the refund will + /// be sent to the `msg.sender` address. + /// - If `_refundRecipient` is set to `address(0)` and the sender has deployed bytecode on L1, the refund will be + /// sent to the aliased `msg.sender` address. + /// @dev The address aliasing of L1 contracts as refund recipient on L2 is necessary to guarantee that the funds + /// are controllable through the Mailbox, since the Mailbox applies address aliasing to the from address for the + /// L2 tx if the L1 msg.sender is a contract. Without address aliasing for L1 contracts as refund recipients they + /// would not be able to make proper L2 tx requests through the Mailbox to use or withdraw the funds from L2, and + /// the funds would be lost. + /// @return l2TxHash The L2 transaction hash of deposit finalization. + function depositLegacyErc20Bridge( + address _prevMsgSender, + address _l2Receiver, + address _l1Token, + uint256 _amount, + uint256 _l2TxGasLimit, + uint256 _l2TxGasPerPubdataByte, + address _refundRecipient + ) external payable override onlyLegacyBridge nonReentrant returns (bytes32 l2TxHash) { + require(l2BridgeAddress[ERA_CHAIN_ID] != address(0), "ShB b. n dep"); + require(_l1Token != l1WethAddress, "ShB: WETH deposit not supported 2"); + + // Note that funds have been transferred to this contract in the legacy ERC20 bridge. + if (!hyperbridgingEnabled[ERA_CHAIN_ID]) { + chainBalance[ERA_CHAIN_ID][_l1Token] += _amount; + } + + bytes memory l2TxCalldata = _getDepositL2Calldata(_prevMsgSender, _l2Receiver, _l1Token, _amount); + + { + // If the refund recipient is not specified, the refund will be sent to the sender of the transaction. + // Otherwise, the refund will be sent to the specified address. + // If the recipient is a contract on L1, the address alias will be applied. + address refundRecipient = _refundRecipient; + if (_refundRecipient == address(0)) { + refundRecipient = _prevMsgSender != tx.origin + ? AddressAliasHelper.applyL1ToL2Alias(_prevMsgSender) + : _prevMsgSender; + } + + L2TransactionRequestDirect memory request = L2TransactionRequestDirect({ + chainId: ERA_CHAIN_ID, + l2Contract: l2BridgeAddress[ERA_CHAIN_ID], + mintValue: msg.value, // l2 gas + l2 msg.Value the bridgehub will withdraw the mintValue from the base token bridge for gas + l2Value: 0, // L2 msg.value, this contract doesn't support base token deposits or wrapping functionality, for direct deposits use bridgehub + l2Calldata: l2TxCalldata, + l2GasLimit: _l2TxGasLimit, + l2GasPerPubdataByteLimit: _l2TxGasPerPubdataByte, + factoryDeps: new bytes[](0), + refundRecipient: refundRecipient + }); + l2TxHash = bridgehub.requestL2TransactionDirect{value: msg.value}(request); + } + + bytes32 txDataHash = keccak256(abi.encode(_prevMsgSender, _l1Token, _amount)); + // Save the deposited amount to claim funds on L1 if the deposit failed on L2 + depositHappened[ERA_CHAIN_ID][l2TxHash] = txDataHash; + + emit LegacyDepositInitiated(ERA_CHAIN_ID, l2TxHash, _prevMsgSender, _l2Receiver, _l1Token, _amount); + } + + /// @notice Finalizes the withdrawal for transactions initiated via the legacy ERC20 bridge. + /// @param _l2BatchNumber The L2 batch number where the withdrawal was processed + /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message + /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the log was sent + /// @param _message The L2 withdraw data, stored in an L2 -> L1 message + /// @param _merkleProof The Merkle proof of the inclusion L2 -> L1 message about withdrawal initialization + /// + /// @return l1Receiver The address on L1 that will receive the withdrawn funds + /// @return l1Token The address of the L1 token being withdrawn + /// @return amount The amount of the token being withdrawns + function finalizeWithdrawalLegacyErc20Bridge( + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes calldata _message, + bytes32[] calldata _merkleProof + ) external override onlyLegacyBridge returns (address l1Receiver, address l1Token, uint256 amount) { + (l1Receiver, l1Token, amount) = _finalizeWithdrawal( + ERA_CHAIN_ID, + _l2BatchNumber, + _l2MessageIndex, + _l2TxNumberInBatch, + _message, + _merkleProof + ); + } + + /// @notice Withdraw funds from the initiated deposit, that failed when finalizing on zkSync Era chain. + /// This function is specifically designed for maintaining backward-compatibility with legacy `claimFailedDeposit` + /// method in `L1ERC20Bridge`. + /// + /// @param _depositSender The address of the deposit initiator + /// @param _l1Token The address of the deposited L1 ERC20 token + /// @param _amount The amount of the deposit that failed. + /// @param _l2TxHash The L2 transaction hash of the failed deposit finalization + /// @param _l2BatchNumber The L2 batch number where the deposit finalization was processed + /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message + /// @param _l2TxNumberInBatch The L2 transaction number in a batch, in which the log was sent + /// @param _merkleProof The Merkle proof of the processing L1 -> L2 transaction with deposit finalization + function claimFailedDepositLegacyErc20Bridge( + address _depositSender, + address _l1Token, + uint256 _amount, + bytes32 _l2TxHash, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes32[] calldata _merkleProof + ) external override onlyLegacyBridge { + _claimFailedDeposit( + true, + ERA_CHAIN_ID, + _depositSender, + _l1Token, + _amount, + _l2TxHash, + _l2BatchNumber, + _l2MessageIndex, + _l2TxNumberInBatch, + _merkleProof + ); + } +} diff --git a/l1-contracts/contracts/bridge/L1WethBridge.sol b/l1-contracts/contracts/bridge/L1WethBridge.sol deleted file mode 100644 index 53bc45bd0e16..000000000000 --- a/l1-contracts/contracts/bridge/L1WethBridge.sol +++ /dev/null @@ -1,313 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -import {IL1Bridge} from "./interfaces/IL1Bridge.sol"; -import {IL2WethBridge} from "./interfaces/IL2WethBridge.sol"; -import {IL2Bridge} from "./interfaces/IL2Bridge.sol"; -import {IWETH9} from "./interfaces/IWETH9.sol"; -import {IZkSync} from "../zksync/interfaces/IZkSync.sol"; - -import {BridgeInitializationHelper} from "./libraries/BridgeInitializationHelper.sol"; - -import {IMailbox} from "../zksync/interfaces/IMailbox.sol"; -import {L2Message} from "../zksync/Storage.sol"; - -import {UnsafeBytes} from "../common/libraries/UnsafeBytes.sol"; -import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; -import {L2ContractHelper} from "../common/libraries/L2ContractHelper.sol"; -import {L2_ETH_TOKEN_SYSTEM_CONTRACT_ADDR} from "../common/L2ContractAddresses.sol"; -import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; - -/// @author Matter Labs -/// @custom:security-contact security@matterlabs.dev -/// @dev This contract is designed to streamline and enhance the user experience -/// for bridging WETH tokens between L1 and L2 networks. The primary goal of this bridge is to -/// simplify the process by minimizing the number of transactions required, thus improving -/// efficiency and user experience. -/// @dev The default workflow for bridging WETH is performing three separate transactions: unwrap WETH to ETH, -/// deposit ETH to L2, and wrap ETH to WETH on L2. The `L1WethBridge` reduces this to a single -/// transaction, enabling users to bridge their WETH tokens directly between L1 and L2 networks. -/// @dev This contract accepts WETH deposits on L1, unwraps them to ETH, and sends the ETH to the L2 -/// WETH bridge contract, where it is wrapped back into WETH and delivered to the L2 recipient. -/// @dev For withdrawals, the contract receives ETH from the L2 WETH bridge contract, wraps it into -/// WETH, and sends the WETH to the L1 recipient. -/// @dev The `L1WethBridge` contract works in conjunction with its L2 counterpart, `L2WethBridge`. -contract L1WethBridge is IL1Bridge, ReentrancyGuard { - using SafeERC20 for IERC20; - - /// @dev Event emitted when ETH is received by the contract. - event EthReceived(uint256 amount); - - /// @dev The address of the WETH token on L1 - address payable public immutable l1WethAddress; - - /// @dev zkSync smart contract that is used to operate with L2 via asynchronous L2 <-> L1 communication - IZkSync public immutable zkSync; - - /// @dev The address of deployed L2 WETH bridge counterpart - address public l2Bridge; - - /// @dev The address of the WETH on L2 - address public l2WethAddress; - - /// @dev A mapping L2 batch number => message number => flag - /// @dev Used to indicate that zkSync L2 -> L1 WETH message was already processed - mapping(uint256 l2BatchNumber => mapping(uint256 l2ToL1MessageNumber => bool isFinalized)) - public isWithdrawalFinalized; - - /// @dev Contract is expected to be used as proxy implementation. - /// @dev Initialize the implementation to prevent Parity hack. - constructor(address payable _l1WethAddress, IZkSync _zkSync) reentrancyGuardInitializer { - l1WethAddress = _l1WethAddress; - zkSync = _zkSync; - } - - /// @dev Initializes a contract bridge for later use. Expected to be used in the proxy - /// @dev During initialization deploys L2 WETH bridge counterpart as well as provides some factory deps for it - /// @param _factoryDeps A list of raw bytecodes that are needed for deployment of the L2 WETH bridge - /// @notice _factoryDeps[0] == a raw bytecode of L2 WETH bridge implementation - /// @notice _factoryDeps[1] == a raw bytecode of proxy that is used as L2 WETH bridge - /// @param _l2WethAddress Pre-calculated address of L2 WETH token - /// @param _governor Address which can change L2 WETH token implementation and upgrade the bridge - /// @param _deployBridgeImplementationFee The fee that will be paid for the L1 -> L2 transaction for deploying the L2 - /// bridge implementation - /// @param _deployBridgeProxyFee The fee that will be paid for the L1 -> L2 transaction for deploying the L2 bridge - /// proxy - function initialize( - bytes[] calldata _factoryDeps, - address _l2WethAddress, - address _governor, - uint256 _deployBridgeImplementationFee, - uint256 _deployBridgeProxyFee - ) external payable reentrancyGuardInitializer { - require(_l2WethAddress != address(0), "L2 WETH address cannot be zero"); - require(_governor != address(0), "Governor address cannot be zero"); - require(_factoryDeps.length == 2, "Invalid factory deps length provided"); - require( - msg.value == _deployBridgeImplementationFee + _deployBridgeProxyFee, - "Miscalculated deploy transactions fees" - ); - - l2WethAddress = _l2WethAddress; - - bytes32 l2WethBridgeImplementationBytecodeHash = L2ContractHelper.hashL2Bytecode(_factoryDeps[0]); - bytes32 l2WethBridgeProxyBytecodeHash = L2ContractHelper.hashL2Bytecode(_factoryDeps[1]); - - // Deploy L2 bridge implementation contract - address wethBridgeImplementationAddr = BridgeInitializationHelper.requestDeployTransaction( - zkSync, - _deployBridgeImplementationFee, - l2WethBridgeImplementationBytecodeHash, - "", // Empty constructor data - _factoryDeps // All factory deps are needed for L2 bridge - ); - - // Prepare the proxy constructor data - bytes memory l2WethBridgeProxyConstructorData; - { - // Data to be used in delegate call to initialize the proxy - bytes memory proxyInitializationParams = abi.encodeCall( - IL2WethBridge.initialize, - (address(this), l1WethAddress, _l2WethAddress) - ); - l2WethBridgeProxyConstructorData = abi.encode( - wethBridgeImplementationAddr, - _governor, - proxyInitializationParams - ); - } - - // Deploy L2 bridge proxy contract - l2Bridge = BridgeInitializationHelper.requestDeployTransaction( - zkSync, - _deployBridgeProxyFee, - l2WethBridgeProxyBytecodeHash, - l2WethBridgeProxyConstructorData, - // No factory deps are needed for the L2 bridge proxy, because it is already passed in the previous step - new bytes[](0) - ); - } - - /// @notice Initiates a WETH deposit by depositing WETH into the L1 bridge contract, unwrapping it to ETH - /// and sending it to the L2 bridge contract where ETH will be wrapped again to WETH and sent to the L2 recipient. - /// @param _l2Receiver The account address that should receive WETH on L2 - /// @param _l1Token The L1 token address which is deposited (needs to be WETH address) - /// @param _amount The total amount of tokens to be bridged - /// @param _l2TxGasLimit The L2 gas limit to be used in the corresponding L2 transaction - /// @param _l2TxGasPerPubdataByte The gasPerPubdataByteLimit to be used in the corresponding L2 transaction - /// @param _refundRecipient The address on L2 that will receive the refund for the transaction. - /// @dev If the L2 deposit finalization transaction fails, the `_refundRecipient` will receive the `_l2Value`. - /// Please note, the contract may change the refund recipient's address to eliminate sending funds to addresses - /// out of control. - /// - If `_refundRecipient` is a contract on L1, the refund will be sent to the aliased `_refundRecipient`. - /// - If `_refundRecipient` is set to `address(0)` and the sender has NO deployed bytecode on L1, the refund will - /// be sent to the `msg.sender` address. - /// - If `_refundRecipient` is set to `address(0)` and the sender has deployed bytecode on L1, the refund will be - /// sent to the aliased `msg.sender` address. - /// @dev The address aliasing of L1 contracts as refund recipient on L2 is necessary to guarantee that the funds - /// are controllable through the Mailbox, - /// since the Mailbox applies address aliasing to the from address for the L2 tx if the L1 msg.sender is a contract. - /// Without address aliasing for L1 contracts as refund recipients they would not be able to make proper L2 tx - /// requests - /// through the Mailbox to use or withdraw the funds from L2, and the funds would be lost. - /// @return txHash The L2 transaction hash of deposit finalization - function deposit( - address _l2Receiver, - address _l1Token, - uint256 _amount, - uint256 _l2TxGasLimit, - uint256 _l2TxGasPerPubdataByte, - address _refundRecipient - ) external payable nonReentrant returns (bytes32 txHash) { - require(_l1Token == l1WethAddress, "Invalid L1 token address"); - require(_amount != 0, "Amount cannot be zero"); - - // Deposit WETH tokens from the depositor address to the smart contract address - IERC20(l1WethAddress).safeTransferFrom(msg.sender, address(this), _amount); - // Unwrap WETH tokens (smart contract address receives the equivalent amount of ETH) - IWETH9(l1WethAddress).withdraw(_amount); - - // Request the finalization of the deposit on the L2 side - bytes memory l2TxCalldata = _getDepositL2Calldata(msg.sender, _l2Receiver, l1WethAddress, _amount); - - // If the refund recipient is not specified, the refund will be sent to the sender of the transaction. - // Otherwise, the refund will be sent to the specified address. - // If the recipient is a contract on L1, the address alias will be applied. - address refundRecipient = _refundRecipient; - if (_refundRecipient == address(0)) { - refundRecipient = msg.sender != tx.origin ? AddressAliasHelper.applyL1ToL2Alias(msg.sender) : msg.sender; - } - txHash = zkSync.requestL2Transaction{value: _amount + msg.value}( - l2Bridge, - _amount, - l2TxCalldata, - _l2TxGasLimit, - _l2TxGasPerPubdataByte, - new bytes[](0), - refundRecipient - ); - - emit DepositInitiated(txHash, msg.sender, _l2Receiver, _l1Token, _amount); - } - - /// @dev Generate a calldata for calling the deposit finalization on the L2 WETH bridge contract - function _getDepositL2Calldata( - address _l1Sender, - address _l2Receiver, - address _l1Token, - uint256 _amount - ) internal pure returns (bytes memory txCalldata) { - txCalldata = abi.encodeCall( - IL2Bridge.finalizeDeposit, - (_l1Sender, _l2Receiver, _l1Token, _amount, new bytes(0)) - ); - } - - /// @notice Withdraw funds from the initiated deposit, that failed when finalizing on L2. - /// Note: Refund is performed by sending an equivalent amount of ETH on L2 to the specified deposit refund - /// recipient address. - function claimFailedDeposit( - address, // _depositSender, - address, // _l1Token, - bytes32, // _l2TxHash - uint256, // _l2BatchNumber, - uint256, // _l2MessageIndex, - uint16, // _l2TxNumberInBatch, - bytes32[] calldata // _merkleProof - ) external pure { - revert("Method not supported. Failed deposit funds are sent to the L2 refund recipient address."); - } - - /// @notice Finalize the withdrawal and release funds - /// @param _l2BatchNumber The L2 batch number where the ETH (WETH) withdrawal was processed - /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the ETH - /// withdrawal message containing additional data about WETH withdrawal - /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the ETH withdrawal log was sent - /// @param _message The L2 withdraw data, stored in an L2 -> L1 message - /// @param _merkleProof The Merkle proof of the inclusion L2 -> L1 message about withdrawal initialization - function finalizeWithdrawal( - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes calldata _message, - bytes32[] calldata _merkleProof - ) external nonReentrant { - require(!isWithdrawalFinalized[_l2BatchNumber][_l2MessageIndex], "Withdrawal is already finalized"); - - (address l1WethWithdrawReceiver, uint256 amount) = _parseL2EthWithdrawalMessage(_message); - - // Check if the withdrawal has already been finalized on L2. - bool alreadyFinalised = zkSync.isEthWithdrawalFinalized(_l2BatchNumber, _l2MessageIndex); - if (alreadyFinalised) { - // Check that the specified message was actually sent while withdrawing eth from L2. - L2Message memory l2ToL1Message = L2Message({ - txNumberInBatch: _l2TxNumberInBatch, - sender: L2_ETH_TOKEN_SYSTEM_CONTRACT_ADDR, - data: _message - }); - bool success = zkSync.proveL2MessageInclusion(_l2BatchNumber, _l2MessageIndex, l2ToL1Message, _merkleProof); - require(success, "vq"); - } else { - // Finalize the withdrawal if it is not yet done. - zkSync.finalizeEthWithdrawal(_l2BatchNumber, _l2MessageIndex, _l2TxNumberInBatch, _message, _merkleProof); - } - - // Wrap ETH to WETH tokens (smart contract address receives the equivalent amount of WETH) - IWETH9(l1WethAddress).deposit{value: amount}(); - // Transfer WETH tokens from the smart contract address to the withdrawal receiver - IERC20(l1WethAddress).safeTransfer(l1WethWithdrawReceiver, amount); - - isWithdrawalFinalized[_l2BatchNumber][_l2MessageIndex] = true; - - emit WithdrawalFinalized(l1WethWithdrawReceiver, l1WethAddress, amount); - } - - /// @dev Decode the ETH withdraw message with additional data about WETH withdrawal that came from L2EthToken - /// contract - function _parseL2EthWithdrawalMessage( - bytes memory _message - ) internal view returns (address l1WethReceiver, uint256 ethAmount) { - // Check that the message length is correct. - // additionalData (WETH withdrawal data): l2 sender address + weth receiver address = 20 + 20 = 40 (bytes) - // It should be equal to the length of the function signature + eth receiver address + uint256 amount + - // additionalData = 4 + 20 + 32 + 40 = 96 (bytes). - require(_message.length == 96, "Incorrect ETH message with additional data length"); - - (uint32 functionSignature, uint256 offset) = UnsafeBytes.readUint32(_message, 0); - require( - bytes4(functionSignature) == IMailbox.finalizeEthWithdrawal.selector, - "Incorrect ETH message function selector" - ); - - address l1EthReceiver; - (l1EthReceiver, offset) = UnsafeBytes.readAddress(_message, offset); - require(l1EthReceiver == address(this), "Wrong L1 ETH withdraw receiver"); - - (ethAmount, offset) = UnsafeBytes.readUint256(_message, offset); - - address l2Sender; - (l2Sender, offset) = UnsafeBytes.readAddress(_message, offset); - require(l2Sender == l2Bridge, "The withdrawal was not initiated by L2 bridge"); - - // Parse additional data - (l1WethReceiver, offset) = UnsafeBytes.readAddress(_message, offset); - } - - /// @return l2Token Address of an L2 token counterpart. - function l2TokenAddress(address _l1Token) public view override returns (address l2Token) { - l2Token = _l1Token == l1WethAddress ? l2WethAddress : address(0); - } - - /// @dev The receive function is called when ETH is sent directly to the contract. - receive() external payable { - // Expected to receive ether in two cases: - // 1. l1 WETH sends ether on `withdraw` - // 2. zkSync contract withdraw funds in `finalizeEthWithdrawal` - require(msg.sender == l1WethAddress || msg.sender == address(zkSync), "pn"); - emit EthReceived(msg.value); - } -} diff --git a/l1-contracts/contracts/bridge/interfaces/IL1BridgeLegacy.sol b/l1-contracts/contracts/bridge/interfaces/IL1BridgeLegacy.sol deleted file mode 100644 index b8185061fba2..000000000000 --- a/l1-contracts/contracts/bridge/interfaces/IL1BridgeLegacy.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -/// @title L1 Bridge contract legacy interface -/// @author Matter Labs -/// @custom:security-contact security@matterlabs.dev -interface IL1BridgeLegacy { - function deposit( - address _l2Receiver, - address _l1Token, - uint256 _amount, - uint256 _l2TxGasLimit, - uint256 _l2TxGasPerPubdataByte - ) external payable returns (bytes32 txHash); -} diff --git a/l1-contracts/contracts/bridge/interfaces/IL1Bridge.sol b/l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol similarity index 65% rename from l1-contracts/contracts/bridge/interfaces/IL1Bridge.sol rename to l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol index 2cb38c448e6c..3082113e8de9 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL1Bridge.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol @@ -2,10 +2,13 @@ pragma solidity 0.8.20; -/// @title L1 Bridge contract interface +import "./IL1SharedBridge.sol"; + +/// @title L1 Bridge contract legacy interface /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -interface IL1Bridge { +/// @notice Legacy Bridge interface before hyperchain migration, used for backward compatibility with zkSync Era +interface IL1ERC20Bridge { event DepositInitiated( bytes32 indexed l2DepositTxHash, address indexed from, @@ -29,6 +32,14 @@ interface IL1Bridge { address _refundRecipient ) external payable returns (bytes32 txHash); + function deposit( + address _l2Receiver, + address _l1Token, + uint256 _amount, + uint256 _l2TxGasLimit, + uint256 _l2TxGasPerPubdataByte + ) external payable returns (bytes32 txHash); + function claimFailedDeposit( address _depositSender, address _l1Token, @@ -49,5 +60,17 @@ interface IL1Bridge { function l2TokenAddress(address _l1Token) external view returns (address); + function sharedBridge() external view returns (IL1SharedBridge); + + function l2TokenBeacon() external view returns (address); + function l2Bridge() external view returns (address); + + function depositAmount( + address _account, + address _l1Token, + bytes32 _depositL2TxHash + ) external returns (uint256 amount); + + function tranferTokenToSharedBridge(address _token, uint256 _amount) external; } diff --git a/l1-contracts/contracts/bridge/interfaces/IL1SharedBridge.sol b/l1-contracts/contracts/bridge/interfaces/IL1SharedBridge.sol new file mode 100644 index 000000000000..22e1d9e934d1 --- /dev/null +++ b/l1-contracts/contracts/bridge/interfaces/IL1SharedBridge.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {L2TransactionRequestTwoBridgesInner} from "../../bridgehub/IBridgehub.sol"; +import {IBridgehub} from "../../bridgehub/IBridgehub.sol"; +import {IL1ERC20Bridge} from "./IL1ERC20Bridge.sol"; + +/// @title L1 Bridge contract interface +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +interface IL1SharedBridge { + event LegacyDepositInitiated( + uint256 indexed chainId, + bytes32 indexed l2DepositTxHash, + address indexed from, + address to, + address l1Token, + uint256 amount + ); + + event BridgehubDepositInitiated( + uint256 indexed chainId, + bytes32 indexed txDataHash, + address indexed from, + address to, + address l1Token, + uint256 amount + ); + + event BridgehubDepositBaseTokenInitiated( + uint256 indexed chainId, + address indexed from, + address l1Token, + uint256 amount + ); + + event BridgehubDepositFinalized( + uint256 indexed chainId, + bytes32 indexed txDataHash, + bytes32 indexed l2DepositTxHash + ); + + event WithdrawalFinalizedSharedBridge( + uint256 indexed chainId, + address indexed to, + address indexed l1Token, + uint256 amount + ); + + event ClaimedFailedDepositSharedBridge( + uint256 indexed chainId, + address indexed to, + address indexed l1Token, + uint256 amount + ); + + function isWithdrawalFinalized( + uint256 _chainId, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex + ) external view returns (bool); + + function depositLegacyErc20Bridge( + address _msgSender, + address _l2Receiver, + address _l1Token, + uint256 _amount, + uint256 _l2TxGasLimit, + uint256 _l2TxGasPerPubdataByte, + address _refundRecipient + ) external payable returns (bytes32 txHash); + + function claimFailedDepositLegacyErc20Bridge( + address _depositSender, + address _l1Token, + uint256 _amount, + bytes32 _l2TxHash, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes32[] calldata _merkleProof + ) external; + + function claimFailedDeposit( + uint256 _chainId, + address _depositSender, + address _l1Token, + uint256 _amount, + bytes32 _l2TxHash, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes32[] calldata _merkleProof + ) external; + + function finalizeWithdrawalLegacyErc20Bridge( + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes calldata _message, + bytes32[] calldata _merkleProof + ) external returns (address l1Receiver, address l1Token, uint256 amount); + + function finalizeWithdrawal( + uint256 _chainId, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes calldata _message, + bytes32[] calldata _merkleProof + ) external; + + function l1WethAddress() external view returns (address); + + function bridgehub() external view returns (IBridgehub); + + function legacyBridge() external view returns (IL1ERC20Bridge); + + function l2BridgeAddress(uint256 _chainId) external view returns (address); + + function depositHappened(uint256 _chainId, bytes32 _l2TxHash) external view returns (bytes32); + + /// data is abi encoded : + /// address _l1Token, + /// uint256 _amount, + /// address _l2Receiver + function bridgehubDeposit( + uint256 _chainId, + address _prevMsgSender, + uint256 _l2Value, + bytes calldata _data + ) external payable returns (L2TransactionRequestTwoBridgesInner memory request); + + function bridgehubDepositBaseToken( + uint256 _chainId, + address _prevMsgSender, + address _l1Token, + uint256 _amount + ) external payable; + + function bridgehubConfirmL2Transaction(uint256 _chainId, bytes32 _txDataHash, bytes32 _txHash) external; + + function receiveEth(uint256 _chainId) external payable; +} diff --git a/l1-contracts/contracts/bridge/interfaces/IL2ERC20Bridge.sol b/l1-contracts/contracts/bridge/interfaces/IL2ERC20Bridge.sol deleted file mode 100644 index 7c9cc4cd1b26..000000000000 --- a/l1-contracts/contracts/bridge/interfaces/IL2ERC20Bridge.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -/// @author Matter Labs -interface IL2ERC20Bridge { - function initialize(address _l1Bridge, bytes32 _l2TokenProxyBytecodeHash, address _governor) external; -} diff --git a/l1-contracts/contracts/bridge/interfaces/IL2WethBridge.sol b/l1-contracts/contracts/bridge/interfaces/IL2WethBridge.sol deleted file mode 100644 index a6bff3f3df3b..000000000000 --- a/l1-contracts/contracts/bridge/interfaces/IL2WethBridge.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -interface IL2WethBridge { - function initialize(address _l1Bridge, address _l1WethAddress, address _l2WethAddress) external; -} diff --git a/l1-contracts/contracts/bridge/libraries/BridgeInitializationHelper.sol b/l1-contracts/contracts/bridge/libraries/BridgeInitializationHelper.sol deleted file mode 100644 index b88a42a54169..000000000000 --- a/l1-contracts/contracts/bridge/libraries/BridgeInitializationHelper.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -import "../../zksync/interfaces/IZkSync.sol"; -import "../../vendor/AddressAliasHelper.sol"; -import "../../common/libraries/L2ContractHelper.sol"; -import {L2_DEPLOYER_SYSTEM_CONTRACT_ADDR} from "../../common/L2ContractAddresses.sol"; -import "../../common/interfaces/IL2ContractDeployer.sol"; - -/// @author Matter Labs -/// @custom:security-contact security@matterlabs.dev -/// @dev A helper library for initializing L2 bridges in zkSync L2 network. -library BridgeInitializationHelper { - /// @dev The L2 gas limit for requesting L1 -> L2 transaction of deploying L2 bridge instance. - /// @dev It is big enough to deploy any contract, so we can use the same value for all bridges. - /// NOTE: this constant will be accurately calculated in the future. - uint256 constant DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT = $(DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT); - - /// @dev The default l2GasPricePerPubdata to be used in bridges. - uint256 constant REQUIRED_L2_GAS_PRICE_PER_PUBDATA = $(REQUIRED_L2_GAS_PRICE_PER_PUBDATA); - - /// @notice Requests L2 transaction that will deploy a contract with a given bytecode hash and constructor data. - /// NOTE: it is always used to deploy via create2 with ZERO salt - /// @param _zkSync The address of the zkSync contract - /// @param _deployTransactionFee The fee that will be paid for the L1 -> L2 transaction - /// @param _bytecodeHash The hash of the bytecode of the contract to be deployed - /// @param _constructorData The data to be passed to the contract constructor - /// @param _factoryDeps A list of raw bytecodes that are needed for deployment - function requestDeployTransaction( - IZkSync _zkSync, - uint256 _deployTransactionFee, - bytes32 _bytecodeHash, - bytes memory _constructorData, - bytes[] memory _factoryDeps - ) internal returns (address deployedAddress) { - bytes memory deployCalldata = abi.encodeCall( - IL2ContractDeployer.create2, - (bytes32(0), _bytecodeHash, _constructorData) - ); - _zkSync.requestL2Transaction{value: _deployTransactionFee}( - L2_DEPLOYER_SYSTEM_CONTRACT_ADDR, - 0, - deployCalldata, - DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - _factoryDeps, - msg.sender - ); - - deployedAddress = L2ContractHelper.computeCreate2Address( - // Apply the alias to the address of the bridge contract, to get the `msg.sender` in L2. - AddressAliasHelper.applyL1ToL2Alias(address(this)), - bytes32(0), // Zero salt - _bytecodeHash, - keccak256(_constructorData) - ); - } -} diff --git a/l1-contracts/contracts/bridgehub/Bridgehub.sol b/l1-contracts/contracts/bridgehub/Bridgehub.sol new file mode 100644 index 000000000000..201b42fba610 --- /dev/null +++ b/l1-contracts/contracts/bridgehub/Bridgehub.sol @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; + +import "./IBridgehub.sol"; +import "../bridge/interfaces/IL1SharedBridge.sol"; +import "../state-transition/IStateTransitionManager.sol"; +import "../common/ReentrancyGuard.sol"; +import "../state-transition/chain-interfaces/IZkSyncStateTransition.sol"; +import {ETH_TOKEN_ADDRESS, TWO_BRIDGES_MAGIC_VALUE, BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS} from "../common/Config.sol"; +import {BridgehubL2TransactionRequest} from "../common/Messaging.sol"; +import "../vendor/AddressAliasHelper.sol"; + +contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2Step { + /// @notice all the ether is held by the weth bridge + IL1SharedBridge public sharedBridge; + + /// @notice we store registered stateTransitionManagers + mapping(address _stateTransitionManager => bool) public stateTransitionManagerIsRegistered; + /// @notice we store registered tokens (for arbitrary base token) + mapping(address _token => bool) public tokenIsRegistered; + + /// @notice chainID => StateTransitionManager contract address, storing StateTransitionManager + mapping(uint256 _chainId => address) public stateTransitionManager; + + /// @notice chainID => baseToken contract address, storing baseToken + mapping(uint256 _chainId => address) public baseToken; + + /// @dev used to manage non critical updates + address public admin; + + /// @dev used to accept the admin role + address private pendingAdmin; + + /// @notice to avoid parity hack + constructor() reentrancyGuardInitializer {} + + /// @notice used to initialize the contract + function initialize(address _owner) external reentrancyGuardInitializer { + _transferOwnership(_owner); + } + + modifier onlyOwnerOrAdmin() { + require(msg.sender == admin || msg.sender == owner(), "Bridgehub: not owner or admin"); + _; + } + + /// @inheritdoc IBridgehub + function setPendingAdmin(address _newPendingAdmin) external onlyOwnerOrAdmin { + // Save previous value into the stack to put it into the event later + address oldPendingAdmin = pendingAdmin; + // Change pending admin + pendingAdmin = _newPendingAdmin; + emit NewPendingAdmin(oldPendingAdmin, _newPendingAdmin); + } + + /// @inheritdoc IBridgehub + function acceptAdmin() external { + address currentPendingAdmin = pendingAdmin; + require(msg.sender == currentPendingAdmin, "n42"); // Only proposed by current admin address can claim the admin rights + + address previousAdmin = admin; + admin = currentPendingAdmin; + delete pendingAdmin; + + emit NewPendingAdmin(currentPendingAdmin, address(0)); + emit NewAdmin(previousAdmin, pendingAdmin); + } + + ///// Getters + + /// @notice return the state transition chain contract for a chainId + function getStateTransition(uint256 _chainId) public view returns (address) { + return IStateTransitionManager(stateTransitionManager[_chainId]).stateTransition(_chainId); + } + + //// Registry + + /// @notice State Transition can be any contract with the appropriate interface/functionality + function addStateTransitionManager(address _stateTransitionManager) external onlyOwner { + require( + !stateTransitionManagerIsRegistered[_stateTransitionManager], + "Bridgehub: state transition already registered" + ); + stateTransitionManagerIsRegistered[_stateTransitionManager] = true; + } + + /// @notice State Transition can be any contract with the appropriate interface/functionality + /// @notice this stops new Chains from using the STF, old chains are not affected + function removeStateTransitionManager(address _stateTransitionManager) external onlyOwner { + require( + stateTransitionManagerIsRegistered[_stateTransitionManager], + "Bridgehub: state transition not registered yet" + ); + stateTransitionManagerIsRegistered[_stateTransitionManager] = false; + } + + /// @notice token can be any contract with the appropriate interface/functionality + function addToken(address _token) external onlyOwner { + require(!tokenIsRegistered[_token], "Bridgehub: token already registered"); + tokenIsRegistered[_token] = true; + } + + /// @notice To set shared bridge, only Owner. Not done in initialize, as + /// the order of deployment is Bridgehub, Shared bridge, and then we call this + function setSharedBridge(address _sharedBridge) external onlyOwner { + sharedBridge = IL1SharedBridge(_sharedBridge); + } + + /// @notice register new chain + /// @notice for Eth the baseToken address is 1 + function createNewChain( + uint256 _chainId, + address _stateTransitionManager, + address _baseToken, + uint256, //_salt + address _admin, + bytes calldata _initData + ) external onlyOwnerOrAdmin nonReentrant returns (uint256 chainId) { + require(_chainId != 0, "Bridgehub: chainId cannot be 0"); + require(_chainId <= type(uint48).max, "Bridgehub: chainId too large"); + + require( + stateTransitionManagerIsRegistered[_stateTransitionManager], + "Bridgehub: state transition not registered" + ); + require(tokenIsRegistered[_baseToken], "Bridgehub: token not registered"); + require(address(sharedBridge) != address(0), "Bridgehub: weth bridge not set"); + + require(stateTransitionManager[_chainId] == address(0), "Bridgehub: chainId already registered"); + + stateTransitionManager[_chainId] = _stateTransitionManager; + baseToken[_chainId] = _baseToken; + + IStateTransitionManager(_stateTransitionManager).createNewChain( + _chainId, + _baseToken, + address(sharedBridge), + _admin, + _initData + ); + + emit NewChain(_chainId, _stateTransitionManager, _admin); + return _chainId; + } + + //// Mailbox forwarder + + /// @notice forwards function call to Mailbox based on ChainId + function proveL2MessageInclusion( + uint256 _chainId, + uint256 _batchNumber, + uint256 _index, + L2Message calldata _message, + bytes32[] calldata _proof + ) external view override returns (bool) { + address stateTransition = getStateTransition(_chainId); + return IZkSyncStateTransition(stateTransition).proveL2MessageInclusion(_batchNumber, _index, _message, _proof); + } + + /// @notice forwards function call to Mailbox based on ChainId + function proveL2LogInclusion( + uint256 _chainId, + uint256 _batchNumber, + uint256 _index, + L2Log memory _log, + bytes32[] calldata _proof + ) external view override returns (bool) { + address stateTransition = getStateTransition(_chainId); + return IZkSyncStateTransition(stateTransition).proveL2LogInclusion(_batchNumber, _index, _log, _proof); + } + + /// @notice forwards function call to Mailbox based on ChainId + function proveL1ToL2TransactionStatus( + uint256 _chainId, + bytes32 _l2TxHash, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes32[] calldata _merkleProof, + TxStatus _status + ) external view override returns (bool) { + address stateTransition = getStateTransition(_chainId); + return + IZkSyncStateTransition(stateTransition).proveL1ToL2TransactionStatus( + _l2TxHash, + _l2BatchNumber, + _l2MessageIndex, + _l2TxNumberInBatch, + _merkleProof, + _status + ); + } + + /// @notice forwards function call to Mailbox based on ChainId + function l2TransactionBaseCost( + uint256 _chainId, + uint256 _gasPrice, + uint256 _l2GasLimit, + uint256 _l2GasPerPubdataByteLimit + ) external view returns (uint256) { + address stateTransition = getStateTransition(_chainId); + return + IZkSyncStateTransition(stateTransition).l2TransactionBaseCost( + _gasPrice, + _l2GasLimit, + _l2GasPerPubdataByteLimit + ); + } + + /// @notice the mailbox is called directly after the sharedBridge received the deposit + /// this assumes that either ether is the base token or + /// the msg.sender has approved mintValue allowance for the sharedBridge. + /// This means this is not ideal for contract calls, as the contract would have to handle token allowance of the base Token + function requestL2TransactionDirect( + L2TransactionRequestDirect calldata _request + ) external payable override nonReentrant returns (bytes32 canonicalTxHash) { + { + address token = baseToken[_request.chainId]; + if (token == ETH_TOKEN_ADDRESS) { + require(msg.value == _request.mintValue, "Bridgehub: msg.value mismatch 1"); + } else { + require(msg.value == 0, "Bridgehub: non-eth bridge with msg.value"); + } + + sharedBridge.bridgehubDepositBaseToken{value: msg.value}( + _request.chainId, + msg.sender, + token, + _request.mintValue + ); + } + + address stateTransition = getStateTransition(_request.chainId); + address refundRecipient = _actualRefundRecipient(_request.refundRecipient); + canonicalTxHash = IZkSyncStateTransition(stateTransition).bridgehubRequestL2Transaction( + BridgehubL2TransactionRequest({ + sender: msg.sender, + contractL2: _request.l2Contract, + mintValue: _request.mintValue, + l2Value: _request.l2Value, + l2Calldata: _request.l2Calldata, + l2GasLimit: _request.l2GasLimit, + l2GasPerPubdataByteLimit: _request.l2GasPerPubdataByteLimit, + factoryDeps: _request.factoryDeps, + refundRecipient: refundRecipient + }) + ); + } + + /// @notice After depositing funds to the sharedBridge, the secondBridge is called + /// to return the actual L2 message which is sent to the Mailbox. + /// This assumes that either ether is the base token or + /// the msg.sender has approved the sharedBridge with the mintValue, + /// and also the necessary approvals are given for the second bridge. + /// @notice The logic of this bridge is to allow easy depositing for bridges. + /// Each contract that handles the users ERC20 tokens needs approvals from the user, this contract allows + /// the user to approve for each token only its respective bridge + /// @notice This function is great for contract calls to L2, the secondBridge can be any contract. + function requestL2TransactionTwoBridges( + L2TransactionRequestTwoBridgesOuter calldata _request + ) external payable override nonReentrant returns (bytes32 canonicalTxHash) { + { + address token = baseToken[_request.chainId]; + uint256 baseTokenMsgValue; + if (token == ETH_TOKEN_ADDRESS) { + require( + msg.value == _request.mintValue + _request.secondBridgeValue, + "Bridgehub: msg.value mismatch 2" + ); + baseTokenMsgValue = _request.mintValue; + } else { + require(msg.value == _request.secondBridgeValue, "Bridgehub: msg.value mismatch 3"); + baseTokenMsgValue = 0; + } + sharedBridge.bridgehubDepositBaseToken{value: baseTokenMsgValue}( + _request.chainId, + msg.sender, + token, + _request.mintValue + ); + } + + address stateTransition = getStateTransition(_request.chainId); + + L2TransactionRequestTwoBridgesInner memory outputRequest = IL1SharedBridge(_request.secondBridgeAddress) + .bridgehubDeposit{value: _request.secondBridgeValue}( + _request.chainId, + msg.sender, + _request.l2Value, + _request.secondBridgeCalldata + ); + + require(outputRequest.magicValue == TWO_BRIDGES_MAGIC_VALUE, "Bridgehub: magic value mismatch"); + + address refundRecipient = _actualRefundRecipient(_request.refundRecipient); + + require( + _request.secondBridgeAddress > BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS, + "Bridgehub: second bridge address too low" + ); // to avoid calls to precompiles + canonicalTxHash = IZkSyncStateTransition(stateTransition).bridgehubRequestL2Transaction( + BridgehubL2TransactionRequest({ + sender: _request.secondBridgeAddress, + contractL2: outputRequest.l2Contract, + mintValue: _request.mintValue, + l2Value: _request.l2Value, + l2Calldata: outputRequest.l2Calldata, + l2GasLimit: _request.l2GasLimit, + l2GasPerPubdataByteLimit: _request.l2GasPerPubdataByteLimit, + factoryDeps: outputRequest.factoryDeps, + refundRecipient: refundRecipient + }) + ); + + IL1SharedBridge(_request.secondBridgeAddress).bridgehubConfirmL2Transaction( + _request.chainId, + outputRequest.txDataHash, + canonicalTxHash + ); + } + + function _actualRefundRecipient(address _refundRecipient) internal view returns (address _recipient) { + if (_refundRecipient == address(0)) { + // If the `_refundRecipient` is not provided, we use the `msg.sender` as the recipient. + _recipient = msg.sender == tx.origin ? msg.sender : AddressAliasHelper.applyL1ToL2Alias(msg.sender); + } else if (_refundRecipient.code.length > 0) { + // If the `_refundRecipient` is a smart contract, we apply the L1 to L2 alias to prevent foot guns. + _recipient = AddressAliasHelper.applyL1ToL2Alias(_refundRecipient); + } else { + _recipient = _refundRecipient; + } + } +} diff --git a/l1-contracts/contracts/bridgehub/IBridgehub.sol b/l1-contracts/contracts/bridgehub/IBridgehub.sol new file mode 100644 index 000000000000..33423ea4bf72 --- /dev/null +++ b/l1-contracts/contracts/bridgehub/IBridgehub.sol @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {IL1SharedBridge} from "../bridge/interfaces/IL1SharedBridge.sol"; +import "../common/Messaging.sol"; +import "../state-transition/IStateTransitionManager.sol"; +import "../state-transition/libraries/Diamond.sol"; + +struct L2TransactionRequestDirect { + uint256 chainId; + uint256 mintValue; + address l2Contract; + uint256 l2Value; + bytes l2Calldata; + uint256 l2GasLimit; + uint256 l2GasPerPubdataByteLimit; + bytes[] factoryDeps; + address refundRecipient; +} + +struct L2TransactionRequestTwoBridgesOuter { + uint256 chainId; + uint256 mintValue; + uint256 l2Value; + uint256 l2GasLimit; + uint256 l2GasPerPubdataByteLimit; + address refundRecipient; + address secondBridgeAddress; + uint256 secondBridgeValue; + bytes secondBridgeCalldata; +} + +struct L2TransactionRequestTwoBridgesInner { + bytes32 magicValue; + address l2Contract; + bytes l2Calldata; + bytes[] factoryDeps; + bytes32 txDataHash; +} + +interface IBridgehub { + /// @notice pendingAdmin is changed + /// @dev Also emitted when new admin is accepted and in this case, `newPendingAdmin` would be zero address + event NewPendingAdmin(address indexed oldPendingAdmin, address indexed newPendingAdmin); + + /// @notice Admin changed + event NewAdmin(address indexed oldAdmin, address indexed newAdmin); + + /// @notice Starts the transfer of admin rights. Only the current admin can propose a new pending one. + /// @notice New admin can accept admin rights by calling `acceptAdmin` function. + /// @param _newPendingAdmin Address of the new admin + function setPendingAdmin(address _newPendingAdmin) external; + + /// @notice Accepts transfer of admin rights. Only pending admin can accept the role. + function acceptAdmin() external; + + /// Getters + function stateTransitionManagerIsRegistered(address _stateTransitionManager) external view returns (bool); + + function stateTransitionManager(uint256 _chainId) external view returns (address); + + function tokenIsRegistered(address _baseToken) external view returns (bool); + + function baseToken(uint256 _chainId) external view returns (address); + + function sharedBridge() external view returns (IL1SharedBridge); + + function getStateTransition(uint256 _chainId) external view returns (address); + + /// Mailbox forwarder + + function proveL2MessageInclusion( + uint256 _chainId, + uint256 _batchNumber, + uint256 _index, + L2Message calldata _message, + bytes32[] calldata _proof + ) external view returns (bool); + + function proveL2LogInclusion( + uint256 _chainId, + uint256 _batchNumber, + uint256 _index, + L2Log memory _log, + bytes32[] calldata _proof + ) external view returns (bool); + + function proveL1ToL2TransactionStatus( + uint256 _chainId, + bytes32 _l2TxHash, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes32[] calldata _merkleProof, + TxStatus _status + ) external view returns (bool); + + function requestL2TransactionDirect( + L2TransactionRequestDirect calldata _request + ) external payable returns (bytes32 canonicalTxHash); + + function requestL2TransactionTwoBridges( + L2TransactionRequestTwoBridgesOuter calldata _request + ) external payable returns (bytes32 canonicalTxHash); + + function l2TransactionBaseCost( + uint256 _chainId, + uint256 _gasPrice, + uint256 _l2GasLimit, + uint256 _l2GasPerPubdataByteLimit + ) external view returns (uint256); + + //// Registry + + function createNewChain( + uint256 _chainId, + address _stateTransitionManager, + address _baseToken, + uint256 _salt, + address _admin, + bytes calldata _initData + ) external returns (uint256 chainId); + + function addStateTransitionManager(address _stateTransitionManager) external; + + function removeStateTransitionManager(address _stateTransitionManager) external; + + function addToken(address _token) external; + + function setSharedBridge(address _sharedBridge) external; + + event NewChain(uint256 indexed chainId, address stateTransitionManager, address indexed chainGovernance); +} diff --git a/l1-contracts/contracts/zksync/Config.sol b/l1-contracts/contracts/common/Config.sol similarity index 89% rename from l1-contracts/contracts/zksync/Config.sol rename to l1-contracts/contracts/common/Config.sol index 0f849c7a57d0..ec70a2dcda1a 100644 --- a/l1-contracts/contracts/zksync/Config.sol +++ b/l1-contracts/contracts/common/Config.sol @@ -97,3 +97,19 @@ uint256 constant TX_SLOT_OVERHEAD_L2_GAS = 10000; /// @dev It is expected that the L1 contracts will enforce that the L2 gas price will be high enough to compensate /// the operator in case the batch is closed because of the memory for transactions being filled up. uint256 constant MEMORY_OVERHEAD_GAS = 10; + +address constant ETH_TOKEN_ADDRESS = address(1); + +/// @dev Era's chainID +uint256 constant ERA_CHAIN_ID = $(ERA_CHAIN_ID); + +/// @dev The address of legacy L1 ERC20 bridge. +address constant ERA_ERC20_BRIDGE_ADDRESS = $(ERA_ERC20_BRIDGE_ADDRESS); + +/// @dev The address of zkSync Era diamond proxy contract. +address constant ERA_DIAMOND_PROXY = $(ERA_DIAMOND_PROXY); + +bytes32 constant TWO_BRIDGES_MAGIC_VALUE = bytes32(uint256(keccak256("TWO_BRIDGES_MAGIC_VALUE")) - 1); + +/// @dev https://eips.ethereum.org/EIPS/eip-1352 +address constant BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS = address(uint160(type(uint16).max)); diff --git a/l1-contracts/contracts/common/Dependencies.sol b/l1-contracts/contracts/common/Dependencies.sol index f894b58863f9..51f59e4fc234 100644 --- a/l1-contracts/contracts/common/Dependencies.sol +++ b/l1-contracts/contracts/common/Dependencies.sol @@ -3,3 +3,4 @@ pragma solidity 0.8.20; import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; diff --git a/l1-contracts/contracts/common/L2ContractAddresses.sol b/l1-contracts/contracts/common/L2ContractAddresses.sol index 1da4ed52d7f9..beb178fdbc81 100644 --- a/l1-contracts/contracts/common/L2ContractAddresses.sol +++ b/l1-contracts/contracts/common/L2ContractAddresses.sol @@ -24,7 +24,7 @@ address constant L2_FORCE_DEPLOYER_ADDR = address(0x8007); address constant L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR = address(0x8008); /// @dev The address of the eth token system contract -address constant L2_ETH_TOKEN_SYSTEM_CONTRACT_ADDR = address(0x800a); +address constant L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR = address(0x800a); /// @dev The address of the context system contract address constant L2_SYSTEM_CONTEXT_SYSTEM_CONTRACT_ADDR = address(0x800b); diff --git a/l1-contracts/contracts/common/Messaging.sol b/l1-contracts/contracts/common/Messaging.sol new file mode 100644 index 000000000000..a241dd6aba42 --- /dev/null +++ b/l1-contracts/contracts/common/Messaging.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +/// @dev The enum that represents the transaction execution status +/// @param Failure The transaction execution failed +/// @param Success The transaction execution succeeded +enum TxStatus { + Failure, + Success +} + +/// @dev The log passed from L2 +/// @param l2ShardId The shard identifier, 0 - rollup, 1 - porter +/// All other values are not used but are reserved for the future +/// @param isService A boolean flag that is part of the log along with `key`, `value`, and `sender` address. +/// This field is required formally but does not have any special meaning +/// @param txNumberInBatch The L2 transaction number in a Batch, in which the log was sent +/// @param sender The L2 address which sent the log +/// @param key The 32 bytes of information that was sent in the log +/// @param value The 32 bytes of information that was sent in the log +// Both `key` and `value` are arbitrary 32-bytes selected by the log sender +struct L2Log { + uint8 l2ShardId; + bool isService; + uint16 txNumberInBatch; + address sender; + bytes32 key; + bytes32 value; +} + +/// @dev An arbitrary length message passed from L2 +/// @notice Under the hood it is `L2Log` sent from the special system L2 contract +/// @param txNumberInBatch The L2 transaction number in a Batch, in which the message was sent +/// @param sender The address of the L2 account from which the message was passed +/// @param data An arbitrary length message +struct L2Message { + uint16 txNumberInBatch; + address sender; + bytes data; +} + +/// @dev Internal structure that contains the parameters for the writePriorityOp +/// internal function. +/// @param sender The sender's address. +/// @param txId The id of the priority transaction. +/// @param l2Value The msg.value of the L2 transaction. +/// @param contractAddressL2 The address of the contract on L2 to call. +/// @param expirationTimestamp The timestamp by which the priority operation must be processed by the operator. +/// @param l2GasLimit The limit of the L2 gas for the L2 transaction +/// @param l2GasPricePerPubdata The price for a single pubdata byte in L2 gas. +/// @param valueToMint The amount of ether that should be minted on L2 as the result of this transaction. +/// @param refundRecipient The recipient of the refund for the transaction on L2. If the transaction fails, then +/// this address will receive the `l2Value`. +struct WritePriorityOpParams { + address sender; + uint256 txId; + uint256 l2Value; + address contractAddressL2; + uint64 expirationTimestamp; + uint256 l2GasLimit; + uint256 l2GasPrice; + uint256 l2GasPricePerPubdata; + uint256 valueToMint; + address refundRecipient; +} + +/// @dev Structure that includes all fields of the L2 transaction +/// @dev The hash of this structure is the "canonical L2 transaction hash" and can +/// be used as a unique identifier of a tx +/// @param txType The tx type number, depending on which the L2 transaction can be +/// interpreted differently +/// @param from The sender's address. `uint256` type for possible address format changes +/// and maintaining backward compatibility +/// @param to The recipient's address. `uint256` type for possible address format changes +/// and maintaining backward compatibility +/// @param gasLimit The L2 gas limit for L2 transaction. Analog to the `gasLimit` on an +/// L1 transactions +/// @param gasPerPubdataByteLimit Maximum number of L2 gas that will cost one byte of pubdata +/// (every piece of data that will be stored on L1 as calldata) +/// @param maxFeePerGas The absolute maximum sender willing to pay per unit of L2 gas to get +/// the transaction included in a Batch. Analog to the EIP-1559 `maxFeePerGas` on an L1 transactions +/// @param maxPriorityFeePerGas The additional fee that is paid directly to the validator +/// to incentivize them to include the transaction in a Batch. Analog to the EIP-1559 +/// `maxPriorityFeePerGas` on an L1 transactions +/// @param paymaster The address of the EIP-4337 paymaster, that will pay fees for the +/// transaction. `uint256` type for possible address format changes and maintaining backward compatibility +/// @param nonce The nonce of the transaction. For L1->L2 transactions it is the priority +/// operation Id +/// @param value The value to pass with the transaction +/// @param reserved The fixed-length fields for usage in a future extension of transaction +/// formats +/// @param data The calldata that is transmitted for the transaction call +/// @param signature An abstract set of bytes that are used for transaction authorization +/// @param factoryDeps The set of L2 bytecode hashes whose preimages were shown on L1 +/// @param paymasterInput The arbitrary-length data that is used as a calldata to the paymaster pre-call +/// @param reservedDynamic The arbitrary-length field for usage in a future extension of transaction formats +struct L2CanonicalTransaction { + uint256 txType; + uint256 from; + uint256 to; + uint256 gasLimit; + uint256 gasPerPubdataByteLimit; + uint256 maxFeePerGas; + uint256 maxPriorityFeePerGas; + uint256 paymaster; + uint256 nonce; + uint256 value; + // In the future, we might want to add some + // new fields to the struct. The `txData` struct + // is to be passed to account and any changes to its structure + // would mean a breaking change to these accounts. To prevent this, + // we should keep some fields as "reserved" + // It is also recommended that their length is fixed, since + // it would allow easier proof integration (in case we will need + // some special circuit for preprocessing transactions) + uint256[4] reserved; + bytes data; + bytes signature; + uint256[] factoryDeps; + bytes paymasterInput; + // Reserved dynamic type for the future use-case. Using it should be avoided, + // But it is still here, just in case we want to enable some additional functionality + bytes reservedDynamic; +} + +struct BridgehubL2TransactionRequest { + address sender; + address contractL2; + uint256 mintValue; + uint256 l2Value; + bytes l2Calldata; + uint256 l2GasLimit; + uint256 l2GasPerPubdataByteLimit; + bytes[] factoryDeps; + address refundRecipient; +} diff --git a/l1-contracts/contracts/common/interfaces/IL2ContractDeployer.sol b/l1-contracts/contracts/common/interfaces/IL2ContractDeployer.sol index 4781950db7de..a387cf0fc0e5 100644 --- a/l1-contracts/contracts/common/interfaces/IL2ContractDeployer.sol +++ b/l1-contracts/contracts/common/interfaces/IL2ContractDeployer.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.20; /** * @author Matter Labs - * @notice System smart contract that is responsible for deploying other smart contracts on zkSync. + * @notice System smart contract that is responsible for deploying other smart contracts on a zkSync hyperchain. */ interface IL2ContractDeployer { /// @notice A struct that describes a forced deployment on an address. diff --git a/l1-contracts/contracts/dev-contracts/ConstructorForwarder.sol b/l1-contracts/contracts/dev-contracts/ConstructorForwarder.sol index d9749e5af1fd..ac935013f306 100644 --- a/l1-contracts/contracts/dev-contracts/ConstructorForwarder.sol +++ b/l1-contracts/contracts/dev-contracts/ConstructorForwarder.sol @@ -3,6 +3,9 @@ pragma solidity 0.8.20; contract ConstructorForwarder { + // add this to be excluded from coverage report + function test() internal virtual {} + constructor(address to, bytes memory data) payable { (bool success, ) = payable(to).call{value: msg.value}(data); require(success); diff --git a/l1-contracts/contracts/dev-contracts/EventOnFallback.sol b/l1-contracts/contracts/dev-contracts/EventOnFallback.sol index 649093aa26f1..0aa089921ea1 100644 --- a/l1-contracts/contracts/dev-contracts/EventOnFallback.sol +++ b/l1-contracts/contracts/dev-contracts/EventOnFallback.sol @@ -3,6 +3,9 @@ pragma solidity 0.8.20; contract EventOnFallback { + // add this to be excluded from coverage report + function test() internal virtual {} + event Called(address msgSender, uint256 value, bytes data); fallback() external payable { diff --git a/l1-contracts/contracts/dev-contracts/FeeOnTransferToken.sol b/l1-contracts/contracts/dev-contracts/FeeOnTransferToken.sol new file mode 100644 index 000000000000..309741f5292b --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/FeeOnTransferToken.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {TestnetERC20Token} from "./TestnetERC20Token.sol"; + +contract FeeOnTransferToken is TestnetERC20Token { + // add this to be excluded from coverage report + function test() internal override {} + + constructor( + string memory name_, + string memory symbol_, + uint8 decimals_ + ) TestnetERC20Token(name_, symbol_, decimals_) {} + + function _transfer(address from, address to, uint256 amount) internal override { + super._transfer(from, to, amount - 1); + super._transfer(from, address(1), 1); + } +} diff --git a/l1-contracts/contracts/dev-contracts/Forwarder.sol b/l1-contracts/contracts/dev-contracts/Forwarder.sol index 76463b7c2d33..a331b8d46e04 100644 --- a/l1-contracts/contracts/dev-contracts/Forwarder.sol +++ b/l1-contracts/contracts/dev-contracts/Forwarder.sol @@ -3,6 +3,9 @@ pragma solidity 0.8.20; contract Forwarder { + // add this to be excluded from coverage report + function test() internal virtual {} + function forward(address to, bytes calldata data) external payable returns (bytes memory returnValue) { bool success; (success, returnValue) = payable(to).call{value: msg.value}(data); diff --git a/l1-contracts/contracts/dev-contracts/Multicall.sol b/l1-contracts/contracts/dev-contracts/Multicall.sol index a9a7fb5c5765..288b0b758933 100644 --- a/l1-contracts/contracts/dev-contracts/Multicall.sol +++ b/l1-contracts/contracts/dev-contracts/Multicall.sol @@ -22,6 +22,9 @@ pragma solidity 0.8.20; /// @title Multicall - Aggregate results from multiple read-only function calls contract Multicall { + // add this to be excluded from coverage report + function test() internal virtual {} + struct Call { address target; bytes callData; @@ -32,7 +35,7 @@ contract Multicall { returnData = new bytes[](calls.length); for (uint256 i = 0; i < calls.length; ++i) { (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData); - require(success); + require(success, "multicall 1"); returnData[i] = ret; } } diff --git a/l1-contracts/contracts/dev-contracts/Multicall3.sol b/l1-contracts/contracts/dev-contracts/Multicall3.sol index 7698d2f011d9..6a0ad2273c92 100644 --- a/l1-contracts/contracts/dev-contracts/Multicall3.sol +++ b/l1-contracts/contracts/dev-contracts/Multicall3.sol @@ -11,6 +11,9 @@ pragma solidity 0.8.20; /// @author Andreas Bigger /// @author Matt Solomon contract Multicall3 { + // add this to be excluded from coverage report + function test() internal virtual {} + struct Call { address target; bytes callData; diff --git a/l1-contracts/contracts/dev-contracts/ReturnSomething.sol b/l1-contracts/contracts/dev-contracts/ReturnSomething.sol index 227940a077c6..e2345586bc95 100644 --- a/l1-contracts/contracts/dev-contracts/ReturnSomething.sol +++ b/l1-contracts/contracts/dev-contracts/ReturnSomething.sol @@ -3,6 +3,9 @@ pragma solidity 0.8.20; contract ReturnSomething { + // add this to be excluded from coverage report + function test() internal virtual {} + fallback() external payable { assembly { return(0, 0x20) diff --git a/l1-contracts/contracts/dev-contracts/RevertFallback.sol b/l1-contracts/contracts/dev-contracts/RevertFallback.sol index bc6f803f2043..6a8de0f55a47 100644 --- a/l1-contracts/contracts/dev-contracts/RevertFallback.sol +++ b/l1-contracts/contracts/dev-contracts/RevertFallback.sol @@ -3,6 +3,9 @@ pragma solidity 0.8.20; contract RevertFallback { + // add this to be excluded from coverage report + function test() internal virtual {} + fallback() external payable { revert(); } diff --git a/l1-contracts/contracts/dev-contracts/RevertReceiveAccount.sol b/l1-contracts/contracts/dev-contracts/RevertReceiveAccount.sol index 3def9f679666..0cb58620ed23 100644 --- a/l1-contracts/contracts/dev-contracts/RevertReceiveAccount.sol +++ b/l1-contracts/contracts/dev-contracts/RevertReceiveAccount.sol @@ -5,6 +5,9 @@ pragma solidity 0.8.20; /// @title RevertReceiveAccount - An account which reverts receiving funds depending on the flag /// @dev Used for testing failed withdrawals from the zkSync smart contract contract RevertReceiveAccount { + // add this to be excluded from coverage report + function test() internal virtual {} + bool public revertReceive; constructor() { diff --git a/l1-contracts/contracts/dev-contracts/RevertTransferERC20.sol b/l1-contracts/contracts/dev-contracts/RevertTransferERC20.sol index 85f9e6e18446..9c76f28ed23d 100644 --- a/l1-contracts/contracts/dev-contracts/RevertTransferERC20.sol +++ b/l1-contracts/contracts/dev-contracts/RevertTransferERC20.sol @@ -7,6 +7,9 @@ import "./TestnetERC20Token.sol"; /// @title RevertTransferERC20Token - A ERC20 token contract which can revert transfers depending on a flag /// @dev Used for testing failed ERC-20 withdrawals from the zkSync smart contract contract RevertTransferERC20 is TestnetERC20Token { + // add this to be excluded from coverage report + function test() internal override {} + bool public revertTransfer; constructor(string memory name, string memory symbol, uint8 decimals) TestnetERC20Token(name, symbol, decimals) { diff --git a/l1-contracts/contracts/dev-contracts/SingletonFactory.sol b/l1-contracts/contracts/dev-contracts/SingletonFactory.sol index 846106a1c1f5..52634d47605d 100644 --- a/l1-contracts/contracts/dev-contracts/SingletonFactory.sol +++ b/l1-contracts/contracts/dev-contracts/SingletonFactory.sol @@ -7,6 +7,9 @@ pragma solidity 0.8.20; * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) */ contract SingletonFactory { + // add this to be excluded from coverage report + function test() internal virtual {} + /** * @notice Deploys `_initCode` using `_salt` for defining the deterministic address. * @param _initCode Initialization code. diff --git a/l1-contracts/contracts/dev-contracts/TestnetERC20Token.sol b/l1-contracts/contracts/dev-contracts/TestnetERC20Token.sol index 68986dcfe0ac..00229a916628 100644 --- a/l1-contracts/contracts/dev-contracts/TestnetERC20Token.sol +++ b/l1-contracts/contracts/dev-contracts/TestnetERC20Token.sol @@ -5,6 +5,9 @@ pragma solidity 0.8.20; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract TestnetERC20Token is ERC20 { + // add this to be excluded from coverage report + function test() internal virtual {} + uint8 private _decimals; constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) { diff --git a/l1-contracts/contracts/dev-contracts/WETH9.sol b/l1-contracts/contracts/dev-contracts/WETH9.sol index a45ae4e47bfb..70dfd8542a63 100644 --- a/l1-contracts/contracts/dev-contracts/WETH9.sol +++ b/l1-contracts/contracts/dev-contracts/WETH9.sol @@ -3,6 +3,9 @@ pragma solidity 0.8.20; contract WETH9 { + // add this to be excluded from coverage report + function test() internal virtual {} + string public name = "Wrapped Ether"; string public symbol = "WETH"; uint8 public decimals = 18; @@ -25,7 +28,7 @@ contract WETH9 { } function withdraw(uint256 wad) public { - require(balanceOf[msg.sender] >= wad); + require(balanceOf[msg.sender] >= wad, "weth9, 1"); balanceOf[msg.sender] -= wad; payable(msg.sender).transfer(wad); emit Withdrawal(msg.sender, wad); @@ -46,10 +49,10 @@ contract WETH9 { } function transferFrom(address src, address dst, uint256 wad) public returns (bool) { - require(balanceOf[src] >= wad); + require(balanceOf[src] >= wad, "weth9, 2"); if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) { - require(allowance[src][msg.sender] >= wad); + require(allowance[src][msg.sender] >= wad, "weth9, 3"); allowance[src][msg.sender] -= wad; } diff --git a/l1-contracts/contracts/dev-contracts/interfaces/ITestnetERC20Token.sol b/l1-contracts/contracts/dev-contracts/interfaces/ITestnetERC20Token.sol new file mode 100644 index 000000000000..596c985217aa --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/interfaces/ITestnetERC20Token.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +interface ITestnetERC20Token { + function mint(address _to, uint256 _amount) external returns (bool); + + function decimals() external returns (uint8); +} diff --git a/l1-contracts/contracts/dev-contracts/test/AddressAliasHelperTest.sol b/l1-contracts/contracts/dev-contracts/test/AddressAliasHelperTest.sol new file mode 100644 index 000000000000..a4031cfef77b --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/test/AddressAliasHelperTest.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import "../../vendor/AddressAliasHelper.sol"; + +contract AddressAliasHelperTest { + function applyL1ToL2Alias(address _l1Address) external pure returns (address) { + return AddressAliasHelper.applyL1ToL2Alias(_l1Address); + } + + function undoL1ToL2Alias(address _l2Address) external pure returns (address) { + return AddressAliasHelper.undoL1ToL2Alias(_l2Address); + } +} diff --git a/l1-contracts/contracts/dev-contracts/test/AdminFacetTest.sol b/l1-contracts/contracts/dev-contracts/test/AdminFacetTest.sol index b6d937a8fcca..6d1c53a147cc 100644 --- a/l1-contracts/contracts/dev-contracts/test/AdminFacetTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/AdminFacetTest.sol @@ -2,30 +2,34 @@ pragma solidity 0.8.20; -import "../../zksync/facets/Admin.sol"; +import "../../state-transition/chain-deps/facets/Admin.sol"; contract AdminFacetTest is AdminFacet { + // add this to be excluded from coverage report + function test() internal virtual {} + constructor() { - s.governor = msg.sender; + s.admin = msg.sender; + s.stateTransitionManager = msg.sender; } function getPorterAvailability() external view returns (bool) { return s.zkPorterIsAvailable; } - function isValidator(address _address) external view returns (bool) { - return s.validators[_address]; + function isValidator(address _validator) external view returns (bool) { + return s.validators[_validator]; } function getPriorityTxMaxGasLimit() external view returns (uint256) { return s.priorityTxMaxGasLimit; } - function getGovernor() external view returns (address) { - return s.governor; + function getAdmin() external view returns (address) { + return s.admin; } - function getPendingGovernor() external view returns (address) { - return s.pendingGovernor; + function getPendingAdmin() external view returns (address) { + return s.pendingAdmin; } } diff --git a/l1-contracts/contracts/dev-contracts/test/CustomUpgradeTest.sol b/l1-contracts/contracts/dev-contracts/test/CustomUpgradeTest.sol index b77c8d86278e..4a659ddf3e07 100644 --- a/l1-contracts/contracts/dev-contracts/test/CustomUpgradeTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/CustomUpgradeTest.sol @@ -2,10 +2,13 @@ pragma solidity 0.8.20; -import "../../zksync/libraries/Diamond.sol"; +import "../../state-transition/libraries/Diamond.sol"; import "../../upgrades/BaseZkSyncUpgrade.sol"; contract CustomUpgradeTest is BaseZkSyncUpgrade { + // add this to be excluded from coverage report + function test() internal virtual {} + event Test(); /// @notice Placeholder function for custom logic for upgrading L1 contract. diff --git a/l1-contracts/contracts/dev-contracts/test/DiamondCutTestContract.sol b/l1-contracts/contracts/dev-contracts/test/DiamondCutTestContract.sol index efa9515a53e3..66e1835b4131 100644 --- a/l1-contracts/contracts/dev-contracts/test/DiamondCutTestContract.sol +++ b/l1-contracts/contracts/dev-contracts/test/DiamondCutTestContract.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.20; -import "../../zksync/libraries/Diamond.sol"; -import "../../zksync/facets/Getters.sol"; +import "../../state-transition/libraries/Diamond.sol"; +import "../../state-transition/chain-deps/facets/Getters.sol"; contract DiamondCutTestContract is GettersFacet { function diamondCut(Diamond.DiamondCutData memory _diamondCut) external { diff --git a/l1-contracts/contracts/dev-contracts/test/DiamondProxyTest.sol b/l1-contracts/contracts/dev-contracts/test/DiamondProxyTest.sol index 9eb293f097ab..c762840a12bf 100644 --- a/l1-contracts/contracts/dev-contracts/test/DiamondProxyTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/DiamondProxyTest.sol @@ -2,10 +2,13 @@ pragma solidity 0.8.20; -import "../../zksync/libraries/Diamond.sol"; -import "../../zksync/facets/Base.sol"; +import "../../state-transition/libraries/Diamond.sol"; +import "../../state-transition/chain-deps/facets/ZkSyncStateTransitionBase.sol"; + +contract DiamondProxyTest is ZkSyncStateTransitionBase { + // add this to be excluded from coverage report + function test() internal virtual {} -contract DiamondProxyTest is Base { function setFreezability(bool _freeze) external returns (bytes32) { Diamond.DiamondStorage storage diamondStorage = Diamond.getDiamondStorage(); diamondStorage.isFrozen = _freeze; diff --git a/l1-contracts/contracts/dev-contracts/test/DummyAdminFacet.sol b/l1-contracts/contracts/dev-contracts/test/DummyAdminFacet.sol new file mode 100644 index 000000000000..a17940c39dd4 --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/test/DummyAdminFacet.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {Diamond} from "../../state-transition/libraries/Diamond.sol"; +import "../../state-transition/chain-deps/facets/ZkSyncStateTransitionBase.sol"; + +contract DummyAdminFacet is ZkSyncStateTransitionBase { + // add this to be excluded from coverage report + function test() internal virtual {} + + function getName() external pure returns (string memory) { + return "DummyAdminFacet"; + } + + function dummySetValidator(address _validator) external { + s.validators[_validator] = true; + } + + function executeUpgrade2(Diamond.DiamondCutData calldata _diamondCut) external { + Diamond.diamondCut(_diamondCut); + } +} diff --git a/l1-contracts/contracts/dev-contracts/test/DummyAdminFacet2.sol b/l1-contracts/contracts/dev-contracts/test/DummyAdminFacet2.sol new file mode 100644 index 000000000000..20995650cdff --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/test/DummyAdminFacet2.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {Diamond} from "../../state-transition/libraries/Diamond.sol"; +import "../../state-transition/chain-deps/facets/ZkSyncStateTransitionBase.sol"; + +contract DummyAdminFacet2 is ZkSyncStateTransitionBase { + // add this to be excluded from coverage report + function test() internal virtual {} + + function getName() external pure returns (string memory) { + return "DummyAdminFacet"; + } + + function dummySetValidator(address _validator) external { + s.validators[_validator] = true; + } + + function executeUpgrade(Diamond.DiamondCutData calldata _diamondCut) external { + Diamond.diamondCut(_diamondCut); + } +} diff --git a/l1-contracts/contracts/dev-contracts/test/DummyERC20BytesTransferReturnValue.sol b/l1-contracts/contracts/dev-contracts/test/DummyERC20BytesTransferReturnValue.sol index d8d06ebae050..ce7951a2b7b8 100644 --- a/l1-contracts/contracts/dev-contracts/test/DummyERC20BytesTransferReturnValue.sol +++ b/l1-contracts/contracts/dev-contracts/test/DummyERC20BytesTransferReturnValue.sol @@ -3,6 +3,9 @@ pragma solidity 0.8.20; contract DummyERC20BytesTransferReturnValue { + // add this to be excluded from coverage report + function test() internal virtual {} + bytes returnValue; constructor(bytes memory _returnValue) { diff --git a/l1-contracts/contracts/dev-contracts/test/DummyERC20NoTransferReturnValue.sol b/l1-contracts/contracts/dev-contracts/test/DummyERC20NoTransferReturnValue.sol index cc56d917473b..4f637df28f61 100644 --- a/l1-contracts/contracts/dev-contracts/test/DummyERC20NoTransferReturnValue.sol +++ b/l1-contracts/contracts/dev-contracts/test/DummyERC20NoTransferReturnValue.sol @@ -3,5 +3,8 @@ pragma solidity 0.8.20; contract DummyERC20NoTransferReturnValue { + // add this to be excluded from coverage report + function test() internal virtual {} + function transfer(address recipient, uint256 amount) external {} } diff --git a/l1-contracts/contracts/dev-contracts/test/DummyEraBaseTokenBridge.sol b/l1-contracts/contracts/dev-contracts/test/DummyEraBaseTokenBridge.sol new file mode 100644 index 000000000000..9cfd4d484c3c --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/test/DummyEraBaseTokenBridge.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +contract DummyEraBaseTokenBridge { + // add this to be excluded from coverage report + function test() internal virtual {} + + function bridgehubDepositBaseToken( + uint256 _chainId, + address _prevMsgSender, + address _l1Token, + uint256 _amount + ) external payable {} +} diff --git a/l1-contracts/contracts/dev-contracts/test/DummyExecutor.sol b/l1-contracts/contracts/dev-contracts/test/DummyExecutor.sol index 16684fe22d40..05079a31e842 100644 --- a/l1-contracts/contracts/dev-contracts/test/DummyExecutor.sol +++ b/l1-contracts/contracts/dev-contracts/test/DummyExecutor.sol @@ -2,12 +2,14 @@ pragma solidity 0.8.20; -import "../../zksync/interfaces/IExecutor.sol"; +import "../../state-transition/chain-interfaces/IExecutor.sol"; /// @title DummyExecutor -/// @notice A test smart contract implementing the IExecutor interface to simulate Executor behavior for testing -/// purposes. +/// @notice A test smart contract implementing the IExecutor interface to simulate Executor behavior for testing purposes. contract DummyExecutor is IExecutor { + // add this to be excluded from coverage report + function test() internal virtual {} + address owner; // Flags to control if the contract should revert during commit, prove, and execute batch operations @@ -32,7 +34,17 @@ contract DummyExecutor is IExecutor { _; } - /// @notice Allows the owner to set whether the contract should revert during commit batches operation + function getAdmin() external view returns (address) { + return owner; + } + + /// @notice Removing txs from the priority queue + function removePriorityQueueFront(uint256 _index) external { + // KL todo + // s.priorityQueue.removeFront(_index); + } + + /// @notice Allows the owner to set whether the contract should revert during commit blocks operation function setShouldRevertOnCommitBatches(bool _shouldRevert) external onlyOwner { shouldRevertOnCommitBatches = _shouldRevert; } @@ -50,7 +62,7 @@ contract DummyExecutor is IExecutor { function commitBatches( StoredBatchInfo calldata _lastCommittedBatchData, CommitBatchInfo[] calldata _newBatchesData - ) external { + ) public { require(!shouldRevertOnCommitBatches, "DummyExecutor: shouldRevertOnCommitBatches"); require( _lastCommittedBatchData.batchNumber == getTotalBatchesCommitted, @@ -65,18 +77,26 @@ contract DummyExecutor is IExecutor { getTotalBatchesCommitted += batchesLength; } + function commitBatchesSharedBridge( + uint256, + StoredBatchInfo calldata _lastCommittedBatchData, + CommitBatchInfo[] calldata _newBatchesData + ) external { + commitBatches(_lastCommittedBatchData, _newBatchesData); + } + function proveBatches( StoredBatchInfo calldata _prevBatch, StoredBatchInfo[] calldata _committedBatches, ProofInput calldata - ) external { + ) public { require(!shouldRevertOnProveBatches, "DummyExecutor: shouldRevertOnProveBatches"); require(_prevBatch.batchNumber == getTotalBatchesVerified, "DummyExecutor: Invalid previous batch number"); require(_committedBatches.length == 1, "DummyExecutor: Can prove only one batch"); require( _committedBatches[0].batchNumber == _prevBatch.batchNumber + 1, - "DummyExecutor: Can't prove batch out of order" + "DummyExecutor 1: Can't prove batch out of order" ); getTotalBatchesVerified += 1; @@ -86,7 +106,16 @@ contract DummyExecutor is IExecutor { ); } - function executeBatches(StoredBatchInfo[] calldata _batchesData) external { + function proveBatchesSharedBridge( + uint256, + StoredBatchInfo calldata _prevBatch, + StoredBatchInfo[] calldata _committedBatches, + ProofInput calldata _proof + ) external { + proveBatches(_prevBatch, _committedBatches, _proof); + } + + function executeBatches(StoredBatchInfo[] calldata _batchesData) public { require(!shouldRevertOnExecuteBatches, "DummyExecutor: shouldRevertOnExecuteBatches"); uint256 nBatches = _batchesData.length; for (uint256 i = 0; i < nBatches; ++i) { @@ -95,11 +124,15 @@ contract DummyExecutor is IExecutor { getTotalBatchesExecuted += nBatches; require( getTotalBatchesExecuted <= getTotalBatchesVerified, - "DummyExecutor: Can't execute batches more than committed and proven currently" + "DummyExecutor 2: Can't execute batches more than committed and proven currently" ); } - function revertBatches(uint256 _newLastBatch) external { + function executeBatchesSharedBridge(uint256, StoredBatchInfo[] calldata _batchesData) external { + executeBatches(_batchesData); + } + + function revertBatches(uint256 _newLastBatch) public { require( getTotalBatchesCommitted > _newLastBatch, "DummyExecutor: The last committed batch is less than new last batch" @@ -112,6 +145,10 @@ contract DummyExecutor is IExecutor { getTotalBatchesCommitted = newTotalBatchesCommitted; } + function revertBatchesSharedBridge(uint256, uint256 _newLastBatch) external { + revertBatches(_newLastBatch); + } + /// @notice Returns larger of two values function _maxU256(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? b : a; diff --git a/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol b/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol new file mode 100644 index 000000000000..1d259817544d --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import {L2TransactionRequestTwoBridgesInner, L2TransactionRequestDirect} from "../../bridgehub/IBridgehub.sol"; +import {ETH_TOKEN_ADDRESS, TWO_BRIDGES_MAGIC_VALUE, BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS} from "../../common/Config.sol"; + +contract DummySharedBridge { + event BridgehubDepositBaseTokenInitiated( + uint256 indexed chainId, + address indexed from, + address l1Token, + uint256 amount + ); + + bytes32 dummyL2DepositTxHash; + + /// @dev Maps token balances for each chain to prevent unauthorized spending across hyperchains. + /// This serves as a security measure until hyperbridging is implemented. + mapping(uint256 chainId => mapping(address l1Token => uint256 balance)) internal chainBalance; + + /// @dev Indicates whether the hyperbridging is enabled for a given chain. + mapping(uint256 chainId => bool enabled) internal hyperbridgingEnabled; + + address l1ReceiverReturnInFinalizeWithdrawal; + address l1TokenReturnInFinalizeWithdrawal; + uint256 amountReturnInFinalizeWithdrawal; + + constructor(bytes32 _dummyL2DepositTxHash) { + dummyL2DepositTxHash = _dummyL2DepositTxHash; + } + + function setDataToBeReturnedInFinalizeWithdrawal(address _l1Receiver, address _l1Token, uint256 _amount) external { + l1ReceiverReturnInFinalizeWithdrawal = _l1Receiver; + l1TokenReturnInFinalizeWithdrawal = _l1Token; + amountReturnInFinalizeWithdrawal = _amount; + } + + function depositLegacyErc20Bridge( + address, //_msgSender, + address, //_l2Receiver, + address, //_l1Token, + uint256, //_amount, + uint256, //_l2TxGasLimit, + uint256, //_l2TxGasPerPubdataByte, + address //_refundRecipient + ) external payable returns (bytes32 txHash) { + txHash = dummyL2DepositTxHash; + } + + function claimFailedDepositLegacyErc20Bridge( + address, //_depositSender, + address, //_l1Token, + uint256, //_amount, + bytes32, //_l2TxHash, + uint256, //_l2BatchNumber, + uint256, //_l2MessageIndex, + uint16, //_l2TxNumberInBatch, + bytes32[] calldata // _merkleProof + ) external {} + + function finalizeWithdrawalLegacyErc20Bridge( + uint256, //_l2BatchNumber, + uint256, //_l2MessageIndex, + uint16, //_l2TxNumberInBatch, + bytes calldata, //_message, + bytes32[] calldata //_merkleProof + ) external view returns (address l1Receiver, address l1Token, uint256 amount) { + l1Receiver = l1ReceiverReturnInFinalizeWithdrawal; + l1Token = l1TokenReturnInFinalizeWithdrawal; + amount = amountReturnInFinalizeWithdrawal; + } + + event Debugger(uint); + + function bridgehubDepositBaseToken( + uint256 _chainId, + address _prevMsgSender, + address _l1Token, + uint256 _amount + ) external payable { + if (_l1Token == address(1)) { + require(msg.value == _amount, "L1SharedBridge: msg.value not equal to amount"); + } else { + // The Bridgehub also checks this, but we want to be sure + require(msg.value == 0, "ShB m.v > 0 b d.it"); + uint256 amount = _depositFunds(_prevMsgSender, IERC20(_l1Token), _amount); // note if _prevMsgSender is this contract, this will return 0. This does not happen. + require(amount == _amount, "3T"); // The token has non-standard transfer logic + } + + if (!hyperbridgingEnabled[_chainId]) { + chainBalance[_chainId][_l1Token] += _amount; + } + + emit Debugger(5); + // Note that we don't save the deposited amount, as this is for the base token, which gets sent to the refundRecipient if the tx fails + emit BridgehubDepositBaseTokenInitiated(_chainId, _prevMsgSender, _l1Token, _amount); + } + + function _depositFunds(address _from, IERC20 _token, uint256 _amount) internal returns (uint256) { + uint256 balanceBefore = _token.balanceOf(address(this)); + _token.transferFrom(_from, address(this), _amount); + uint256 balanceAfter = _token.balanceOf(address(this)); + + return balanceAfter - balanceBefore; + } + + function bridgehubDeposit( + uint256, //_chainId, + address, //_prevMsgSender, + uint256, // l2Value, needed for Weth deposits in the future + bytes calldata //_data + ) external payable returns (L2TransactionRequestTwoBridgesInner memory request) { + // Request the finalization of the deposit on the L2 side + bytes memory l2TxCalldata = bytes("0xabcd123"); + bytes32 txDataHash = bytes32("0x1212121212abf"); + + request = L2TransactionRequestTwoBridgesInner({ + magicValue: TWO_BRIDGES_MAGIC_VALUE, + l2Contract: address(0xCAFE), + l2Calldata: l2TxCalldata, + factoryDeps: new bytes[](0), + txDataHash: txDataHash + }); + } + + function bridgehubConfirmL2Transaction(uint256 _chainId, bytes32 _txDataHash, bytes32 _txHash) external {} +} diff --git a/l1-contracts/contracts/dev-contracts/test/DummyStateTransition.sol b/l1-contracts/contracts/dev-contracts/test/DummyStateTransition.sol new file mode 100644 index 000000000000..53a28985c4a1 --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/test/DummyStateTransition.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {MailboxFacet} from "../../state-transition/chain-deps/facets/Mailbox.sol"; +import {FeeParams, PubdataPricingMode} from "../../state-transition/chain-deps/ZkSyncStateTransitionStorage.sol"; + +contract DummyStateTransition is MailboxFacet { + constructor(address bridgeHubAddress) { + s.bridgehub = bridgeHubAddress; + } + + function setBridgeHubAddress(address bridgeHubAddress) public { + s.bridgehub = bridgeHubAddress; + } + + function setBaseTokenGasMultiplierPrice(uint128 nominator, uint128 denominator) public { + s.baseTokenGasPriceMultiplierNominator = nominator; + s.baseTokenGasPriceMultiplierDenominator = denominator; + } + + function getBridgeHubAddress() public view returns (address) { + return s.bridgehub; + } + + function setFeeParams() external { + FeeParams memory _feeParams = _randomFeeParams(); + s.feeParams = _feeParams; + s.priorityTxMaxGasLimit = type(uint256).max; + } + + function _randomFeeParams() internal pure returns (FeeParams memory) { + return + FeeParams({ + pubdataPricingMode: PubdataPricingMode.Rollup, + batchOverheadL1Gas: 1_000_000, + maxPubdataPerBatch: 110_000, + maxL2GasPerBatch: 80_000_000, + priorityTxMaxPubdata: 99_000, + minimalL2GasPrice: 250_000_000 + }); + } +} diff --git a/l1-contracts/contracts/dev-contracts/test/DummyStateTransitionManager.sol b/l1-contracts/contracts/dev-contracts/test/DummyStateTransitionManager.sol new file mode 100644 index 000000000000..4823bdf1d59d --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/test/DummyStateTransitionManager.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import "../../state-transition/StateTransitionManager.sol"; + +/// @title DummyExecutor +/// @notice A test smart contract implementing the IExecutor interface to simulate Executor behavior for testing purposes. +contract DummyStateTransitionManager is StateTransitionManager { + // add this to be excluded from coverage report + function test() internal virtual {} + + /// @notice Constructor + constructor() StateTransitionManager(address(0)) {} + + function setStateTransition(uint256 _chainId, address _stateTransition) external { + stateTransition[_chainId] = _stateTransition; + } +} diff --git a/l1-contracts/contracts/dev-contracts/test/DummyStateTransitionManagerForValidatorTimelock.sol b/l1-contracts/contracts/dev-contracts/test/DummyStateTransitionManagerForValidatorTimelock.sol new file mode 100644 index 000000000000..b8f40f7f94a3 --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/test/DummyStateTransitionManagerForValidatorTimelock.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +/// @title DummyStateTransitionManagerForValidatorTimelock +/// @notice A test smart contract implementing the IExecutor interface to simulate Executor behavior for testing purposes. +contract DummyStateTransitionManagerForValidatorTimelock { + // add this to be excluded from coverage report + function test() internal virtual {} + + address public chainAdmin; + address public stateTransitionChain; + + constructor(address _chainAdmin, address _stateTransition) { + chainAdmin = _chainAdmin; + stateTransitionChain = _stateTransition; + } + + function getChainAdmin(uint256) external returns (address) { + return chainAdmin; + } + + function stateTransition(uint256) external returns (address) { + return stateTransitionChain; + } +} diff --git a/l1-contracts/contracts/dev-contracts/test/DummyStateTransitionManagerWithBridgeHubAddress.sol b/l1-contracts/contracts/dev-contracts/test/DummyStateTransitionManagerWithBridgeHubAddress.sol new file mode 100644 index 000000000000..065fc54565af --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/test/DummyStateTransitionManagerWithBridgeHubAddress.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import "../../state-transition/StateTransitionManager.sol"; + +/// @title DummyExecutor +/// @notice A test smart contract implementing the IExecutor interface to simulate Executor behavior for testing purposes. +contract DummyStateTransitionManagerWBH is StateTransitionManager { + /// @notice Constructor + constructor(address bridgeHub) StateTransitionManager(bridgeHub) {} + + function setStateTransition(uint256 _chainId, address _stateTransition) external { + stateTransition[_chainId] = _stateTransition; + } +} diff --git a/l1-contracts/contracts/dev-contracts/test/ExecutorProvingTest.sol b/l1-contracts/contracts/dev-contracts/test/ExecutorProvingTest.sol index adc18647cb1a..55b426cbcfa4 100644 --- a/l1-contracts/contracts/dev-contracts/test/ExecutorProvingTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/ExecutorProvingTest.sol @@ -1,9 +1,11 @@ +// SPDX-License-Identifier: MIT + pragma solidity 0.8.20; -import {ExecutorFacet} from "../../zksync/facets/Executor.sol"; -import {VerifierParams} from "../../zksync/Storage.sol"; -import {LogProcessingOutput} from "../../zksync/interfaces/IExecutor.sol"; -import {PubdataSource} from "../../zksync/interfaces/IExecutor.sol"; +import {ExecutorFacet} from "../../state-transition/chain-deps/facets/Executor.sol"; +import {VerifierParams, PubdataPricingMode} from "../../state-transition/chain-deps/ZkSyncStateTransitionStorage.sol"; +import {LogProcessingOutput} from "../../state-transition/chain-interfaces/IExecutor.sol"; +import {PubdataSource, LogProcessingOutput} from "../../state-transition/chain-interfaces/IExecutor.sol"; contract ExecutorProvingTest is ExecutorFacet { function getBatchProofPublicInput( @@ -25,7 +27,8 @@ contract ExecutorProvingTest is ExecutorFacet { function processL2Logs( CommitBatchInfo calldata _newBatch, - bytes32 _expectedSystemContractUpgradeTxHash + bytes32 _expectedSystemContractUpgradeTxHash, + PubdataPricingMode ) external pure returns (LogProcessingOutput memory logOutput) { return _processL2Logs(_newBatch, _expectedSystemContractUpgradeTxHash); } diff --git a/l1-contracts/contracts/dev-contracts/test/L1ERC20BridgeTest.sol b/l1-contracts/contracts/dev-contracts/test/L1ERC20BridgeTest.sol index ebea0e4400d2..d73bba266387 100644 --- a/l1-contracts/contracts/dev-contracts/test/L1ERC20BridgeTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/L1ERC20BridgeTest.sol @@ -3,13 +3,17 @@ pragma solidity 0.8.20; import "../../bridge/L1ERC20Bridge.sol"; -import {IMailbox} from "../../zksync/interfaces/IMailbox.sol"; +import {IMailbox} from "../../state-transition/chain-interfaces/IMailbox.sol"; +import "../../bridge/interfaces/IL1SharedBridge.sol"; /// @author Matter Labs contract L1ERC20BridgeTest is L1ERC20Bridge { - constructor(IZkSync _zkSync) L1ERC20Bridge(_zkSync) {} + // add this to be excluded from coverage report + function test() internal virtual {} - function getZkSyncMailbox() public view returns (IMailbox) { - return zkSync; - } + constructor(IBridgehub _zkSync) L1ERC20Bridge(IL1SharedBridge(address(0))) {} + + // function getBridgehub() public view returns (IBridgehub) { + // return bridgehub; + // } } diff --git a/l1-contracts/contracts/dev-contracts/test/L1SharedBridgeTest.sol b/l1-contracts/contracts/dev-contracts/test/L1SharedBridgeTest.sol new file mode 100644 index 000000000000..9655b21ff6c4 --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/test/L1SharedBridgeTest.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import "../../bridge/L1SharedBridge.sol"; +import {IMailbox} from "../../state-transition/chain-interfaces/IMailbox.sol"; +import "../../bridge/interfaces/IL1SharedBridge.sol"; + +/// @author Matter Labs +contract L1SharedBridgeTest is L1SharedBridge { + // add this to be excluded from coverage report + function test() internal virtual {} + + address private immutable eraDiamondProxy; + + constructor( + address _diamondProxyAddress, + address payable _l1WethAddress, + IBridgehub _bridgehub, + IL1ERC20Bridge _legacyBridge + ) L1SharedBridge(_l1WethAddress, _bridgehub, _legacyBridge) { + eraDiamondProxy = _diamondProxyAddress; + } + + /// @notice Checks that the message sender is the bridgehub or Era + modifier onlyBridgehubOrTestEra(uint256 _chainId) { + require( + (msg.sender == address(bridgehub)) || (_chainId == ERA_CHAIN_ID && msg.sender == eraDiamondProxy), + "L1SharedBridge: not bridgehub or era chain" + ); + _; + } + + /// @notice used by bridgehub to aquire mintValue. If l2Tx fails refunds are sent to refundrecipient on L2 + /// we also use it to keep to track each chain's assets + function bridgehubDepositBaseToken( + uint256 _chainId, + address _prevMsgSender, + address _l1Token, + uint256 _amount + ) external payable override onlyBridgehubOrTestEra(_chainId) { + if (_l1Token == ETH_TOKEN_ADDRESS) { + require(msg.value == _amount, "L1SharedBridge: msg.value not equal to amount"); + } else { + // The Bridgehub also checks this, but we want to be sure + require(msg.value == 0, "ShB m.v > 0 b d.it"); + + uint256 amount = _depositFunds(_prevMsgSender, IERC20(_l1Token), _amount); // note if _prevMsgSender is this contract, this will return 0. This does not happen. + require(amount == _amount, "3T"); // The token has non-standard transfer logic + } + + if (!hyperbridgingEnabled[_chainId]) { + chainBalance[_chainId][_l1Token] += _amount; + } + // Note we don't save the deposited amount, as this is for the base token, which gets sent to the refundRecipient if the tx fails + } +} diff --git a/l1-contracts/contracts/dev-contracts/test/MailboxFacetTest.sol b/l1-contracts/contracts/dev-contracts/test/MailboxFacetTest.sol index 95d88dbfd40f..d7969c5a5416 100644 --- a/l1-contracts/contracts/dev-contracts/test/MailboxFacetTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/MailboxFacetTest.sol @@ -2,12 +2,16 @@ pragma solidity 0.8.20; -import "../../zksync/facets/Mailbox.sol"; -import "../../zksync/Config.sol"; +import {FeeParams} from "../../state-transition/chain-deps/ZkSyncStateTransitionStorage.sol"; +import {MailboxFacet} from "../../state-transition/chain-deps/facets/Mailbox.sol"; +import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA} from "../../common/Config.sol"; contract MailboxFacetTest is MailboxFacet { + // add this to be excluded from coverage report + function test() internal virtual {} + constructor() { - s.governor = msg.sender; + s.admin = msg.sender; } function setFeeParams(FeeParams memory _feeParams) external { diff --git a/l1-contracts/contracts/dev-contracts/test/MerkleTest.sol b/l1-contracts/contracts/dev-contracts/test/MerkleTest.sol index 3d81d479231d..17d77f24b533 100644 --- a/l1-contracts/contracts/dev-contracts/test/MerkleTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/MerkleTest.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.20; -import "../../zksync/libraries/Merkle.sol"; +import "../../state-transition/libraries/Merkle.sol"; contract MerkleTest { function calculateRoot( diff --git a/l1-contracts/contracts/dev-contracts/test/MockExecutor.sol b/l1-contracts/contracts/dev-contracts/test/MockExecutor.sol index ae705973eccf..41ea6ee17038 100644 --- a/l1-contracts/contracts/dev-contracts/test/MockExecutor.sol +++ b/l1-contracts/contracts/dev-contracts/test/MockExecutor.sol @@ -2,9 +2,12 @@ pragma solidity 0.8.20; -import "../../zksync/facets/Base.sol"; +import "../../state-transition/chain-deps/facets/ZkSyncStateTransitionBase.sol"; + +contract MockExecutorFacet is ZkSyncStateTransitionBase { + // add this to be excluded from coverage report + function test() internal virtual {} -contract MockExecutorFacet is Base { function saveL2LogsRootHash(uint256 _batchNumber, bytes32 _l2LogsTreeRoot) external { s.totalBatchesExecuted = _batchNumber; s.l2LogsRootHashes[_batchNumber] = _l2LogsTreeRoot; diff --git a/l1-contracts/contracts/dev-contracts/test/PriorityQueueTest.sol b/l1-contracts/contracts/dev-contracts/test/PriorityQueueTest.sol index 17d31e41f30e..8fd0e00c2098 100644 --- a/l1-contracts/contracts/dev-contracts/test/PriorityQueueTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/PriorityQueueTest.sol @@ -2,38 +2,36 @@ pragma solidity 0.8.20; -import "../../zksync/libraries/PriorityQueue.sol"; +import "../../state-transition/libraries/PriorityQueue.sol"; contract PriorityQueueTest { - using PriorityQueue for PriorityQueue.Queue; - PriorityQueue.Queue priorityQueue; function getFirstUnprocessedPriorityTx() external view returns (uint256) { - return priorityQueue.getFirstUnprocessedPriorityTx(); + return PriorityQueue.getFirstUnprocessedPriorityTx(priorityQueue); } function getTotalPriorityTxs() external view returns (uint256) { - return priorityQueue.getTotalPriorityTxs(); + return PriorityQueue.getTotalPriorityTxs(priorityQueue); } function getSize() external view returns (uint256) { - return priorityQueue.getSize(); + return PriorityQueue.getSize(priorityQueue); } function isEmpty() external view returns (bool) { - return priorityQueue.isEmpty(); + return PriorityQueue.isEmpty(priorityQueue); } function pushBack(PriorityOperation memory _operation) external { - return priorityQueue.pushBack(_operation); + return PriorityQueue.pushBack(priorityQueue, _operation); } function front() external view returns (PriorityOperation memory) { - return priorityQueue.front(); + return PriorityQueue.front(priorityQueue); } function popFront() external returns (PriorityOperation memory operation) { - return priorityQueue.popFront(); + return PriorityQueue.popFront(priorityQueue); } } diff --git a/l1-contracts/contracts/dev-contracts/test/ReenterGovernance.sol b/l1-contracts/contracts/dev-contracts/test/ReenterGovernance.sol index 50b54fee2e3c..3ab2d36841c0 100644 --- a/l1-contracts/contracts/dev-contracts/test/ReenterGovernance.sol +++ b/l1-contracts/contracts/dev-contracts/test/ReenterGovernance.sol @@ -5,6 +5,9 @@ pragma solidity 0.8.20; import {IGovernance} from "../../governance/IGovernance.sol"; contract ReenterGovernance { + // add this to be excluded from coverage report + function test() internal virtual {} + IGovernance governance; // Store call, predecessor and salt separately, diff --git a/l1-contracts/contracts/dev-contracts/test/ReenterL1ERC20Bridge.sol b/l1-contracts/contracts/dev-contracts/test/ReenterL1ERC20Bridge.sol new file mode 100644 index 000000000000..819002b7b0da --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/test/ReenterL1ERC20Bridge.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {IL1ERC20Bridge} from "../../bridge/interfaces/IL1ERC20Bridge.sol"; + +contract ReenterL1ERC20Bridge { + // add this to be excluded from coverage report + function test() internal virtual {} + + IL1ERC20Bridge l1Erc20Bridge; + + enum FunctionToCall { + Unset, + LegacyDeposit, + Deposit, + ClaimFailedDeposit, + FinalizeWithdrawal + } + + FunctionToCall functionToCall; + + function setBridge(IL1ERC20Bridge _l1Erc20Bridge) external { + l1Erc20Bridge = _l1Erc20Bridge; + } + + function setFunctionToCall(FunctionToCall _functionToCall) external { + functionToCall = _functionToCall; + } + + fallback() external payable { + if (functionToCall == FunctionToCall.LegacyDeposit) { + l1Erc20Bridge.deposit(address(0), address(0), 0, 0, 0); + } else if (functionToCall == FunctionToCall.Deposit) { + l1Erc20Bridge.deposit(address(0), address(0), 0, 0, 0, address(0)); + } else if (functionToCall == FunctionToCall.ClaimFailedDeposit) { + bytes32[] memory merkleProof; + l1Erc20Bridge.claimFailedDeposit(address(0), address(0), bytes32(0), 0, 0, 0, merkleProof); + } else if (functionToCall == FunctionToCall.FinalizeWithdrawal) { + bytes32[] memory merkleProof; + l1Erc20Bridge.finalizeWithdrawal(0, 0, 0, bytes(""), merkleProof); + } else { + revert("Unset function to call"); + } + } + + receive() external payable { + // revert("Receive not allowed"); + } +} diff --git a/l1-contracts/contracts/dev-contracts/test/UncheckedMathTest.sol b/l1-contracts/contracts/dev-contracts/test/UncheckedMathTest.sol new file mode 100644 index 000000000000..4af25cf0d727 --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/test/UncheckedMathTest.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import "../../common/libraries/UncheckedMath.sol"; + +contract UncheckedMathTest { + function uncheckedInc(uint256 _number) external pure returns (uint256) { + return UncheckedMath.uncheckedInc(_number); + } + + function uncheckedAdd(uint256 _lhs, uint256 _rhs) external pure returns (uint256) { + return UncheckedMath.uncheckedAdd(_lhs, _rhs); + } +} diff --git a/l1-contracts/contracts/dev-contracts/test/UnsafeBytesTest.sol b/l1-contracts/contracts/dev-contracts/test/UnsafeBytesTest.sol index b643cf001ce8..d80a1c5b8e8a 100644 --- a/l1-contracts/contracts/dev-contracts/test/UnsafeBytesTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/UnsafeBytesTest.sol @@ -5,6 +5,9 @@ pragma solidity 0.8.20; import "../../common/libraries/UnsafeBytes.sol"; contract UnsafeBytesTest { + // add this to be excluded from coverage report + function test() internal virtual {} + using UnsafeBytes for bytes; function readUint32(bytes memory _bytes, uint256 _start) external pure returns (uint32 readValue, uint256 offset) { diff --git a/l1-contracts/contracts/dev-contracts/test/VerifierRecursiveTest.sol b/l1-contracts/contracts/dev-contracts/test/VerifierRecursiveTest.sol index 42b48bf43419..e6ebdf976051 100644 --- a/l1-contracts/contracts/dev-contracts/test/VerifierRecursiveTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/VerifierRecursiveTest.sol @@ -2,10 +2,13 @@ pragma solidity 0.8.20; -import "../../zksync/Verifier.sol"; +import "../../state-transition/Verifier.sol"; /// @author Matter Labs contract VerifierRecursiveTest is Verifier { + // add this to be excluded from coverage report + function test() internal virtual {} + function _loadVerificationKey() internal pure override { assembly { // gate setup commitments diff --git a/l1-contracts/contracts/dev-contracts/test/VerifierTest.sol b/l1-contracts/contracts/dev-contracts/test/VerifierTest.sol index 8a50b4f4a0a2..11eee2c1c5ff 100644 --- a/l1-contracts/contracts/dev-contracts/test/VerifierTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/VerifierTest.sol @@ -2,10 +2,13 @@ pragma solidity 0.8.20; -import "../../zksync/Verifier.sol"; +import "../../state-transition/Verifier.sol"; /// @author Matter Labs contract VerifierTest is Verifier { + // add this to be excluded from coverage report + function test() internal virtual {} + function _loadVerificationKey() internal pure override { assembly { // gate setup commitments diff --git a/l1-contracts/contracts/governance/Governance.sol b/l1-contracts/contracts/governance/Governance.sol index cbfa6f09d75b..d9aec5d6c947 100644 --- a/l1-contracts/contracts/governance/Governance.sol +++ b/l1-contracts/contracts/governance/Governance.sol @@ -11,7 +11,7 @@ import {IGovernance} from "./IGovernance.sol"; /// @notice This contract manages operations (calls with preconditions) for governance tasks. /// The contract allows for operations to be scheduled, executed, and canceled with /// appropriate permissions and delays. It is used for managing and coordinating upgrades -/// and changes in all zkSync Era governed contracts. +/// and changes in all zkSync hyperchain governed contracts. /// /// Operations can be proposed as either fully transparent upgrades with on-chain data, /// or "shadow" upgrades where upgrade data is not published on-chain before execution. Proposed operations diff --git a/l1-contracts/contracts/state-transition/IStateTransitionManager.sol b/l1-contracts/contracts/state-transition/IStateTransitionManager.sol new file mode 100644 index 000000000000..c0baba71888b --- /dev/null +++ b/l1-contracts/contracts/state-transition/IStateTransitionManager.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {Diamond} from "./libraries/Diamond.sol"; +import {L2CanonicalTransaction} from "../common/Messaging.sol"; + +/// @notice Struct that holds all data needed for initializing STM Proxy. +/// @dev We use struct instead of raw parameters in `initialize` function to prevent "Stack too deep" error +/// @param _governor address who can manage non-critical updates in the contract +/// @param _validatorTimelock address that serves as consensus, i.e. can submit blocks to be processed +/// @param _genesisBatchHash Batch hash of the genesis (initial) batch +/// @param _genesisIndexRepeatedStorageChanges The serial number of the shortcut storage key for genesis batch +/// @param _genesisBatchCommitment The zk-proof commitment for the genesis batch +struct StateTransitionManagerInitializeData { + address governor; + address validatorTimelock; + address genesisUpgrade; + bytes32 genesisBatchHash; + uint64 genesisIndexRepeatedStorageChanges; + bytes32 genesisBatchCommitment; + Diamond.DiamondCutData diamondCut; + uint256 protocolVersion; +} + +interface IStateTransitionManager { + // when a new Chain is added + event StateTransitionNewChain(uint256 indexed _chainId, address indexed _stateTransitionContract); + + event SetChainIdUpgrade( + address indexed _stateTransitionChain, + L2CanonicalTransaction _l2Transaction, + uint256 indexed _protocolVersion + ); + + /// @notice pendingAdmin is changed + /// @dev Also emitted when new admin is accepted and in this case, `newPendingAdmin` would be zero address + event NewPendingAdmin(address indexed oldPendingAdmin, address indexed newPendingAdmin); + + /// @notice Admin changed + event NewAdmin(address indexed oldAdmin, address indexed newAdmin); + + function bridgehub() external view returns (address); + + /// @notice Starts the transfer of admin rights. Only the current admin can propose a new pending one. + /// @notice New admin can accept admin rights by calling `acceptAdmin` function. + /// @param _newPendingAdmin Address of the new admin + function setPendingAdmin(address _newPendingAdmin) external; + + /// @notice Accepts transfer of admin rights. Only pending admin can accept the role. + function acceptAdmin() external; + + function stateTransition(uint256 _chainId) external view returns (address); + + function storedBatchZero() external view returns (bytes32); + + function initialCutHash() external view returns (bytes32); + + function genesisUpgrade() external view returns (address); + + function upgradeCutHash(uint256 _protocolVersion) external view returns (bytes32); + + function protocolVersion() external view returns (uint256); + + function initialize(StateTransitionManagerInitializeData calldata _initalizeData) external; + + function setInitialCutHash(Diamond.DiamondCutData calldata _diamondCut) external; + + function setValidatorTimelock(address _validatorTimelock) external; + + function getChainAdmin(uint256 _chainId) external view returns (address); + + /// @notice + function createNewChain( + uint256 _chainId, + address _baseToken, + address _sharedBridge, + address _admin, + bytes calldata _diamondCut + ) external; + + function registerAlreadyDeployedStateTransition(uint256 _chainId, address _stateTransitionContract) external; + + function setNewVersionUpgrade( + Diamond.DiamondCutData calldata _cutData, + uint256 _oldProtocolVersion, + uint256 _newProtocolVersion + ) external; + + function setUpgradeDiamondCut(Diamond.DiamondCutData calldata _cutData, uint256 _oldProtocolVersion) external; + + function freezeChain(uint256 _chainId) external; + + function unfreezeChain(uint256 _chainId) external; +} diff --git a/l1-contracts/contracts/state-transition/StateTransitionManager.sol b/l1-contracts/contracts/state-transition/StateTransitionManager.sol new file mode 100644 index 000000000000..0c274397ff49 --- /dev/null +++ b/l1-contracts/contracts/state-transition/StateTransitionManager.sol @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {Diamond} from "./libraries/Diamond.sol"; +import {DiamondProxy} from "./chain-deps/DiamondProxy.sol"; +import {IAdmin} from "./chain-interfaces/IAdmin.sol"; +import {IDefaultUpgrade} from "../upgrades/IDefaultUpgrade.sol"; +import {IDiamondInit} from "./chain-interfaces/IDiamondInit.sol"; +import {IExecutor} from "./chain-interfaces/IExecutor.sol"; +import {IStateTransitionManager, StateTransitionManagerInitializeData} from "./IStateTransitionManager.sol"; +import {ISystemContext} from "./l2-deps/ISystemContext.sol"; +import {IZkSyncStateTransition} from "./chain-interfaces/IZkSyncStateTransition.sol"; +import {L2_SYSTEM_CONTEXT_SYSTEM_CONTRACT_ADDR, L2_FORCE_DEPLOYER_ADDR} from "../common/L2ContractAddresses.sol"; +import {L2CanonicalTransaction} from "../common/Messaging.sol"; +import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; +import {ProposedUpgrade} from "../upgrades/BaseZkSyncUpgrade.sol"; +import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; +import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, L2_TO_L1_LOG_SERIALIZE_SIZE, DEFAULT_L2_LOGS_TREE_ROOT_HASH, EMPTY_STRING_KECCAK, SYSTEM_UPGRADE_L2_TX_TYPE, ERA_DIAMOND_PROXY, ERA_CHAIN_ID} from "../common/Config.sol"; +import {VerifierParams} from "./chain-interfaces/IVerifier.sol"; + +/// @title StateTransition contract +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Ownable2Step { + /// @notice Address of the bridgehub + address public immutable bridgehub; + + /// @notice chainId => chainContract + mapping(uint256 => address) public stateTransition; + + /// @dev Batch hash zero, calculated at initialization + bytes32 public storedBatchZero; + + /// @dev Stored cutData for diamond cut + bytes32 public initialCutHash; + + /// @dev genesisUpgrade contract address, used to setChainId + address public genesisUpgrade; + + /// @dev current protocolVersion + uint256 public protocolVersion; + + /// @dev validatorTimelock contract address, used to setChainId + address public validatorTimelock; + + /// @dev Stored cutData for upgrade diamond cut. protocolVersion => cutHash + mapping(uint256 => bytes32) public upgradeCutHash; + + /// @dev used to manage non critical updates + address public admin; + + /// @dev used to accept the admin role + address private pendingAdmin; + + /// @dev Contract is expected to be used as proxy implementation. + /// @dev Initialize the implementation to prevent Parity hack. + constructor(address _bridgehub) reentrancyGuardInitializer { + bridgehub = _bridgehub; + } + + /// @notice only the bridgehub can call + modifier onlyBridgehub() { + require(msg.sender == bridgehub, "StateTransition: only bridgehub"); + _; + } + + /// @notice the admin can call, for non-critical updates + modifier onlyOwnerOrAdmin() { + require(msg.sender == admin || msg.sender == owner(), "Bridgehub: not owner or admin"); + _; + } + + function getChainAdmin(uint256 _chainId) external view override returns (address) { + return IZkSyncStateTransition(stateTransition[_chainId]).getAdmin(); + } + + /// @dev initialize + function initialize( + StateTransitionManagerInitializeData calldata _initializeData + ) external reentrancyGuardInitializer { + require(_initializeData.governor != address(0), "StateTransition: governor zero"); + _transferOwnership(_initializeData.governor); + + genesisUpgrade = _initializeData.genesisUpgrade; + protocolVersion = _initializeData.protocolVersion; + validatorTimelock = _initializeData.validatorTimelock; + + // We need to initialize the state hash because it is used in the commitment of the next batch + IExecutor.StoredBatchInfo memory batchZero = IExecutor.StoredBatchInfo( + 0, + _initializeData.genesisBatchHash, + _initializeData.genesisIndexRepeatedStorageChanges, + 0, + EMPTY_STRING_KECCAK, + DEFAULT_L2_LOGS_TREE_ROOT_HASH, + 0, + _initializeData.genesisBatchCommitment + ); + storedBatchZero = keccak256(abi.encode(batchZero)); + + initialCutHash = keccak256(abi.encode(_initializeData.diamondCut)); + + // While this does not provide a protection in the production, it is needed for local testing + // Length of the L2Log encoding should not be equal to the length of other L2Logs' tree nodes preimages + assert(L2_TO_L1_LOG_SERIALIZE_SIZE != 2 * 32); + } + + /// @inheritdoc IStateTransitionManager + function setPendingAdmin(address _newPendingAdmin) external onlyOwnerOrAdmin { + // Save previous value into the stack to put it into the event later + address oldPendingAdmin = pendingAdmin; + // Change pending admin + pendingAdmin = _newPendingAdmin; + emit NewPendingAdmin(oldPendingAdmin, _newPendingAdmin); + } + + /// @inheritdoc IStateTransitionManager + function acceptAdmin() external { + address currentPendingAdmin = pendingAdmin; + require(msg.sender == currentPendingAdmin, "n42"); // Only proposed by current admin address can claim the admin rights + + address previousAdmin = admin; + admin = currentPendingAdmin; + delete pendingAdmin; + + emit NewPendingAdmin(currentPendingAdmin, address(0)); + emit NewAdmin(previousAdmin, pendingAdmin); + } + + /// @dev set validatorTimelock. Cannot do it an initialization, as validatorTimelock is deployed after STM + function setValidatorTimelock(address _validatorTimelock) external onlyOwnerOrAdmin { + validatorTimelock = _validatorTimelock; + } + + /// @dev set initial cutHash + function setInitialCutHash(Diamond.DiamondCutData calldata _diamondCut) external onlyOwner { + initialCutHash = keccak256(abi.encode(_diamondCut)); + } + + /// @dev set New Version with upgrade from old version + function setNewVersionUpgrade( + Diamond.DiamondCutData calldata _cutData, + uint256 _oldProtocolVersion, + uint256 _newProtocolVersion + ) external onlyOwner { + upgradeCutHash[_oldProtocolVersion] = keccak256(abi.encode(_cutData)); + protocolVersion = _newProtocolVersion; + } + + /// @dev set upgrade for some protocolVersion + function setUpgradeDiamondCut( + Diamond.DiamondCutData calldata _cutData, + uint256 _oldProtocolVersion + ) external onlyOwner { + upgradeCutHash[_oldProtocolVersion] = keccak256(abi.encode(_cutData)); + } + + /// @dev freezes the specified chain + function freezeChain(uint256 _chainId) external onlyOwner { + IZkSyncStateTransition(stateTransition[_chainId]).freezeDiamond(); + } + + /// @dev freezes the specified chain + function unfreezeChain(uint256 _chainId) external onlyOwner { + IZkSyncStateTransition(stateTransition[_chainId]).freezeDiamond(); + } + + /// @dev reverts batches on the specified chain + function revertBatches(uint256 _chainId, uint256 _newLastBatch) external onlyOwnerOrAdmin { + IZkSyncStateTransition(stateTransition[_chainId]).revertBatches(_newLastBatch); + } + + /// registration + + /// @dev we have to set the chainId at genesis, as blockhashzero is the same for all chains with the same chainId + function _setChainIdUpgrade(uint256 _chainId, address _chainContract) internal { + bytes memory systemContextCalldata = abi.encodeCall(ISystemContext.setChainId, (_chainId)); + uint256[] memory uintEmptyArray; + bytes[] memory bytesEmptyArray; + + L2CanonicalTransaction memory l2ProtocolUpgradeTx = L2CanonicalTransaction({ + txType: SYSTEM_UPGRADE_L2_TX_TYPE, + from: uint256(uint160(L2_FORCE_DEPLOYER_ADDR)), + to: uint256(uint160(L2_SYSTEM_CONTEXT_SYSTEM_CONTRACT_ADDR)), + gasLimit: $(PRIORITY_TX_MAX_GAS_LIMIT), + gasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + maxFeePerGas: uint256(0), + maxPriorityFeePerGas: uint256(0), + paymaster: uint256(0), + // Note, that the priority operation id is used as "nonce" for L1->L2 transactions + nonce: protocolVersion, + value: 0, + reserved: [uint256(0), 0, 0, 0], + data: systemContextCalldata, + signature: new bytes(0), + factoryDeps: uintEmptyArray, + paymasterInput: new bytes(0), + reservedDynamic: new bytes(0) + }); + + ProposedUpgrade memory proposedUpgrade = ProposedUpgrade({ + l2ProtocolUpgradeTx: l2ProtocolUpgradeTx, + factoryDeps: bytesEmptyArray, + bootloaderHash: bytes32(0), + defaultAccountHash: bytes32(0), + verifier: address(0), + verifierParams: VerifierParams({ + recursionNodeLevelVkHash: bytes32(0), + recursionLeafLevelVkHash: bytes32(0), + recursionCircuitsSetVksHash: bytes32(0) + }), + l1ContractsUpgradeCalldata: new bytes(0), + postUpgradeCalldata: new bytes(0), + upgradeTimestamp: 0, + newProtocolVersion: protocolVersion + }); + + Diamond.FacetCut[] memory emptyArray; + Diamond.DiamondCutData memory cutData = Diamond.DiamondCutData({ + facetCuts: emptyArray, + initAddress: genesisUpgrade, + initCalldata: abi.encodeCall(IDefaultUpgrade.upgrade, (proposedUpgrade)) + }); + + IAdmin(_chainContract).executeUpgrade(cutData); + emit SetChainIdUpgrade(_chainContract, l2ProtocolUpgradeTx, protocolVersion); + } + + function registerAlreadyDeployedStateTransition( + uint256 _chainId, + address _stateTransitionContract + ) external onlyOwner { + stateTransition[_chainId] = _stateTransitionContract; + emit StateTransitionNewChain(_chainId, _stateTransitionContract); + } + + /// @notice called by Bridgehub when a chain registers + function createNewChain( + uint256 _chainId, + address _baseToken, + address _sharedBridge, + address _admin, + bytes calldata _diamondCut + ) external onlyBridgehub { + if (stateTransition[_chainId] != address(0)) { + // StateTransition chain already registered + return; + } + + // check not registered + Diamond.DiamondCutData memory diamondCut = abi.decode(_diamondCut, (Diamond.DiamondCutData)); + + // check input + bytes32 cutHashInput = keccak256(_diamondCut); + require(cutHashInput == initialCutHash, "StateTransition: initial cutHash mismatch"); + + // construct init data + bytes memory initData; + /// all together 4+9*32=292 bytes + initData = bytes.concat( + IDiamondInit.initialize.selector, + bytes32(_chainId), + bytes32(uint256(uint160(bridgehub))), + bytes32(uint256(uint160(address(this)))), + bytes32(uint256(protocolVersion)), + bytes32(uint256(uint160(_admin))), + bytes32(uint256(uint160(validatorTimelock))), + bytes32(uint256(uint160(_baseToken))), + bytes32(uint256(uint160(_sharedBridge))), + bytes32(storedBatchZero), + diamondCut.initCalldata + ); + + diamondCut.initCalldata = initData; + // deploy stateTransitionContract + DiamondProxy stateTransitionContract = new DiamondProxy{salt: bytes32(0)}(block.chainid, diamondCut); + + // save data + address stateTransitionAddress = address(stateTransitionContract); + + stateTransition[_chainId] = stateTransitionAddress; + + // set chainId in VM + _setChainIdUpgrade(_chainId, stateTransitionAddress); + + emit StateTransitionNewChain(_chainId, stateTransitionAddress); + } +} diff --git a/l1-contracts/contracts/state-transition/ValidatorTimelock.sol b/l1-contracts/contracts/state-transition/ValidatorTimelock.sol new file mode 100644 index 000000000000..944e7cb1bc81 --- /dev/null +++ b/l1-contracts/contracts/state-transition/ValidatorTimelock.sol @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; +import {LibMap} from "./libraries/LibMap.sol"; +import {IExecutor} from "./chain-interfaces/IExecutor.sol"; +import {IStateTransitionManager} from "./IStateTransitionManager.sol"; +import {ERA_CHAIN_ID} from "../common/Config.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice Intermediate smart contract between the validator EOA account and the hyperchains state transition diamond smart contract. +/// @dev The primary purpose of this contract is to provide a trustless means of delaying batch execution without +/// modifying the main hyperchain diamond contract. As such, even if this contract is compromised, it will not impact the main +/// contract. +/// @dev zkSync actively monitors the chain activity and reacts to any suspicious activity by freezing the chain. +/// This allows time for investigation and mitigation before resuming normal operations. +/// @dev The contract overloads all of the 4 methods, that are used in state transition. When the batch is committed, +/// the timestamp is stored for it. Later, when the owner calls the batch execution, the contract checks that batch +/// was committed not earlier than X time ago. +contract ValidatorTimelock is IExecutor, Ownable2Step { + using LibMap for LibMap.Uint32Map; + + /// @dev Part of the IBase interface. Not used in this contract. + string public constant override getName = "ValidatorTimelock"; + + /// @notice The delay between committing and executing batches is changed. + event NewExecutionDelay(uint256 _newExecutionDelay); + + /// @notice A new validator has been added. + event ValidatorAdded(uint256 _chainId, address _addedValidator); + + /// @notice A validator has been removed. + event ValidatorRemoved(uint256 _chainId, address _removedValidator); + + /// @notice Error for when an address is already a validator. + error AddressAlreadyValidator(uint256 _chainId); + + /// @notice Error for when an address is not a validator. + error ValidatorDoesNotExist(uint256 _chainId); + + /// @dev The stateTransitionManager smart contract. + IStateTransitionManager public stateTransitionManager; + + /// @dev The mapping of L2 chainId => batch number => timestamp when it was committed. + mapping(uint256 => LibMap.Uint32Map) internal committedBatchTimestamp; + + /// @dev The address that can commit/revert/validate/execute batches. + mapping(uint256 _chainId => mapping(address _validator => bool)) public validators; + + /// @dev The delay between committing and executing batches. + uint32 public executionDelay; + + constructor(address _initialOwner, uint32 _executionDelay) { + _transferOwnership(_initialOwner); + executionDelay = _executionDelay; + } + + /// @notice Checks if the caller is the admin of the chain. + modifier onlyChainAdmin(uint256 _chainId) { + require(msg.sender == stateTransitionManager.getChainAdmin(_chainId), "ValidatorTimelock: only chain admin"); + _; + } + + /// @notice Checks if the caller is a validator. + modifier onlyValidator(uint256 _chainId) { + require(validators[_chainId][msg.sender] == true, "ValidatorTimelock: only validator"); + _; + } + + /// @dev Set new validator address. + function setStateTransitionManager(IStateTransitionManager _stateTransitionManager) external onlyOwner { + stateTransitionManager = _stateTransitionManager; + } + + /// @dev Sets an address as a validator. + function addValidator(uint256 _chainId, address _newValidator) external onlyChainAdmin(_chainId) { + if (validators[_chainId][_newValidator]) { + revert AddressAlreadyValidator(_chainId); + } + validators[_chainId][_newValidator] = true; + emit ValidatorAdded(_chainId, _newValidator); + } + + /// @dev Removes an address as a validator. + function removeValidator(uint256 _chainId, address _validator) external onlyChainAdmin(_chainId) { + if (!validators[_chainId][_validator]) { + revert ValidatorDoesNotExist(_chainId); + } + validators[_chainId][_validator] = false; + emit ValidatorRemoved(_chainId, _validator); + } + + /// @dev Set the delay between committing and executing batches. + function setExecutionDelay(uint32 _executionDelay) external onlyOwner { + executionDelay = _executionDelay; + emit NewExecutionDelay(_executionDelay); + } + + /// @dev Returns the timestamp when `_l2BatchNumber` was committed. + function getCommittedBatchTimestamp(uint256 _chainId, uint256 _l2BatchNumber) external view returns (uint256) { + return committedBatchTimestamp[_chainId].get(_l2BatchNumber); + } + + /// @dev Records the timestamp for all provided committed batches and make + /// a call to the hyperchain diamond contract with the same calldata. + function commitBatches( + StoredBatchInfo calldata, + CommitBatchInfo[] calldata _newBatchesData + ) external onlyValidator(ERA_CHAIN_ID) { + unchecked { + // This contract is only a temporary solution, that hopefully will be disabled until 2106 year, so... + // It is safe to cast. + uint32 timestamp = uint32(block.timestamp); + for (uint256 i = 0; i < _newBatchesData.length; ++i) { + committedBatchTimestamp[ERA_CHAIN_ID].set(_newBatchesData[i].batchNumber, timestamp); + } + } + + _propagateToZkSyncStateTransition(ERA_CHAIN_ID); + } + + /// @dev Records the timestamp for all provided committed batches and make + /// a call to the hyperchain diamond contract with the same calldata. + function commitBatchesSharedBridge( + uint256 _chainId, + StoredBatchInfo calldata, + CommitBatchInfo[] calldata _newBatchesData + ) external onlyValidator(_chainId) { + unchecked { + // This contract is only a temporary solution, that hopefully will be disabled until 2106 year, so... + // It is safe to cast. + uint32 timestamp = uint32(block.timestamp); + for (uint256 i = 0; i < _newBatchesData.length; ++i) { + committedBatchTimestamp[_chainId].set(_newBatchesData[i].batchNumber, timestamp); + } + } + + _propagateToZkSyncStateTransition(_chainId); + } + + /// @dev Make a call to the hyperchain diamond contract with the same calldata. + /// Note: If the batch is reverted, it needs to be committed first before the execution. + /// So it's safe to not override the committed batches. + function revertBatches(uint256) external onlyValidator(ERA_CHAIN_ID) { + _propagateToZkSyncStateTransition(ERA_CHAIN_ID); + } + + /// @dev Make a call to the hyperchain diamond contract with the same calldata. + /// Note: If the batch is reverted, it needs to be committed first before the execution. + /// So it's safe to not override the committed batches. + function revertBatchesSharedBridge(uint256 _chainId, uint256) external onlyValidator(_chainId) { + _propagateToZkSyncStateTransition(_chainId); + } + + /// @dev Make a call to the hyperchain diamond contract with the same calldata. + /// Note: We don't track the time when batches are proven, since all information about + /// the batch is known on the commit stage and the proved is not finalized (may be reverted). + function proveBatches( + StoredBatchInfo calldata, + StoredBatchInfo[] calldata, + ProofInput calldata + ) external onlyValidator(ERA_CHAIN_ID) { + _propagateToZkSyncStateTransition(ERA_CHAIN_ID); + } + + /// @dev Make a call to the hyperchain diamond contract with the same calldata. + /// Note: We don't track the time when batches are proven, since all information about + /// the batch is known on the commit stage and the proved is not finalized (may be reverted). + function proveBatchesSharedBridge( + uint256 _chainId, + StoredBatchInfo calldata, + StoredBatchInfo[] calldata, + ProofInput calldata + ) external onlyValidator(_chainId) { + _propagateToZkSyncStateTransition(_chainId); + } + + /// @dev Check that batches were committed at least X time ago and + /// make a call to the hyperchain diamond contract with the same calldata. + function executeBatches(StoredBatchInfo[] calldata _newBatchesData) external onlyValidator(ERA_CHAIN_ID) { + uint256 delay = executionDelay; // uint32 + unchecked { + for (uint256 i = 0; i < _newBatchesData.length; ++i) { + uint256 commitBatchTimestamp = committedBatchTimestamp[ERA_CHAIN_ID].get( + _newBatchesData[i].batchNumber + ); + + // Note: if the `commitBatchTimestamp` is zero, that means either: + // * The batch was committed, but not through this contract. + // * The batch wasn't committed at all, so execution will fail in the zkSync contract. + // We allow executing such batches. + + require(block.timestamp >= commitBatchTimestamp + delay, "5c"); // The delay is not passed + } + } + _propagateToZkSyncStateTransition(ERA_CHAIN_ID); + } + + /// @dev Check that batches were committed at least X time ago and + /// make a call to the hyperchain diamond contract with the same calldata. + function executeBatchesSharedBridge( + uint256 _chainId, + StoredBatchInfo[] calldata _newBatchesData + ) external onlyValidator(_chainId) { + uint256 delay = executionDelay; // uint32 + unchecked { + for (uint256 i = 0; i < _newBatchesData.length; ++i) { + uint256 commitBatchTimestamp = committedBatchTimestamp[_chainId].get(_newBatchesData[i].batchNumber); + + // Note: if the `commitBatchTimestamp` is zero, that means either: + // * The batch was committed, but not through this contract. + // * The batch wasn't committed at all, so execution will fail in the zkSync contract. + // We allow executing such batches. + + require(block.timestamp >= commitBatchTimestamp + delay, "5c"); // The delay is not passed + } + } + _propagateToZkSyncStateTransition(_chainId); + } + + /// @dev Call the hyperchain diamond contract with the same calldata as this contract was called. + /// Note: it is called the hyperchain diamond contract, not delegatecalled! + function _propagateToZkSyncStateTransition(uint256 _chainId) internal { + address contractAddress = stateTransitionManager.stateTransition(_chainId); + assembly { + // Copy function signature and arguments from calldata at zero position into memory at pointer position + calldatacopy(0, 0, calldatasize()) + // Call method of the hyperchain diamond contract returns 0 on error + let result := call(gas(), contractAddress, 0, 0, calldatasize(), 0, 0) + // Get the size of the last return data + let size := returndatasize() + // Copy the size length of bytes from return data at zero position to pointer position + returndatacopy(0, 0, size) + // Depending on the result value + switch result + case 0 { + // End execution and revert state changes + revert(0, size) + } + default { + // Return data with length of size at pointers position + return(0, size) + } + } + } +} diff --git a/l1-contracts/contracts/zksync/Verifier.sol b/l1-contracts/contracts/state-transition/Verifier.sol similarity index 99% rename from l1-contracts/contracts/zksync/Verifier.sol rename to l1-contracts/contracts/state-transition/Verifier.sol index b4553c81daad..505f2d8471d8 100644 --- a/l1-contracts/contracts/zksync/Verifier.sol +++ b/l1-contracts/contracts/state-transition/Verifier.sol @@ -2,13 +2,13 @@ pragma solidity 0.8.20; -import "./interfaces/IVerifier.sol"; +import "./chain-interfaces/IVerifier.sol"; /* solhint-disable max-line-length */ /// @author Matter Labs /// @notice Modified version of the Permutations over Lagrange-bases for Oecumenical Noninteractive arguments of /// Knowledge (PLONK) verifier. -/// Modifications have been made to optimize the proof system for zkSync Era circuits. +/// Modifications have been made to optimize the proof system for zkSync hyperchain circuits. /// @dev It uses a custom memory layout inside the inline assembly block. Each reserved memory cell is declared in the /// constants below. /// @dev For a better understanding of the verifier algorithm please refer to the following papers: diff --git a/l1-contracts/contracts/state-transition/chain-deps/DiamondInit.sol b/l1-contracts/contracts/state-transition/chain-deps/DiamondInit.sol new file mode 100644 index 000000000000..339af32c0fb2 --- /dev/null +++ b/l1-contracts/contracts/state-transition/chain-deps/DiamondInit.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {Diamond} from "../libraries/Diamond.sol"; +import {ZkSyncStateTransitionBase} from "./facets/ZkSyncStateTransitionBase.sol"; +import {FeeParams} from "./ZkSyncStateTransitionStorage.sol"; +import {L2_TO_L1_LOG_SERIALIZE_SIZE, MAX_GAS_PER_TRANSACTION} from "../../common/Config.sol"; +import {InitializeData, IDiamondInit} from "../chain-interfaces/IDiamondInit.sol"; +import {VerifierParams} from "../chain-interfaces/IVerifier.sol"; + +import "../l2-deps/ISystemContext.sol"; + +/// @author Matter Labs +/// @dev The contract is used only once to initialize the diamond proxy. +/// @dev The deployment process takes care of this contract's initialization. +contract DiamondInit is ZkSyncStateTransitionBase, IDiamondInit { + /// @dev Initialize the implementation to prevent any possibility of a Parity hack. + constructor() reentrancyGuardInitializer {} + + /// @notice hyperchain diamond contract initialization + /// @return Magic 32 bytes, which indicates that the contract logic is expected to be used as a diamond proxy + /// initializer + function initialize(InitializeData calldata _initializeData) external reentrancyGuardInitializer returns (bytes32) { + require(address(_initializeData.verifier) != address(0), "vt"); + require(_initializeData.admin != address(0), "vy"); + require(_initializeData.validatorTimelock != address(0), "hc"); + require(_initializeData.priorityTxMaxGasLimit <= MAX_GAS_PER_TRANSACTION, "vu"); + + s.chainId = _initializeData.chainId; + s.bridgehub = _initializeData.bridgehub; + s.stateTransitionManager = _initializeData.stateTransitionManager; + s.baseToken = _initializeData.baseToken; + s.baseTokenBridge = _initializeData.baseTokenBridge; + s.protocolVersion = _initializeData.protocolVersion; + + s.verifier = _initializeData.verifier; + s.admin = _initializeData.admin; + s.validators[_initializeData.validatorTimelock] = true; + + s.storedBatchHashes[0] = _initializeData.storedBatchZero; + s.verifierParams = _initializeData.verifierParams; + s.l2BootloaderBytecodeHash = _initializeData.l2BootloaderBytecodeHash; + s.l2DefaultAccountBytecodeHash = _initializeData.l2DefaultAccountBytecodeHash; + s.priorityTxMaxGasLimit = _initializeData.priorityTxMaxGasLimit; + s.feeParams = _initializeData.feeParams; + s.blobVersionedHashRetriever = _initializeData.blobVersionedHashRetriever; + + // While this does not provide a protection in the production, it is needed for local testing + // Length of the L2Log encoding should not be equal to the length of other L2Logs' tree nodes preimages + assert(L2_TO_L1_LOG_SERIALIZE_SIZE != 2 * 32); + + return Diamond.DIAMOND_INIT_SUCCESS_RETURN_VALUE; + } +} diff --git a/l1-contracts/contracts/zksync/DiamondProxy.sol b/l1-contracts/contracts/state-transition/chain-deps/DiamondProxy.sol similarity index 98% rename from l1-contracts/contracts/zksync/DiamondProxy.sol rename to l1-contracts/contracts/state-transition/chain-deps/DiamondProxy.sol index 2273dc1eec84..8d16c566bf92 100644 --- a/l1-contracts/contracts/zksync/DiamondProxy.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/DiamondProxy.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.20; -import {Diamond} from "./libraries/Diamond.sol"; +import {Diamond} from "../libraries/Diamond.sol"; /// @title Diamond Proxy Contract (EIP-2535) /// @author Matter Labs diff --git a/l1-contracts/contracts/zksync/Storage.sol b/l1-contracts/contracts/state-transition/chain-deps/ZkSyncStateTransitionStorage.sol similarity index 79% rename from l1-contracts/contracts/zksync/Storage.sol rename to l1-contracts/contracts/state-transition/chain-deps/ZkSyncStateTransitionStorage.sol index d55bc859c269..4beec8f61dc8 100644 --- a/l1-contracts/contracts/zksync/Storage.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/ZkSyncStateTransitionStorage.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.20; -import {IVerifier} from "./../zksync/interfaces/IVerifier.sol"; -import {PriorityQueue} from "./libraries/PriorityQueue.sol"; +import {IVerifier, VerifierParams} from "../chain-interfaces/IVerifier.sol"; +import {PriorityQueue} from "../../state-transition/libraries/PriorityQueue.sol"; /// @notice Indicates whether an upgrade is initiated and if yes what type /// @param None Upgrade is NOT initiated @@ -33,43 +33,6 @@ struct UpgradeStorage { uint40 currentProposalId; } -/// @dev The log passed from L2 -/// @param l2ShardId The shard identifier, 0 - rollup, 1 - porter. All other values are not used but are reserved for -/// the future -/// @param isService A boolean flag that is part of the log along with `key`, `value`, and `sender` address. -/// This field is required formally but does not have any special meaning. -/// @param txNumberInBatch The L2 transaction number in the batch, in which the log was sent -/// @param sender The L2 address which sent the log -/// @param key The 32 bytes of information that was sent in the log -/// @param value The 32 bytes of information that was sent in the log -// Both `key` and `value` are arbitrary 32-bytes selected by the log sender -struct L2Log { - uint8 l2ShardId; - bool isService; - uint16 txNumberInBatch; - address sender; - bytes32 key; - bytes32 value; -} - -/// @dev An arbitrary length message passed from L2 -/// @notice Under the hood it is `L2Log` sent from the special system L2 contract -/// @param txNumberInBatch The L2 transaction number in the batch, in which the message was sent -/// @param sender The address of the L2 account from which the message was passed -/// @param data An arbitrary length message -struct L2Message { - uint16 txNumberInBatch; - address sender; - bytes data; -} - -/// @notice Part of the configuration parameters of ZKP circuits -struct VerifierParams { - bytes32 recursionNodeLevelVkHash; - bytes32 recursionLeafLevelVkHash; - bytes32 recursionCircuitsSetVksHash; -} - /// @notice The struct that describes whether users will be charged for pubdata for L1->L2 transactions. /// @param Rollup The users are charged for pubdata & it is priced based on the gas price on Ethereum. /// @param Validium The pubdata is considered free with regard to the L1 gas price. @@ -95,18 +58,18 @@ struct FeeParams { uint64 minimalL2GasPrice; } -/// @dev storing all storage variables for zkSync facets +/// @dev storing all storage variables for hyperchain diamond facets /// NOTE: It is used in a proxy, so it is possible to add new variables to the end /// but NOT to modify already existing variables or change their order. /// NOTE: variables prefixed with '__DEPRECATED_' are deprecated and shouldn't be used. /// Their presence is maintained for compatibility and to prevent storage collision. -struct AppStorage { +struct ZkSyncStateTransitionStorage { /// @dev Storage of variables needed for deprecated diamond cut facet uint256[7] __DEPRECATED_diamondCutStorage; - /// @notice Address which will exercise critical changes to the Diamond Proxy (upgrades, freezing & unfreezing) - address governor; + /// @notice Address which will exercise critical changes to the Diamond Proxy (upgrades, freezing & unfreezing). Replaced by STM + address __DEPRECATED_governor; /// @notice Address that the governor proposed as one that will replace it - address pendingGovernor; + address __DEPRECATED_pendingGovernor; /// @notice List of permitted validators mapping(address validatorAddress => bool isValidator) validators; /// @dev Verifier contract. Used to verify aggregated proof for batches @@ -165,11 +128,26 @@ struct AppStorage { uint256 l2SystemContractsUpgradeBatchNumber; /// @dev Address which will exercise non-critical changes to the Diamond Proxy (changing validator set & unfreezing) address admin; - /// @notice Address that the governor or admin proposed as one that will replace admin role + /// @notice Address that the admin proposed as one that will replace admin role address pendingAdmin; /// @dev Fee params used to derive gasPrice for the L1->L2 transactions. For L2 transactions, /// the bootloader gives enough freedom to the operator. FeeParams feeParams; /// @dev Address of the blob versioned hash getter smart contract used for EIP-4844 versioned hashes. address blobVersionedHashRetriever; + /// new fields + /// @dev The chainId of the chain + uint256 chainId; + /// @dev The address of the bridgehub + address bridgehub; + /// @dev The address of the StateTransitionManager + address stateTransitionManager; + /// @dev The address of the baseToken contract. Eth is address(1) + address baseToken; + /// @dev The address of the baseTokenbridge. Eth also uses the shared bridge + address baseTokenBridge; + /// @notice gasPriceMultiplier for each baseToken, so that each L1->L2 transaction pays for its transaction on the destination + /// we multiply by the nominator, and divide by the denominator + uint128 baseTokenGasPriceMultiplierNominator; + uint128 baseTokenGasPriceMultiplierDenominator; } diff --git a/l1-contracts/contracts/zksync/facets/Admin.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol similarity index 57% rename from l1-contracts/contracts/zksync/facets/Admin.sol rename to l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol index 47955b5eb4a7..9be5b168c1f0 100644 --- a/l1-contracts/contracts/zksync/facets/Admin.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol @@ -2,46 +2,25 @@ pragma solidity 0.8.20; -import {IAdmin} from "../interfaces/IAdmin.sol"; -import {Diamond} from "../libraries/Diamond.sol"; -import {MAX_GAS_PER_TRANSACTION} from "../Config.sol"; -import {FeeParams} from "../Storage.sol"; -import {Base} from "./Base.sol"; +import {IAdmin} from "../../chain-interfaces/IAdmin.sol"; +import {Diamond} from "../../libraries/Diamond.sol"; +import {MAX_GAS_PER_TRANSACTION} from "../../../common/Config.sol"; +import {FeeParams, PubdataPricingMode} from "../ZkSyncStateTransitionStorage.sol"; +import {ZkSyncStateTransitionBase} from "./ZkSyncStateTransitionBase.sol"; +import {IStateTransitionManager} from "../../IStateTransitionManager.sol"; // While formally the following import is not used, it is needed to inherit documentation from it -import {IBase} from "../interfaces/IBase.sol"; +import {IZkSyncStateTransitionBase} from "../../chain-interfaces/IZkSyncStateTransitionBase.sol"; /// @title Admin Contract controls access rights for contract management. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -contract AdminFacet is Base, IAdmin { - /// @inheritdoc IBase +contract AdminFacet is ZkSyncStateTransitionBase, IAdmin { + /// @inheritdoc IZkSyncStateTransitionBase string public constant override getName = "AdminFacet"; /// @inheritdoc IAdmin - function setPendingGovernor(address _newPendingGovernor) external onlyGovernor { - // Save previous value into the stack to put it into the event later - address oldPendingGovernor = s.pendingGovernor; - // Change pending governor - s.pendingGovernor = _newPendingGovernor; - emit NewPendingGovernor(oldPendingGovernor, _newPendingGovernor); - } - - /// @inheritdoc IAdmin - function acceptGovernor() external { - address pendingGovernor = s.pendingGovernor; - require(msg.sender == pendingGovernor, "n4"); // Only proposed by current governor address can claim the governor rights - - address previousGovernor = s.governor; - s.governor = pendingGovernor; - delete s.pendingGovernor; - - emit NewPendingGovernor(pendingGovernor, address(0)); - emit NewGovernor(previousGovernor, pendingGovernor); - } - - /// @inheritdoc IAdmin - function setPendingAdmin(address _newPendingAdmin) external onlyGovernor { + function setPendingAdmin(address _newPendingAdmin) external onlyAdmin { // Save previous value into the stack to put it into the event later address oldPendingAdmin = s.pendingAdmin; // Change pending admin @@ -63,20 +42,20 @@ contract AdminFacet is Base, IAdmin { } /// @inheritdoc IAdmin - function setValidator(address _validator, bool _active) external onlyGovernorOrAdmin { + function setValidator(address _validator, bool _active) external onlyStateTransitionManager { s.validators[_validator] = _active; emit ValidatorStatusUpdate(_validator, _active); } /// @inheritdoc IAdmin - function setPorterAvailability(bool _zkPorterIsAvailable) external onlyGovernor { + function setPorterAvailability(bool _zkPorterIsAvailable) external onlyStateTransitionManager { // Change the porter availability s.zkPorterIsAvailable = _zkPorterIsAvailable; emit IsPorterAvailableStatusUpdate(_zkPorterIsAvailable); } /// @inheritdoc IAdmin - function setPriorityTxMaxGasLimit(uint256 _newPriorityTxMaxGasLimit) external onlyGovernor { + function setPriorityTxMaxGasLimit(uint256 _newPriorityTxMaxGasLimit) external onlyStateTransitionManager { require(_newPriorityTxMaxGasLimit <= MAX_GAS_PER_TRANSACTION, "n5"); uint256 oldPriorityTxMaxGasLimit = s.priorityTxMaxGasLimit; @@ -85,7 +64,7 @@ contract AdminFacet is Base, IAdmin { } /// @inheritdoc IAdmin - function changeFeeParams(FeeParams calldata _newFeeParams) external onlyGovernor { + function changeFeeParams(FeeParams calldata _newFeeParams) external onlyAdminOrStateTransitionManager { // Double checking that the new fee params are valid, i.e. // the maximal pubdata per batch is not less than the maximal pubdata per priority transaction. require(_newFeeParams.maxPubdataPerBatch >= _newFeeParams.priorityTxMaxPubdata, "n6"); @@ -96,12 +75,52 @@ contract AdminFacet is Base, IAdmin { emit NewFeeParams(oldFeeParams, _newFeeParams); } + /// @inheritdoc IAdmin + function setTokenMultiplier(uint128 _nominator, uint128 _denominator) external onlyAdminOrStateTransitionManager { + uint128 oldNominator = s.baseTokenGasPriceMultiplierNominator; + uint128 oldDenominator = s.baseTokenGasPriceMultiplierDenominator; + + s.baseTokenGasPriceMultiplierNominator = _nominator; + s.baseTokenGasPriceMultiplierDenominator = _denominator; + + emit NewBaseTokenMultiplier(oldNominator, oldDenominator, _nominator, _denominator); + } + + function setValidiumMode(PubdataPricingMode _validiumMode) external onlyAdmin { + require(s.totalBatchesCommitted == 0, "AdminFacet: set validium only after genesis"); // Validium mode can be set only before the first batch is committed + s.feeParams.pubdataPricingMode = _validiumMode; + emit ValidiumModeStatusUpdate(_validiumMode); + } + /*////////////////////////////////////////////////////////////// UPGRADE EXECUTION //////////////////////////////////////////////////////////////*/ + /// upgrade a specific chain + function upgradeChainFromVersion( + uint256 _oldProtocolVersion, + Diamond.DiamondCutData calldata _diamondCut + ) external onlyAdminOrStateTransitionManager { + bytes32 cutHashInput = keccak256(abi.encode(_diamondCut)); + require( + cutHashInput == IStateTransitionManager(s.stateTransitionManager).upgradeCutHash(_oldProtocolVersion), + "StateTransition: cutHash mismatch" + ); + + require( + s.protocolVersion == _oldProtocolVersion, + "StateTransition: protocolVersion mismatch in STC when upgrading" + ); + Diamond.diamondCut(_diamondCut); + emit ExecuteUpgrade(_diamondCut); + require( + s.protocolVersion > _oldProtocolVersion, + "StateTransition: protocolVersion mismatch in STC after upgrading" + ); + } + /// @inheritdoc IAdmin - function executeUpgrade(Diamond.DiamondCutData calldata _diamondCut) external onlyGovernor { + function executeUpgrade(Diamond.DiamondCutData calldata _diamondCut) external onlyStateTransitionManager { Diamond.diamondCut(_diamondCut); emit ExecuteUpgrade(_diamondCut); } @@ -111,7 +130,7 @@ contract AdminFacet is Base, IAdmin { //////////////////////////////////////////////////////////////*/ /// @inheritdoc IAdmin - function freezeDiamond() external onlyGovernor { + function freezeDiamond() external onlyAdminOrStateTransitionManager { Diamond.DiamondStorage storage diamondStorage = Diamond.getDiamondStorage(); require(!diamondStorage.isFrozen, "a9"); // diamond proxy is frozen already @@ -121,7 +140,7 @@ contract AdminFacet is Base, IAdmin { } /// @inheritdoc IAdmin - function unfreezeDiamond() external onlyGovernorOrAdmin { + function unfreezeDiamond() external onlyAdminOrStateTransitionManager { Diamond.DiamondStorage storage diamondStorage = Diamond.getDiamondStorage(); require(diamondStorage.isFrozen, "a7"); // diamond proxy is not frozen diff --git a/l1-contracts/contracts/zksync/facets/Executor.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol similarity index 87% rename from l1-contracts/contracts/zksync/facets/Executor.sol rename to l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol index 488eb04da3af..7b8d48bab733 100644 --- a/l1-contracts/contracts/zksync/facets/Executor.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol @@ -2,26 +2,28 @@ pragma solidity 0.8.20; -import {Base} from "./Base.sol"; -import {COMMIT_TIMESTAMP_NOT_OLDER, COMMIT_TIMESTAMP_APPROXIMATION_DELTA, EMPTY_STRING_KECCAK, L2_TO_L1_LOG_SERIALIZE_SIZE, MAX_L2_TO_L1_LOGS_COMMITMENT_BYTES, PACKED_L2_BLOCK_TIMESTAMP_MASK, PUBLIC_INPUT_SHIFT, POINT_EVALUATION_PRECOMPILE_ADDR} from "../Config.sol"; -import {IExecutor, L2_LOG_ADDRESS_OFFSET, L2_LOG_KEY_OFFSET, L2_LOG_VALUE_OFFSET, SystemLogKey, LogProcessingOutput, PubdataSource, BLS_MODULUS, PUBDATA_COMMITMENT_SIZE, PUBDATA_COMMITMENT_CLAIMED_VALUE_OFFSET, PUBDATA_COMMITMENT_COMMITMENT_OFFSET, MAX_NUMBER_OF_BLOBS} from "../interfaces/IExecutor.sol"; -import {PriorityQueue, PriorityOperation} from "../libraries/PriorityQueue.sol"; -import {UncheckedMath} from "../../common/libraries/UncheckedMath.sol"; -import {UnsafeBytes} from "../../common/libraries/UnsafeBytes.sol"; -import {VerifierParams} from "../Storage.sol"; -import {L2_BOOTLOADER_ADDRESS, L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, L2_SYSTEM_CONTEXT_SYSTEM_CONTRACT_ADDR, L2_PUBDATA_CHUNK_PUBLISHER_ADDR} from "../../common/L2ContractAddresses.sol"; +import {ZkSyncStateTransitionBase} from "./ZkSyncStateTransitionBase.sol"; +import {COMMIT_TIMESTAMP_NOT_OLDER, COMMIT_TIMESTAMP_APPROXIMATION_DELTA, EMPTY_STRING_KECCAK, L2_TO_L1_LOG_SERIALIZE_SIZE, MAX_L2_TO_L1_LOGS_COMMITMENT_BYTES, PACKED_L2_BLOCK_TIMESTAMP_MASK, PUBLIC_INPUT_SHIFT, POINT_EVALUATION_PRECOMPILE_ADDR} from "../../../common/Config.sol"; +import {IExecutor, L2_LOG_ADDRESS_OFFSET, L2_LOG_KEY_OFFSET, L2_LOG_VALUE_OFFSET, SystemLogKey, LogProcessingOutput, PubdataSource, BLS_MODULUS, PUBDATA_COMMITMENT_SIZE, PUBDATA_COMMITMENT_CLAIMED_VALUE_OFFSET, PUBDATA_COMMITMENT_COMMITMENT_OFFSET, MAX_NUMBER_OF_BLOBS} from "../../chain-interfaces/IExecutor.sol"; +import {PriorityQueue, PriorityOperation} from "../../libraries/PriorityQueue.sol"; +import {UncheckedMath} from "../../../common/libraries/UncheckedMath.sol"; +import {UnsafeBytes} from "../../../common/libraries/UnsafeBytes.sol"; +import {VerifierParams} from "../../chain-interfaces/IVerifier.sol"; +import {L2_BOOTLOADER_ADDRESS, L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, L2_SYSTEM_CONTEXT_SYSTEM_CONTRACT_ADDR, L2_PUBDATA_CHUNK_PUBLISHER_ADDR} from "../../../common/L2ContractAddresses.sol"; +import {PubdataPricingMode} from "../ZkSyncStateTransitionStorage.sol"; +import {IStateTransitionManager} from "../../IStateTransitionManager.sol"; // While formally the following import is not used, it is needed to inherit documentation from it -import {IBase} from "../interfaces/IBase.sol"; +import {IZkSyncStateTransitionBase} from "../../chain-interfaces/IZkSyncStateTransitionBase.sol"; -/// @title zkSync Executor contract capable of processing events emitted in the zkSync protocol. +/// @title zkSync hyperchain Executor contract capable of processing events emitted in the zkSync hyperchain protocol. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -contract ExecutorFacet is Base, IExecutor { +contract ExecutorFacet is ZkSyncStateTransitionBase, IExecutor { using UncheckedMath for uint256; using PriorityQueue for PriorityQueue.Queue; - /// @inheritdoc IBase + /// @inheritdoc IZkSyncStateTransitionBase string public constant override getName = "ExecutorFacet"; /// @dev Process one batch commit using the previous batch StoredBatchInfo @@ -43,7 +45,11 @@ contract ExecutorFacet is Base, IExecutor { bytes32[] memory blobCommitments = new bytes32[](MAX_NUMBER_OF_BLOBS); bytes32[] memory blobHashes = new bytes32[](MAX_NUMBER_OF_BLOBS); - if (pubdataSource == uint8(PubdataSource.Blob)) { + if (s.feeParams.pubdataPricingMode == PubdataPricingMode.Validium) { + // skipping data validation for validium, we just check that the data is empty + require(logOutput.pubdataHash == 0x00, "v0h"); + require(_newBatch.pubdataCommitments.length == 0); + } else if (pubdataSource == uint8(PubdataSource.Blob)) { // We want only want to include the actual blob linear hashes when we send pubdata via blobs. // Otherwise we should be using bytes32(0) blobHashes[0] = logOutput.blob1Hash; @@ -194,6 +200,33 @@ contract ExecutorFacet is Base, IExecutor { StoredBatchInfo memory _lastCommittedBatchData, CommitBatchInfo[] calldata _newBatchesData ) external nonReentrant onlyValidator { + _commitBatches(_lastCommittedBatchData, _newBatchesData); + } + + /// @inheritdoc IExecutor + function commitBatchesSharedBridge( + uint256, // _chainId + StoredBatchInfo memory _lastCommittedBatchData, + CommitBatchInfo[] calldata _newBatchesData + ) external nonReentrant onlyValidator { + _commitBatches(_lastCommittedBatchData, _newBatchesData); + } + + function _commitBatches( + StoredBatchInfo memory _lastCommittedBatchData, + CommitBatchInfo[] calldata _newBatchesData + ) internal { + // check that we have the right protocol version + // three comments: + // 1. A chain has to keep their protocol version up to date, as processing a block requires the latest or previous protocol version + // to solve this we will need to add the feature to create batches with only the protocol upgrade tx, without any other txs. + // 2. A chain might become out of sync if it launches while we are in the middle of a protocol upgrade. This would mean they cannot process their genesis upgrade + // as thier protocolversion would be outdated, and they also cannot process the protocol upgrade tx as they have a pending upgrade. + // 3. The protocol upgrade is increased in the BaseZkSyncUpgrade, in the executor only the systemContractsUpgradeTxHash is checked + require( + IStateTransitionManager(s.stateTransitionManager).protocolVersion() == s.protocolVersion, + "Executor facet: wrong protocol version" + ); // With the new changes for EIP-4844, namely the restriction on number of blobs per block, we only allow for a single batch to be committed at a time. require(_newBatchesData.length == 1, "e4"); // Check that we commit batches after last committed batch @@ -300,8 +333,20 @@ contract ExecutorFacet is Base, IExecutor { s.l2LogsRootHashes[currentBatchNumber] = _storedBatch.l2LogsTreeRoot; } + /// @inheritdoc IExecutor + function executeBatchesSharedBridge( + uint256, + StoredBatchInfo[] calldata _batchesData + ) external nonReentrant onlyValidator { + _executeBatches(_batchesData); + } + /// @inheritdoc IExecutor function executeBatches(StoredBatchInfo[] calldata _batchesData) external nonReentrant onlyValidator { + _executeBatches(_batchesData); + } + + function _executeBatches(StoredBatchInfo[] calldata _batchesData) internal { uint256 nBatches = _batchesData.length; for (uint256 i = 0; i < nBatches; i = i.uncheckedInc()) { _executeOneBatch(_batchesData[i], i); @@ -325,6 +370,24 @@ contract ExecutorFacet is Base, IExecutor { StoredBatchInfo[] calldata _committedBatches, ProofInput calldata _proof ) external nonReentrant onlyValidator { + _proveBatches(_prevBatch, _committedBatches, _proof); + } + + /// @inheritdoc IExecutor + function proveBatchesSharedBridge( + uint256, // _chainId + StoredBatchInfo calldata _prevBatch, + StoredBatchInfo[] calldata _committedBatches, + ProofInput calldata _proof + ) external nonReentrant onlyValidator { + _proveBatches(_prevBatch, _committedBatches, _proof); + } + + function _proveBatches( + StoredBatchInfo calldata _prevBatch, + StoredBatchInfo[] calldata _committedBatches, + ProofInput calldata _proof + ) internal { // Save the variables into the stack to save gas on reading them later uint256 currentTotalBatchesVerified = s.totalBatchesVerified; uint256 committedBatchesLength = _committedBatches.length; @@ -406,7 +469,16 @@ contract ExecutorFacet is Base, IExecutor { } /// @inheritdoc IExecutor - function revertBatches(uint256 _newLastBatch) external nonReentrant onlyValidator { + function revertBatches(uint256 _newLastBatch) external nonReentrant onlyValidatorOrStateTransitionManager { + _revertBatches(_newLastBatch); + } + + /// @inheritdoc IExecutor + function revertBatchesSharedBridge(uint256, uint256 _newLastBatch) external nonReentrant onlyValidator { + _revertBatches(_newLastBatch); + } + + function _revertBatches(uint256 _newLastBatch) internal { require(s.totalBatchesCommitted > _newLastBatch, "v1"); // The last committed batch is less than new last batch require(_newLastBatch >= s.totalBatchesExecuted, "v2"); // Already executed batches cannot be reverted diff --git a/l1-contracts/contracts/zksync/facets/Getters.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Getters.sol similarity index 77% rename from l1-contracts/contracts/zksync/facets/Getters.sol rename to l1-contracts/contracts/state-transition/chain-deps/facets/Getters.sol index 6ff706a76058..e9a5617e6183 100644 --- a/l1-contracts/contracts/zksync/facets/Getters.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Getters.sol @@ -2,25 +2,26 @@ pragma solidity 0.8.20; -import {Base} from "./Base.sol"; -import {VerifierParams} from "../Storage.sol"; -import {Diamond} from "../libraries/Diamond.sol"; -import {PriorityQueue, PriorityOperation} from "../libraries/PriorityQueue.sol"; -import {UncheckedMath} from "../../common/libraries/UncheckedMath.sol"; -import {IGetters} from "../interfaces/IGetters.sol"; -import {ILegacyGetters} from "../interfaces/ILegacyGetters.sol"; +import {ZkSyncStateTransitionBase} from "./ZkSyncStateTransitionBase.sol"; +import {PubdataPricingMode} from "../ZkSyncStateTransitionStorage.sol"; +import {VerifierParams} from "../../../state-transition/chain-interfaces/IVerifier.sol"; +import {Diamond} from "../../libraries/Diamond.sol"; +import {PriorityQueue, PriorityOperation} from "../../../state-transition/libraries/PriorityQueue.sol"; +import {UncheckedMath} from "../../../common/libraries/UncheckedMath.sol"; +import {IGetters} from "../../chain-interfaces/IGetters.sol"; +import {ILegacyGetters} from "../../chain-interfaces/ILegacyGetters.sol"; // While formally the following import is not used, it is needed to inherit documentation from it -import {IBase} from "../interfaces/IBase.sol"; +import {IZkSyncStateTransitionBase} from "../../chain-interfaces/IZkSyncStateTransitionBase.sol"; /// @title Getters Contract implements functions for getting contract state from outside the blockchain. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -contract GettersFacet is Base, IGetters, ILegacyGetters { +contract GettersFacet is ZkSyncStateTransitionBase, IGetters, ILegacyGetters { using UncheckedMath for uint256; using PriorityQueue for PriorityQueue.Queue; - /// @inheritdoc IBase + /// @inheritdoc IZkSyncStateTransitionBase string public constant override getName = "GettersFacet"; /*////////////////////////////////////////////////////////////// @@ -33,13 +34,43 @@ contract GettersFacet is Base, IGetters, ILegacyGetters { } /// @inheritdoc IGetters - function getGovernor() external view returns (address) { - return s.governor; + function getAdmin() external view returns (address) { + return s.admin; } /// @inheritdoc IGetters - function getPendingGovernor() external view returns (address) { - return s.pendingGovernor; + function getPendingAdmin() external view returns (address) { + return s.pendingAdmin; + } + + /// @inheritdoc IGetters + function getBridgehub() external view returns (address) { + return address(s.bridgehub); + } + + /// @inheritdoc IGetters + function getStateTransitionManager() external view returns (address) { + return address(s.stateTransitionManager); + } + + /// @inheritdoc IGetters + function getBaseToken() external view returns (address) { + return address(s.baseToken); + } + + /// @inheritdoc IGetters + function getBaseTokenBridge() external view returns (address) { + return address(s.baseTokenBridge); + } + + /// @inheritdoc IGetters + function baseTokenGasPriceMultiplierNominator() external view returns (uint128) { + return s.baseTokenGasPriceMultiplierNominator; + } + + /// @inheritdoc IGetters + function baseTokenGasPriceMultiplierDenominator() external view returns (uint128) { + return s.baseTokenGasPriceMultiplierDenominator; } /// @inheritdoc IGetters @@ -158,6 +189,11 @@ contract GettersFacet is Base, IGetters, ILegacyGetters { return s.isEthWithdrawalFinalized[_l2BatchNumber][_l2MessageIndex]; } + /// @inheritdoc IGetters + function getPubdataPricingMode() external view returns (PubdataPricingMode) { + return s.feeParams.pubdataPricingMode; + } + /*////////////////////////////////////////////////////////////// DIAMOND LOUPE //////////////////////////////////////////////////////////////*/ diff --git a/l1-contracts/contracts/zksync/facets/Mailbox.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol similarity index 61% rename from l1-contracts/contracts/zksync/facets/Mailbox.sol rename to l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol index f24ab945f8ef..a570fb751e88 100644 --- a/l1-contracts/contracts/zksync/facets/Mailbox.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol @@ -4,32 +4,52 @@ pragma solidity 0.8.20; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {IMailbox, TxStatus} from "../interfaces/IMailbox.sol"; -import {Merkle} from "../libraries/Merkle.sol"; -import {PriorityQueue, PriorityOperation} from "../libraries/PriorityQueue.sol"; -import {TransactionValidator} from "../libraries/TransactionValidator.sol"; -import {L2Message, L2Log, FeeParams, PubdataPricingMode} from "../Storage.sol"; -import {UncheckedMath} from "../../common/libraries/UncheckedMath.sol"; -import {UnsafeBytes} from "../../common/libraries/UnsafeBytes.sol"; -import {L2ContractHelper} from "../../common/libraries/L2ContractHelper.sol"; -import {AddressAliasHelper} from "../../vendor/AddressAliasHelper.sol"; -import {Base} from "./Base.sol"; -import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, L1_GAS_PER_PUBDATA_BYTE, L2_L1_LOGS_TREE_DEFAULT_LEAF_HASH, PRIORITY_OPERATION_L2_TX_TYPE, PRIORITY_EXPIRATION, MAX_NEW_FACTORY_DEPS} from "../Config.sol"; -import {L2_BOOTLOADER_ADDRESS, L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, L2_ETH_TOKEN_SYSTEM_CONTRACT_ADDR} from "../../common/L2ContractAddresses.sol"; +import {IMailbox} from "../../chain-interfaces/IMailbox.sol"; +import {Merkle} from "../../libraries/Merkle.sol"; +import {PriorityQueue, PriorityOperation} from "../../libraries/PriorityQueue.sol"; +import {TransactionValidator} from "../../libraries/TransactionValidator.sol"; +import {WritePriorityOpParams, L2CanonicalTransaction, L2Message, L2Log, TxStatus, BridgehubL2TransactionRequest} from "../../../common/Messaging.sol"; +import {FeeParams, PubdataPricingMode} from "../ZkSyncStateTransitionStorage.sol"; +import {UncheckedMath} from "../../../common/libraries/UncheckedMath.sol"; +import {UnsafeBytes} from "../../../common/libraries/UnsafeBytes.sol"; +import {L2ContractHelper} from "../../../common/libraries/L2ContractHelper.sol"; +import {AddressAliasHelper} from "../../../vendor/AddressAliasHelper.sol"; +import {ZkSyncStateTransitionBase} from "./ZkSyncStateTransitionBase.sol"; +import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, L1_GAS_PER_PUBDATA_BYTE, L2_L1_LOGS_TREE_DEFAULT_LEAF_HASH, PRIORITY_OPERATION_L2_TX_TYPE, PRIORITY_EXPIRATION, MAX_NEW_FACTORY_DEPS, ETH_TOKEN_ADDRESS, ERA_CHAIN_ID} from "../../../common/Config.sol"; +import {L2_BOOTLOADER_ADDRESS, L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR} from "../../../common/L2ContractAddresses.sol"; + +import {IBridgehub} from "../../../bridgehub/IBridgehub.sol"; +import {IL1SharedBridge} from "../../../bridge/interfaces/IL1SharedBridge.sol"; // While formally the following import is not used, it is needed to inherit documentation from it -import {IBase} from "../interfaces/IBase.sol"; +import {IZkSyncStateTransitionBase} from "../../chain-interfaces/IZkSyncStateTransitionBase.sol"; /// @title zkSync Mailbox contract providing interfaces for L1 <-> L2 interaction. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -contract MailboxFacet is Base, IMailbox { +contract MailboxFacet is ZkSyncStateTransitionBase, IMailbox { using UncheckedMath for uint256; using PriorityQueue for PriorityQueue.Queue; - /// @inheritdoc IBase + /// @inheritdoc IZkSyncStateTransitionBase string public constant override getName = "MailboxFacet"; + /// @inheritdoc IMailbox + function transferEthToSharedBridge() external onlyBaseTokenBridge { + require(s.chainId == ERA_CHAIN_ID, "transferEthToSharedBridge only available for Era on mailbox"); + + uint256 amount = address(this).balance; + address sharedBridgeAddress = s.baseTokenBridge; + IL1SharedBridge(sharedBridgeAddress).receiveEth{value: amount}(ERA_CHAIN_ID); + } + + /// @notice when requesting transactions through the bridgehub + function bridgehubRequestL2Transaction( + BridgehubL2TransactionRequest memory _request + ) external payable onlyBridgehub returns (bytes32 canonicalTxHash) { + canonicalTxHash = _requestL2TransactionSender(_request); + } + /// @inheritdoc IMailbox function proveL2MessageInclusion( uint256 _batchNumber, @@ -80,17 +100,6 @@ contract MailboxFacet is Base, IMailbox { return _proveL2LogInclusion(_l2BatchNumber, _l2MessageIndex, l2Log, _merkleProof); } - /// @notice Transfer ether from the contract to the receiver - /// @dev Reverts only if the transfer call failed - function _withdrawFunds(address _to, uint256 _amount) internal { - bool callSuccess; - // Low-level assembly call, to avoid any memory copying (save gas) - assembly { - callSuccess := call(gas(), _to, _amount, 0, 0, 0, 0) - } - require(callSuccess, "pz"); - } - /// @dev Prove that a specific L2 log was sent in a specific L2 batch number function _proveL2LogInclusion( uint256 _batchNumber, @@ -141,24 +150,28 @@ contract MailboxFacet is Base, IMailbox { } /// @notice Derives the price for L2 gas in ETH to be paid. - /// @param _l1GasPrice The gas price on L1. + /// @param _l1GasPrice The gas price on L1 /// @param _gasPerPubdata The price for each pubdata byte in L2 gas /// @return The price of L2 gas in ETH function _deriveL2GasPrice(uint256 _l1GasPrice, uint256 _gasPerPubdata) internal view returns (uint256) { FeeParams memory feeParams = s.feeParams; - - uint256 pubdataPriceETH; + require(s.baseTokenGasPriceMultiplierDenominator > 0, "Mailbox: baseTokenGasPriceDenominator not set"); + uint256 l1GasPriceConverted = (_l1GasPrice * s.baseTokenGasPriceMultiplierNominator) / + s.baseTokenGasPriceMultiplierDenominator; + uint256 pubdataPriceBaseToken; if (feeParams.pubdataPricingMode == PubdataPricingMode.Rollup) { - pubdataPriceETH = L1_GAS_PER_PUBDATA_BYTE * _l1GasPrice; + pubdataPriceBaseToken = L1_GAS_PER_PUBDATA_BYTE * l1GasPriceConverted; } - uint256 batchOverheadETH = uint256(feeParams.batchOverheadL1Gas) * _l1GasPrice; - uint256 fullPubdataPriceETH = pubdataPriceETH + batchOverheadETH / uint256(feeParams.maxPubdataPerBatch); + uint256 batchOverheadBaseToken = uint256(feeParams.batchOverheadL1Gas) * l1GasPriceConverted; + uint256 fullPubdataPriceBaseToken = pubdataPriceBaseToken + + batchOverheadBaseToken / + uint256(feeParams.maxPubdataPerBatch); - uint256 l2GasPrice = feeParams.minimalL2GasPrice + batchOverheadETH / uint256(feeParams.maxL2GasPerBatch); - uint256 minL2GasPriceETH = (fullPubdataPriceETH + _gasPerPubdata - 1) / _gasPerPubdata; + uint256 l2GasPrice = feeParams.minimalL2GasPrice + batchOverheadBaseToken / uint256(feeParams.maxL2GasPerBatch); + uint256 minL2GasPriceBaseToken = (fullPubdataPriceBaseToken + _gasPerPubdata - 1) / _gasPerPubdata; - return Math.max(l2GasPrice, minL2GasPriceETH); + return Math.max(l2GasPrice, minL2GasPriceBaseToken); } /// @inheritdoc IMailbox @@ -169,26 +182,18 @@ contract MailboxFacet is Base, IMailbox { bytes calldata _message, bytes32[] calldata _merkleProof ) external nonReentrant { - require(!s.isEthWithdrawalFinalized[_l2BatchNumber][_l2MessageIndex], "jj"); - - L2Message memory l2ToL1Message = L2Message({ - txNumberInBatch: _l2TxNumberInBatch, - sender: L2_ETH_TOKEN_SYSTEM_CONTRACT_ADDR, - data: _message - }); - - (address _l1WithdrawReceiver, uint256 _amount) = _parseL2WithdrawalMessage(_message); - - bool proofValid = proveL2MessageInclusion(_l2BatchNumber, _l2MessageIndex, l2ToL1Message, _merkleProof); - require(proofValid, "pi"); // Failed to verify that withdrawal was actually initialized on L2 - - s.isEthWithdrawalFinalized[_l2BatchNumber][_l2MessageIndex] = true; - _withdrawFunds(_l1WithdrawReceiver, _amount); - - emit EthWithdrawalFinalized(_l1WithdrawReceiver, _amount); + require(s.chainId == ERA_CHAIN_ID, "finalizeEthWithdrawal only available for Era on mailbox"); + IL1SharedBridge(s.baseTokenBridge).finalizeWithdrawal( + ERA_CHAIN_ID, + _l2BatchNumber, + _l2MessageIndex, + _l2TxNumberInBatch, + _message, + _merkleProof + ); } - /// @inheritdoc IMailbox + /// @inheritdoc IMailbox function requestL2Transaction( address _contractL2, uint256 _l2Value, @@ -197,82 +202,94 @@ contract MailboxFacet is Base, IMailbox { uint256 _l2GasPerPubdataByteLimit, bytes[] calldata _factoryDeps, address _refundRecipient - ) external payable nonReentrant returns (bytes32 canonicalTxHash) { + ) external payable returns (bytes32 canonicalTxHash) { + require(s.chainId == ERA_CHAIN_ID, "legacy interface only available for era token"); + canonicalTxHash = _requestL2TransactionSender( + BridgehubL2TransactionRequest({ + sender: msg.sender, + contractL2: _contractL2, + mintValue: msg.value, + l2Value: _l2Value, + l2GasLimit: _l2GasLimit, + l2Calldata: _calldata, + l2GasPerPubdataByteLimit: _l2GasPerPubdataByteLimit, + factoryDeps: _factoryDeps, + refundRecipient: _refundRecipient + }) + ); + IL1SharedBridge(s.baseTokenBridge).bridgehubDepositBaseToken{value: msg.value}( + s.chainId, + msg.sender, + ETH_TOKEN_ADDRESS, + msg.value + ); + } + + function _requestL2TransactionSender( + BridgehubL2TransactionRequest memory _request + ) internal nonReentrant returns (bytes32 canonicalTxHash) { // Change the sender address if it is a smart contract to prevent address collision between L1 and L2. // Please note, currently zkSync address derivation is different from Ethereum one, but it may be changed in the future. - address sender = msg.sender; - if (sender != tx.origin) { - sender = AddressAliasHelper.applyL1ToL2Alias(msg.sender); + address l2Sender = _request.sender; + if (l2Sender != tx.origin) { + l2Sender = AddressAliasHelper.applyL1ToL2Alias(_request.sender); } - // Enforcing that `_l2GasPerPubdataByteLimit` equals to a certain constant number. This is needed - // to ensure that users do not get used to using "exotic" numbers for _l2GasPerPubdataByteLimit, e.g. 1-2, etc. + // Enforcing that `_request.l2GasPerPubdataByteLimit` equals to a certain constant number. This is needed + // to ensure that users do not get used to using "exotic" numbers for _request.l2GasPerPubdataByteLimit, e.g. 1-2, etc. // VERY IMPORTANT: nobody should rely on this constant to be fixed and every contract should give their users the ability to provide the - // ability to provide `_l2GasPerPubdataByteLimit` for each independent transaction. + // ability to provide `_request.l2GasPerPubdataByteLimit` for each independent transaction. // CHANGING THIS CONSTANT SHOULD BE A CLIENT-SIDE CHANGE. - require(_l2GasPerPubdataByteLimit == REQUIRED_L2_GAS_PRICE_PER_PUBDATA, "qp"); - - canonicalTxHash = _requestL2Transaction( - sender, - _contractL2, - _l2Value, - _calldata, - _l2GasLimit, - _l2GasPerPubdataByteLimit, - _factoryDeps, - _refundRecipient - ); + require(_request.l2GasPerPubdataByteLimit == REQUIRED_L2_GAS_PRICE_PER_PUBDATA, "qp"); + + // Here we manually assign fields for the struct to prevent "stack too deep" error + WritePriorityOpParams memory params; + + params.sender = l2Sender; + params.l2Value = _request.l2Value; + params.contractAddressL2 = _request.contractL2; + params.l2GasLimit = _request.l2GasLimit; + params.l2GasPricePerPubdata = _request.l2GasPerPubdataByteLimit; + params.refundRecipient = _request.refundRecipient; + + canonicalTxHash = _requestL2Transaction(_request.mintValue, params, _request.l2Calldata, _request.factoryDeps); } function _requestL2Transaction( - address _sender, - address _contractAddressL2, - uint256 _l2Value, - bytes calldata _calldata, - uint256 _l2GasLimit, - uint256 _l2GasPerPubdataByteLimit, - bytes[] calldata _factoryDeps, - address _refundRecipient + uint256 _mintValue, + WritePriorityOpParams memory _params, + bytes memory _calldata, + bytes[] memory _factoryDeps ) internal returns (bytes32 canonicalTxHash) { require(_factoryDeps.length <= MAX_NEW_FACTORY_DEPS, "uj"); - uint64 expirationTimestamp = uint64(block.timestamp + PRIORITY_EXPIRATION); // Safe to cast - uint256 txId = s.priorityQueue.getTotalPriorityTxs(); - - // Here we manually assign fields for the struct to prevent "stack too deep" error - WritePriorityOpParams memory params; + _params.txId = s.priorityQueue.getTotalPriorityTxs(); // Checking that the user provided enough ether to pay for the transaction. // Using a new scope to prevent "stack too deep" error - { - params.l2GasPrice = _deriveL2GasPrice(tx.gasprice, _l2GasPerPubdataByteLimit); - uint256 baseCost = params.l2GasPrice * _l2GasLimit; - require(msg.value >= baseCost + _l2Value, "mv"); // The `msg.value` doesn't cover the transaction cost - } + + _params.l2GasPrice = _deriveL2GasPrice(tx.gasprice, _params.l2GasPricePerPubdata); + uint256 baseCost = _params.l2GasPrice * _params.l2GasLimit; + require(_mintValue >= baseCost + _params.l2Value, "mv"); // The `msg.value` doesn't cover the transaction cost // If the `_refundRecipient` is not provided, we use the `_sender` as the recipient. - address refundRecipient = _refundRecipient == address(0) ? _sender : _refundRecipient; + address refundRecipient = _params.refundRecipient == address(0) ? _params.sender : _params.refundRecipient; // If the `_refundRecipient` is a smart contract, we apply the L1 to L2 alias to prevent foot guns. if (refundRecipient.code.length > 0) { refundRecipient = AddressAliasHelper.applyL1ToL2Alias(refundRecipient); } + _params.refundRecipient = refundRecipient; - params.sender = _sender; - params.txId = txId; - params.l2Value = _l2Value; - params.contractAddressL2 = _contractAddressL2; - params.expirationTimestamp = expirationTimestamp; - params.l2GasLimit = _l2GasLimit; - params.l2GasPricePerPubdata = _l2GasPerPubdataByteLimit; - params.valueToMint = msg.value; - params.refundRecipient = refundRecipient; - - canonicalTxHash = _writePriorityOp(params, _calldata, _factoryDeps); + // populate missing fields + _params.expirationTimestamp = uint64(block.timestamp + PRIORITY_EXPIRATION); // Safe to cast + _params.valueToMint = _mintValue; + + canonicalTxHash = _writePriorityOp(_params, _calldata, _factoryDeps); } function _serializeL2Transaction( WritePriorityOpParams memory _priorityOpParams, - bytes calldata _calldata, - bytes[] calldata _factoryDeps + bytes memory _calldata, + bytes[] memory _factoryDeps ) internal pure returns (L2CanonicalTransaction memory transaction) { transaction = L2CanonicalTransaction({ txType: PRIORITY_OPERATION_L2_TX_TYPE, @@ -298,8 +315,8 @@ contract MailboxFacet is Base, IMailbox { /// @notice Stores a transaction record in storage & send event about that function _writePriorityOp( WritePriorityOpParams memory _priorityOpParams, - bytes calldata _calldata, - bytes[] calldata _factoryDeps + bytes memory _calldata, + bytes[] memory _factoryDeps ) internal returns (bytes32 canonicalTxHash) { L2CanonicalTransaction memory transaction = _serializeL2Transaction(_priorityOpParams, _calldata, _factoryDeps); @@ -333,9 +350,7 @@ contract MailboxFacet is Base, IMailbox { } /// @notice Hashes the L2 bytecodes and returns them in the format in which they are processed by the bootloader - function _hashFactoryDeps( - bytes[] calldata _factoryDeps - ) internal pure returns (uint256[] memory hashedFactoryDeps) { + function _hashFactoryDeps(bytes[] memory _factoryDeps) internal pure returns (uint256[] memory hashedFactoryDeps) { uint256 factoryDepsLen = _factoryDeps.length; hashedFactoryDeps = new uint256[](factoryDepsLen); for (uint256 i = 0; i < factoryDepsLen; i = i.uncheckedInc()) { @@ -347,27 +362,4 @@ contract MailboxFacet is Base, IMailbox { } } } - - /// @dev Decode the withdraw message that came from L2 - function _parseL2WithdrawalMessage( - bytes memory _message - ) internal pure returns (address l1Receiver, uint256 amount) { - // We check that the message is long enough to read the data. - // Please note that there are two versions of the message: - // 1. The message that is sent by `withdraw(address _l1Receiver)` - // It should be equal to the length of the bytes4 function signature + address l1Receiver + uint256 amount = 4 + 20 + 32 = 56 (bytes). - // 2. The message that is sent by `withdrawWithMessage(address _l1Receiver, bytes calldata _additionalData)` - // It should be equal to the length of the following: - // bytes4 function signature + address l1Receiver + uint256 amount + address l2Sender + bytes _additionalData = - // = 4 + 20 + 32 + 32 + _additionalData.length >= 68 (bytes). - - // So the data is expected to be at least 56 bytes long. - require(_message.length >= 56, "pm"); - - (uint32 functionSignature, uint256 offset) = UnsafeBytes.readUint32(_message, 0); - require(bytes4(functionSignature) == this.finalizeEthWithdrawal.selector, "is"); - - (l1Receiver, offset) = UnsafeBytes.readAddress(_message, offset); - (amount, offset) = UnsafeBytes.readUint256(_message, offset); - } } diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/ZkSyncStateTransitionBase.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/ZkSyncStateTransitionBase.sol new file mode 100644 index 000000000000..9c3dfd76cb77 --- /dev/null +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/ZkSyncStateTransitionBase.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {ZkSyncStateTransitionStorage} from "../ZkSyncStateTransitionStorage.sol"; +import {ReentrancyGuard} from "../../../common/ReentrancyGuard.sol"; + +/// @title Base contract containing functions accessible to the other facets. +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +contract ZkSyncStateTransitionBase is ReentrancyGuard { + ZkSyncStateTransitionStorage internal s; + + /// @notice Checks that the message sender is an active admin + modifier onlyAdmin() { + require(msg.sender == s.admin, "StateTransition Chain: not admin"); + _; + } + + /// @notice Checks if validator is active + modifier onlyValidator() { + require(s.validators[msg.sender], "StateTransition Chain: not validator"); + _; + } + + modifier onlyStateTransitionManager() { + require(msg.sender == s.stateTransitionManager, "StateTransition Chain: not state transition manager"); + _; + } + + modifier onlyBridgehub() { + require(msg.sender == s.bridgehub, "StateTransition Chain: not bridgehub"); + _; + } + + modifier onlyAdminOrStateTransitionManager() { + require( + msg.sender == s.admin || msg.sender == s.stateTransitionManager, + "StateTransition Chain: Only by admin or state transition manager" + ); + _; + } + + modifier onlyValidatorOrStateTransitionManager() { + require( + s.validators[msg.sender] || msg.sender == s.stateTransitionManager, + "StateTransition Chain: Only by validator or state transition manager" + ); + _; + } + + modifier onlyBaseTokenBridge() { + require(msg.sender == s.baseTokenBridge, "Only shared bridge can call this function"); + _; + } +} diff --git a/l1-contracts/contracts/zksync/interfaces/IAdmin.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol similarity index 73% rename from l1-contracts/contracts/zksync/interfaces/IAdmin.sol rename to l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol index 5ed5167a5f01..0993515ebf99 100644 --- a/l1-contracts/contracts/zksync/interfaces/IAdmin.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol @@ -2,23 +2,16 @@ pragma solidity 0.8.20; -import {IBase} from "./IBase.sol"; +import {IZkSyncStateTransitionBase} from "../chain-interfaces/IZkSyncStateTransitionBase.sol"; + import {Diamond} from "../libraries/Diamond.sol"; -import {FeeParams} from "../Storage.sol"; +import {FeeParams, PubdataPricingMode} from "../chain-deps/ZkSyncStateTransitionStorage.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 is IBase { - /// @notice Starts the transfer of governor rights. Only the current governor can propose a new pending one. - /// @notice New governor can accept governor rights by calling `acceptGovernor` function. - /// @param _newPendingGovernor Address of the new governor - function setPendingGovernor(address _newPendingGovernor) external; - - /// @notice Accepts transfer of governor rights. Only pending governor can accept the role. - function acceptGovernor() external; - - /// @notice Starts the transfer of admin rights. Only the current governor or admin can propose a new pending one. +interface IAdmin is IZkSyncStateTransitionBase { + /// @notice Starts the transfer of admin rights. Only the current admin can propose a new pending one. /// @notice New admin can accept admin rights by calling `acceptAdmin` function. /// @param _newPendingAdmin Address of the new admin function setPendingAdmin(address _newPendingAdmin) external; @@ -43,8 +36,16 @@ interface IAdmin is IBase { /// @param _newFeeParams The new fee params function changeFeeParams(FeeParams calldata _newFeeParams) external; + /// @notice Change the token multiplier for L1->L2 transactions + function setTokenMultiplier(uint128 _nominator, uint128 _denominator) external; + + /// @notice Used to set to validium directly after genesis + function setValidiumMode(PubdataPricingMode _validiumMode) external; + + function upgradeChainFromVersion(uint256 _protocolVersion, Diamond.DiamondCutData calldata _cutData) external; + /// @notice Executes a proposed governor upgrade - /// @dev Only the current governor can execute the upgrade + /// @dev Only the current admin can execute the upgrade /// @param _diamondCut The diamond cut parameters to be executed function executeUpgrade(Diamond.DiamondCutData calldata _diamondCut) external; @@ -53,7 +54,7 @@ interface IAdmin is IBase { function freezeDiamond() external; /// @notice Unpause the functionality of all freezable facets & their selectors - /// @dev Both the governor and its owner can unfreeze Diamond Proxy + /// @dev Both the admin and the STM can unfreeze Diamond Proxy function unfreezeDiamond() external; /// @notice Porter availability status changes @@ -62,13 +63,6 @@ interface IAdmin is IBase { /// @notice Validator's status changed event ValidatorStatusUpdate(address indexed validatorAddress, bool isActive); - /// @notice pendingGovernor is changed - /// @dev Also emitted when new governor is accepted and in this case, `newPendingGovernor` would be zero address - event NewPendingGovernor(address indexed oldPendingGovernor, address indexed newPendingGovernor); - - /// @notice Governor changed - event NewGovernor(address indexed oldGovernor, address indexed newGovernor); - /// @notice pendingAdmin is changed /// @dev Also emitted when new admin is accepted and in this case, `newPendingAdmin` would be zero address event NewPendingAdmin(address indexed oldPendingAdmin, address indexed newPendingAdmin); @@ -82,6 +76,17 @@ interface IAdmin is IBase { /// @notice Fee params for L1->L2 transactions changed event NewFeeParams(FeeParams oldFeeParams, FeeParams newFeeParams); + /// @notice Validium mode status changed + event ValidiumModeStatusUpdate(PubdataPricingMode validiumMode); + + /// @notice BaseToken multiplier for L1->L2 transactions changed + event NewBaseTokenMultiplier( + uint128 oldNominator, + uint128 oldDenominator, + uint128 newNominator, + uint128 newDenominator + ); + /// @notice Emitted when an upgrade is executed. event ExecuteUpgrade(Diamond.DiamondCutData diamondCut); diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IDiamondInit.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IDiamondInit.sol new file mode 100644 index 000000000000..3212af146e6c --- /dev/null +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IDiamondInit.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {L2CanonicalTransaction} from "../../common/Messaging.sol"; +import {IVerifier, VerifierParams} from "./IVerifier.sol"; +import {FeeParams} from "../chain-deps/ZkSyncStateTransitionStorage.sol"; + +/// @param chainId the id of the chain +/// @param bridgehub the address of the bridgehub contract +/// @param stateTransitionManager contract's address +/// @param protocolVersion initial protocol version +/// @param validatorTimelock address of the validator timelock that delays execution +/// @param admin address who can manage the contract +/// @param baseToken address of the base token of the chain +/// @param baseTokenBridge address of the L1 shared bridge contract +/// @param storedBatchZero hash of the initial genesis batch +/// @param verifier address of Verifier contract +/// @param verifierParams Verifier config parameters that describes the circuit to be verified +/// @param l2BootloaderBytecodeHash The hash of bootloader L2 bytecode +/// @param l2DefaultAccountBytecodeHash The hash of default account L2 bytecode +/// @param priorityTxMaxGasLimit maximum number of the L2 gas that a user can request for L1 -> L2 transactions +/// @param feeParams Fee parameters to be used for L1->L2 transactions +/// @param blobVersionedHashRetriever Address of contract used to pull the blob versioned hash for a transaction. +struct InitializeData { + uint256 chainId; + address bridgehub; + address stateTransitionManager; + uint256 protocolVersion; + address admin; + address validatorTimelock; + address baseToken; + address baseTokenBridge; + bytes32 storedBatchZero; + IVerifier verifier; + VerifierParams verifierParams; + bytes32 l2BootloaderBytecodeHash; + bytes32 l2DefaultAccountBytecodeHash; + uint256 priorityTxMaxGasLimit; + FeeParams feeParams; + address blobVersionedHashRetriever; +} + +/// @param verifier address of Verifier contract +/// @param verifierParams Verifier config parameters that describes the circuit to be verified +/// @param l2BootloaderBytecodeHash The hash of bootloader L2 bytecode +/// @param l2DefaultAccountBytecodeHash The hash of default account L2 bytecode +/// @param priorityTxMaxGasLimit maximum number of the L2 gas that a user can request for L1 -> L2 transactions +/// @param feeParams Fee parameters to be used for L1->L2 transactions +/// @param blobVersionedHashRetriever Address of contract used to pull the blob versioned hash for a transaction. +struct InitializeDataNewChain { + IVerifier verifier; + VerifierParams verifierParams; + bytes32 l2BootloaderBytecodeHash; + bytes32 l2DefaultAccountBytecodeHash; + uint256 priorityTxMaxGasLimit; + FeeParams feeParams; + address blobVersionedHashRetriever; +} + +interface IDiamondInit { + function initialize(InitializeData calldata _initData) external returns (bytes32); +} diff --git a/l1-contracts/contracts/zksync/interfaces/IExecutor.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IExecutor.sol similarity index 89% rename from l1-contracts/contracts/zksync/interfaces/IExecutor.sol rename to l1-contracts/contracts/state-transition/chain-interfaces/IExecutor.sol index 7ab8f3d8ee93..ba2ba2e3b1cf 100644 --- a/l1-contracts/contracts/zksync/interfaces/IExecutor.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IExecutor.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.20; -import {IBase} from "./IBase.sol"; +import {IZkSyncStateTransitionBase} from "./IZkSyncStateTransitionBase.sol"; /// @dev Enum used by L2 System Contracts to differentiate logs. enum SystemLogKey { @@ -65,7 +65,7 @@ uint256 constant MAX_NUMBER_OF_BLOBS = 2; /// @title The interface of the zkSync Executor contract capable of processing events emitted in the zkSync protocol. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -interface IExecutor is IBase { +interface IExecutor is IZkSyncStateTransitionBase { /// @notice Rollup batch stored data /// @param batchNumber Rollup batch number /// @param batchHash Hash of L2 batch @@ -134,6 +134,13 @@ interface IExecutor is IBase { CommitBatchInfo[] calldata _newBatchesData ) external; + /// @notice same as `commitBatches` but with the chainId so ValidatorTimelock can sort the inputs. + function commitBatchesSharedBridge( + uint256 _chainId, + StoredBatchInfo calldata _lastCommittedBatchData, + CommitBatchInfo[] calldata _newBatchesData + ) external; + /// @notice Batches commitment verification. /// @dev Only verifies batch commitments without any other processing. /// @param _prevBatch Stored data of the last committed batch. @@ -145,18 +152,32 @@ interface IExecutor is IBase { ProofInput calldata _proof ) external; + /// @notice same as `proveBatches` but with the chainId so ValidatorTimelock can sort the inputs. + function proveBatchesSharedBridge( + uint256 _chainId, + StoredBatchInfo calldata _prevBatch, + StoredBatchInfo[] calldata _committedBatches, + ProofInput calldata _proof + ) external; + /// @notice The function called by the operator to finalize (execute) batches. It is responsible for: /// - Processing all pending operations (commpleting priority requests). /// - Finalizing this batch (i.e. allowing to withdraw funds from the system) /// @param _batchesData Data of the batches to be executed. function executeBatches(StoredBatchInfo[] calldata _batchesData) external; + /// @notice same as `executeBatches` but with the chainId so ValidatorTimelock can sort the inputs. + function executeBatchesSharedBridge(uint256 _chainId, StoredBatchInfo[] calldata _batchesData) external; + /// @notice Reverts unexecuted batches /// @param _newLastBatch batch number after which batches should be reverted /// NOTE: Doesn't delete the stored data about batches, but only decreases /// counters that are responsible for the number of batches function revertBatches(uint256 _newLastBatch) external; + /// @notice same as `revertBatches` but with the chainId so ValidatorTimelock can sort the inputs. + function revertBatchesSharedBridge(uint256 _chainId, uint256 _newLastBatch) external; + /// @notice Event emitted when a batch is committed /// @param batchNumber Number of the batch committed /// @param batchHash Hash of the L2 batch diff --git a/l1-contracts/contracts/zksync/interfaces/IGetters.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol similarity index 78% rename from l1-contracts/contracts/zksync/interfaces/IGetters.sol rename to l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol index 3bfa21d7dab7..60c1674cf3fe 100644 --- a/l1-contracts/contracts/zksync/interfaces/IGetters.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol @@ -3,13 +3,14 @@ pragma solidity 0.8.20; import {PriorityOperation} from "../libraries/PriorityQueue.sol"; -import {VerifierParams, UpgradeState} from "../Storage.sol"; -import "./IBase.sol"; +import {VerifierParams} from "../chain-interfaces/IVerifier.sol"; +import {PubdataPricingMode} from "../chain-deps/ZkSyncStateTransitionStorage.sol"; +import {IZkSyncStateTransitionBase} from "./IZkSyncStateTransitionBase.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 is IBase { +interface IGetters is IZkSyncStateTransitionBase { /*////////////////////////////////////////////////////////////// CUSTOM GETTERS //////////////////////////////////////////////////////////////*/ @@ -17,11 +18,23 @@ interface IGetters is IBase { /// @return The address of the verifier smart contract function getVerifier() external view returns (address); - /// @return The address of the current governor - function getGovernor() external view returns (address); + /// @return The address of the current admin + function getAdmin() external view returns (address); - /// @return The address of the pending governor - function getPendingGovernor() external view returns (address); + /// @return The address of the pending admin + function getPendingAdmin() external view returns (address); + + /// @return The address of the bridgehub + function getBridgehub() external view returns (address); + + /// @return The address of the state transition + function getStateTransitionManager() external view returns (address); + + /// @return The address of the base token + function getBaseToken() external view returns (address); + + /// @return The address of the base token bridge + function getBaseTokenBridge() external view returns (address); /// @return The total number of batches that were committed function getTotalBatchesCommitted() external view returns (uint256); @@ -92,6 +105,15 @@ interface IGetters is IBase { /// @param _l2MessageIndex The index of the L2->L1 message denoting the withdrawal. function isEthWithdrawalFinalized(uint256 _l2BatchNumber, uint256 _l2MessageIndex) external view returns (bool); + /// @return The pubdata pricing mode. + function getPubdataPricingMode() external view returns (PubdataPricingMode); + + /// @return the baseTokenGasPriceMultiplierNominator, used to compare the baseTokenPrice to ether for L1->L2 transactions + function baseTokenGasPriceMultiplierNominator() external view returns (uint128); + + /// @return the baseTokenGasPriceMultiplierDenominator, used to compare the baseTokenPrice to ether for L1->L2 transactions + function baseTokenGasPriceMultiplierDenominator() external view returns (uint128); + /*////////////////////////////////////////////////////////////// DIAMOND LOUPE //////////////////////////////////////////////////////////////*/ @@ -116,9 +138,9 @@ interface IGetters is IBase { /// @return facet The facet address associated with a selector. Zero if the selector is not added to the diamond function facetAddress(bytes4 _selector) external view returns (address facet); - /// @return Whether the selector can be frozen by the governor or always accessible + /// @return Whether the selector can be frozen by the admin or always accessible function isFunctionFreezable(bytes4 _selector) external view returns (bool); - /// @return isFreezable Whether the facet can be frozen by the governor or always accessible + /// @return isFreezable Whether the facet can be frozen by the admin or always accessible function isFacetFreezable(address _facet) external view returns (bool isFreezable); } diff --git a/l1-contracts/contracts/zksync/interfaces/ILegacyGetters.sol b/l1-contracts/contracts/state-transition/chain-interfaces/ILegacyGetters.sol similarity index 93% rename from l1-contracts/contracts/zksync/interfaces/ILegacyGetters.sol rename to l1-contracts/contracts/state-transition/chain-interfaces/ILegacyGetters.sol index 2ecc9dde835b..572e7d61a830 100644 --- a/l1-contracts/contracts/zksync/interfaces/ILegacyGetters.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/ILegacyGetters.sol @@ -2,13 +2,13 @@ pragma solidity 0.8.20; -import {IBase} from "./IBase.sol"; +import {IZkSyncStateTransitionBase} from "./IZkSyncStateTransitionBase.sol"; /// @author Matter Labs /// @dev This interface contains getters for the zkSync contract that should not be used, /// but still are kept for backward compatibility. /// @custom:security-contact security@matterlabs.dev -interface ILegacyGetters is IBase { +interface ILegacyGetters is IZkSyncStateTransitionBase { /// @return The total number of batches that were committed /// @dev It is a *deprecated* method, please use `getTotalBatchesCommitted` instead function getTotalBlocksCommitted() external view returns (uint256); diff --git a/l1-contracts/contracts/zksync/interfaces/IMailbox.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IMailbox.sol similarity index 57% rename from l1-contracts/contracts/zksync/interfaces/IMailbox.sol rename to l1-contracts/contracts/state-transition/chain-interfaces/IMailbox.sol index 6fbe09c86f42..f5650d96265f 100644 --- a/l1-contracts/contracts/zksync/interfaces/IMailbox.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IMailbox.sol @@ -2,115 +2,34 @@ pragma solidity 0.8.20; -import {L2Log, L2Message} from "../Storage.sol"; -import {IBase} from "./IBase.sol"; - -/// @dev The enum that represents the transaction execution status -/// @param Failure The transaction execution failed -/// @param Success The transaction execution succeeded -enum TxStatus { - Failure, - Success -} +import {IZkSyncStateTransitionBase} from "./IZkSyncStateTransitionBase.sol"; +import {L2CanonicalTransaction, L2Log, L2Message, TxStatus, BridgehubL2TransactionRequest} from "../../common/Messaging.sol"; /// @title The interface of the zkSync Mailbox contract that provides interfaces for L1 <-> L2 interaction. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -interface IMailbox is IBase { - /// @dev Structure that includes all fields of the L2 transaction - /// @dev The hash of this structure is the "canonical L2 transaction hash" and can be used as a unique identifier of a tx - /// @param txType The tx type number, depending on which the L2 transaction can be interpreted differently - /// @param from The sender's address. `uint256` type for possible address format changes and maintaining backward compatibility - /// @param to The recipient's address. `uint256` type for possible address format changes and maintaining backward compatibility - /// @param gasLimit The L2 gas limit for L2 transaction. Analog to the `gasLimit` on an L1 transactions - /// @param gasPerPubdataByteLimit Maximum number of L2 gas that will cost one byte of pubdata (every piece of data that will be stored on L1 as calldata) - /// @param maxFeePerGas The absolute maximum sender willing to pay per unit of L2 gas to get the transaction included in a batch. Analog to the EIP-1559 `maxFeePerGas` on an L1 transactions - /// @param maxPriorityFeePerGas The additional fee that is paid directly to the validator to incentivize them to include the transaction in a batch. Analog to the EIP-1559 `maxPriorityFeePerGas` on an L1 transactions - /// @param paymaster The address of the EIP-4337 paymaster, that will pay fees for the transaction. `uint256` type for possible address format changes and maintaining backward compatibility - /// @param nonce The nonce of the transaction. For L1->L2 transactions it is the priority operation Id. - /// @param value The value to pass with the transaction - /// @param reserved The fixed-length fields for usage in a future extension of transaction formats - /// @param data The calldata that is transmitted for the transaction call - /// @param signature An abstract set of bytes that are used for transaction authorization - /// @param factoryDeps The set of L2 bytecode hashes whose preimages were shown on L1 - /// @param paymasterInput The arbitrary-length data that is used as a calldata to the paymaster pre-call - /// @param reservedDynamic The arbitrary-length field for usage in a future extension of transaction formats - struct L2CanonicalTransaction { - uint256 txType; - uint256 from; - uint256 to; - uint256 gasLimit; - uint256 gasPerPubdataByteLimit; - uint256 maxFeePerGas; - uint256 maxPriorityFeePerGas; - uint256 paymaster; - uint256 nonce; - uint256 value; - // In the future, we might want to add some - // new fields to the struct. The `txData` struct - // is to be passed to account and any changes to its structure - // would mean a breaking change to these accounts. To prevent this, - // we should keep some fields as "reserved". - // It is also recommended that their length is fixed, since - // it would allow easier proof integration (in case we will need - // some special circuit for preprocessing transactions). - uint256[4] reserved; - bytes data; - bytes signature; - uint256[] factoryDeps; - bytes paymasterInput; - // Reserved dynamic type for the future use-case. Using it should be avoided, - // But it is still here, just in case we want to enable some additional functionality. - bytes reservedDynamic; - } - - /// @dev Internal structure that contains the parameters for the writePriorityOp - /// internal function. - /// @param sender The sender's address. - /// @param txId The id of the priority transaction. - /// @param l2Value The msg.value of the L2 transaction. - /// @param contractAddressL2 The address of the contract on L2 to call. - /// @param expirationTimestamp The timestamp by which the priority operation must be processed by the operator. - /// @param l2GasLimit The limit of the L2 gas for the L2 transaction - /// @param l2GasPrice The price of the L2 gas in Wei to be used for this transaction. - /// @param l2GasPricePerPubdata The price for a single pubdata byte in L2 gas. - /// @param valueToMint The amount of ether that should be minted on L2 as the result of this transaction. - /// @param refundRecipient The recipient of the refund for the transaction on L2. If the transaction fails, then - /// this address will receive the `l2Value`. - struct WritePriorityOpParams { - address sender; - uint256 txId; - uint256 l2Value; - address contractAddressL2; - uint64 expirationTimestamp; - uint256 l2GasLimit; - uint256 l2GasPrice; - uint256 l2GasPricePerPubdata; - uint256 valueToMint; - address refundRecipient; - } - +interface IMailbox is IZkSyncStateTransitionBase { /// @notice Prove that a specific arbitrary-length message was sent in a specific L2 batch number - /// @param _l2BatchNumber The executed L2 batch number in which the message appeared + /// @param _batchNumber The executed L2 batch number in which the message appeared /// @param _index The position in the L2 logs Merkle tree of the l2Log that was sent with the message /// @param _message Information about the sent message: sender address, the message itself, tx index in the L2 batch where the message was sent /// @param _proof Merkle proof for inclusion of L2 log that was sent with the message /// @return Whether the proof is valid function proveL2MessageInclusion( - uint256 _l2BatchNumber, + uint256 _batchNumber, uint256 _index, L2Message calldata _message, bytes32[] calldata _proof ) external view returns (bool); /// @notice Prove that a specific L2 log was sent in a specific L2 batch - /// @param _l2BatchNumber The executed L2 batch number in which the log appeared + /// @param _batchNumber The executed L2 batch number in which the log appeared /// @param _index The position of the l2log in the L2 logs Merkle tree /// @param _log Information about the sent log /// @param _proof Merkle proof for inclusion of the L2 log /// @return Whether the proof is correct and L2 log is included in batch function proveL2LogInclusion( - uint256 _l2BatchNumber, + uint256 _batchNumber, uint256 _index, L2Log memory _log, bytes32[] calldata _proof @@ -176,6 +95,10 @@ interface IMailbox is IBase { address _refundRecipient ) external payable returns (bytes32 canonicalTxHash); + function bridgehubRequestL2Transaction( + BridgehubL2TransactionRequest calldata _request + ) external payable returns (bytes32 canonicalTxHash); + /// @notice Estimates the cost in Ether of requesting execution of an L2 transaction from L1 /// @param _gasPrice expected L1 gas price at which the user requests the transaction execution /// @param _l2GasLimit Maximum amount of L2 gas that transaction can consume during execution on L2 @@ -187,12 +110,16 @@ interface IMailbox is IBase { uint256 _l2GasPerPubdataByteLimit ) external view returns (uint256); + /// @notice tranfer Eth to shared bridge as part of migration process + function transferEthToSharedBridge() external; + /// @notice New priority request event. Emitted when a request is placed into the priority queue /// @param txId Serial number of the priority operation /// @param txHash keccak256 hash of encoded transaction representation /// @param expirationTimestamp Timestamp up to which priority request should be processed /// @param transaction The whole transaction structure that is requested to be executed on L2 - /// @param factoryDeps An array of bytecodes that were shown in the L1 public data. Will be marked as known bytecodes in L2 + /// @param factoryDeps An array of bytecodes that were shown in the L1 public data. + /// Will be marked as known bytecodes in L2 event NewPriorityRequest( uint256 txId, bytes32 txHash, diff --git a/l1-contracts/contracts/zksync/interfaces/IVerifier.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IVerifier.sol similarity index 81% rename from l1-contracts/contracts/zksync/interfaces/IVerifier.sol rename to l1-contracts/contracts/state-transition/chain-interfaces/IVerifier.sol index 1fbac964c153..11221c94a610 100644 --- a/l1-contracts/contracts/zksync/interfaces/IVerifier.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IVerifier.sol @@ -2,6 +2,13 @@ pragma solidity 0.8.20; +/// @notice Part of the configuration parameters of ZKP circuits +struct VerifierParams { + bytes32 recursionNodeLevelVkHash; + bytes32 recursionLeafLevelVkHash; + bytes32 recursionCircuitsSetVksHash; +} + /// @title The interface of the Verifier contract, responsible for the zero knowledge proof verification. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncStateTransition.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncStateTransition.sol new file mode 100644 index 000000000000..cbc2d4d8f5aa --- /dev/null +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncStateTransition.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {IAdmin} from "./IAdmin.sol"; +import {IExecutor} from "./IExecutor.sol"; +import {IGetters} from "./IGetters.sol"; +import {IMailbox} from "./IMailbox.sol"; +import {Verifier} from "../Verifier.sol"; +import {VerifierParams} from "./IVerifier.sol"; + +// kl to do remove this, needed for the server for now +import "../libraries/Diamond.sol"; + +interface IZkSyncStateTransition is IAdmin, IExecutor, IGetters, IMailbox { + // KL todo: need this in the server for now + event ProposeTransparentUpgrade( + Diamond.DiamondCutData diamondCut, + uint256 indexed proposalId, + bytes32 proposalSalt + ); +} diff --git a/l1-contracts/contracts/zksync/interfaces/IBase.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncStateTransitionBase.sol similarity index 89% rename from l1-contracts/contracts/zksync/interfaces/IBase.sol rename to l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncStateTransitionBase.sol index bbe7af652dfe..547bb0c01230 100644 --- a/l1-contracts/contracts/zksync/interfaces/IBase.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncStateTransitionBase.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.20; /// @title The interface of the zkSync contract, responsible for the main zkSync logic. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -interface IBase { +interface IZkSyncStateTransitionBase { /// @return Returns facet name. function getName() external view returns (string memory); } diff --git a/l1-contracts/contracts/state-transition/l2-deps/ISystemContext.sol b/l1-contracts/contracts/state-transition/l2-deps/ISystemContext.sol new file mode 100644 index 000000000000..fded0f4d2edf --- /dev/null +++ b/l1-contracts/contracts/state-transition/l2-deps/ISystemContext.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +interface ISystemContext { + function setChainId(uint256 _newChainId) external; +} diff --git a/l1-contracts/contracts/zksync/libraries/Diamond.sol b/l1-contracts/contracts/state-transition/libraries/Diamond.sol similarity index 100% rename from l1-contracts/contracts/zksync/libraries/Diamond.sol rename to l1-contracts/contracts/state-transition/libraries/Diamond.sol diff --git a/l1-contracts/contracts/zksync/libraries/LibMap.sol b/l1-contracts/contracts/state-transition/libraries/LibMap.sol similarity index 100% rename from l1-contracts/contracts/zksync/libraries/LibMap.sol rename to l1-contracts/contracts/state-transition/libraries/LibMap.sol diff --git a/l1-contracts/contracts/zksync/libraries/Merkle.sol b/l1-contracts/contracts/state-transition/libraries/Merkle.sol similarity index 100% rename from l1-contracts/contracts/zksync/libraries/Merkle.sol rename to l1-contracts/contracts/state-transition/libraries/Merkle.sol diff --git a/l1-contracts/contracts/zksync/libraries/PriorityQueue.sol b/l1-contracts/contracts/state-transition/libraries/PriorityQueue.sol similarity index 100% rename from l1-contracts/contracts/zksync/libraries/PriorityQueue.sol rename to l1-contracts/contracts/state-transition/libraries/PriorityQueue.sol diff --git a/l1-contracts/contracts/zksync/libraries/TransactionValidator.sol b/l1-contracts/contracts/state-transition/libraries/TransactionValidator.sol similarity index 95% rename from l1-contracts/contracts/zksync/libraries/TransactionValidator.sol rename to l1-contracts/contracts/state-transition/libraries/TransactionValidator.sol index 40abd51ee980..7bb30b9a111d 100644 --- a/l1-contracts/contracts/zksync/libraries/TransactionValidator.sol +++ b/l1-contracts/contracts/state-transition/libraries/TransactionValidator.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.20; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {IMailbox} from "../interfaces/IMailbox.sol"; -import {TX_SLOT_OVERHEAD_L2_GAS, MEMORY_OVERHEAD_GAS, L1_TX_INTRINSIC_L2_GAS, L1_TX_DELTA_544_ENCODING_BYTES, L1_TX_DELTA_FACTORY_DEPS_L2_GAS, L1_TX_MIN_L2_GAS_BASE, L1_TX_INTRINSIC_PUBDATA, L1_TX_DELTA_FACTORY_DEPS_PUBDATA, MAX_GAS_PER_TRANSACTION} from "../Config.sol"; +import {L2CanonicalTransaction} from "../../common/Messaging.sol"; +import {TX_SLOT_OVERHEAD_L2_GAS, MEMORY_OVERHEAD_GAS, L1_TX_INTRINSIC_L2_GAS, L1_TX_DELTA_544_ENCODING_BYTES, L1_TX_DELTA_FACTORY_DEPS_L2_GAS, L1_TX_MIN_L2_GAS_BASE, L1_TX_INTRINSIC_PUBDATA, L1_TX_DELTA_FACTORY_DEPS_PUBDATA} from "../../common/Config.sol"; /// @title zkSync Library for validating L1 -> L2 transactions /// @author Matter Labs @@ -17,7 +17,7 @@ library TransactionValidator { /// @param _priorityTxMaxGasLimit The max gas limit, generally provided from Storage.sol /// @param _priorityTxMaxPubdata The maximal amount of pubdata that a single L1->L2 transaction can emit function validateL1ToL2Transaction( - IMailbox.L2CanonicalTransaction memory _transaction, + L2CanonicalTransaction memory _transaction, bytes memory _encoded, uint256 _priorityTxMaxGasLimit, uint256 _priorityTxMaxPubdata @@ -43,7 +43,7 @@ library TransactionValidator { /// @dev Used to validate upgrade transactions /// @param _transaction The transaction to validate - function validateUpgradeTransaction(IMailbox.L2CanonicalTransaction memory _transaction) internal pure { + function validateUpgradeTransaction(L2CanonicalTransaction memory _transaction) internal pure { // Restrict from to be within system contract range (0...2^16 - 1) require(_transaction.from <= type(uint16).max, "ua"); require(_transaction.to <= type(uint160).max, "ub"); @@ -56,7 +56,7 @@ library TransactionValidator { require(_transaction.reserved[2] == 0, "ug"); require(_transaction.reserved[3] == 0, "uo"); require(_transaction.signature.length == 0, "uh"); - require(_transaction.paymasterInput.length == 0, "ul"); + require(_transaction.paymasterInput.length == 0, "ul1"); require(_transaction.reservedDynamic.length == 0, "um"); } diff --git a/l1-contracts/contracts/zksync/utils/BlobVersionedHashRetriever.yul b/l1-contracts/contracts/state-transition/utils/BlobVersionedHashRetriever.yul similarity index 100% rename from l1-contracts/contracts/zksync/utils/BlobVersionedHashRetriever.yul rename to l1-contracts/contracts/state-transition/utils/BlobVersionedHashRetriever.yul diff --git a/l1-contracts/contracts/upgrades/BaseZkSyncUpgrade.sol b/l1-contracts/contracts/upgrades/BaseZkSyncUpgrade.sol index f73db5859637..ccdec34429e0 100644 --- a/l1-contracts/contracts/upgrades/BaseZkSyncUpgrade.sol +++ b/l1-contracts/contracts/upgrades/BaseZkSyncUpgrade.sol @@ -2,47 +2,46 @@ pragma solidity 0.8.20; -import {Base} from "../zksync/facets/Base.sol"; -import {IMailbox} from "../zksync/interfaces/IMailbox.sol"; -import {VerifierParams} from "../zksync/Storage.sol"; -import {IVerifier} from "../zksync/interfaces/IVerifier.sol"; +import {ZkSyncStateTransitionBase} from "../state-transition/chain-deps/facets/ZkSyncStateTransitionBase.sol"; +import {IMailbox} from "../state-transition/chain-interfaces/IMailbox.sol"; +import {VerifierParams} from "../state-transition/chain-interfaces/IVerifier.sol"; +import {IVerifier} from "../state-transition/chain-interfaces/IVerifier.sol"; import {L2ContractHelper} from "../common/libraries/L2ContractHelper.sol"; -import {TransactionValidator} from "../zksync/libraries/TransactionValidator.sol"; -import {SYSTEM_UPGRADE_L2_TX_TYPE, MAX_NEW_FACTORY_DEPS, MAX_ALLOWED_PROTOCOL_VERSION_DELTA} from "../zksync/Config.sol"; +import {TransactionValidator} from "../state-transition/libraries/TransactionValidator.sol"; +import {MAX_NEW_FACTORY_DEPS, SYSTEM_UPGRADE_L2_TX_TYPE, MAX_ALLOWED_PROTOCOL_VERSION_DELTA} from "../common/Config.sol"; +import {L2CanonicalTransaction} from "../common/Messaging.sol"; + +/// @notice The struct that represents the upgrade proposal. +/// @param l2ProtocolUpgradeTx The system upgrade transaction. +/// @param factoryDeps The list of factory deps for the l2ProtocolUpgradeTx. +/// @param bootloaderHash The hash of the new bootloader bytecode. If zero, it will not be updated. +/// @param defaultAccountHash The hash of the new default account bytecode. If zero, it will not be updated. +/// @param verifier The address of the new verifier. If zero, the verifier will not be updated. +/// @param verifierParams The new verifier params. If all of its fields are 0, the params will not be updated. +/// @param l1ContractsUpgradeCalldata Custom calldata for L1 contracts upgrade, it may be interpreted differently +/// in each upgrade. Usually empty. +/// @param postUpgradeCalldata Custom calldata for post upgrade hook, it may be interpreted differently in each +/// upgrade. Usually empty. +/// @param upgradeTimestamp The timestamp after which the upgrade can be executed. +/// @param newProtocolVersion The new version number for the protocol after this upgrade. Should be greater than +/// the previous protocol version. +struct ProposedUpgrade { + L2CanonicalTransaction l2ProtocolUpgradeTx; + bytes[] factoryDeps; + bytes32 bootloaderHash; + bytes32 defaultAccountHash; + address verifier; + VerifierParams verifierParams; + bytes l1ContractsUpgradeCalldata; + bytes postUpgradeCalldata; + uint256 upgradeTimestamp; + uint256 newProtocolVersion; +} /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev /// @notice Interface to which all the upgrade implementations should adhere -abstract contract BaseZkSyncUpgrade is Base { - /// @notice The struct that represents the upgrade proposal. - /// @param l2ProtocolUpgradeTx The system upgrade transaction. - /// @param factoryDeps The list of factory deps for the l2ProtocolUpgradeTx. - /// @param bootloaderHash The hash of the new bootloader bytecode. If zero, it will not be updated. - /// @param defaultAccountHash The hash of the new default account bytecode. If zero, it will not be updated. - /// @param verifier The address of the new verifier. If zero, the verifier will not be updated. - /// @param verifierParams The new verifier params. If all of its fields are 0, the params will not be updated. - /// @param l1ContractsUpgradeCalldata Custom calldata for L1 contracts upgrade, it may be interpreted differently - /// in each upgrade. Usually empty. - /// @param postUpgradeCalldata Custom calldata for post upgrade hook, it may be interpreted differently in each - /// upgrade. Usually empty. - /// @param upgradeTimestamp The timestamp after which the upgrade can be executed. - /// @param newProtocolVersion The new version number for the protocol after this upgrade. Should be greater than - /// the previous protocol version. - /// @param newAllowList The address of the new allowlist contract. If zero, it will not be updated. - struct ProposedUpgrade { - IMailbox.L2CanonicalTransaction l2ProtocolUpgradeTx; - bytes[] factoryDeps; - bytes32 bootloaderHash; - bytes32 defaultAccountHash; - address verifier; - VerifierParams verifierParams; - bytes l1ContractsUpgradeCalldata; - bytes postUpgradeCalldata; - uint256 upgradeTimestamp; - uint256 newProtocolVersion; - address newAllowList; - } - +abstract contract BaseZkSyncUpgrade is ZkSyncStateTransitionBase { /// @notice Changes the protocol version event NewProtocolVersion(uint256 indexed previousProtocolVersion, uint256 indexed newProtocolVersion); @@ -64,8 +63,8 @@ abstract contract BaseZkSyncUpgrade is Base { /// @notice The main function that will be provided by the upgrade proxy /// @dev This is a virtual function and should be overridden by custom upgrade implementations. /// @param _proposedUpgrade The upgrade to be executed. - /// @return The hash of the L2 system contract upgrade transaction. - function upgrade(ProposedUpgrade calldata _proposedUpgrade) public virtual returns (bytes32) { + /// @return txHash The hash of the L2 system contract upgrade transaction. + function upgrade(ProposedUpgrade calldata _proposedUpgrade) public virtual returns (bytes32 txHash) { // Note that due to commitment delay, the timestamp of the L2 upgrade batch may be earlier than the timestamp // of the L1 block at which the upgrade occurred. This means that using timestamp as a signifier of "upgraded" // on the L2 side would be inaccurate. The effects of this "back-dating" of L2 upgrade batches will be reduced @@ -77,7 +76,7 @@ abstract contract BaseZkSyncUpgrade is Base { _upgradeVerifier(_proposedUpgrade.verifier, _proposedUpgrade.verifierParams); _setBaseSystemContracts(_proposedUpgrade.bootloaderHash, _proposedUpgrade.defaultAccountHash); - bytes32 txHash = _setL2SystemContractUpgrade( + txHash = _setL2SystemContractUpgrade( _proposedUpgrade.l2ProtocolUpgradeTx, _proposedUpgrade.factoryDeps, _proposedUpgrade.newProtocolVersion @@ -179,7 +178,7 @@ abstract contract BaseZkSyncUpgrade is Base { /// @param _l2ProtocolUpgradeTx The L2 system contract upgrade transaction. /// @return System contracts upgrade transaction hash. Zero if no upgrade transaction is set. function _setL2SystemContractUpgrade( - IMailbox.L2CanonicalTransaction calldata _l2ProtocolUpgradeTx, + L2CanonicalTransaction calldata _l2ProtocolUpgradeTx, bytes[] calldata _factoryDeps, uint256 _newProtocolVersion ) internal returns (bytes32) { @@ -211,7 +210,9 @@ abstract contract BaseZkSyncUpgrade is Base { _verifyFactoryDeps(_factoryDeps, _l2ProtocolUpgradeTx.factoryDeps); bytes32 l2ProtocolUpgradeTxHash = keccak256(encodedTransaction); + s.l2SystemContractsUpgradeTxHash = l2ProtocolUpgradeTxHash; + return l2ProtocolUpgradeTxHash; } @@ -232,7 +233,7 @@ abstract contract BaseZkSyncUpgrade is Base { /// @notice Changes the protocol version /// @param _newProtocolVersion The new protocol version - function _setNewProtocolVersion(uint256 _newProtocolVersion) internal { + function _setNewProtocolVersion(uint256 _newProtocolVersion) internal virtual { uint256 previousProtocolVersion = s.protocolVersion; require( _newProtocolVersion > previousProtocolVersion, @@ -244,6 +245,7 @@ abstract contract BaseZkSyncUpgrade is Base { ); // If the previous upgrade had an L2 system upgrade transaction, we require that it is finalized. + // Note it is important to keep this check, as otherwise hyperchains might skip upgrades by overwriting require(s.l2SystemContractsUpgradeTxHash == bytes32(0), "Previous upgrade has not been finalized"); require( s.l2SystemContractsUpgradeBatchNumber == 0, diff --git a/l1-contracts/contracts/upgrades/BaseZkSyncUpgradeGenesis.sol b/l1-contracts/contracts/upgrades/BaseZkSyncUpgradeGenesis.sol new file mode 100644 index 000000000000..bff9d693978b --- /dev/null +++ b/l1-contracts/contracts/upgrades/BaseZkSyncUpgradeGenesis.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import "../state-transition/chain-deps/facets/ZkSyncStateTransitionBase.sol"; +import "../state-transition/chain-interfaces/IMailbox.sol"; +import "../state-transition/chain-interfaces/IVerifier.sol"; +import "../common/libraries/L2ContractHelper.sol"; +import "../common/Messaging.sol"; +import "../state-transition/libraries/TransactionValidator.sol"; +import {MAX_NEW_FACTORY_DEPS, SYSTEM_UPGRADE_L2_TX_TYPE, MAX_ALLOWED_PROTOCOL_VERSION_DELTA} from "../common/Config.sol"; +import {ProposedUpgrade, BaseZkSyncUpgrade} from "./BaseZkSyncUpgrade.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice Interface to which all the upgrade implementations should adhere +abstract contract BaseZkSyncUpgradeGenesis is BaseZkSyncUpgrade { + /// @notice Changes the protocol version + /// @param _newProtocolVersion The new protocol version + function _setNewProtocolVersion(uint256 _newProtocolVersion) internal override { + uint256 previousProtocolVersion = s.protocolVersion; + require( + // Genesis Upgrade difference: Note this is the only thing change > to >= + _newProtocolVersion >= previousProtocolVersion, + "New protocol version is not greater than the current one" + ); + require( + _newProtocolVersion - previousProtocolVersion <= MAX_ALLOWED_PROTOCOL_VERSION_DELTA, + "Too big protocol version difference" + ); + + // If the previous upgrade had an L2 system upgrade transaction, we require that it is finalized. + require(s.l2SystemContractsUpgradeTxHash == bytes32(0), "Previous upgrade has not been finalized"); + require( + s.l2SystemContractsUpgradeBatchNumber == 0, + "The batch number of the previous upgrade has not been cleaned" + ); + + s.protocolVersion = _newProtocolVersion; + emit NewProtocolVersion(previousProtocolVersion, _newProtocolVersion); + } + + /// @notice The main function that will be provided by the upgrade proxy + /// @dev This is a virtual function and should be overridden by custom upgrade implementations. + /// @param _proposedUpgrade The upgrade to be executed. + /// @return The hash of the L2 system contract upgrade transaction. + function upgrade(ProposedUpgrade calldata _proposedUpgrade) public virtual override returns (bytes32) { + // Note that due to commitment delay, the timestamp of the L2 upgrade batch may be earlier than the timestamp + // of the L1 block at which the upgrade occurred. This means that using timestamp as a signifier of "upgraded" + // on the L2 side would be inaccurate. The effects of this "back-dating" of L2 upgrade batches will be reduced + // as the permitted delay window is reduced in the future. + require(block.timestamp >= _proposedUpgrade.upgradeTimestamp, "Upgrade is not ready yet"); + + _setNewProtocolVersion(_proposedUpgrade.newProtocolVersion); + _upgradeL1Contract(_proposedUpgrade.l1ContractsUpgradeCalldata); + _upgradeVerifier(_proposedUpgrade.verifier, _proposedUpgrade.verifierParams); + _setBaseSystemContracts(_proposedUpgrade.bootloaderHash, _proposedUpgrade.defaultAccountHash); + + bytes32 txHash = _setL2SystemContractUpgrade( + _proposedUpgrade.l2ProtocolUpgradeTx, + _proposedUpgrade.factoryDeps, + _proposedUpgrade.newProtocolVersion + ); + + _postUpgrade(_proposedUpgrade.postUpgradeCalldata); + + emit UpgradeComplete(_proposedUpgrade.newProtocolVersion, txHash, _proposedUpgrade); + } +} diff --git a/l1-contracts/contracts/upgrades/DefaultUpgrade.sol b/l1-contracts/contracts/upgrades/DefaultUpgrade.sol index 64fba27f7ead..49ef673d4d1d 100644 --- a/l1-contracts/contracts/upgrades/DefaultUpgrade.sol +++ b/l1-contracts/contracts/upgrades/DefaultUpgrade.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.20; -import {Diamond} from "../zksync/libraries/Diamond.sol"; -import {BaseZkSyncUpgrade} from "./BaseZkSyncUpgrade.sol"; +import {Diamond} from "../state-transition/libraries/Diamond.sol"; +import {BaseZkSyncUpgrade, ProposedUpgrade} from "./BaseZkSyncUpgrade.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev diff --git a/l1-contracts/contracts/upgrades/GenesisUpgrade.sol b/l1-contracts/contracts/upgrades/GenesisUpgrade.sol new file mode 100644 index 000000000000..5ad4321a060f --- /dev/null +++ b/l1-contracts/contracts/upgrades/GenesisUpgrade.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import "../state-transition/libraries/Diamond.sol"; +import "./BaseZkSyncUpgradeGenesis.sol"; +import "./IDefaultUpgrade.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +contract GenesisUpgrade is BaseZkSyncUpgradeGenesis { + /// @notice The main function that will be called by the upgrade proxy. + /// @param _proposedUpgrade The upgrade to be executed. + function upgrade(ProposedUpgrade calldata _proposedUpgrade) public override returns (bytes32) { + super.upgrade(_proposedUpgrade); + return Diamond.DIAMOND_INIT_SUCCESS_RETURN_VALUE; + } +} diff --git a/l1-contracts/contracts/upgrades/IDefaultUpgrade.sol b/l1-contracts/contracts/upgrades/IDefaultUpgrade.sol new file mode 100644 index 000000000000..719e40d5dd2a --- /dev/null +++ b/l1-contracts/contracts/upgrades/IDefaultUpgrade.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {ProposedUpgrade} from "./BaseZkSyncUpgrade.sol"; + +interface IDefaultUpgrade { + function upgrade(ProposedUpgrade calldata _upgrade) external returns (bytes32); +} diff --git a/l1-contracts/contracts/upgrades/Upgrade_4844.sol b/l1-contracts/contracts/upgrades/Upgrade_4844.sol index 5ee359389309..29ad4e848b77 100644 --- a/l1-contracts/contracts/upgrades/Upgrade_4844.sol +++ b/l1-contracts/contracts/upgrades/Upgrade_4844.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.20; -import {Diamond} from "../zksync/libraries/Diamond.sol"; -import {BaseZkSyncUpgrade} from "./BaseZkSyncUpgrade.sol"; +import {Diamond} from "../state-transition/libraries/Diamond.sol"; +import {BaseZkSyncUpgrade, ProposedUpgrade} from "./BaseZkSyncUpgrade.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev diff --git a/l1-contracts/contracts/upgrades/Upgrade_v1_4_1.sol b/l1-contracts/contracts/upgrades/Upgrade_v1_4_1.sol index b0307bd6d74e..6144496be1c1 100644 --- a/l1-contracts/contracts/upgrades/Upgrade_v1_4_1.sol +++ b/l1-contracts/contracts/upgrades/Upgrade_v1_4_1.sol @@ -2,9 +2,9 @@ pragma solidity 0.8.20; -import {Diamond} from "../zksync/libraries/Diamond.sol"; -import {BaseZkSyncUpgrade} from "./BaseZkSyncUpgrade.sol"; -import {PubdataPricingMode, FeeParams} from "../zksync/Storage.sol"; +import {Diamond} from "../state-transition/libraries/Diamond.sol"; +import {BaseZkSyncUpgrade, ProposedUpgrade} from "./BaseZkSyncUpgrade.sol"; +import {PubdataPricingMode, FeeParams} from "../state-transition/chain-deps/ZkSyncStateTransitionStorage.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev diff --git a/l1-contracts/contracts/zksync/DiamondInit.sol b/l1-contracts/contracts/zksync/DiamondInit.sol deleted file mode 100644 index 2eadafa23104..000000000000 --- a/l1-contracts/contracts/zksync/DiamondInit.sol +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -import {IVerifier} from "./interfaces/IVerifier.sol"; -import {IExecutor} from "./interfaces/IExecutor.sol"; -import {Diamond} from "./libraries/Diamond.sol"; -import {Base} from "./facets/Base.sol"; -import {Verifier} from "./Verifier.sol"; -import {VerifierParams, FeeParams} from "./Storage.sol"; -/* solhint-disable max-line-length */ -import {L2_TO_L1_LOG_SERIALIZE_SIZE, EMPTY_STRING_KECCAK, DEFAULT_L2_LOGS_TREE_ROOT_HASH, MAX_GAS_PER_TRANSACTION} from "./Config.sol"; - -/// @author Matter Labs -/// @custom:security-contact security@matterlabs.dev -/// @dev The contract is used only once to initialize the diamond proxy. -/// @dev The deployment process takes care of this contract's initialization. -contract DiamondInit is Base { - /// @notice Struct that holds all data needed for initializing zkSync Diamond Proxy. - /// @dev We use struct instead of raw parameters in `initialize` function to prevent "Stack too deep" error - /// @param verifier address of Verifier contract - /// @param governor address who can manage critical updates in the contract - /// @param admin address who can manage non-critical updates in the contract - /// @param genesisBatchHash Batch hash of the genesis (initial) batch - /// @param genesisIndexRepeatedStorageChanges The serial number of the shortcut storage key for genesis batch - /// @param genesisBatchCommitment The zk-proof commitment for the genesis batch - /// @param verifierParams Verifier config parameters that describes the circuit to be verified - /// @param zkPorterIsAvailable The availability of zk porter shard - /// @param l2BootloaderBytecodeHash The hash of bootloader L2 bytecode - /// @param l2DefaultAccountBytecodeHash The hash of default account L2 bytecode - /// @param priorityTxMaxGasLimit maximum number of the L2 gas that a user can request for L1 -> L2 transactions - /// @param initialProtocolVersion initial protocol version - /// @param feeParams Fee parameters to be used for L1->L2 transactions - /// @param blobVersionedHashRetriever Address of contract used to pull the blob versioned hash for a transaction. - struct InitializeData { - IVerifier verifier; - address governor; - address admin; - bytes32 genesisBatchHash; - uint64 genesisIndexRepeatedStorageChanges; - bytes32 genesisBatchCommitment; - VerifierParams verifierParams; - bool zkPorterIsAvailable; - bytes32 l2BootloaderBytecodeHash; - bytes32 l2DefaultAccountBytecodeHash; - uint256 priorityTxMaxGasLimit; - uint256 initialProtocolVersion; - FeeParams feeParams; - address blobVersionedHashRetriever; - } - - /// @dev Initialize the implementation to prevent any possibility of a Parity hack. - constructor() reentrancyGuardInitializer {} - - /// @notice zkSync contract initialization - /// @return Magic 32 bytes, which indicates that the contract logic is expected to be used as a diamond proxy - /// initializer - function initialize(InitializeData calldata _initalizeData) external reentrancyGuardInitializer returns (bytes32) { - require(address(_initalizeData.verifier) != address(0), "vt"); - require(_initalizeData.governor != address(0), "vy"); - require(_initalizeData.admin != address(0), "hc"); - require(_initalizeData.priorityTxMaxGasLimit <= MAX_GAS_PER_TRANSACTION, "vu"); - - s.verifier = _initalizeData.verifier; - s.governor = _initalizeData.governor; - s.admin = _initalizeData.admin; - - // We need to initialize the state hash because it is used in the commitment of the next batch - IExecutor.StoredBatchInfo memory storedBatchZero = IExecutor.StoredBatchInfo( - 0, - _initalizeData.genesisBatchHash, - _initalizeData.genesisIndexRepeatedStorageChanges, - 0, - EMPTY_STRING_KECCAK, - DEFAULT_L2_LOGS_TREE_ROOT_HASH, - 0, - _initalizeData.genesisBatchCommitment - ); - - s.storedBatchHashes[0] = keccak256(abi.encode(storedBatchZero)); - s.verifierParams = _initalizeData.verifierParams; - s.zkPorterIsAvailable = _initalizeData.zkPorterIsAvailable; - s.l2BootloaderBytecodeHash = _initalizeData.l2BootloaderBytecodeHash; - s.l2DefaultAccountBytecodeHash = _initalizeData.l2DefaultAccountBytecodeHash; - s.priorityTxMaxGasLimit = _initalizeData.priorityTxMaxGasLimit; - s.protocolVersion = _initalizeData.initialProtocolVersion; - s.feeParams = _initalizeData.feeParams; - s.blobVersionedHashRetriever = _initalizeData.blobVersionedHashRetriever; - - // While this does not provide a protection in the production, it is needed for local testing - // Length of the L2Log encoding should not be equal to the length of other L2Logs' tree nodes preimages - assert(L2_TO_L1_LOG_SERIALIZE_SIZE != 2 * 32); - - return Diamond.DIAMOND_INIT_SUCCESS_RETURN_VALUE; - } -} diff --git a/l1-contracts/contracts/zksync/ValidatorTimelock.sol b/l1-contracts/contracts/zksync/ValidatorTimelock.sol deleted file mode 100644 index fbd53bfbe68e..000000000000 --- a/l1-contracts/contracts/zksync/ValidatorTimelock.sol +++ /dev/null @@ -1,178 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; -import {LibMap} from "./libraries/LibMap.sol"; -import {IExecutor} from "./interfaces/IExecutor.sol"; - -/// @author Matter Labs -/// @custom:security-contact security@matterlabs.dev -/// @notice Intermediate smart contract between the validator EOA account and the zkSync smart contract. -/// @dev The primary purpose of this contract is to provide a trustless means of delaying batch execution without -/// modifying the main zkSync contract. As such, even if this contract is compromised, it will not impact the main -/// contract. -/// @dev zkSync actively monitors the chain activity and reacts to any suspicious activity by freezing the chain. -/// This allows time for investigation and mitigation before resuming normal operations. -/// @dev The contract overloads all of the 4 methods, that are used in state transition. When the batch is committed, -/// the timestamp is stored for it. Later, when the owner calls the batch execution, the contract checks that batch -/// was committed not earlier than X time ago. -contract ValidatorTimelock is IExecutor, Ownable2Step { - using LibMap for LibMap.Uint32Map; - - /// @dev Part of the IBase interface. Not used in this contract. - string public constant override getName = "ValidatorTimelock"; - - /// @notice The delay between committing and executing batches is changed. - event NewExecutionDelay(uint256 _newExecutionDelay); - - /// @notice A new validator has been added. - event ValidatorAdded(address _addedValidator); - - /// @notice A validator has been removed. - event ValidatorRemoved(address _removedValidator); - - /// @notice Error for when an address is already a validator. - error AddressAlreadyValidator(); - - /// @notice Error for when an address is not a validator. - error ValidatorDoesNotExist(); - - /// @dev The main zkSync smart contract. - address public immutable zkSyncContract; - - /// @dev The mapping of L2 batch number => timestamp when it was committed. - LibMap.Uint32Map internal committedBatchTimestamp; - - /// @dev The delay between committing and executing batches.ValidatorTimelock - uint32 public executionDelay; - - /// @dev Mapping denoting if an address is also a validator - mapping(address => bool) public validators; - - constructor(address _initialOwner, address _zkSyncContract, uint32 _executionDelay, address[] memory _validators) { - _transferOwnership(_initialOwner); - zkSyncContract = _zkSyncContract; - executionDelay = _executionDelay; - - for (uint256 i = 0; i < _validators.length; i++) { - validators[_validators[i]] = true; - } - } - - /// @dev Sets an address as a validator. - function addValidator(address _newValidator) external onlyOwner { - if (validators[_newValidator]) { - revert AddressAlreadyValidator(); - } - validators[_newValidator] = true; - emit ValidatorAdded(_newValidator); - } - - /// @dev Removes an address as a validator. - function removeValidator(address _validator) external onlyOwner { - if (!validators[_validator]) { - revert ValidatorDoesNotExist(); - } - validators[_validator] = false; - emit ValidatorRemoved(_validator); - } - - /// @dev Set the delay between committing and executing batches. - function setExecutionDelay(uint32 _executionDelay) external onlyOwner { - executionDelay = _executionDelay; - emit NewExecutionDelay(_executionDelay); - } - - /// @notice Checks if the caller is a validator. - modifier onlyValidator() { - require(validators[msg.sender] == true, "8h"); - _; - } - - /// @dev Returns the timestamp when `_l2BatchNumber` was committed. - function getCommittedBatchTimestamp(uint256 _l2BatchNumber) external view returns (uint256) { - return committedBatchTimestamp.get(_l2BatchNumber); - } - - /// @dev Records the timestamp for all provided committed batches and make - /// a call to the zkSync contract with the same calldata. - function commitBatches( - StoredBatchInfo calldata, - CommitBatchInfo[] calldata _newBatchesData - ) external onlyValidator { - unchecked { - // This contract is only a temporary solution, that hopefully will be disabled until 2106 year, so... - // It is safe to cast. - uint32 timestamp = uint32(block.timestamp); - for (uint256 i = 0; i < _newBatchesData.length; ++i) { - committedBatchTimestamp.set(_newBatchesData[i].batchNumber, timestamp); - } - } - - _propagateToZkSync(); - } - - /// @dev Make a call to the zkSync contract with the same calldata. - /// Note: If the batch is reverted, it needs to be committed first before the execution. - /// So it's safe to not override the committed batches. - function revertBatches(uint256) external onlyValidator { - _propagateToZkSync(); - } - - /// @dev Make a call to the zkSync contract with the same calldata. - /// Note: We don't track the time when batches are proven, since all information about - /// the batch is known on the commit stage and the proved is not finalized (may be reverted). - function proveBatches( - StoredBatchInfo calldata, - StoredBatchInfo[] calldata, - ProofInput calldata - ) external onlyValidator { - _propagateToZkSync(); - } - - /// @dev Check that batches were committed at least X time ago and - /// make a call to the zkSync contract with the same calldata. - function executeBatches(StoredBatchInfo[] calldata _newBatchesData) external onlyValidator { - uint256 delay = executionDelay; // uint32 - unchecked { - for (uint256 i = 0; i < _newBatchesData.length; ++i) { - uint256 commitBatchTimestamp = committedBatchTimestamp.get(_newBatchesData[i].batchNumber); - - // Note: if the `commitBatchTimestamp` is zero, that means either: - // * The batch was committed, but not through this contract. - // * The batch wasn't committed at all, so execution will fail in the zkSync contract. - // We allow executing such batches. - require(block.timestamp >= commitBatchTimestamp + delay, "5c"); // The delay is not passed - } - } - - _propagateToZkSync(); - } - - /// @dev Call the zkSync contract with the same calldata as this contract was called. - /// Note: it is called the zkSync contract, not delegatecalled! - function _propagateToZkSync() internal { - address contractAddress = zkSyncContract; - assembly { - // Copy function signature and arguments from calldata at zero position into memory at pointer position - calldatacopy(0, 0, calldatasize()) - // Call method of the zkSync contract returns 0 on error - let result := call(gas(), contractAddress, 0, 0, calldatasize(), 0, 0) - // Get the size of the last return data - let size := returndatasize() - // Copy the size length of bytes from return data at zero position to pointer position - returndatacopy(0, 0, size) - // Depending on the result value - switch result - case 0 { - // End execution and revert state changes - revert(0, size) - } - default { - // Return data with length of size at pointers position - return(0, size) - } - } - } -} diff --git a/l1-contracts/contracts/zksync/facets/Base.sol b/l1-contracts/contracts/zksync/facets/Base.sol deleted file mode 100644 index e2b5fd45e2d7..000000000000 --- a/l1-contracts/contracts/zksync/facets/Base.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -import {AppStorage} from "../Storage.sol"; -import {ReentrancyGuard} from "../../common/ReentrancyGuard.sol"; - -/// @title Base contract containing functions accessible to the other facets. -/// @author Matter Labs -/// @custom:security-contact security@matterlabs.dev -contract Base is ReentrancyGuard { - AppStorage internal s; - - /// @notice Checks that the message sender is an active governor - modifier onlyGovernor() { - require(msg.sender == s.governor, "1g"); // only by governor - _; - } - - /// @notice Checks that the message sender is an active governor or admin - modifier onlyGovernorOrAdmin() { - require(msg.sender == s.governor || msg.sender == s.admin, "1k"); - _; - } - - /// @notice Checks if validator is active - modifier onlyValidator() { - require(s.validators[msg.sender], "1h"); // validator is not active - _; - } -} diff --git a/l1-contracts/contracts/zksync/interfaces/IZkSync.sol b/l1-contracts/contracts/zksync/interfaces/IZkSync.sol deleted file mode 100644 index 8a75ef1a72ca..000000000000 --- a/l1-contracts/contracts/zksync/interfaces/IZkSync.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -import {IMailbox} from "./IMailbox.sol"; -import {IAdmin} from "./IAdmin.sol"; -import {IExecutor} from "./IExecutor.sol"; -import {IGetters} from "./IGetters.sol"; - -/// @title The interface of the zkSync contract, responsible for the main zkSync logic. -/// @author Matter Labs -/// @dev This interface combines the interfaces of all the facets of the zkSync contract. -/// @custom:security-contact security@matterlabs -interface IZkSync is IMailbox, IAdmin, IExecutor, IGetters {} diff --git a/l1-contracts/hardhat.config.ts b/l1-contracts/hardhat.config.ts index 76d9d68e0349..9f8f491f578a 100644 --- a/l1-contracts/hardhat.config.ts +++ b/l1-contracts/hardhat.config.ts @@ -8,7 +8,7 @@ import "hardhat-typechain"; import { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } from "hardhat/builtin-tasks/task-names"; import { task } from "hardhat/config"; import "solidity-coverage"; -import { getNumberFromEnv } from "./scripts/utils"; +import { getNumberFromEnv } from "./src.ts/utils"; // If no network is specified, use the default config if (!process.env.CHAIN_ETH_NETWORK) { @@ -30,6 +30,7 @@ const prodConfig = { PRIORITY_TX_MAX_GAS_LIMIT, DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, DUMMY_VERIFIER: false, + ERA_CHAIN_ID: 324, BLOB_VERSIONED_HASH_GETTER_ADDR: "0x0000000000000000000000000000000000001337", }; const testnetConfig = { @@ -40,20 +41,39 @@ const testnetConfig = { PRIORITY_TX_MAX_GAS_LIMIT, DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, DUMMY_VERIFIER: true, + ERA_CHAIN_ID: 300, + ERA_DIAMOND_PROXY: "address(0x32400084C286CF3E17e7B677ea9583e60a000324)", + ERA_TOKEN_BEACON_ADDRESS: "address(0x89057dEA64Da472A8422287C6cF0B2Ebb3B3D8DF)", + ERA_ERC20_BRIDGE_ADDRESS: "address(0x681A1AFdC2e06776816386500D2D461a6C96cB45)", + ERA_WETH_ADDRESS: "address(0)", BLOB_VERSIONED_HASH_GETTER_ADDR: "0x0000000000000000000000000000000000001337", }; -const testConfig = { +const hardhatConfig = { UPGRADE_NOTICE_PERIOD: 0, PRIORITY_EXPIRATION: 101, SECURITY_COUNCIL_APPROVALS_FOR_EMERGENCY_UPGRADE: 2, PRIORITY_TX_MAX_GAS_LIMIT, DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, DUMMY_VERIFIER: true, + ERA_CHAIN_ID: 9, + ERA_DIAMOND_PROXY: "0x77AE03789270Ad12d3d47FCAf7234241FceeC532", // for some tests this has to be set based on era + ERA_TOKEN_BEACON_ADDRESS: "address(1232)", + ERA_ERC20_BRIDGE_ADDRESS: "address(1233)", + ERA_WETH_ADDRESS: "address(1234)", BLOB_VERSIONED_HASH_GETTER_ADDR: "0x0000000000000000000000000000000000001337", }; const localConfig = { ...prodConfig, + UPGRADE_NOTICE_PERIOD: 0, DUMMY_VERIFIER: true, + EOA_GOVERNOR: true, + ERA_CHAIN_ID: 9, + ERA_DIAMOND_PROXY: "address(0)", + ERA_TOKEN_BEACON_ADDRESS: "address(0)", + ERA_ERC20_BRIDGE_ADDRESS: "address(0)", + ERA_WETH_ADDRESS: "address(0)", + ERA_WETH_BRIDGE_ADDRESS: "address(0)", + ERC20_BRIDGE_IS_BASETOKEN_BRIDGE: true, }; const contractDefs = { @@ -62,7 +82,7 @@ const contractDefs = { ropsten: testnetConfig, goerli: testnetConfig, mainnet: prodConfig, - test: testConfig, + hardhat: hardhatConfig, localhost: localConfig, }; @@ -91,7 +111,7 @@ export default { }, solpp: { defs: (() => { - const defs = process.env.CONTRACT_TESTS ? contractDefs.test : contractDefs[process.env.CHAIN_ETH_NETWORK]; + const defs = contractDefs[process.env.CHAIN_ETH_NETWORK]; return { ...systemParams, diff --git a/l1-contracts/package.json b/l1-contracts/package.json index 1738578ba50e..b9a3214db87e 100644 --- a/l1-contracts/package.json +++ b/l1-contracts/package.json @@ -2,6 +2,9 @@ "name": "l1-contracts", "version": "0.1.0", "license": "MIT", + "engines": { + "node": ">=16" + }, "devDependencies": { "@nomiclabs/hardhat-ethers": "^2.0.0", "@nomiclabs/hardhat-etherscan": "^3.1.0", @@ -47,24 +50,25 @@ "ts-generator": "^0.1.1", "ts-node": "^10.1.0", "typechain": "^4.0.0", - "typescript": "^4.6.4" + "typescript": "^4.6.4", + "zksync-ethers": "https://github.com/kelemeno/zksync-ethers#ethers-v5-feat/bridgehub" }, "scripts": { "build": "hardhat compile", "clean": "hardhat clean", - "test": "CONTRACT_TESTS=1 hardhat test test/unit_tests/*.spec.ts --network hardhat", + "clean:foundry": "forge clean", + "test": "hardhat test test/unit_tests/*.spec.ts --network hardhat", "test:foundry": "hardhat solpp && forge test", - "test:fork": "CONTRACT_TESTS=1 TEST_CONTRACTS_FORK=1 yarn run hardhat test test/unit_tests/*.fork.ts --network hardhat", + "test:fork": "TEST_CONTRACTS_FORK=1 yarn run hardhat test test/unit_tests/*.fork.ts --network hardhat", "coverage:foundry": "hardhat solpp && forge coverage", "deploy-no-build": "ts-node scripts/deploy.ts", + "register-hyperchain": "ts-node scripts/register-hyperchain.ts", "deploy-weth-bridges": "ts-node scripts/deploy-weth-bridges.ts", - "initialize-weth-bridges": "ts-node scripts/initialize-weth-bridges.ts", "initialize-l2-weth-token": "ts-node scripts/initialize-l2-weth-token.ts", "deploy-erc20": "ts-node scripts/deploy-erc20.ts", "token-info": "ts-node scripts/token-info.ts", "deploy-testkit": "ts-node scripts/deploy-testkit.ts", "verify": "hardhat run --network env scripts/verify.ts", - "deploy-testnet-erc20": "ts-node scripts/deploy-testnet-token.ts", "read-variable": "ts-node scripts/read-variable.ts", "initialize-bridges": "ts-node scripts/initialize-bridges.ts", "initialize-validator": "ts-node scripts/initialize-validator.ts", @@ -74,9 +78,7 @@ "upgrade-system": "ts-node upgrade-system/index.ts" }, "dependencies": { - "dotenv": "^16.0.3" - }, - "optionalDependencies": { - "zksync-web3": "^0.14.3" + "dotenv": "^16.0.3", + "solhint-plugin-prettier": "^0.0.5" } } diff --git a/l1-contracts/remappings.txt b/l1-contracts/remappings.txt index d882fbd10e7c..3060fc79ec3a 100644 --- a/l1-contracts/remappings.txt +++ b/l1-contracts/remappings.txt @@ -6,3 +6,4 @@ forge-std/=lib/forge-std/src/ hardhat/=node_modules/hardhat/ solpp/=cache/solpp-generated-contracts/ murky/=lib/murky/src/ +foundry-test/=test/foundry/ diff --git a/l1-contracts/scripts/deploy-erc20.ts b/l1-contracts/scripts/deploy-erc20.ts index 63a3a0e2ec58..3dd7be14e03e 100644 --- a/l1-contracts/scripts/deploy-erc20.ts +++ b/l1-contracts/scripts/deploy-erc20.ts @@ -1,118 +1,19 @@ // hardhat import should be the first import in the file +// eslint-disable-next-line @typescript-eslint/no-unused-vars import * as hardhat from "hardhat"; -import "@nomiclabs/hardhat-ethers"; +// import "@nomiclabs/hardhat-ethers"; import { Command } from "commander"; -import type { Contract } from "ethers"; import { Wallet } from "ethers"; -import { parseEther } from "ethers/lib/utils"; import { web3Provider } from "./utils"; -import * as fs from "fs"; -import * as path from "path"; -const DEFAULT_ERC20 = "TestnetERC20Token"; +import type { TokenDescription } from "../src.ts/deploy-token"; +import { deployTokens, deployContracts, mintTokens } from "../src.ts/deploy-token"; -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); +import { ethTestConfig } from "../src.ts/utils"; const provider = web3Provider(); -type Token = { - address: string | null; - name: string; - symbol: string; - decimals: number; -}; - -type TokenDescription = Token & { - implementation?: string; - contract?: Contract; -}; - -async function deployContracts(tokens: TokenDescription[], wallet: Wallet): Promise { - let nonce = await wallet.getTransactionCount("pending"); - - for (const token of tokens) { - token.implementation = token.implementation || DEFAULT_ERC20; - const tokenFactory = await hardhat.ethers.getContractFactory(token.implementation, wallet); - const args = token.implementation !== "WETH9" ? [token.name, token.symbol, token.decimals] : []; - - token.contract = await tokenFactory.deploy(...args, { gasLimit: 5000000, nonce: nonce++ }); - } - - await Promise.all(tokens.map(async (token) => token.contract.deployTransaction.wait())); - - return nonce; -} - -function getTestAddresses(): string[] { - return Array.from( - { length: 10 }, - (_, i) => - Wallet.fromMnemonic(ethTestConfig.test_mnemonic as string, `m/44'/60'/0'/0/${i}`).connect(provider).address - ); -} - -function unwrapToken(token: TokenDescription): Token { - token.address = token.contract.address; - - delete token.contract; - if (token.implementation) { - delete token.implementation; - } - - return token; -} - -async function mintTokens(tokens: TokenDescription[], wallet: Wallet, nonce: number): Promise { - const targetAddresses = [wallet.address, ...getTestAddresses()]; - - const results = []; - const promises = []; - for (const token of tokens) { - if (token.implementation !== "WETH9") { - for (const address of targetAddresses) { - const tx = await token.contract.mint(address, parseEther("3000000000"), { nonce: nonce++ }); - promises.push(tx.wait()); - } - } - - results.push(unwrapToken(token)); - } - await Promise.all(promises); - - return results; -} - -async function deployToken(token: TokenDescription, wallet: Wallet): Promise { - token.implementation = token.implementation || DEFAULT_ERC20; - const tokenFactory = await hardhat.ethers.getContractFactory(token.implementation, wallet); - const args = token.implementation !== "WETH9" ? [token.name, token.symbol, token.decimals] : []; - const erc20 = await tokenFactory.deploy(...args, { gasLimit: 5000000 }); - await erc20.deployTransaction.wait(); - - if (token.implementation !== "WETH9") { - await erc20.mint(wallet.address, parseEther("3000000000")); - - for (let i = 0; i < 10; ++i) { - const testWallet = Wallet.fromMnemonic(ethTestConfig.test_mnemonic as string, "m/44'/60'/0'/0/" + i).connect( - provider - ); - - await erc20.mint(testWallet.address, parseEther("3000000000")); - } - } - - token.address = erc20.address; - - // Remove the unneeded field - if (token.implementation) { - delete token.implementation; - } - - return token; -} - async function main() { const program = new Command(); @@ -120,6 +21,7 @@ async function main() { program .command("add") + .option("--private-key ") .option("-n, --token-name ") .option("-s, --symbol ") .option("-d, --decimals ") @@ -138,7 +40,7 @@ async function main() { ? new Wallet(cmd.privateKey, provider) : Wallet.fromMnemonic(ethTestConfig.mnemonic, "m/44'/60'/0'/0/1").connect(provider); - console.log(JSON.stringify(await deployToken(token, wallet), null, 2)); + console.log(JSON.stringify(await deployTokens([token], wallet, ethTestConfig.mnemonic, true, false), null, 2)); }); program @@ -153,7 +55,7 @@ async function main() { : Wallet.fromMnemonic(ethTestConfig.mnemonic, "m/44'/60'/0'/0/1").connect(provider); const nonce = await deployContracts(tokens, wallet); - const result = await mintTokens(tokens, wallet, nonce); + const result = await mintTokens(tokens, wallet, nonce, ethTestConfig.mnemonic); console.log(JSON.stringify(result, null, 2)); }); diff --git a/l1-contracts/scripts/deploy-testkit.ts b/l1-contracts/scripts/deploy-testkit.ts index f910ebb0893f..8b40f18c60f3 100644 --- a/l1-contracts/scripts/deploy-testkit.ts +++ b/l1-contracts/scripts/deploy-testkit.ts @@ -1,4 +1,5 @@ // hardhat import should be the first import in the file +// eslint-disable-next-line @typescript-eslint/no-unused-vars import * as hardhat from "hardhat"; import "@nomiclabs/hardhat-ethers"; @@ -6,12 +7,9 @@ import { Command } from "commander"; import { ethers, Wallet } from "ethers"; import { Deployer } from "../src.ts/deploy"; -import * as fs from "fs"; -import * as path from "path"; import { web3Provider } from "./utils"; -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); +import { ethTestConfig } from "../src.ts/utils"; async function main() { const program = new Command(); @@ -40,7 +38,7 @@ async function main() { const deployer = new Deployer({ deployWallet, verbose: true }); await deployer.deployAll(); - const zkSyncContract = deployer.zkSyncContract(deployWallet); + const zkSyncContract = deployer.bridgehubContract(deployWallet); await (await zkSyncContract.setValidator(deployWallet.address, true)).wait(); const tokenFactory = await hardhat.ethers.getContractFactory("TestnetERC20Token", deployWallet); diff --git a/l1-contracts/scripts/deploy-testnet-token.ts b/l1-contracts/scripts/deploy-testnet-token.ts deleted file mode 100644 index 317da9323164..000000000000 --- a/l1-contracts/scripts/deploy-testnet-token.ts +++ /dev/null @@ -1,96 +0,0 @@ -// hardhat import should be the first import in the file -import * as hardhat from "hardhat"; - -import "@nomiclabs/hardhat-ethers"; -import { ArgumentParser } from "argparse"; -import { Wallet } from "ethers"; -import * as fs from "fs"; -import * as path from "path"; -import { web3Provider } from "./utils"; - -// eslint-disable-next-line @typescript-eslint/no-var-requires -const mainnetTokens = require(`${process.env.ZKSYNC_HOME}/etc/tokens/mainnet`); - -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); - -async function main() { - const parser = new ArgumentParser({ - version: "0.1.0", - addHelp: true, - description: "Deploy contracts and publish them on Etherscan", - }); - parser.addArgument("--publish", { - required: false, - action: "storeTrue", - help: "Only publish code for deployed tokens", - }); - parser.addArgument("--deployerPrivateKey", { required: false, help: "Wallet used to deploy contracts" }); - const args = parser.parseArgs(process.argv.slice(2)); - - const provider = web3Provider(); - const wallet = args.deployerPrivateKey - ? new Wallet(args.deployerPrivateKey, provider) - : Wallet.fromMnemonic(ethTestConfig.mnemonic, "m/44'/60'/0'/0/1").connect(provider); - - if (process.env.CHAIN_ETH_NETWORK === "mainnet") { - throw new Error("Test ERC20 tokens should not be deployed to mainnet"); - } - - if (args.publish) { - // TODO: restore after testnet (SMA-388) - // console.log('Publishing source code'); - // let verifiedOnce = false; - // const networkTokens = require(`${process.env.ZKSYNC_HOME}/etc/tokens/${process.env.ETH_NETWORK}`); - // for (const token of networkTokens) { - // if (verifiedOnce) { - // break; - // } - // try { - // console.log(`Publishing code for : ${token.symbol}, ${token.address}`); - // const constructorArgs = [ - // `${token.name} (${process.env.CHAIN_ETH_NETWORK})`, - // token.symbol, - // token.decimals - // ]; - // const rawArgs = encodeConstructorArgs(contractCode, constructorArgs); - // await publishSourceCodeToEtherscan(token.address, 'TestnetERC20Token', rawArgs, 'contracts/test'); - // verifiedOnce = true; - // } catch (e) { - // console.log('Error failed to verified code:', e); - // } - // } - // return; - } - - const result = []; - - for (const token of mainnetTokens) { - const constructorArgs = [ - `${token.name} (${process.env.CHAIN_ETH_NETWORK})`, - token.symbol, - token.decimals, - { gasLimit: 800000 }, - ]; - - console.log(`Deploying testnet ERC20: ${constructorArgs.toString()}`); - const tokenFactory = await hardhat.ethers.getContractFactory("TestnetERC20Token", wallet); - const erc20 = await tokenFactory.deploy(...constructorArgs); - - const testnetToken = token; - testnetToken.address = erc20.address; - result.push(testnetToken); - } - - fs.writeFileSync( - `${process.env.ZKSYNC_HOME}/etc/tokens/${process.env.CHAIN_ETH_NETWORK}.json`, - JSON.stringify(result, null, 2) - ); -} - -main() - .then(() => process.exit(0)) - .catch((err) => { - console.error("Error:", err.message || err); - process.exit(1); - }); diff --git a/l1-contracts/scripts/deploy.ts b/l1-contracts/scripts/deploy.ts index 45f039d8979c..88ff9dc2a6c7 100644 --- a/l1-contracts/scripts/deploy.ts +++ b/l1-contracts/scripts/deploy.ts @@ -1,14 +1,16 @@ +// hardhat import should be the first import in the file +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import * as hardhat from "hardhat"; import { Command } from "commander"; import { Wallet, ethers } from "ethers"; import { Deployer } from "../src.ts/deploy"; import { formatUnits, parseUnits } from "ethers/lib/utils"; -import * as fs from "fs"; -import * as path from "path"; -import { web3Provider } from "./utils"; +import { web3Provider, GAS_MULTIPLIER } from "./utils"; +import { deployedAddressesFromEnv } from "../src.ts/deploy-utils"; +import { initialBridgehubDeployment } from "../src.ts/deploy-process"; +import { ethTestConfig } from "../src.ts/utils"; const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); async function main() { const program = new Command(); @@ -17,6 +19,7 @@ async function main() { program .option("--private-key ") + .option("--chain-id ") .option("--gas-price ") .option("--nonce ") .option("--owner-address ") @@ -35,46 +38,32 @@ async function main() { const ownerAddress = cmd.ownerAddress ? cmd.ownerAddress : deployWallet.address; console.log(`Using owner address: ${ownerAddress}`); - const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice(); + const gasPrice = cmd.gasPrice + ? parseUnits(cmd.gasPrice, "gwei") + : (await provider.getGasPrice()).mul(GAS_MULTIPLIER); console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); - let nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); + const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); console.log(`Using nonce: ${nonce}`); const create2Salt = cmd.create2Salt ? cmd.create2Salt : ethers.utils.hexlify(ethers.utils.randomBytes(32)); const deployer = new Deployer({ deployWallet, + addresses: deployedAddressesFromEnv(), ownerAddress, verbose: true, }); - // Create2 factory already deployed on the public networks, only deploy it on local node - if (process.env.CHAIN_ETH_NETWORK === "localhost") { - await deployer.deployCreate2Factory({ gasPrice, nonce }); - nonce++; - - await deployer.deployMulticall3(create2Salt, { gasPrice, nonce }); - nonce++; - } - - if (cmd.onlyVerifier) { - await deployer.deployVerifier(create2Salt, { gasPrice, nonce }); - return; - } - - await deployer.deployDefaultUpgrade(create2Salt, { + await initialBridgehubDeployment( + deployer, + [], gasPrice, - nonce, - }); - nonce++; - - await deployer.deployBlobVersionedHashRetriever(create2Salt, { gasPrice, nonce: nonce++ }); - await deployer.deployGovernance(create2Salt, { gasPrice, nonce }); - await deployer.deployZkSyncContract(create2Salt, gasPrice, nonce + 1); - await deployer.deployBridgeContracts(create2Salt, gasPrice); // Do not pass nonce, since it was increment after deploying zkSync contracts - await deployer.deployWethBridgeContracts(create2Salt, gasPrice); - await deployer.deployValidatorTimelock(create2Salt, { gasPrice }); + cmd.onlyVerifier, + cmd.diamondUpgradeInit, + create2Salt, + nonce + ); }); await program.parseAsync(process.argv); diff --git a/l1-contracts/scripts/display-governance.ts b/l1-contracts/scripts/display-governance.ts index 6dba4407776e..33dc90a67c18 100644 --- a/l1-contracts/scripts/display-governance.ts +++ b/l1-contracts/scripts/display-governance.ts @@ -1,25 +1,28 @@ /// Temporary script that generated the needed calldata for the migration of the governance. +// hardhat import should be the first import in the file +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import * as hardhat from "hardhat"; import { Command } from "commander"; import { ethers, Wallet } from "ethers"; import { Deployer } from "../src.ts/deploy"; -import { applyL1ToL2Alias, getAddressFromEnv } from "./utils"; +import { applyL1ToL2Alias, getAddressFromEnv } from "../src.ts/utils"; import * as fs from "fs"; -import { UpgradeableBeaconFactory } from "../../zksync/typechain/UpgradeableBeaconFactory"; +import { UpgradeableBeaconFactory } from "../../l2-contracts/typechain/UpgradeableBeaconFactory"; import { Provider } from "zksync-web3"; -const L2ERC20BridgeABI = JSON.parse( +const l2SharedBridgeABI = JSON.parse( fs .readFileSync( - "../zksync/artifacts-zk/cache-zk/solpp-generated-contracts/bridge/L2ERC20Bridge.sol/L2ERC20Bridge.json" + "../zksync/artifacts-zk/cache-zk/solpp-generated-contracts/bridge/L2SharedBridge.sol/L2SharedBridge.json" ) .toString() ).abi; -async function getERC20BeaconAddress(l2Erc20BridgeAddress: string) { +async function getERC20BeaconAddress(l2SharedBridgeAddress: string) { const provider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); - const contract = new ethers.Contract(l2Erc20BridgeAddress, L2ERC20BridgeABI, provider); + const contract = new ethers.Contract(l2SharedBridgeAddress, l2SharedBridgeABI, provider); return await contract.l2TokenBeacon(); } @@ -54,21 +57,21 @@ async function main() { // Firstly, we deploy the info about the L1 contracts - const zkSync = deployer.zkSyncContract(deployWallet); + const zkSync = deployer.stateTransitionContract(deployWallet); - console.log("zkSync governor: ", await zkSync.getGovernor()); - console.log("zkSync pendingGovernor: ", await zkSync.getPendingGovernor()); + console.log("zkSync admin: ", await zkSync.getAdmin()); + console.log("zkSync pendingGovernor: ", await zkSync.getPendingAdmin()); const validatorTimelock = deployer.validatorTimelock(deployWallet); - console.log("validatorTimelock governor: ", await validatorTimelock.owner()); - console.log("validatorTimelock pendingGovernor: ", await validatorTimelock.pendingOwner()); + console.log("validatorTimelock owner: ", await validatorTimelock.owner()); + console.log("validatorTimelock pendingOwner: ", await validatorTimelock.pendingOwner()); const l1Erc20Bridge = deployer.transparentUpgradableProxyContract( deployer.addresses.Bridges.ERC20BridgeProxy, deployWallet ); - console.log("l1Erc20Bridge governor: ", await proxyGov(l1Erc20Bridge.address, deployWallet.provider)); + console.log("l1Erc20Bridge proxy admin: ", await proxyGov(l1Erc20Bridge.address, deployWallet.provider)); // Now, starting to deploy the info about the L2 contracts @@ -76,23 +79,23 @@ async function main() { new ethers.providers.JsonRpcProvider(process.env.API_WEB3_JSON_RPC_HTTP_URL) ); - const l2ERC20Bridge = deployer.transparentUpgradableProxyContract( - process.env.CONTRACTS_L2_ERC20_BRIDGE_ADDR!, + const l2SharedBridge = deployer.transparentUpgradableProxyContract( + process.env.CONTRACTS_L2_SHARED_BRIDGE_ADDR!, deployWallet2 ); - console.log("l2ERC20Bridge governor: ", await proxyGov(l2ERC20Bridge.address, deployWallet2.provider)); + console.log("L2SharedBridge proxy admin: ", await proxyGov(l2SharedBridge.address, deployWallet2.provider)); const l2wethToken = deployer.transparentUpgradableProxyContract( process.env.CONTRACTS_L2_WETH_TOKEN_PROXY_ADDR!, deployWallet2 ); - console.log("l2wethToken governor: ", await proxyGov(l2wethToken.address, deployWallet2.provider)); + console.log("l2wethToken proxy admin: ", await proxyGov(l2wethToken.address, deployWallet2.provider)); // L2 Tokens are BeaconProxies - const l2Erc20BeaconAddress: string = await getERC20BeaconAddress(l2ERC20Bridge.address); + const l2Erc20BeaconAddress: string = await getERC20BeaconAddress(l2SharedBridge.address); const l2Erc20TokenBeacon = UpgradeableBeaconFactory.connect(l2Erc20BeaconAddress, deployWallet2); - console.log("l2Erc20TokenBeacon governor: ", await l2Erc20TokenBeacon.owner()); + console.log("l2Erc20TokenBeacon owner: ", await l2Erc20TokenBeacon.owner()); }); await program.parseAsync(process.argv); diff --git a/l1-contracts/scripts/initialize-bridges.ts b/l1-contracts/scripts/initialize-bridges.ts deleted file mode 100644 index 6a53a72b91fd..000000000000 --- a/l1-contracts/scripts/initialize-bridges.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { Command } from "commander"; -import { ethers, Wallet } from "ethers"; -import { formatUnits, parseUnits } from "ethers/lib/utils"; -import { Deployer } from "../src.ts/deploy"; -import { - applyL1ToL2Alias, - computeL2Create2Address, - getNumberFromEnv, - hashL2Bytecode, - SYSTEM_CONFIG, - web3Provider, -} from "./utils"; - -import * as fs from "fs"; -import * as path from "path"; - -const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); - -const contractArtifactsPath = path.join(process.env.ZKSYNC_HOME as string, "contracts/l2-contracts/artifacts-zk/"); - -const l2BridgeArtifactsPath = path.join(contractArtifactsPath, "cache-zk/solpp-generated-contracts/bridge/"); - -const openzeppelinTransparentProxyArtifactsPath = path.join( - contractArtifactsPath, - "@openzeppelin/contracts/proxy/transparent/" -); -const openzeppelinBeaconProxyArtifactsPath = path.join(contractArtifactsPath, "@openzeppelin/contracts/proxy/beacon"); - -function readBytecode(path: string, fileName: string) { - return JSON.parse(fs.readFileSync(`${path}/${fileName}.sol/${fileName}.json`, { encoding: "utf-8" })).bytecode; -} - -function readInterface(path: string, fileName: string) { - const abi = JSON.parse(fs.readFileSync(`${path}/${fileName}.sol/${fileName}.json`, { encoding: "utf-8" })).abi; - return new ethers.utils.Interface(abi); -} - -const L2_ERC20_BRIDGE_PROXY_BYTECODE = readBytecode( - openzeppelinTransparentProxyArtifactsPath, - "TransparentUpgradeableProxy" -); -const L2_ERC20_BRIDGE_IMPLEMENTATION_BYTECODE = readBytecode(l2BridgeArtifactsPath, "L2ERC20Bridge"); -const L2_STANDARD_ERC20_IMPLEMENTATION_BYTECODE = readBytecode(l2BridgeArtifactsPath, "L2StandardERC20"); -const L2_STANDARD_ERC20_PROXY_BYTECODE = readBytecode(openzeppelinBeaconProxyArtifactsPath, "BeaconProxy"); -const L2_STANDARD_ERC20_PROXY_FACTORY_BYTECODE = readBytecode( - openzeppelinBeaconProxyArtifactsPath, - "UpgradeableBeacon" -); -const L2_ERC20_BRIDGE_INTERFACE = readInterface(l2BridgeArtifactsPath, "L2ERC20Bridge"); -const DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT = getNumberFromEnv("CONTRACTS_DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT"); - -async function main() { - const program = new Command(); - - program.version("0.1.0").name("initialize-bridges"); - - program - .option("--private-key ") - .option("--gas-price ") - .option("--nonce ") - .option("--erc20-bridge ") - .action(async (cmd) => { - const deployWallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/0" - ).connect(provider); - console.log(`Using deployer wallet: ${deployWallet.address}`); - - const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice(); - console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); - - const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); - console.log(`Using nonce: ${nonce}`); - - const deployer = new Deployer({ - deployWallet, - verbose: true, - }); - - const zkSync = deployer.zkSyncContract(deployWallet); - const erc20Bridge = cmd.erc20Bridge - ? deployer.defaultERC20Bridge(deployWallet).attach(cmd.erc20Bridge) - : deployer.defaultERC20Bridge(deployWallet); - - const priorityTxMaxGasLimit = getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT"); - const l1GovernorAddress = await zkSync.getGovernor(); - // Check whether governor is a smart contract on L1 to apply alias if needed. - const l1GovernorCodeSize = ethers.utils.hexDataLength(await deployWallet.provider.getCode(l1GovernorAddress)); - const l2GovernorAddress = l1GovernorCodeSize == 0 ? l1GovernorAddress : applyL1ToL2Alias(l1GovernorAddress); - const abiCoder = new ethers.utils.AbiCoder(); - - const l2ERC20BridgeImplAddr = computeL2Create2Address( - applyL1ToL2Alias(erc20Bridge.address), - L2_ERC20_BRIDGE_IMPLEMENTATION_BYTECODE, - "0x", - ethers.constants.HashZero - ); - - const proxyInitializationParams = L2_ERC20_BRIDGE_INTERFACE.encodeFunctionData("initialize", [ - erc20Bridge.address, - hashL2Bytecode(L2_STANDARD_ERC20_PROXY_BYTECODE), - l2GovernorAddress, - ]); - const l2ERC20BridgeProxyAddr = computeL2Create2Address( - applyL1ToL2Alias(erc20Bridge.address), - L2_ERC20_BRIDGE_PROXY_BYTECODE, - ethers.utils.arrayify( - abiCoder.encode( - ["address", "address", "bytes"], - [l2ERC20BridgeImplAddr, l2GovernorAddress, proxyInitializationParams] - ) - ), - ethers.constants.HashZero - ); - - const l2StandardToken = computeL2Create2Address( - l2ERC20BridgeProxyAddr, - L2_STANDARD_ERC20_IMPLEMENTATION_BYTECODE, - "0x", - ethers.constants.HashZero - ); - const l2TokenFactoryAddr = computeL2Create2Address( - l2ERC20BridgeProxyAddr, - L2_STANDARD_ERC20_PROXY_FACTORY_BYTECODE, - ethers.utils.arrayify(abiCoder.encode(["address"], [l2StandardToken])), - ethers.constants.HashZero - ); - - // There will be two deployments done during the initial initialization - const requiredValueToInitializeBridge = await zkSync.l2TransactionBaseCost( - gasPrice, - DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, - SYSTEM_CONFIG.requiredL2GasPricePerPubdata - ); - - const requiredValueToPublishBytecodes = await zkSync.l2TransactionBaseCost( - gasPrice, - priorityTxMaxGasLimit, - SYSTEM_CONFIG.requiredL2GasPricePerPubdata - ); - - const independentInitialization = [ - zkSync.requestL2Transaction( - ethers.constants.AddressZero, - 0, - "0x", - priorityTxMaxGasLimit, - SYSTEM_CONFIG.requiredL2GasPricePerPubdata, - [L2_STANDARD_ERC20_PROXY_FACTORY_BYTECODE, L2_STANDARD_ERC20_IMPLEMENTATION_BYTECODE], - deployWallet.address, - { gasPrice, nonce, value: requiredValueToPublishBytecodes } - ), - erc20Bridge.initialize( - [L2_ERC20_BRIDGE_IMPLEMENTATION_BYTECODE, L2_ERC20_BRIDGE_PROXY_BYTECODE, L2_STANDARD_ERC20_PROXY_BYTECODE], - l2TokenFactoryAddr, - l2GovernorAddress, - requiredValueToInitializeBridge, - requiredValueToInitializeBridge, - { - gasPrice, - nonce: nonce + 1, - value: requiredValueToInitializeBridge.mul(2), - } - ), - ]; - - const txs = await Promise.all(independentInitialization); - for (const tx of txs) { - console.log(`Transaction sent with hash ${tx.hash} and nonce ${tx.nonce}. Waiting for receipt...`); - } - const receipts = await Promise.all(txs.map((tx) => tx.wait(2))); - - console.log(`ERC20 bridge initialized, gasUsed: ${receipts[1].gasUsed.toString()}`); - console.log(`CONTRACTS_L2_ERC20_BRIDGE_ADDR=${await erc20Bridge.l2Bridge()}`); - }); - - await program.parseAsync(process.argv); -} - -main() - .then(() => process.exit(0)) - .catch((err) => { - console.error("Error:", err); - process.exit(1); - }); diff --git a/l1-contracts/scripts/deploy-weth-bridges.ts b/l1-contracts/scripts/initialize-erc20-bridge.ts similarity index 60% rename from l1-contracts/scripts/deploy-weth-bridges.ts rename to l1-contracts/scripts/initialize-erc20-bridge.ts index edba179943fd..f835070befc5 100644 --- a/l1-contracts/scripts/deploy-weth-bridges.ts +++ b/l1-contracts/scripts/initialize-erc20-bridge.ts @@ -1,10 +1,16 @@ +// hardhat import should be the first import in the file +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import * as hardhat from "hardhat"; import { Command } from "commander"; -import { Wallet, ethers } from "ethers"; -import { Deployer } from "../src.ts/deploy"; +import { Wallet } from "ethers"; import { formatUnits, parseUnits } from "ethers/lib/utils"; +import { Deployer } from "../src.ts/deploy"; +// import { initializeErc20Bridge } from "../src.ts/shared-bridge-initialize"; +import { GAS_MULTIPLIER, web3Provider } from "./utils"; +import { deployedAddressesFromEnv } from "../src.ts/deploy-utils"; + import * as fs from "fs"; import * as path from "path"; -import { web3Provider } from "./utils"; const provider = web3Provider(); const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); @@ -13,14 +19,16 @@ const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { async function main() { const program = new Command(); - program.version("0.1.0").name("deploy").description("deploy weth bridges"); + program.version("0.1.0").name("initialize-bridges-chain"); program .option("--private-key ") + .option("--chain-id ") .option("--gas-price ") .option("--nonce ") - .option("--create2-salt ") + .option("--erc20-bridge ") .action(async (cmd) => { + const chainId: string = cmd.chainId ? cmd.chainId : process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID; const deployWallet = cmd.privateKey ? new Wallet(cmd.privateKey, provider) : Wallet.fromMnemonic( @@ -29,21 +37,23 @@ async function main() { ).connect(provider); console.log(`Using deployer wallet: ${deployWallet.address}`); - const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice(); + const gasPrice = cmd.gasPrice + ? parseUnits(cmd.gasPrice, "gwei") + : (await provider.getGasPrice()).mul(GAS_MULTIPLIER); console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); console.log(`Using nonce: ${nonce}`); - const create2Salt = cmd.create2Salt ? cmd.create2Salt : ethers.utils.hexlify(ethers.utils.randomBytes(32)); - const deployer = new Deployer({ deployWallet, + addresses: deployedAddressesFromEnv(), verbose: true, }); - - await deployer.deployWethBridgeContracts(create2Salt, gasPrice); + deployer.chainId = parseInt(chainId) || 270; + // await initializeErc20Bridge(deployer, deployWallet, gasPrice, cmd.erc20Bridge); }); + await program.parseAsync(process.argv); } diff --git a/l1-contracts/scripts/initialize-governance.ts b/l1-contracts/scripts/initialize-governance.ts index f382fb0f3fc5..800e2d117563 100644 --- a/l1-contracts/scripts/initialize-governance.ts +++ b/l1-contracts/scripts/initialize-governance.ts @@ -1,15 +1,15 @@ +// hardhat import should be the first import in the file +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import * as hardhat from "hardhat"; import { Command } from "commander"; -import { ethers, Wallet } from "ethers"; -import { Deployer } from "../src.ts/deploy"; +import { Wallet } from "ethers"; import { formatUnits, parseUnits } from "ethers/lib/utils"; -import { web3Provider } from "./utils"; - -import * as fs from "fs"; -import * as path from "path"; +import { Deployer } from "../src.ts/deploy"; +import { GAS_MULTIPLIER, web3Provider } from "./utils"; +import { deployedAddressesFromEnv } from "../src.ts/deploy-utils"; +import { ethTestConfig } from "../src.ts/utils"; const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); async function main() { const program = new Command(); @@ -29,48 +29,32 @@ async function main() { ).connect(provider); console.log(`Using deployer wallet: ${deployWallet.address}`); - const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice(); + const gasPrice = cmd.gasPrice + ? parseUnits(cmd.gasPrice, "gwei") + : (await provider.getGasPrice()).mul(GAS_MULTIPLIER); console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); const ownerAddress = cmd.ownerAddress ? cmd.ownerAddress : deployWallet.address; const deployer = new Deployer({ deployWallet, + addresses: deployedAddressesFromEnv(), ownerAddress, verbose: true, }); - const governance = deployer.governanceContract(deployWallet); - const zkSync = deployer.zkSyncContract(deployWallet); - - const erc20Bridge = deployer.transparentUpgradableProxyContract( - deployer.addresses.Bridges.ERC20BridgeProxy, - deployWallet - ); - const wethBridge = deployer.transparentUpgradableProxyContract( - deployer.addresses.Bridges.WethBridgeProxy, - deployWallet - ); - - await (await erc20Bridge.changeAdmin(governance.address)).wait(); - await (await wethBridge.changeAdmin(governance.address)).wait(); - - await (await zkSync.setPendingGovernor(governance.address)).wait(); - - const call = { - target: zkSync.address, - value: 0, - data: zkSync.interface.encodeFunctionData("acceptGovernor"), - }; + // const governance = + deployer.governanceContract(deployWallet); - const operation = { - calls: [call], - predecessor: ethers.constants.HashZero, - salt: ethers.constants.HashZero, - }; + // const erc20Bridge = + deployer.transparentUpgradableProxyContract(deployer.addresses.Bridges.ERC20BridgeProxy, deployWallet); + // const wethBridge = deployer.transparentUpgradableProxyContract( + // deployer.addresses.Bridges.WethBridgeProxy, + // deployWallet + // ); - await (await governance.scheduleTransparent(operation, 0)).wait(); - await (await governance.execute(operation)).wait(); + // await (await erc20Bridge.changeAdmin(governance.address)).wait(); + // await (await wethBridge.changeAdmin(governance.address)).wait(); }); await program.parseAsync(process.argv); diff --git a/l1-contracts/scripts/initialize-l2-weth-token.ts b/l1-contracts/scripts/initialize-l2-weth-token.ts index d3fbad8300ab..e00332ea5cfd 100644 --- a/l1-contracts/scripts/initialize-l2-weth-token.ts +++ b/l1-contracts/scripts/initialize-l2-weth-token.ts @@ -1,8 +1,13 @@ +// hardhat import should be the first import in the file +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import * as hardhat from "hardhat"; import { Command } from "commander"; import { ethers, Wallet } from "ethers"; import { formatUnits, parseUnits } from "ethers/lib/utils"; import { Deployer } from "../src.ts/deploy"; -import { getNumberFromEnv, getTokens, SYSTEM_CONFIG, web3Provider } from "./utils"; +import { GAS_MULTIPLIER, SYSTEM_CONFIG, web3Provider } from "./utils"; +import { getNumberFromEnv } from "../src.ts/utils"; +import { getTokens } from "../src.ts/deploy-token"; import * as fs from "fs"; import * as path from "path"; @@ -25,44 +30,50 @@ function readInterface(path: string, fileName: string, solFileName?: string) { } const DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT = getNumberFromEnv("CONTRACTS_DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT"); -const L2_WETH_INTERFACE = readInterface(l2BridgeArtifactsPath, "L2Weth"); +const L2_WETH_INTERFACE = readInterface(l2BridgeArtifactsPath, "L2WrappedBaseToken"); const TRANSPARENT_UPGRADEABLE_PROXY = readInterface( openzeppelinTransparentProxyArtifactsPath, "ITransparentUpgradeableProxy", "TransparentUpgradeableProxy" ); -function getL2Calldata(l2WethBridgeAddress: string, l1WethTokenAddress: string, l2WethTokenImplAddress: string) { - const upgradeData = L2_WETH_INTERFACE.encodeFunctionData("initializeV2", [l2WethBridgeAddress, l1WethTokenAddress]); +function getL2Calldata(l2SharedBridgeAddress: string, l1WethTokenAddress: string, l2WethTokenImplAddress: string) { + const upgradeData = L2_WETH_INTERFACE.encodeFunctionData("initializeV2", [l2SharedBridgeAddress, l1WethTokenAddress]); return TRANSPARENT_UPGRADEABLE_PROXY.encodeFunctionData("upgradeToAndCall", [l2WethTokenImplAddress, upgradeData]); } async function getL1TxInfo( deployer: Deployer, + chainId: string, to: string, l2Calldata: string, refundRecipient: string, gasPrice: ethers.BigNumber ) { - const zksync = deployer.zkSyncContract(ethers.Wallet.createRandom().connect(provider)); - const l1Calldata = zksync.interface.encodeFunctionData("requestL2Transaction", [ - to, - 0, - l2Calldata, - DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, - SYSTEM_CONFIG.requiredL2GasPricePerPubdata, - [], // It is assumed that the target has already been deployed - refundRecipient, + const bridgehub = deployer.bridgehubContract(ethers.Wallet.createRandom().connect(provider)); + const l1Calldata = bridgehub.interface.encodeFunctionData("requestL2TransactionDirect", [ + { + chainId, + l2Contract: to, + mintValue: 0, + l2Value: 0, + l2Calldata, + l2GasLimit: DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, + l2GasPerPubdataByteLimit: SYSTEM_CONFIG.requiredL2GasPricePerPubdata, + factoryDeps: [], // It is assumed that the target has already been deployed + refundRecipient, + }, ]); - const neededValue = await zksync.l2TransactionBaseCost( + const neededValue = await bridgehub.l2TransactionBaseCost( + chainId, gasPrice, DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, SYSTEM_CONFIG.requiredL2GasPricePerPubdata ); return { - to: zksync.address, + to: bridgehub.address, data: l1Calldata, value: neededValue.toString(), gasPrice: gasPrice.toString(), @@ -74,17 +85,19 @@ async function main() { program.version("0.1.0").name("initialize-l2-weth-token"); - const l2WethBridgeAddress = process.env.CONTRACTS_L2_WETH_BRIDGE_ADDR; + const l2SharedBridgeAddress = process.env.CONTRACTS_L2_WETH_BRIDGE_ADDR; const l2WethTokenProxyAddress = process.env.CONTRACTS_L2_WETH_TOKEN_PROXY_ADDR; const l2WethTokenImplAddress = process.env.CONTRACTS_L2_WETH_TOKEN_IMPL_ADDR; - const tokens = getTokens(process.env.CHAIN_ETH_NETWORK || "localhost"); + const tokens = getTokens(); const l1WethTokenAddress = tokens.find((token: { symbol: string }) => token.symbol == "WETH")!.address; program .command("prepare-calldata") .option("--private-key ") + .option("--chain-id ") .option("--gas-price ") .action(async (cmd) => { + const chainId: string = cmd.chainId ? cmd.chainId : process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID; if (!l1WethTokenAddress) { console.log("Base Layer WETH address not provided. Skipping."); return; @@ -98,7 +111,9 @@ async function main() { ).connect(provider); console.log(`Using deployer wallet: ${deployWallet.address}`); - const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice(); + const gasPrice = cmd.gasPrice + ? parseUnits(cmd.gasPrice, "gwei") + : (await provider.getGasPrice()).mul(GAS_MULTIPLIER); console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); const deployer = new Deployer({ @@ -106,9 +121,10 @@ async function main() { verbose: true, }); - const l2Calldata = getL2Calldata(l2WethBridgeAddress, l1WethTokenAddress, l2WethTokenImplAddress); + const l2Calldata = getL2Calldata(l2SharedBridgeAddress, l1WethTokenAddress, l2WethTokenImplAddress); const l1TxInfo = await getL1TxInfo( deployer, + chainId, l2WethTokenProxyAddress, l2Calldata, ethers.constants.AddressZero, @@ -121,9 +137,11 @@ async function main() { program .command("instant-call") .option("--private-key ") + .option("--chain-id ") .option("--gas-price ") .option("--nonce ") .action(async (cmd) => { + const chainId: string = cmd.chainId ? cmd.chainId : process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID; if (!l1WethTokenAddress) { console.log("Base Layer WETH address not provided. Skipping."); return; @@ -137,7 +155,9 @@ async function main() { ).connect(provider); console.log(`Using deployer wallet: ${deployWallet.address}`); - const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice(); + const gasPrice = cmd.gasPrice + ? parseUnits(cmd.gasPrice, "gwei") + : (await provider.getGasPrice()).mul(GAS_MULTIPLIER); console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); @@ -148,22 +168,27 @@ async function main() { verbose: true, }); - const zkSync = deployer.zkSyncContract(deployWallet); - const requiredValueToInitializeBridge = await zkSync.l2TransactionBaseCost( + const bridgehub = deployer.bridgehubContract(deployWallet); + const requiredValueToInitializeBridge = await bridgehub.l2TransactionBaseCost( + chainId, gasPrice, DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, SYSTEM_CONFIG.requiredL2GasPricePerPubdata ); - const calldata = getL2Calldata(l2WethBridgeAddress, l1WethTokenAddress, l2WethTokenImplAddress); + const calldata = getL2Calldata(l2SharedBridgeAddress, l1WethTokenAddress, l2WethTokenImplAddress); - const tx = await zkSync.requestL2Transaction( - l2WethTokenProxyAddress, - 0, - calldata, - DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, - SYSTEM_CONFIG.requiredL2GasPricePerPubdata, - [], - deployWallet.address, + const tx = await bridgehub.requestL2TransactionDirect( + { + chainId, + l2Contract: l2WethTokenProxyAddress, + mintValue: requiredValueToInitializeBridge, + l2Value: 0, + l2Calldata: calldata, + l2GasLimit: DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, + l2GasPerPubdataByteLimit: SYSTEM_CONFIG.requiredL2GasPricePerPubdata, + factoryDeps: [], + refundRecipient: deployWallet.address, + }, { gasPrice, value: requiredValueToInitializeBridge, diff --git a/l1-contracts/scripts/initialize-validator.ts b/l1-contracts/scripts/initialize-validator.ts index cd3019b9d191..30d917eca980 100644 --- a/l1-contracts/scripts/initialize-validator.ts +++ b/l1-contracts/scripts/initialize-validator.ts @@ -1,21 +1,21 @@ +// hardhat import should be the first import in the file +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import * as hardhat from "hardhat"; import { Command } from "commander"; import { Wallet } from "ethers"; import { Deployer } from "../src.ts/deploy"; import { formatUnits, parseUnits } from "ethers/lib/utils"; -import { web3Provider } from "./utils"; - -import * as fs from "fs"; -import * as path from "path"; +import { GAS_MULTIPLIER, web3Provider } from "./utils"; +import { ethTestConfig } from "../src.ts/utils"; const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); async function main() { const program = new Command(); program .option("--private-key ") + .option("--chain-id ") .option("--gas-price ") .option("--nonce ") .action(async (cmd) => { @@ -27,7 +27,9 @@ async function main() { ).connect(provider); console.log(`Using deployer wallet: ${deployWallet.address}`); - const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice(); + const gasPrice = cmd.gasPrice + ? parseUnits(cmd.gasPrice, "gwei") + : (await provider.getGasPrice()).mul(GAS_MULTIPLIER); console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); @@ -38,7 +40,7 @@ async function main() { verbose: true, }); - const zkSync = deployer.zkSyncContract(deployWallet); + const zkSync = deployer.stateTransitionContract(deployWallet); const validatorTimelock = deployer.validatorTimelock(deployWallet); const tx = await zkSync.setValidator(validatorTimelock.address, true); console.log(`Transaction sent with hash ${tx.hash} and nonce ${tx.nonce}`); diff --git a/l1-contracts/scripts/initialize-weth-bridges.ts b/l1-contracts/scripts/initialize-weth-bridges.ts deleted file mode 100644 index e77ea1afbd5e..000000000000 --- a/l1-contracts/scripts/initialize-weth-bridges.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { Command } from "commander"; -import { ethers, Wallet } from "ethers"; -import { formatUnits, parseUnits } from "ethers/lib/utils"; -import { Deployer } from "../src.ts/deploy"; -import { applyL1ToL2Alias, getNumberFromEnv, SYSTEM_CONFIG, web3Provider } from "./utils"; - -import * as fs from "fs"; -import * as path from "path"; - -const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); - -const contractArtifactsPath = path.join(process.env.ZKSYNC_HOME as string, "contracts/l2-contracts/artifacts-zk/"); -const l2BridgeArtifactsPath = path.join(contractArtifactsPath, "cache-zk/solpp-generated-contracts/bridge/"); - -const openzeppelinTransparentProxyArtifactsPath = path.join( - contractArtifactsPath, - "@openzeppelin/contracts/proxy/transparent/" -); - -function readBytecode(path: string, fileName: string) { - return JSON.parse(fs.readFileSync(`${path}/${fileName}.sol/${fileName}.json`, { encoding: "utf-8" })).bytecode; -} - -const L2_WETH_BRIDGE_PROXY_BYTECODE = readBytecode( - openzeppelinTransparentProxyArtifactsPath, - "TransparentUpgradeableProxy" -); -const L2_WETH_BRIDGE_IMPLEMENTATION_BYTECODE = readBytecode(l2BridgeArtifactsPath, "L2WethBridge"); -const DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT = getNumberFromEnv("CONTRACTS_DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT"); - -async function main() { - const program = new Command(); - - program.version("0.1.0").name("initialize-weth-bridges"); - - program - .option("--private-key ") - .option("--gas-price ") - .option("--nonce ") - .action(async (cmd) => { - const deployWallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/0" - ).connect(provider); - console.log(`Using deployer wallet: ${deployWallet.address}`); - - const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice(); - console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); - - const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); - console.log(`Using deployer nonce: ${nonce}`); - - const l2WethAddress = process.env.CONTRACTS_L2_WETH_TOKEN_PROXY_ADDR; - - const deployer = new Deployer({ - deployWallet, - verbose: true, - }); - - const zkSync = deployer.zkSyncContract(deployWallet); - const l1WethBridge = deployer.defaultWethBridge(deployWallet); - - const l1GovernorAddress = await zkSync.getGovernor(); - // Check whether governor is a smart contract on L1 to apply alias if needed. - const l1GovernorCodeSize = ethers.utils.hexDataLength(await deployWallet.provider.getCode(l1GovernorAddress)); - const l2GovernorAddress = l1GovernorCodeSize == 0 ? l1GovernorAddress : applyL1ToL2Alias(l1GovernorAddress); - - // There will be two deployments done during the initial initialization - const requiredValueToInitializeBridge = await zkSync.l2TransactionBaseCost( - gasPrice, - DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, - SYSTEM_CONFIG.requiredL2GasPricePerPubdata - ); - - const tx = await l1WethBridge.initialize( - [L2_WETH_BRIDGE_IMPLEMENTATION_BYTECODE, L2_WETH_BRIDGE_PROXY_BYTECODE], - l2WethAddress, - l2GovernorAddress, - requiredValueToInitializeBridge, - requiredValueToInitializeBridge, - { - gasPrice, - value: requiredValueToInitializeBridge.mul(2), - } - ); - console.log(`Transaction sent with hash ${tx.hash} and nonce ${tx.nonce}. Waiting for receipt...`); - - const receipt = await tx.wait(); - - console.log(`WETH bridge initialized, gasUsed: ${receipt.gasUsed.toString()}`); - console.log(`CONTRACTS_L2_WETH_BRIDGE_ADDR=${await l1WethBridge.l2Bridge()}`); - }); - - await program.parseAsync(process.argv); -} - -main() - .then(() => process.exit(0)) - .catch((err) => { - console.error("Error:", err); - process.exit(1); - }); diff --git a/l1-contracts/scripts/migrate-governance.ts b/l1-contracts/scripts/migrate-governance.ts index 1d1a7b5a075d..7e2d94bc3bdd 100644 --- a/l1-contracts/scripts/migrate-governance.ts +++ b/l1-contracts/scripts/migrate-governance.ts @@ -1,38 +1,35 @@ /// Temporary script that generated the needed calldata for the migration of the governance. // hardhat import should be the first import in the file -import * as hre from "hardhat"; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import * as hardhat from "hardhat"; import { Command } from "commander"; import { BigNumber, ethers, Wallet } from "ethers"; import { formatUnits, parseUnits } from "ethers/lib/utils"; import * as fs from "fs"; import { Deployer } from "../src.ts/deploy"; -import { applyL1ToL2Alias, getAddressFromEnv, getNumberFromEnv, web3Provider } from "./utils"; +import { applyL1ToL2Alias, getAddressFromEnv, getNumberFromEnv } from "../src.ts/utils"; +import { GAS_MULTIPLIER, web3Provider } from "./utils"; +import type { TxInfo } from "../../l2-contracts/src/utils"; import { getL1TxInfo } from "../../l2-contracts/src/utils"; -import { Provider } from "zksync-web3"; +import { Provider } from "zksync-ethers"; import { UpgradeableBeaconFactory } from "../../l2-contracts/typechain/UpgradeableBeaconFactory"; const provider = web3Provider(); const priorityTxMaxGasLimit = BigNumber.from(getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT")); -const L2ERC20BridgeABI = JSON.parse( +const l2SharedBridgeABI = JSON.parse( fs - .readFileSync("../l2-contracts/artifacts-zk/contracts-preprocessed/bridge/L2ERC20Bridge.sol/L2ERC20Bridge.json") + .readFileSync("../l2-contracts/artifacts-zk/contracts-preprocessed/bridge/L2SharedBridge.sol/L2SharedBridge.json") .toString() ).abi; -interface TxInfo { - data: string; - to: string; - value?: string; -} - -async function getERC20BeaconAddress(l2Erc20BridgeAddress: string) { +async function getERC20BeaconAddress(l2SharedBridgeAddress: string) { const provider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); - const contract = new ethers.Contract(l2Erc20BridgeAddress, L2ERC20BridgeABI, provider); + const contract = new ethers.Contract(l2SharedBridgeAddress, l2SharedBridgeABI, provider); return await contract.l2TokenBeacon(); } @@ -51,7 +48,9 @@ async function main() { .option("--gas-price ") .option("--refund-recipient ") .action(async (cmd) => { - const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice(); + const gasPrice = cmd.gasPrice + ? parseUnits(cmd.gasPrice, "gwei") + : (await provider.getGasPrice()).mul(GAS_MULTIPLIER); console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); const refundRecipient = cmd.refundRecipient; @@ -79,7 +78,7 @@ async function main() { verbose: true, }); - const expectedDeployedBytecode = hre.artifacts.readArtifactSync("Governance").deployedBytecode; + const expectedDeployedBytecode = hardhat.artifacts.readArtifactSync("Governance").deployedBytecode; const isBytecodeCorrect = (await provider.getCode(userProvidedAddress)).toLowerCase() === expectedDeployedBytecode.toLowerCase(); @@ -93,7 +92,7 @@ async function main() { // Step 1. Transfer ownership of all the contracts to the new governor. // Below we are preparing the calldata for the L1 transactions - const zkSync = deployer.zkSyncContract(deployWallet); + const zkSync = deployer.stateTransitionContract(deployWallet); const validatorTimelock = deployer.validatorTimelock(deployWallet); const l1Erc20Bridge = deployer.transparentUpgradableProxyContract( @@ -104,15 +103,15 @@ async function main() { const erc20MigrationTx = l1Erc20Bridge.interface.encodeFunctionData("changeAdmin", [governanceAddressFromEnv]); displayTx("L1 ERC20 bridge migration calldata:", { data: erc20MigrationTx, - to: l1Erc20Bridge.address, + target: l1Erc20Bridge.address, }); - const zkSyncSetPendingGovernor = zkSync.interface.encodeFunctionData("setPendingGovernor", [ + const zkSyncSetPendingGovernor = zkSync.interface.encodeFunctionData("setPendingAdmin", [ governanceAddressFromEnv, ]); displayTx("zkSync Diamond Proxy migration calldata:", { data: zkSyncSetPendingGovernor, - to: zkSync.address, + target: zkSync.address, }); const validatorTimelockMigration = validatorTimelock.interface.encodeFunctionData("transferOwnership", [ @@ -120,7 +119,7 @@ async function main() { ]); displayTx("Validator timelock migration calldata:", { data: validatorTimelockMigration, - to: validatorTimelock.address, + target: validatorTimelock.address, }); // Below, we prepare the transactions to migrate the L2 contracts. @@ -129,15 +128,15 @@ async function main() { const aliasedNewGovernor = applyL1ToL2Alias(governanceAddressFromEnv); // L2 ERC20 bridge as well as Weth token are a transparent upgradable proxy. - const l2ERC20Bridge = deployer.transparentUpgradableProxyContract( - process.env.CONTRACTS_L2_ERC20_BRIDGE_ADDR!, + const l2SharedBridge = deployer.transparentUpgradableProxyContract( + process.env.CONTRACTS_L2_SHARED_BRIDGE_ADDR!, deployWallet ); - const l2Erc20BridgeCalldata = l2ERC20Bridge.interface.encodeFunctionData("changeAdmin", [aliasedNewGovernor]); + const l2SharedBridgeCalldata = l2SharedBridge.interface.encodeFunctionData("changeAdmin", [aliasedNewGovernor]); const l2TxForErc20Bridge = await getL1TxInfo( deployer, - l2ERC20Bridge.address, - l2Erc20BridgeCalldata, + l2SharedBridge.address, + l2SharedBridgeCalldata, refundRecipient, gasPrice, priorityTxMaxGasLimit, @@ -162,7 +161,7 @@ async function main() { displayTx("L2 Weth upgrade: ", l2TxForWethUpgrade); // L2 Tokens are BeaconProxies - const l2Erc20BeaconAddress: string = await getERC20BeaconAddress(l2ERC20Bridge.address); + const l2Erc20BeaconAddress: string = await getERC20BeaconAddress(l2SharedBridge.address); const l2Erc20TokenBeacon = UpgradeableBeaconFactory.connect(l2Erc20BeaconAddress, deployWallet); const l2Erc20BeaconCalldata = l2Erc20TokenBeacon.interface.encodeFunctionData("transferOwnership", [ aliasedNewGovernor, @@ -192,7 +191,7 @@ async function main() { { target: zkSync.address, value: 0, - data: zkSync.interface.encodeFunctionData("acceptGovernor"), + data: zkSync.interface.encodeFunctionData("acceptAdmin"), }, { target: validatorTimelock.address, @@ -215,13 +214,13 @@ async function main() { ]); displayTx("Schedule transparent calldata:\n", { data: scheduleTransparentCalldata, - to: governance.address, + target: governance.address, }); const executeCalldata = governance.interface.encodeFunctionData("execute", [operation]); displayTx("Execute calldata:\n", { data: executeCalldata, - to: governance.address, + target: governance.address, }); }); diff --git a/l1-contracts/scripts/register-hyperchain.ts b/l1-contracts/scripts/register-hyperchain.ts new file mode 100644 index 000000000000..bb21aeca5f46 --- /dev/null +++ b/l1-contracts/scripts/register-hyperchain.ts @@ -0,0 +1,117 @@ +// hardhat import should be the first import in the file +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import * as hardhat from "hardhat"; +import { Command } from "commander"; +import { Wallet } from "ethers"; +import { formatUnits, parseUnits } from "ethers/lib/utils"; +import * as fs from "fs"; +import * as path from "path"; +import { Deployer } from "../src.ts/deploy"; +import { GAS_MULTIPLIER, web3Provider } from "./utils"; +import { ADDRESS_ONE } from "../src.ts/utils"; +import { getTokens } from "../src.ts/deploy-token"; + +const ETH_TOKEN_ADDRESS = ADDRESS_ONE; + +const provider = web3Provider(); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); + +const getTokenAddress = async (name: string) => { + const tokens = getTokens(); + const token = tokens.find((token: { symbol: string }) => token.symbol == name); + if (!token) { + throw new Error(`Token ${name} not found`); + } + if (!token.address) { + throw new Error(`Token ${name} has no address`); + } + return token.address; +}; + +// If base token is eth, we are ok, otherwise we need to check if token is deployed +const checkTokenAddress = async (address: string) => { + if (address == ETH_TOKEN_ADDRESS) { + return; + } else if ((await provider.getCode(address)) == "0x") { + throw new Error(`Token ${address} is not deployed`); + } +}; + +// If: +// * base token name is provided, we find its address +// * base token address is provided, we use it +// * neither is provided, we fallback to eth +const chooseBaseTokenAddress = async (name?: string, address?: string) => { + if (name) { + return getTokenAddress(name); + } else if (address) { + return address; + } else { + return ETH_TOKEN_ADDRESS; + } +}; + +async function main() { + const program = new Command(); + + program.version("0.1.0").name("register-hyperchain").description("register hyperchains"); + + program + .option("--private-key ") + .option("--chain-id ") + .option("--gas-price ") + .option("--nonce ") + .option("--governor-address ") + .option("--create2-salt ") + .option("--diamond-upgrade-init ") + .option("--only-verifier") + .option("--validium-mode") + .option("--base-token-name ") + .option("--base-token-address ") + .action(async (cmd) => { + const deployWallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + console.log(`Using deployer wallet: ${deployWallet.address}`); + + const ownerAddress = cmd.governorAddress || deployWallet.address; + console.log(`Using governor address: ${ownerAddress}`); + + const gasPrice = cmd.gasPrice + ? parseUnits(cmd.gasPrice, "gwei") + : (await provider.getGasPrice()).mul(GAS_MULTIPLIER); + console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); + + const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); + console.log(`Using nonce: ${nonce}`); + + const deployer = new Deployer({ + deployWallet, + ownerAddress, + verbose: true, + }); + + const baseTokenAddress = await chooseBaseTokenAddress(cmd.baseTokenName, cmd.baseTokenAddress); + await checkTokenAddress(baseTokenAddress); + console.log(`Using base token address: ${baseTokenAddress}`); + + if (!(await deployer.bridgehubContract(deployWallet).tokenIsRegistered(baseTokenAddress))) { + await deployer.registerToken(baseTokenAddress); + } + + await deployer.registerHyperchain(baseTokenAddress, cmd.validiumMode, null, gasPrice); + }); + + await program.parseAsync(process.argv); +} + +main() + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/utils.ts b/l1-contracts/scripts/utils.ts index 75c2d98083e1..3a972b64cbf7 100644 --- a/l1-contracts/scripts/utils.ts +++ b/l1-contracts/scripts/utils.ts @@ -1,12 +1,14 @@ +// hardhat import should be the first import in the file +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import * as hardhat from "hardhat"; import * as chalk from "chalk"; -import type { BytesLike } from "ethers"; import { ethers } from "ethers"; import * as fs from "fs"; import * as path from "path"; const warning = chalk.bold.yellow; -const CREATE2_PREFIX = ethers.utils.solidityKeccak256(["string"], ["zksyncCreate2"]); export const L1_TO_L2_ALIAS_OFFSET = "0x1111000000000000000000000000000000001111"; +export const GAS_MULTIPLIER = 1; interface SystemConfig { requiredL2GasPricePerPubdata: number; @@ -45,43 +47,13 @@ export function web3Provider() { } // Short polling interval for local network - if (network === "localhost") { + if (network === "localhost" || network === "hardhat") { provider.pollingInterval = 100; } return provider; } -export function getAddressFromEnv(envName: string): string { - const address = process.env[envName]; - if (!/^0x[a-fA-F0-9]{40}$/.test(address)) { - throw new Error(`Incorrect address format hash in ${envName} env: ${address}`); - } - return address; -} - -export function getHashFromEnv(envName: string): string { - const hash = process.env[envName]; - if (!/^0x[a-fA-F0-9]{64}$/.test(hash)) { - throw new Error(`Incorrect hash format hash in ${envName} env: ${hash}`); - } - return hash; -} - -export function getNumberFromEnv(envName: string): string { - const number = process.env[envName]; - if (!/^([1-9]\d*|0)$/.test(number)) { - throw new Error(`Incorrect number format number in ${envName} env: ${number}`); - } - return number; -} - -const ADDRESS_MODULO = ethers.BigNumber.from(2).pow(160); - -export function applyL1ToL2Alias(address: string): string { - return ethers.utils.hexlify(ethers.BigNumber.from(address).add(L1_TO_L2_ALIAS_OFFSET).mod(ADDRESS_MODULO)); -} - export function readBatchBootloaderBytecode() { const bootloaderPath = path.join(process.env.ZKSYNC_HOME as string, "contracts/system-contracts/bootloader"); return fs.readFileSync(`${bootloaderPath}/build/artifacts/proved_batch.yul.zbin`); @@ -95,56 +67,6 @@ export function readSystemContractsBytecode(fileName: string) { return JSON.parse(artifact.toString()).bytecode; } -export function hashL2Bytecode(bytecode: ethers.BytesLike): Uint8Array { - // For getting the consistent length we first convert the bytecode to UInt8Array - const bytecodeAsArray = ethers.utils.arrayify(bytecode); - - if (bytecodeAsArray.length % 32 != 0) { - throw new Error("The bytecode length in bytes must be divisible by 32"); - } - - const hashStr = ethers.utils.sha256(bytecodeAsArray); - const hash = ethers.utils.arrayify(hashStr); - - // Note that the length of the bytecode - // should be provided in 32-byte words. - const bytecodeLengthInWords = bytecodeAsArray.length / 32; - if (bytecodeLengthInWords % 2 == 0) { - throw new Error("Bytecode length in 32-byte words must be odd"); - } - const bytecodeLength = ethers.utils.arrayify(bytecodeAsArray.length / 32); - if (bytecodeLength.length > 2) { - throw new Error("Bytecode length must be less than 2^16 bytes"); - } - // The bytecode should always take the first 2 bytes of the bytecode hash, - // so we pad it from the left in case the length is smaller than 2 bytes. - const bytecodeLengthPadded = ethers.utils.zeroPad(bytecodeLength, 2); - - const codeHashVersion = new Uint8Array([1, 0]); - hash.set(codeHashVersion, 0); - hash.set(bytecodeLengthPadded, 2); - - return hash; -} - -export function computeL2Create2Address( - deployWallet: string, - bytecode: BytesLike, - constructorInput: BytesLike, - create2Salt: BytesLike -) { - const senderBytes = ethers.utils.hexZeroPad(deployWallet, 32); - const bytecodeHash = hashL2Bytecode(bytecode); - - const constructorInputHash = ethers.utils.keccak256(constructorInput); - - const data = ethers.utils.keccak256( - ethers.utils.concat([CREATE2_PREFIX, senderBytes, create2Salt, bytecodeHash, constructorInputHash]) - ); - - return ethers.utils.hexDataSlice(data, 12); -} - // eslint-disable-next-line @typescript-eslint/no-explicit-any export function print(name: string, data: any) { console.log(`${name}:\n`, JSON.stringify(data, null, 4), "\n"); @@ -153,72 +75,3 @@ export function print(name: string, data: any) { export function getLowerCaseAddress(address: string) { return ethers.utils.getAddress(address).toLowerCase(); } - -export type L1Token = { - name: string; - symbol: string; - decimals: number; - address: string; -}; - -export function getTokens(network: string): L1Token[] { - const configPath = `${process.env.ZKSYNC_HOME}/etc/tokens/${network}.json`; - return JSON.parse( - fs.readFileSync(configPath, { - encoding: "utf-8", - }) - ); -} - -export interface DeployedAddresses { - ZkSync: { - MailboxFacet: string; - AdminFacet: string; - ExecutorFacet: string; - GettersFacet: string; - Verifier: string; - DiamondInit: string; - DefaultUpgrade: string; - DiamondProxy: string; - }; - Bridges: { - ERC20BridgeImplementation: string; - ERC20BridgeProxy: string; - WethBridgeImplementation: string; - WethBridgeProxy: string; - }; - Governance: string; - ValidatorTimeLock: string; - Create2Factory: string; - BlobVersionedHashRetriever: string; -} - -export function deployedAddressesFromEnv(): DeployedAddresses { - return { - ZkSync: { - MailboxFacet: getAddressFromEnv("CONTRACTS_MAILBOX_FACET_ADDR"), - AdminFacet: getAddressFromEnv("CONTRACTS_ADMIN_FACET_ADDR"), - ExecutorFacet: getAddressFromEnv("CONTRACTS_EXECUTOR_FACET_ADDR"), - GettersFacet: getAddressFromEnv("CONTRACTS_GETTERS_FACET_ADDR"), - DiamondInit: getAddressFromEnv("CONTRACTS_DIAMOND_INIT_ADDR"), - DefaultUpgrade: getAddressFromEnv("CONTRACTS_DEFAULT_UPGRADE_ADDR"), - DiamondProxy: getAddressFromEnv("CONTRACTS_DIAMOND_PROXY_ADDR"), - Verifier: getAddressFromEnv("CONTRACTS_VERIFIER_ADDR"), - }, - Bridges: { - ERC20BridgeImplementation: getAddressFromEnv("CONTRACTS_L1_ERC20_BRIDGE_IMPL_ADDR"), - ERC20BridgeProxy: getAddressFromEnv("CONTRACTS_L1_ERC20_BRIDGE_PROXY_ADDR"), - WethBridgeImplementation: getAddressFromEnv("CONTRACTS_L1_WETH_BRIDGE_IMPL_ADDR"), - WethBridgeProxy: getAddressFromEnv("CONTRACTS_L1_WETH_BRIDGE_PROXY_ADDR"), - }, - Create2Factory: getAddressFromEnv("CONTRACTS_CREATE2_FACTORY_ADDR"), - ValidatorTimeLock: getAddressFromEnv("CONTRACTS_VALIDATOR_TIMELOCK_ADDR"), - Governance: getAddressFromEnv("CONTRACTS_GOVERNANCE_ADDR"), - BlobVersionedHashRetriever: getAddressFromEnv("CONTRACTS_BLOB_VERSIONED_HASH_RETRIEVER_ADDR"), - }; -} - -export enum PubdataPricingMode { - Rollup = 0, - Porter = 1, -} diff --git a/l1-contracts/scripts/verify.ts b/l1-contracts/scripts/verify.ts index 2cf431bc03ea..d70c346635a8 100644 --- a/l1-contracts/scripts/verify.ts +++ b/l1-contracts/scripts/verify.ts @@ -1,7 +1,6 @@ // hardhat import should be the first import in the file import * as hardhat from "hardhat"; - -import { deployedAddressesFromEnv } from "../scripts/utils"; +import { deployedAddressesFromEnv } from "../src.ts/deploy-utils"; // eslint-disable-next-line @typescript-eslint/no-explicit-any function verifyPromise(address: string, constructorArguments?: Array, libraries?: object): Promise { @@ -27,12 +26,12 @@ async function main() { // Contracts without constructor parameters for (const address of [ - addresses.ZkSync.GettersFacet, - addresses.ZkSync.DiamondInit, - addresses.ZkSync.AdminFacet, - addresses.ZkSync.MailboxFacet, - addresses.ZkSync.ExecutorFacet, - addresses.ZkSync.Verifier, + addresses.StateTransition.GettersFacet, + addresses.StateTransition.DiamondInit, + addresses.StateTransition.AdminFacet, + addresses.StateTransition.MailboxFacet, + addresses.StateTransition.ExecutorFacet, + addresses.StateTransition.Verifier, ]) { const promise = verifyPromise(address); promises.push(promise); @@ -56,7 +55,7 @@ async function main() { // } // Bridges - const promise = verifyPromise(addresses.Bridges.ERC20BridgeImplementation, [addresses.ZkSync.DiamondProxy]); + const promise = verifyPromise(addresses.Bridges.ERC20BridgeImplementation, [addresses.StateTransition.DiamondProxy]); promises.push(promise); const messages = await Promise.allSettled(promises); diff --git a/l1-contracts/src.ts/deploy-process.ts b/l1-contracts/src.ts/deploy-process.ts new file mode 100644 index 000000000000..5d6be15fb61e --- /dev/null +++ b/l1-contracts/src.ts/deploy-process.ts @@ -0,0 +1,107 @@ +// hardhat import should be the first import in the file +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import * as hardhat from "hardhat"; + +import "@nomiclabs/hardhat-ethers"; + +import type { BigNumberish, Wallet } from "ethers"; +import { ethers } from "ethers"; + +import type { FacetCut } from "./diamondCut"; + +import type { Deployer } from "./deploy"; +import { getTokens } from "./deploy-token"; + +import { ADDRESS_ONE } from "../src.ts/utils"; + +const zeroHash = "0x0000000000000000000000000000000000000000000000000000000000000000"; + +export const L2_BOOTLOADER_BYTECODE_HASH = "0x1000100000000000000000000000000000000000000000000000000000000000"; +export const L2_DEFAULT_ACCOUNT_BYTECODE_HASH = "0x1001000000000000000000000000000000000000000000000000000000000000"; + +export async function loadDefaultEnvVarsForTests(deployWallet: Wallet) { + process.env.CONTRACTS_LATEST_PROTOCOL_VERSION = (21).toString(); + process.env.CONTRACTS_GENESIS_ROOT = zeroHash; + process.env.CONTRACTS_GENESIS_ROLLUP_LEAF_INDEX = "0"; + process.env.CONTRACTS_GENESIS_BATCH_COMMITMENT = zeroHash; + process.env.CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT = "72000000"; + process.env.CONTRACTS_RECURSION_NODE_LEVEL_VK_HASH = zeroHash; + process.env.CONTRACTS_RECURSION_LEAF_LEVEL_VK_HASH = zeroHash; + process.env.CONTRACTS_RECURSION_CIRCUITS_SET_VKS_HASH = zeroHash; + process.env.ETH_CLIENT_CHAIN_ID = (await deployWallet.getChainId()).toString(); +} + +export async function initialBridgehubDeployment( + deployer: Deployer, + extraFacets: FacetCut[], + gasPrice: BigNumberish, + onlyVerifier: boolean, + diamondUpgradeInit: number, + create2Salt?: string, + nonce?: number +) { + nonce = nonce || (await deployer.deployWallet.getTransactionCount()); + create2Salt = create2Salt || ethers.utils.hexlify(ethers.utils.randomBytes(32)); + + // Create2 factory already deployed on the public networks, only deploy it on local node + if (process.env.CHAIN_ETH_NETWORK === "localhost" || process.env.CHAIN_ETH_NETWORK === "hardhat") { + await deployer.deployCreate2Factory({ gasPrice, nonce }); + nonce++; + + await deployer.deployMulticall3(create2Salt, { gasPrice, nonce }); + nonce++; + } + + if (onlyVerifier) { + await deployer.deployVerifier(create2Salt, { gasPrice, nonce }); + return; + } + + await deployer.deployDefaultUpgrade(create2Salt, { + gasPrice, + nonce, + }); + nonce++; + + await deployer.deployGenesisUpgrade(create2Salt, { + gasPrice, + nonce, + }); + nonce++; + + await deployer.deployValidatorTimelock(create2Salt, { gasPrice, nonce }); + nonce++; + + await deployer.deployGovernance(create2Salt, { gasPrice, nonce }); + await deployer.deployTransparentProxyAdmin(create2Salt, { gasPrice }); + await deployer.deployBridgehubContract(create2Salt, gasPrice); + await deployer.deployBlobVersionedHashRetriever(create2Salt, { gasPrice }); + await deployer.deployStateTransitionManagerContract(create2Salt, extraFacets, gasPrice); + await deployer.setStateTransitionManagerInValidatorTimelock({ gasPrice }); + + /// not the weird order is in order, mimics historical deployment process + await deployer.deployERC20BridgeProxy(create2Salt, { gasPrice }); + await deployer.deploySharedBridgeContracts(create2Salt, gasPrice); + await deployer.deployERC20BridgeImplementation(create2Salt, { gasPrice }); + await deployer.upgradeL1ERC20Bridge(); +} + +export async function registerHyperchain( + deployer: Deployer, + validiumMode: boolean, + extraFacets: FacetCut[], + gasPrice: BigNumberish, + baseTokenName?: string, + chainId?: string +) { + const testnetTokens = getTokens(); + + const baseTokenAddress = baseTokenName + ? testnetTokens.find((token: { symbol: string }) => token.symbol == baseTokenName).address + : ADDRESS_ONE; + + if (!(await deployer.bridgehubContract(deployer.deployWallet).tokenIsRegistered(baseTokenAddress))) { + await deployer.registerToken(baseTokenAddress); + } + await deployer.registerHyperchain(baseTokenAddress, validiumMode, extraFacets, gasPrice, null, chainId); +} diff --git a/l1-contracts/src.ts/deploy-test-process.ts b/l1-contracts/src.ts/deploy-test-process.ts new file mode 100644 index 000000000000..4ee2dfeec5a7 --- /dev/null +++ b/l1-contracts/src.ts/deploy-test-process.ts @@ -0,0 +1,196 @@ +// hardhat import should be the first import in the file +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import * as hardhat from "hardhat"; + +import "@nomiclabs/hardhat-ethers"; +import * as ethers from "ethers"; +import type { BigNumberish, Wallet } from "ethers"; +import type { FacetCut } from "./diamondCut"; + +import { SYSTEM_CONFIG } from "../scripts/utils"; +import { testConfigPath, getNumberFromEnv, getHashFromEnv, PubdataPricingMode } from "../src.ts/utils"; +import { Deployer } from "./deploy"; +import { Interface } from "ethers/lib/utils"; +import { deployTokens, getTokens } from "./deploy-token"; +import { + L2_BOOTLOADER_BYTECODE_HASH, + L2_DEFAULT_ACCOUNT_BYTECODE_HASH, + loadDefaultEnvVarsForTests, + initialBridgehubDeployment, + registerHyperchain, +} from "./deploy-process"; +import { diamondCut, getCurrentFacetCutsForAdd, facetCut, Action } from "./diamondCut"; +import * as fs from "fs"; +import { ETH_ADDRESS_IN_CONTRACTS } from "zksync-ethers/build/src/utils"; +import { CONTRACTS_LATEST_PROTOCOL_VERSION } from "../test/unit_tests/utils"; +// import { DummyAdminFacet } from "../typechain"; + +const addressConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/addresses.json`, { encoding: "utf-8" })); +const testnetTokenPath = `${testConfigPath}/hardhat.json`; + +export async function defaultDeployerForTests(deployWallet: Wallet, ownerAddress: string): Promise { + return new Deployer({ + deployWallet, + ownerAddress, + verbose: false, // change here to view deployement + addresses: addressConfig, + bootloaderBytecodeHash: L2_BOOTLOADER_BYTECODE_HASH, + defaultAccountBytecodeHash: L2_DEFAULT_ACCOUNT_BYTECODE_HASH, + }); +} + +export async function defaultEraDeployerForTests(deployWallet: Wallet, ownerAddress: string): Promise { + return new EraDeployer({ + deployWallet, + ownerAddress, + verbose: false, // change here to view deployement + addresses: addressConfig, + bootloaderBytecodeHash: L2_BOOTLOADER_BYTECODE_HASH, + defaultAccountBytecodeHash: L2_DEFAULT_ACCOUNT_BYTECODE_HASH, + }); +} + +export async function initialTestnetDeploymentProcess( + deployWallet: Wallet, + ownerAddress: string, + gasPrice: BigNumberish, + extraFacets: FacetCut[], + baseTokenName?: string +): Promise { + await loadDefaultEnvVarsForTests(deployWallet); + const deployer = await defaultDeployerForTests(deployWallet, ownerAddress); + + const testnetTokens = getTokens(); + const result = await deployTokens(testnetTokens, deployer.deployWallet, null, false, deployer.verbose); + fs.writeFileSync(testnetTokenPath, JSON.stringify(result, null, 2)); + + // deploy the verifier first + await initialBridgehubDeployment(deployer, extraFacets, gasPrice, true, 1); + await initialBridgehubDeployment(deployer, extraFacets, gasPrice, false, 1); + await registerHyperchain(deployer, false, extraFacets, gasPrice, baseTokenName); + return deployer; +} + +export async function initialEraTestnetDeploymentProcess( + deployWallet: Wallet, + ownerAddress: string, + gasPrice: BigNumberish, + extraFacets: FacetCut[], + baseTokenName?: string +): Promise { + await loadDefaultEnvVarsForTests(deployWallet); + const deployer = await defaultEraDeployerForTests(deployWallet, ownerAddress); + deployer.chainId = 9; + + const testnetTokens = getTokens(); + const result = await deployTokens(testnetTokens, deployer.deployWallet, null, false, deployer.verbose); + fs.writeFileSync(testnetTokenPath, JSON.stringify(result, null, 2)); + + // deploy the verifier first + await initialBridgehubDeployment(deployer, extraFacets, gasPrice, true, 1); + await initialBridgehubDeployment(deployer, extraFacets, gasPrice, false, 1); + deployer.addresses.Create2Factory = "0x1bba393e38a2CD88638F972D67D73599c094f814"; // this should already be deployed, we need to fix it to fix ERA diamond address + // for Era we first deploy the DiamondProxy manually, set the vars manually, and register it in the system via bridgehub.createNewChain(ERA_CHAIN_ID, ..) + await deployer.deployDiamondProxy(extraFacets, {}); + const stateTransitionManager = deployer.stateTransitionManagerContract(deployer.deployWallet); + const tx0 = await stateTransitionManager.registerAlreadyDeployedStateTransition( + deployer.chainId, + deployer.addresses.StateTransition.DiamondProxy + ); + await tx0.wait(); + await registerHyperchain(deployer, false, extraFacets, gasPrice, baseTokenName, deployer.chainId.toString()); + return deployer; +} + +class EraDeployer extends Deployer { + public async deployDiamondProxy(extraFacets: FacetCut[], ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; + ethTxOptions.gasPrice ??= 5_000_000; // to fix gasPrice + const chainId = getNumberFromEnv("ETH_CLIENT_CHAIN_ID"); + const dummyAdminAddress = await this.deployViaCreate2( + "DummyAdminFacet", + [], + ethers.constants.HashZero, + ethTxOptions + ); + + const adminFacet = await hardhat.ethers.getContractAt("DummyAdminFacet", dummyAdminAddress); + const facetCuts: FacetCut[] = [facetCut(adminFacet.address, adminFacet.interface, Action.Add, false)]; + const contractAddress = await this.deployViaCreate2( + "DiamondProxy", + [chainId, diamondCut(facetCuts, ethers.constants.AddressZero, "0x")], + ethers.constants.HashZero, + ethTxOptions + ); + + if (this.verbose) { + console.log("Copy this CONTRACTS_DIAMOND_PROXY_ADDR to hardhat config as ERA_DIAMOND_PROXY for hardhat"); + console.log(`CONTRACTS_DIAMOND_PROXY_ADDR=${contractAddress}`); + } + + this.addresses.StateTransition.DiamondProxy = contractAddress; + // notably, the DummyAdminFacet does not depend on the + const diamondAdminFacet = await hardhat.ethers.getContractAt("DummyAdminFacet", contractAddress); + await diamondAdminFacet.executeUpgrade2(await this.upgradeZkSyncStateTransitionDiamondCut(extraFacets)); + } + + public async upgradeZkSyncStateTransitionDiamondCut(extraFacets?: FacetCut[]) { + let facetCuts: FacetCut[] = Object.values( + await getCurrentFacetCutsForAdd( + this.addresses.StateTransition.AdminFacet, + this.addresses.StateTransition.GettersFacet, + this.addresses.StateTransition.MailboxFacet, + this.addresses.StateTransition.ExecutorFacet + ) + ); + facetCuts = facetCuts.concat(extraFacets ?? []); + // console.log("kl todo", facetCuts); + const verifierParams = + process.env["CONTRACTS_PROVER_AT_GENESIS"] == "fri" + ? { + recursionNodeLevelVkHash: getHashFromEnv("CONTRACTS_FRI_RECURSION_NODE_LEVEL_VK_HASH"), + recursionLeafLevelVkHash: getHashFromEnv("CONTRACTS_FRI_RECURSION_LEAF_LEVEL_VK_HASH"), + recursionCircuitsSetVksHash: "0x0000000000000000000000000000000000000000000000000000000000000000", + } + : { + recursionNodeLevelVkHash: getHashFromEnv("CONTRACTS_RECURSION_NODE_LEVEL_VK_HASH"), + recursionLeafLevelVkHash: getHashFromEnv("CONTRACTS_RECURSION_LEAF_LEVEL_VK_HASH"), + recursionCircuitsSetVksHash: getHashFromEnv("CONTRACTS_RECURSION_CIRCUITS_SET_VKS_HASH"), + }; + const priorityTxMaxGasLimit = getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT"); + const DiamondInit = new Interface(hardhat.artifacts.readArtifactSync("DiamondInit").abi); + + const feeParams = { + pubdataPricingMode: PubdataPricingMode.Rollup, + batchOverheadL1Gas: SYSTEM_CONFIG.priorityTxBatchOverheadL1Gas, + maxPubdataPerBatch: SYSTEM_CONFIG.priorityTxPubdataPerBatch, + priorityTxMaxPubdata: SYSTEM_CONFIG.priorityTxMaxPubdata, + maxL2GasPerBatch: SYSTEM_CONFIG.priorityTxMaxGasPerBatch, + minimalL2GasPrice: SYSTEM_CONFIG.priorityTxMinimalGasPrice, + }; + const storedBatchZero = await this.stateTransitionManagerContract(this.deployWallet).storedBatchZero(); + const diamondInitCalldata = DiamondInit.encodeFunctionData("initialize", [ + // these first values are set in the contract + { + chainId: this.chainId, // era chain Id + bridgehub: this.addresses.Bridgehub.BridgehubProxy, + stateTransitionManager: this.addresses.StateTransition.StateTransitionProxy, + protocolVersion: CONTRACTS_LATEST_PROTOCOL_VERSION, + admin: this.ownerAddress, + validatorTimelock: this.addresses.ValidatorTimeLock, + baseToken: ETH_ADDRESS_IN_CONTRACTS, + baseTokenBridge: this.addresses.Bridges.SharedBridgeProxy, + storedBatchZero, + verifier: this.addresses.StateTransition.Verifier, + verifierParams, + l2BootloaderBytecodeHash: L2_BOOTLOADER_BYTECODE_HASH, + l2DefaultAccountBytecodeHash: L2_DEFAULT_ACCOUNT_BYTECODE_HASH, + priorityTxMaxGasLimit, + feeParams, + blobVersionedHashRetriever: this.addresses.BlobVersionedHashRetriever, + }, + ]); + + return diamondCut(facetCuts, this.addresses.StateTransition.DiamondInit, diamondInitCalldata); + } +} diff --git a/l1-contracts/src.ts/deploy-token.ts b/l1-contracts/src.ts/deploy-token.ts new file mode 100644 index 000000000000..4574db7dd330 --- /dev/null +++ b/l1-contracts/src.ts/deploy-token.ts @@ -0,0 +1,133 @@ +import * as hardhat from "hardhat"; +import "@nomiclabs/hardhat-ethers"; +import { Wallet } from "ethers"; +import type { Contract } from "ethers"; +import { parseEther } from "ethers/lib/utils"; + +import * as fs from "fs"; + +const DEFAULT_ERC20 = "TestnetERC20Token"; + +export type L1Token = { + address: string | null; + name: string; + symbol: string; + decimals: number; +}; + +export type TokenDescription = L1Token & { + implementation?: string; + contract?: Contract; +}; + +export async function deployContracts(tokens: TokenDescription[], wallet: Wallet): Promise { + let nonce = await wallet.getTransactionCount("pending"); + + for (const token of tokens) { + token.implementation = token.implementation || DEFAULT_ERC20; + const tokenFactory = await hardhat.ethers.getContractFactory(token.implementation, wallet); + const args = token.implementation !== "WETH9" ? [token.name, token.symbol, token.decimals] : []; + + token.contract = await tokenFactory.deploy(...args, { gasLimit: 5000000, nonce: nonce++ }); + } + + await Promise.all(tokens.map(async (token) => token.contract.deployTransaction.wait())); + + return nonce; +} + +function getTestAddresses(mnemonic: string): string[] { + return Array.from({ length: 10 }, (_, i) => Wallet.fromMnemonic(mnemonic as string, `m/44'/60'/0'/0/${i}`).address); +} + +function unwrapToken(token: TokenDescription): L1Token { + token.address = token.contract.address; + + delete token.contract; + if (token.implementation) { + delete token.implementation; + } + + return token; +} + +export async function mintTokens( + tokens: TokenDescription[], + wallet: Wallet, + nonce: number, + mnemonic: string +): Promise { + const targetAddresses = [wallet.address, ...getTestAddresses(mnemonic)]; + + const results = []; + const promises = []; + for (const token of tokens) { + if (token.implementation !== "WETH9") { + for (const address of targetAddresses) { + const tx = await token.contract.mint(address, parseEther("3000000000"), { nonce: nonce++ }); + promises.push(tx.wait()); + } + } + + results.push(unwrapToken(token)); + } + await Promise.all(promises); + + return results; +} + +export function getTokens(): L1Token[] { + const network = process.env.CHAIN_ETH_NETWORK || "localhost"; + const configPath = + network == "hardhat" + ? `./test/test_config/constant/${network}.json` + : `${process.env.ZKSYNC_HOME}/etc/tokens/${network}.json`; + return JSON.parse( + fs.readFileSync(configPath, { + encoding: "utf-8", + }) + ); +} + +export async function deployTokens( + tokens: TokenDescription[], + wallet: Wallet, + mnemonic: string, + mintTokens: boolean = false, + verbose: boolean = false +): Promise { + const result: L1Token[] = []; + for (const token of tokens) { + const implementation = token.implementation || token.symbol != "WETH" ? DEFAULT_ERC20 : "WETH9"; + const tokenFactory = await hardhat.ethers.getContractFactory(implementation, wallet); + const args = + token.symbol != "WETH" ? [`${token.name} (${process.env.CHAIN_ETH_NETWORK})`, token.symbol, token.decimals] : []; + if (verbose) { + console.log(`Deploying testnet ${token.symbol}`, implementation); + } + const erc20 = await tokenFactory.deploy(...args, { gasLimit: 3000000 }); + await erc20.deployTransaction.wait(); + token.address = erc20.address; + if (verbose) { + console.log(`Token ${token.symbol} deployed at ${erc20.address}`); + } + + if (token.symbol !== "WETH" && mintTokens) { + await erc20.mint(wallet.address, parseEther("3000000000")); + } + if (mintTokens) { + for (let i = 0; i < 10; ++i) { + const testWalletAddress = Wallet.fromMnemonic(mnemonic as string, "m/44'/60'/0'/0/" + i).address; + if (token.symbol !== "WETH") { + await erc20.mint(testWalletAddress, parseEther("3000000000")); + } + } + } + // Remove the unneeded field + // if (token.implementation) { + // delete token.implementation; + // } + result.push(token); + } + return result; +} diff --git a/l1-contracts/src.ts/deploy-utils.ts b/l1-contracts/src.ts/deploy-utils.ts index e60f3d30c031..25ebe062cb85 100644 --- a/l1-contracts/src.ts/deploy-utils.ts +++ b/l1-contracts/src.ts/deploy-utils.ts @@ -3,6 +3,8 @@ import "@nomiclabs/hardhat-ethers"; import { ethers } from "ethers"; import { SingletonFactoryFactory } from "../typechain"; +import { getAddressFromEnv } from "./utils"; + export async function deployViaCreate2( deployWallet: ethers.Wallet, contractName: string, @@ -73,8 +75,76 @@ export async function deployBytecodeViaCreate2( const deployedBytecodeAfter = await deployWallet.provider.getCode(expectedAddress); if (ethers.utils.hexDataLength(deployedBytecodeAfter) == 0) { - throw new Error("Failed to deploy bytecode via create2 factory"); + throw new Error(`Failed to deploy ${contractName} bytecode via create2 factory`); } return [expectedAddress, tx.hash]; } + +export interface DeployedAddresses { + Bridgehub: { + BridgehubProxy: string; + BridgehubImplementation: string; + }; + StateTransition: { + StateTransitionProxy: string; + StateTransitionImplementation: string; + Verifier: string; + AdminFacet: string; + MailboxFacet: string; + ExecutorFacet: string; + GettersFacet: string; + DiamondInit: string; + GenesisUpgrade: string; + DiamondUpgradeInit: string; + DefaultUpgrade: string; + DiamondProxy: string; + }; + Bridges: { + ERC20BridgeImplementation: string; + ERC20BridgeProxy: string; + SharedBridgeImplementation: string; + SharedBridgeProxy: string; + }; + BaseToken: string; + TransparentProxyAdmin: string; + Governance: string; + BlobVersionedHashRetriever: string; + ValidatorTimeLock: string; + Create2Factory: string; +} + +export function deployedAddressesFromEnv(): DeployedAddresses { + return { + Bridgehub: { + BridgehubProxy: getAddressFromEnv("CONTRACTS_BRIDGEHUB_PROXY_ADDR"), + BridgehubImplementation: getAddressFromEnv("CONTRACTS_BRIDGEHUB_IMPL_ADDR"), + }, + StateTransition: { + StateTransitionProxy: getAddressFromEnv("CONTRACTS_STATE_TRANSITION_PROXY_ADDR"), + StateTransitionImplementation: getAddressFromEnv("CONTRACTS_STATE_TRANSITION_IMPL_ADDR"), + Verifier: getAddressFromEnv("CONTRACTS_VERIFIER_ADDR"), + AdminFacet: getAddressFromEnv("CONTRACTS_ADMIN_FACET_ADDR"), + MailboxFacet: getAddressFromEnv("CONTRACTS_MAILBOX_FACET_ADDR"), + ExecutorFacet: getAddressFromEnv("CONTRACTS_EXECUTOR_FACET_ADDR"), + GettersFacet: getAddressFromEnv("CONTRACTS_GETTERS_FACET_ADDR"), + DiamondInit: getAddressFromEnv("CONTRACTS_DIAMOND_INIT_ADDR"), + GenesisUpgrade: getAddressFromEnv("CONTRACTS_GENESIS_UPGRADE_ADDR"), + DiamondUpgradeInit: getAddressFromEnv("CONTRACTS_DIAMOND_UPGRADE_INIT_ADDR"), + DefaultUpgrade: getAddressFromEnv("CONTRACTS_DEFAULT_UPGRADE_ADDR"), + DiamondProxy: getAddressFromEnv("CONTRACTS_DIAMOND_PROXY_ADDR"), + }, + Bridges: { + ERC20BridgeImplementation: getAddressFromEnv("CONTRACTS_L1_ERC20_BRIDGE_IMPL_ADDR"), + ERC20BridgeProxy: getAddressFromEnv("CONTRACTS_L1_ERC20_BRIDGE_PROXY_ADDR"), + SharedBridgeImplementation: getAddressFromEnv("CONTRACTS_L1_SHARED_BRIDGE_IMPL_ADDR"), + SharedBridgeProxy: getAddressFromEnv("CONTRACTS_L1_SHARED_BRIDGE_PROXY_ADDR"), + }, + BaseToken: getAddressFromEnv("CONTRACTS_BASE_TOKEN_ADDR"), + TransparentProxyAdmin: getAddressFromEnv("CONTRACTS_TRANSPARENT_PROXY_ADMIN_ADDR"), + Create2Factory: getAddressFromEnv("CONTRACTS_CREATE2_FACTORY_ADDR"), + BlobVersionedHashRetriever: getAddressFromEnv("CONTRACTS_BLOB_VERSIONED_HASH_RETRIEVER_ADDR"), + ValidatorTimeLock: getAddressFromEnv("CONTRACTS_VALIDATOR_TIMELOCK_ADDR"), + Governance: getAddressFromEnv("CONTRACTS_GOVERNANCE_ADDR"), + }; +} diff --git a/l1-contracts/src.ts/deploy.ts b/l1-contracts/src.ts/deploy.ts index 57334b3d594f..e265b2112c2a 100644 --- a/l1-contracts/src.ts/deploy.ts +++ b/l1-contracts/src.ts/deploy.ts @@ -3,64 +3,80 @@ import "@nomiclabs/hardhat-ethers"; import type { BigNumberish, providers, Signer, Wallet } from "ethers"; import { ethers } from "ethers"; -import { Interface, hexlify } from "ethers/lib/utils"; -import { diamondCut, getCurrentFacetCutsForAdd } from "./diamondCut"; -import { IZkSyncFactory } from "../typechain/IZkSyncFactory"; -import { L1ERC20BridgeFactory } from "../typechain/L1ERC20BridgeFactory"; -import { L1WethBridgeFactory } from "../typechain/L1WethBridgeFactory"; -import { ValidatorTimelockFactory } from "../typechain/ValidatorTimelockFactory"; -import { SingletonFactoryFactory } from "../typechain/SingletonFactoryFactory"; -import { ITransparentUpgradeableProxyFactory } from "../typechain/ITransparentUpgradeableProxyFactory"; -import type { DeployedAddresses } from "../scripts/utils"; +import { hexlify, Interface } from "ethers/lib/utils"; +import type { DeployedAddresses } from "./deploy-utils"; +import { deployedAddressesFromEnv, deployBytecodeViaCreate2, deployViaCreate2 } from "./deploy-utils"; +import { readBatchBootloaderBytecode, readSystemContractsBytecode, SYSTEM_CONFIG } from "../scripts/utils"; +import { getTokens } from "./deploy-token"; import { - readSystemContractsBytecode, - hashL2Bytecode, + ADDRESS_ONE, getAddressFromEnv, getHashFromEnv, getNumberFromEnv, - readBatchBootloaderBytecode, - getTokens, - deployedAddressesFromEnv, - SYSTEM_CONFIG, -} from "../scripts/utils"; -import { deployBytecodeViaCreate2, deployViaCreate2 } from "./deploy-utils"; + PubdataPricingMode, + hashL2Bytecode, + DIAMOND_CUT_DATA_ABI_STRING, +} from "./utils"; +import { IBridgehubFactory } from "../typechain/IBridgehubFactory"; import { IGovernanceFactory } from "../typechain/IGovernanceFactory"; -import { PubdataPricingMode } from "../test/unit_tests/utils"; +import { IStateTransitionManagerFactory } from "../typechain/IStateTransitionManagerFactory"; +import { ITransparentUpgradeableProxyFactory } from "../typechain/ITransparentUpgradeableProxyFactory"; +import { ProxyAdminFactory } from "../typechain/ProxyAdminFactory"; + +import { IZkSyncStateTransitionFactory } from "../typechain/IZkSyncStateTransitionFactory"; +import { L1SharedBridgeFactory } from "../typechain/L1SharedBridgeFactory"; + +import { SingletonFactoryFactory } from "../typechain/SingletonFactoryFactory"; +import { ValidatorTimelockFactory } from "../typechain/ValidatorTimelockFactory"; +import type { FacetCut } from "./diamondCut"; +import { diamondCut, getCurrentFacetCutsForAdd } from "./diamondCut"; + +import { ERC20Factory } from "../typechain"; -const L2_BOOTLOADER_BYTECODE_HASH = hexlify(hashL2Bytecode(readBatchBootloaderBytecode())); -const L2_DEFAULT_ACCOUNT_BYTECODE_HASH = hexlify(hashL2Bytecode(readSystemContractsBytecode("DefaultAccount"))); +let L2_BOOTLOADER_BYTECODE_HASH: string; +let L2_DEFAULT_ACCOUNT_BYTECODE_HASH: string; +export const EraLegacyChainId = 324; +export const EraLegacyDiamondProxyAddress = "0x32400084C286CF3E17e7B677ea9583e60a000324"; export interface DeployerConfig { deployWallet: Wallet; + addresses?: DeployedAddresses; ownerAddress?: string; verbose?: boolean; + bootloaderBytecodeHash?: string; + defaultAccountBytecodeHash?: string; } export class Deployer { public addresses: DeployedAddresses; - private deployWallet: Wallet; - private verbose: boolean; - private ownerAddress: string; + public deployWallet: Wallet; + public verbose: boolean; + public chainId: number; + public ownerAddress: string; constructor(config: DeployerConfig) { this.deployWallet = config.deployWallet; this.verbose = config.verbose != null ? config.verbose : false; - this.addresses = deployedAddressesFromEnv(); + this.addresses = config.addresses ? config.addresses : deployedAddressesFromEnv(); + L2_BOOTLOADER_BYTECODE_HASH = config.bootloaderBytecodeHash + ? config.bootloaderBytecodeHash + : hexlify(hashL2Bytecode(readBatchBootloaderBytecode())); + L2_DEFAULT_ACCOUNT_BYTECODE_HASH = config.defaultAccountBytecodeHash + ? config.defaultAccountBytecodeHash + : hexlify(hashL2Bytecode(readSystemContractsBytecode("DefaultAccount"))); this.ownerAddress = config.ownerAddress != null ? config.ownerAddress : this.deployWallet.address; } - public async initialProxyDiamondCut() { - const facetCuts = Object.values( + public async initialZkSyncStateTransitionDiamondCut(extraFacets?: FacetCut[]) { + let facetCuts: FacetCut[] = Object.values( await getCurrentFacetCutsForAdd( - this.addresses.ZkSync.AdminFacet, - this.addresses.ZkSync.GettersFacet, - this.addresses.ZkSync.MailboxFacet, - this.addresses.ZkSync.ExecutorFacet + this.addresses.StateTransition.AdminFacet, + this.addresses.StateTransition.GettersFacet, + this.addresses.StateTransition.MailboxFacet, + this.addresses.StateTransition.ExecutorFacet ) ); - const genesisBatchHash = getHashFromEnv("CONTRACTS_GENESIS_ROOT"); // TODO: confusing name - const genesisIndexRepeatedStorageChanges = getNumberFromEnv("CONTRACTS_GENESIS_ROLLUP_LEAF_INDEX"); - const genesisBatchCommitment = getHashFromEnv("CONTRACTS_GENESIS_BATCH_COMMITMENT"); + facetCuts = facetCuts.concat(extraFacets ?? []); const verifierParams = process.env["CONTRACTS_PROVER_AT_GENESIS"] == "fri" @@ -75,7 +91,6 @@ export class Deployer { recursionCircuitsSetVksHash: getHashFromEnv("CONTRACTS_RECURSION_CIRCUITS_SET_VKS_HASH"), }; const priorityTxMaxGasLimit = getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT"); - const initialProtocolVersion = getNumberFromEnv("CONTRACTS_INITIAL_PROTOCOL_VERSION"); const DiamondInit = new Interface(hardhat.artifacts.readArtifactSync("DiamondInit").abi); const feeParams = { @@ -88,26 +103,32 @@ export class Deployer { }; const diamondInitCalldata = DiamondInit.encodeFunctionData("initialize", [ + // these first values are set in the contract { - verifier: this.addresses.ZkSync.Verifier, - governor: this.ownerAddress, - admin: this.ownerAddress, - genesisBatchHash, - genesisIndexRepeatedStorageChanges, - genesisBatchCommitment, + chainId: "0x0000000000000000000000000000000000000000000000000000000000000001", + bridgehub: "0x0000000000000000000000000000000000001234", + stateTransitionManager: "0x0000000000000000000000000000000000002234", + protocolVersion: "0x0000000000000000000000000000000000002234", + admin: "0x0000000000000000000000000000000000003234", + validatorTimelock: "0x0000000000000000000000000000000000004234", + baseToken: "0x0000000000000000000000000000000000004234", + baseTokenBridge: "0x0000000000000000000000000000000000004234", + storedBatchZero: "0x0000000000000000000000000000000000000000000000000000000000005432", + verifier: this.addresses.StateTransition.Verifier, verifierParams, - zkPorterIsAvailable: false, l2BootloaderBytecodeHash: L2_BOOTLOADER_BYTECODE_HASH, l2DefaultAccountBytecodeHash: L2_DEFAULT_ACCOUNT_BYTECODE_HASH, priorityTxMaxGasLimit, - initialProtocolVersion, feeParams, blobVersionedHashRetriever: this.addresses.BlobVersionedHashRetriever, }, ]); - // @ts-ignore - return diamondCut(facetCuts, this.addresses.ZkSync.DiamondInit, diamondInitCalldata); + return diamondCut( + facetCuts, + this.addresses.StateTransition.DiamondInit, + "0x" + diamondInitCalldata.slice(2 + (4 + 9 * 32) * 2) + ); } public async deployCreate2Factory(ethTxOptions?: ethers.providers.TransactionRequest) { @@ -130,7 +151,7 @@ export class Deployer { this.addresses.Create2Factory = create2Factory.address; } - private async deployViaCreate2( + public async deployViaCreate2( contractName: string, // eslint-disable-next-line @typescript-eslint/no-explicit-any args: any[], @@ -188,15 +209,133 @@ export class Deployer { this.addresses.Governance = contractAddress; } - public async deployMailboxFacet(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + public async deployBridgehubImplementation(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2("MailboxFacet", [], create2Salt, ethTxOptions); + const contractAddress = await this.deployViaCreate2("Bridgehub", [], create2Salt, ethTxOptions); if (this.verbose) { - console.log(`CONTRACTS_MAILBOX_FACET_ADDR=${contractAddress}`); + console.log(`CONTRACTS_BRIDGEHUB_IMPL_ADDR=${contractAddress}`); + } + + this.addresses.Bridgehub.BridgehubImplementation = contractAddress; + } + + public async deployTransparentProxyAdmin(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; + if (this.verbose) { + console.log("Deploying Proxy Admin factory"); + } + + const contractFactory = await hardhat.ethers.getContractFactory("ProxyAdmin", { + signer: this.deployWallet, + }); + + const proxyAdmin = await contractFactory.deploy(...[ethTxOptions]); + const rec = await proxyAdmin.deployTransaction.wait(); + + if (this.verbose) { + console.log(`CONTRACTS_TRANSPARENT_PROXY_ADMIN_ADDR=${proxyAdmin.address}`); + console.log(`Proxy admin deployed, gasUsed: ${rec.gasUsed.toString()}`); + } + + this.addresses.TransparentProxyAdmin = proxyAdmin.address; + + const tx = await proxyAdmin.transferOwnership(this.addresses.Governance); + const receipt = await tx.wait(); + + if (this.verbose) { + console.log( + `ProxyAdmin ownership transferred to Governance in tx ${ + receipt.transactionHash + }, gas used: ${receipt.gasUsed.toString()}` + ); + } + } + + public async deployBridgehubProxy(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; + + const bridgehub = new Interface(hardhat.artifacts.readArtifactSync("Bridgehub").abi); + + const initCalldata = bridgehub.encodeFunctionData("initialize", [this.ownerAddress]); + + const contractAddress = await this.deployViaCreate2( + "TransparentUpgradeableProxy", + [this.addresses.Bridgehub.BridgehubImplementation, this.addresses.TransparentProxyAdmin, initCalldata], + create2Salt, + ethTxOptions + ); + + if (this.verbose) { + console.log(`CONTRACTS_BRIDGEHUB_PROXY_ADDR=${contractAddress}`); + } + + this.addresses.Bridgehub.BridgehubProxy = contractAddress; + } + + public async deployStateTransitionManagerImplementation( + create2Salt: string, + ethTxOptions: ethers.providers.TransactionRequest + ) { + ethTxOptions.gasLimit ??= 10_000_000; + const contractAddress = await this.deployViaCreate2( + "StateTransitionManager", + [this.addresses.Bridgehub.BridgehubProxy], + create2Salt, + ethTxOptions + ); + + if (this.verbose) { + console.log(`CONTRACTS_STATE_TRANSITION_IMPL_ADDR=${contractAddress}`); } - this.addresses.ZkSync.MailboxFacet = contractAddress; + this.addresses.StateTransition.StateTransitionImplementation = contractAddress; + } + + public async deployStateTransitionManagerProxy( + create2Salt: string, + ethTxOptions: ethers.providers.TransactionRequest, + extraFacets?: FacetCut[] + ) { + ethTxOptions.gasLimit ??= 10_000_000; + const genesisBatchHash = getHashFromEnv("CONTRACTS_GENESIS_ROOT"); // TODO: confusing name + const genesisRollupLeafIndex = getNumberFromEnv("CONTRACTS_GENESIS_ROLLUP_LEAF_INDEX"); + const genesisBatchCommitment = getHashFromEnv("CONTRACTS_GENESIS_BATCH_COMMITMENT"); + const diamondCut = await this.initialZkSyncStateTransitionDiamondCut(extraFacets); + const protocolVersion = getNumberFromEnv("CONTRACTS_LATEST_PROTOCOL_VERSION"); + + const stateTransitionManager = new Interface(hardhat.artifacts.readArtifactSync("StateTransitionManager").abi); + + const initCalldata = stateTransitionManager.encodeFunctionData("initialize", [ + { + governor: this.ownerAddress, + validatorTimelock: this.addresses.ValidatorTimeLock, + genesisUpgrade: this.addresses.StateTransition.GenesisUpgrade, + genesisBatchHash, + genesisIndexRepeatedStorageChanges: genesisRollupLeafIndex, + genesisBatchCommitment, + diamondCut, + protocolVersion, + }, + ]); + + const contractAddress = await this.deployViaCreate2( + "TransparentUpgradeableProxy", + [ + this.addresses.StateTransition.StateTransitionImplementation, + this.addresses.TransparentProxyAdmin, + initCalldata, + ], + create2Salt, + ethTxOptions + ); + + if (this.verbose) { + console.log(`StateTransitionManagerProxy deployed, with protocol version: ${protocolVersion}`); + console.log(`CONTRACTS_STATE_TRANSITION_PROXY_ADDR=${contractAddress}`); + } + + this.addresses.StateTransition.StateTransitionProxy = contractAddress; } public async deployAdminFacet(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { @@ -207,7 +346,18 @@ export class Deployer { console.log(`CONTRACTS_ADMIN_FACET_ADDR=${contractAddress}`); } - this.addresses.ZkSync.AdminFacet = contractAddress; + this.addresses.StateTransition.AdminFacet = contractAddress; + } + + public async deployMailboxFacet(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; + const contractAddress = await this.deployViaCreate2("MailboxFacet", [], create2Salt, ethTxOptions); + + if (this.verbose) { + console.log(`CONTRACTS_MAILBOX_FACET_ADDR=${contractAddress}`); + } + + this.addresses.StateTransition.MailboxFacet = contractAddress; } public async deployExecutorFacet(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { @@ -218,7 +368,7 @@ export class Deployer { console.log(`CONTRACTS_EXECUTOR_FACET_ADDR=${contractAddress}`); } - this.addresses.ZkSync.ExecutorFacet = contractAddress; + this.addresses.StateTransition.ExecutorFacet = contractAddress; } public async deployGettersFacet(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { @@ -229,7 +379,7 @@ export class Deployer { console.log(`CONTRACTS_GETTERS_FACET_ADDR=${contractAddress}`); } - this.addresses.ZkSync.GettersFacet = contractAddress; + this.addresses.StateTransition.GettersFacet = contractAddress; } public async deployVerifier(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { @@ -240,14 +390,14 @@ export class Deployer { console.log(`CONTRACTS_VERIFIER_ADDR=${contractAddress}`); } - this.addresses.ZkSync.Verifier = contractAddress; + this.addresses.StateTransition.Verifier = contractAddress; } public async deployERC20BridgeImplementation(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { ethTxOptions.gasLimit ??= 10_000_000; const contractAddress = await this.deployViaCreate2( "L1ERC20Bridge", - [this.addresses.ZkSync.DiamondProxy], + [this.addresses.Bridges.SharedBridgeProxy], create2Salt, ethTxOptions ); @@ -259,15 +409,64 @@ export class Deployer { this.addresses.Bridges.ERC20BridgeImplementation = contractAddress; } + public async upgradeL1ERC20Bridge(alreadyInitialized: boolean = false) { + if (process.env.CHAIN_ETH_NETWORK === "localhost") { + // we need to wait here for a new block + await new Promise((resolve) => setTimeout(resolve, 5000)); + } + const proxyAdminInterface = new Interface(hardhat.artifacts.readArtifactSync("ProxyAdmin").abi); + const l1ERC20BridgeInterface = new Interface(hardhat.artifacts.readArtifactSync("L1ERC20Bridge").abi); + const calldata = alreadyInitialized + ? proxyAdminInterface.encodeFunctionData("upgrade(address,address)", [ + this.addresses.Bridges.ERC20BridgeProxy, + this.addresses.Bridges.ERC20BridgeImplementation, + ]) + : proxyAdminInterface.encodeFunctionData("upgradeAndCall(address,address,bytes)", [ + this.addresses.Bridges.ERC20BridgeProxy, + this.addresses.Bridges.ERC20BridgeImplementation, + l1ERC20BridgeInterface.encodeFunctionData("initialize()", []), + ]); + + await this.executeUpgrade(this.addresses.TransparentProxyAdmin, 0, calldata); + if (this.verbose) { + console.log("L1ERC20Bridge upgrade sent"); + } + } + + public async executeUpgrade(targetAddress: string, value: BigNumberish, callData: string) { + const governance = IGovernanceFactory.connect(this.addresses.Governance, this.deployWallet); + const operation = { + calls: [{ target: targetAddress, value: value, data: callData }], + predecessor: ethers.constants.HashZero, + salt: ethers.constants.HashZero, + }; + const scheduleTx = await governance.scheduleTransparent(operation, 0); + await scheduleTx.wait(); + if (this.verbose) { + console.log("Upgrade scheduled"); + } + const executeTX = await governance.execute(operation); + await executeTX.wait(); + if (this.verbose) { + console.log( + "Upgrade with target ", + targetAddress, + "executed: ", + await governance.isOperationDone(await governance.hashOperation(operation)) + ); + } + } + + // used for testing, mimics deployment process. public async deployERC20BridgeProxy(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { ethTxOptions.gasLimit ??= 10_000_000; const contractAddress = await this.deployViaCreate2( "TransparentUpgradeableProxy", - [this.addresses.Bridges.ERC20BridgeImplementation, this.ownerAddress, "0x"], + [this.addresses.Bridgehub.BridgehubProxy, this.addresses.TransparentProxyAdmin, "0x"], // we have to use an address where a contract is already deployed create2Salt, ethTxOptions ); - + process.env.CONTRACTS_L1_ERC20_BRIDGE_PROXY_ADDR = contractAddress; // we set this for process, so we can read from process in deploySharedBridgeImplementation if (this.verbose) { console.log(`CONTRACTS_L1_ERC20_BRIDGE_PROXY_ADDR=${contractAddress}`); } @@ -275,51 +474,69 @@ export class Deployer { this.addresses.Bridges.ERC20BridgeProxy = contractAddress; } - public async deployWethToken(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + public async deploySharedBridgeImplementation( + create2Salt: string, + ethTxOptions: ethers.providers.TransactionRequest + ) { ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2("WETH9", [], create2Salt, ethTxOptions); - - if (this.verbose) { - console.log(`CONTRACTS_L1_WETH_TOKEN_ADDR=${contractAddress}`); - } - } - - public async deployWethBridgeImplementation(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - const tokens = getTokens(process.env.CHAIN_ETH_NETWORK || "localhost"); + const tokens = getTokens(); const l1WethToken = tokens.find((token: { symbol: string }) => token.symbol == "WETH")!.address; - - ethTxOptions.gasLimit ??= 10_000_000; const contractAddress = await this.deployViaCreate2( - "L1WethBridge", - [l1WethToken, this.addresses.ZkSync.DiamondProxy], + "L1SharedBridge", + [l1WethToken, this.addresses.Bridgehub.BridgehubProxy, process.env.CONTRACTS_L1_ERC20_BRIDGE_PROXY_ADDR], // we load from process.env, as normally L1_ERC20 bridge will already be deployed create2Salt, ethTxOptions ); if (this.verbose) { - console.log(`CONTRACTS_L1_WETH_BRIDGE_IMPL_ADDR=${contractAddress}`); + console.log(`CONTRACTS_L1_SHARED_BRIDGE_IMPL_ADDR=${contractAddress}`); } - this.addresses.Bridges.WethBridgeImplementation = contractAddress; + this.addresses.Bridges.SharedBridgeImplementation = contractAddress; } - public async deployWethBridgeProxy(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + public async deploySharedBridgeProxy(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { ethTxOptions.gasLimit ??= 10_000_000; + const storageSwitch = getNumberFromEnv("CONTRACTS_SHARED_BRIDGE_UPGRADE_STORAGE_SWITCH"); + const initCalldata = new Interface(hardhat.artifacts.readArtifactSync("L1SharedBridge").abi).encodeFunctionData( + "initialize", + [this.addresses.Governance, storageSwitch] + ); const contractAddress = await this.deployViaCreate2( "TransparentUpgradeableProxy", - [this.addresses.Bridges.WethBridgeImplementation, this.ownerAddress, "0x"], + [this.addresses.Bridges.SharedBridgeImplementation, this.addresses.TransparentProxyAdmin, initCalldata], create2Salt, ethTxOptions ); if (this.verbose) { - console.log(`CONTRACTS_L1_WETH_BRIDGE_PROXY_ADDR=${contractAddress}`); + console.log(`CONTRACTS_L1_SHARED_BRIDGE_PROXY_ADDR=${contractAddress}`); } - this.addresses.Bridges.WethBridgeProxy = contractAddress; + this.addresses.Bridges.SharedBridgeProxy = contractAddress; } - public async deployDiamondInit(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + public async registerSharedBridge(ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; + const bridgehub = this.bridgehubContract(this.deployWallet); + + /// registering ETH as a valid token, with address 1. + const tx2 = await bridgehub.addToken(ADDRESS_ONE); + const receipt2 = await tx2.wait(); + + const tx3 = await bridgehub.setSharedBridge(this.addresses.Bridges.SharedBridgeProxy); + const receipt3 = await tx3.wait(); + if (this.verbose) { + console.log( + `Shared bridge was registered, gas used: ${receipt3.gasUsed.toString()} and ${receipt2.gasUsed.toString()}` + ); + } + } + + public async deployStateTransitionDiamondInit( + create2Salt: string, + ethTxOptions: ethers.providers.TransactionRequest + ) { ethTxOptions.gasLimit ??= 10_000_000; const contractAddress = await this.deployViaCreate2("DiamondInit", [], create2Salt, ethTxOptions); @@ -327,7 +544,7 @@ export class Deployer { console.log(`CONTRACTS_DIAMOND_INIT_ADDR=${contractAddress}`); } - this.addresses.ZkSync.DiamondInit = contractAddress; + this.addresses.StateTransition.DiamondInit = contractAddress; } public async deployDefaultUpgrade(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { @@ -338,66 +555,177 @@ export class Deployer { console.log(`CONTRACTS_DEFAULT_UPGRADE_ADDR=${contractAddress}`); } - this.addresses.ZkSync.DefaultUpgrade = contractAddress; + this.addresses.StateTransition.DefaultUpgrade = contractAddress; } - public async deployDiamondProxy(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + public async deployGenesisUpgrade(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { ethTxOptions.gasLimit ??= 10_000_000; - - const chainId = getNumberFromEnv("ETH_CLIENT_CHAIN_ID"); - const initialDiamondCut = await this.initialProxyDiamondCut(); - const contractAddress = await this.deployViaCreate2( - "DiamondProxy", - [chainId, initialDiamondCut], - create2Salt, - ethTxOptions - ); + const contractAddress = await this.deployViaCreate2("GenesisUpgrade", [], create2Salt, ethTxOptions); if (this.verbose) { - console.log(`CONTRACTS_DIAMOND_PROXY_ADDR=${contractAddress}`); + console.log(`CONTRACTS_GENESIS_UPGRADE_ADDR=${contractAddress}`); } - this.addresses.ZkSync.DiamondProxy = contractAddress; + this.addresses.StateTransition.GenesisUpgrade = contractAddress; + } + + public async deployBridgehubContract(create2Salt: string, gasPrice?: BigNumberish, nonce?) { + nonce = nonce ? parseInt(nonce) : await this.deployWallet.getTransactionCount(); + + await this.deployBridgehubImplementation(create2Salt, { gasPrice, nonce }); + await this.deployBridgehubProxy(create2Salt, { gasPrice }); + } + + public async deployStateTransitionManagerContract( + create2Salt: string, + extraFacets?: FacetCut[], + gasPrice?: BigNumberish, + nonce? + ) { + nonce = nonce ? parseInt(nonce) : await this.deployWallet.getTransactionCount(); + + await this.deployStateTransitionDiamondFacets(create2Salt, gasPrice, nonce); + await this.deployStateTransitionManagerImplementation(create2Salt, { gasPrice }); + await this.deployStateTransitionManagerProxy(create2Salt, { gasPrice }, extraFacets); + await this.registerStateTransitionManager(); } - public async deployZkSyncContract(create2Salt: string, gasPrice?: BigNumberish, nonce?) { + public async deployStateTransitionDiamondFacets(create2Salt: string, gasPrice?: BigNumberish, nonce?) { nonce = nonce ? parseInt(nonce) : await this.deployWallet.getTransactionCount(); - // deploy zkSync contract - const independentZkSyncDeployPromises = [ - this.deployMailboxFacet(create2Salt, { gasPrice, nonce }), - this.deployExecutorFacet(create2Salt, { gasPrice, nonce: nonce + 1 }), - this.deployAdminFacet(create2Salt, { gasPrice, nonce: nonce + 2 }), - this.deployGettersFacet(create2Salt, { gasPrice, nonce: nonce + 3 }), - this.deployDiamondInit(create2Salt, { gasPrice, nonce: nonce + 4 }), - ]; - await Promise.all(independentZkSyncDeployPromises); - nonce += 5; + await this.deployExecutorFacet(create2Salt, { gasPrice, nonce: nonce }); + await this.deployAdminFacet(create2Salt, { gasPrice, nonce: nonce + 1 }); + await this.deployMailboxFacet(create2Salt, { gasPrice, nonce: nonce + 2 }); + await this.deployGettersFacet(create2Salt, { gasPrice, nonce: nonce + 3 }); + await this.deployStateTransitionDiamondInit(create2Salt, { gasPrice, nonce: nonce + 4 }); + } + + public async registerStateTransitionManager() { + const bridgehub = this.bridgehubContract(this.deployWallet); - await this.deployDiamondProxy(create2Salt, { gasPrice, nonce }); + const tx = await bridgehub.addStateTransitionManager(this.addresses.StateTransition.StateTransitionProxy); + + const receipt = await tx.wait(); + if (this.verbose) { + console.log(`StateTransition System registered, gas used: ${receipt.gasUsed.toString()}`); + } } - public async deployBridgeContracts(create2Salt: string, gasPrice?: BigNumberish, nonce?) { + public async registerHyperchain( + baseTokenAddress: string, + validiumMode: boolean, + extraFacets?: FacetCut[], + gasPrice?: BigNumberish, + nonce?, + predefinedChainId?: string + ) { + const gasLimit = 10_000_000; + nonce = nonce ? parseInt(nonce) : await this.deployWallet.getTransactionCount(); - await this.deployERC20BridgeImplementation(create2Salt, { gasPrice, nonce: nonce }); - await this.deployERC20BridgeProxy(create2Salt, { gasPrice, nonce: nonce + 1 }); + const bridgehub = this.bridgehubContract(this.deployWallet); + const stateTransitionManager = this.stateTransitionManagerContract(this.deployWallet); + + const inputChainId = predefinedChainId || getNumberFromEnv("CHAIN_ETH_ZKSYNC_NETWORK_ID"); + const admin = process.env.CHAIN_ADMIN_ADDRESS || this.ownerAddress; + const diamondCutData = await this.initialZkSyncStateTransitionDiamondCut(extraFacets); + const initialDiamondCut = new ethers.utils.AbiCoder().encode([DIAMOND_CUT_DATA_ABI_STRING], [diamondCutData]); + + const tx = await bridgehub.createNewChain( + inputChainId, + this.addresses.StateTransition.StateTransitionProxy, + baseTokenAddress, + Date.now(), + admin, + initialDiamondCut, + { + gasPrice, + nonce, + gasLimit, + } + ); + const receipt = await tx.wait(); + const chainId = receipt.logs.find((log) => log.topics[0] == bridgehub.interface.getEventTopic("NewChain")) + .topics[1]; + + nonce++; + + this.addresses.BaseToken = baseTokenAddress; + + if (this.verbose) { + console.log(`Hyperchain registered, gas used: ${receipt.gasUsed.toString()} and ${receipt.gasUsed.toString()}`); + console.log(`Hyperchain registration tx hash: ${receipt.transactionHash}`); + + console.log(`CHAIN_ETH_ZKSYNC_NETWORK_ID=${parseInt(chainId, 16)}`); + + console.log(`CONTRACTS_BASE_TOKEN_ADDR=${baseTokenAddress}`); + } + if (!predefinedChainId) { + const diamondProxyAddress = + "0x" + + receipt.logs + .find((log) => log.topics[0] == stateTransitionManager.interface.getEventTopic("StateTransitionNewChain")) + .topics[2].slice(26); + this.addresses.StateTransition.DiamondProxy = diamondProxyAddress; + if (this.verbose) { + console.log(`CONTRACTS_DIAMOND_PROXY_ADDR=${diamondProxyAddress}`); + } + } + this.chainId = parseInt(chainId, 16); + + const validatorAddress = getAddressFromEnv("ETH_SENDER_SENDER_OPERATOR_COMMIT_ETH_ADDR"); + const validatorTimelock = this.validatorTimelock(this.deployWallet); + const tx2 = await validatorTimelock.addValidator(chainId, validatorAddress, { + gasPrice, + nonce, + gasLimit, + }); + const receipt2 = await tx2.wait(); + if (this.verbose) { + console.log(`Validator registered, gas used: ${receipt2.gasUsed.toString()}`); + } + + const diamondProxy = this.stateTransitionContract(this.deployWallet); + const tx3 = await diamondProxy.setTokenMultiplier(1, 1); + const receipt3 = await tx3.wait(); + if (this.verbose) { + console.log(`BaseTokenMultiplier set, gas used: ${receipt3.gasUsed.toString()}`); + } + + if (validiumMode) { + const tx4 = await diamondProxy.setValidiumMode(PubdataPricingMode.Validium); + const receipt4 = await tx4.wait(); + if (this.verbose) { + console.log(`Validium mode set, gas used: ${receipt4.gasUsed.toString()}`); + } + } } - public async deployWethBridgeContracts(create2Salt: string, gasPrice?: BigNumberish, nonce?) { + public async registerToken(tokenAddress: string) { + const bridgehub = this.bridgehubContract(this.deployWallet); + // kl todo change 1 to general variable. + const tx = await bridgehub.addToken(tokenAddress); + + const receipt = await tx.wait(); + if (this.verbose) { + console.log(`Token ${tokenAddress} was registered, gas used: ${receipt.gasUsed.toString()}`); + } + } + + public async deploySharedBridgeContracts(create2Salt: string, gasPrice?: BigNumberish, nonce?) { nonce = nonce ? parseInt(nonce) : await this.deployWallet.getTransactionCount(); - await this.deployWethBridgeImplementation(create2Salt, { gasPrice, nonce: nonce++ }); - await this.deployWethBridgeProxy(create2Salt, { gasPrice, nonce: nonce++ }); + await this.deploySharedBridgeImplementation(create2Salt, { gasPrice, nonce: nonce }); + await this.deploySharedBridgeProxy(create2Salt, { gasPrice, nonce: nonce + 1 }); + await this.registerSharedBridge({ gasPrice, nonce: nonce + 2 }); } public async deployValidatorTimelock(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { ethTxOptions.gasLimit ??= 10_000_000; const executionDelay = getNumberFromEnv("CONTRACTS_VALIDATOR_TIMELOCK_EXECUTION_DELAY"); - const validatorAddress = getAddressFromEnv("ETH_SENDER_SENDER_OPERATOR_COMMIT_ETH_ADDR"); const contractAddress = await this.deployViaCreate2( "ValidatorTimelock", - [this.ownerAddress, this.addresses.ZkSync.DiamondProxy, executionDelay, [validatorAddress]], + [this.ownerAddress, executionDelay], create2Salt, ethTxOptions ); @@ -405,10 +733,21 @@ export class Deployer { if (this.verbose) { console.log(`CONTRACTS_VALIDATOR_TIMELOCK_ADDR=${contractAddress}`); } - this.addresses.ValidatorTimeLock = contractAddress; } + public async setStateTransitionManagerInValidatorTimelock(ethTxOptions: ethers.providers.TransactionRequest) { + const validatorTimelock = this.validatorTimelock(this.deployWallet); + const tx = await validatorTimelock.setStateTransitionManager( + this.addresses.StateTransition.StateTransitionProxy, + ethTxOptions + ); + const receipt = await tx.wait(); + if (this.verbose) { + console.log(`StateTransitionManager was set in ValidatorTimelock, gas used: ${receipt.gasUsed.toString()}`); + } + } + public async deployMulticall3(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { ethTxOptions.gasLimit ??= 10_000_000; const contractAddress = await this.deployViaCreate2("Multicall3", [], create2Salt, ethTxOptions); @@ -434,7 +773,7 @@ export class Deployer { ); if (this.verbose) { - console.log(`BLOB_VERSIONED_HASH_RETRIEVER_ADDR=${contractAddress}`); + console.log(`CONTRACTS_BLOB_VERSIONED_HASH_RETRIEVER_ADDR=${contractAddress}`); } this.addresses.BlobVersionedHashRetriever = contractAddress; @@ -448,23 +787,38 @@ export class Deployer { return SingletonFactoryFactory.connect(this.addresses.Create2Factory, signerOrProvider); } - public governanceContract(signerOrProvider: Signer | providers.Provider) { - return IGovernanceFactory.connect(this.addresses.Governance, signerOrProvider); + public bridgehubContract(signerOrProvider: Signer | providers.Provider) { + return IBridgehubFactory.connect(this.addresses.Bridgehub.BridgehubProxy, signerOrProvider); + } + + public stateTransitionManagerContract(signerOrProvider: Signer | providers.Provider) { + return IStateTransitionManagerFactory.connect( + this.addresses.StateTransition.StateTransitionProxy, + signerOrProvider + ); } - public zkSyncContract(signerOrProvider: Signer | providers.Provider) { - return IZkSyncFactory.connect(this.addresses.ZkSync.DiamondProxy, signerOrProvider); + public stateTransitionContract(signerOrProvider: Signer | providers.Provider) { + return IZkSyncStateTransitionFactory.connect(this.addresses.StateTransition.DiamondProxy, signerOrProvider); + } + + public governanceContract(signerOrProvider: Signer | providers.Provider) { + return IGovernanceFactory.connect(this.addresses.Governance, signerOrProvider); } public validatorTimelock(signerOrProvider: Signer | providers.Provider) { return ValidatorTimelockFactory.connect(this.addresses.ValidatorTimeLock, signerOrProvider); } - public defaultERC20Bridge(signerOrProvider: Signer | providers.Provider) { - return L1ERC20BridgeFactory.connect(this.addresses.Bridges.ERC20BridgeProxy, signerOrProvider); + public defaultSharedBridge(signerOrProvider: Signer | providers.Provider) { + return L1SharedBridgeFactory.connect(this.addresses.Bridges.SharedBridgeProxy, signerOrProvider); + } + + public baseTokenContract(signerOrProvider: Signer | providers.Provider) { + return ERC20Factory.connect(this.addresses.BaseToken, signerOrProvider); } - public defaultWethBridge(signerOrProvider: Signer | providers.Provider) { - return L1WethBridgeFactory.connect(this.addresses.Bridges.WethBridgeProxy, signerOrProvider); + public proxyAdminContract(signerOrProvider: Signer | providers.Provider) { + return ProxyAdminFactory.connect(this.addresses.TransparentProxyAdmin, signerOrProvider); } } diff --git a/l1-contracts/src.ts/diamondCut.ts b/l1-contracts/src.ts/diamondCut.ts index 564a3e53febc..1819f56598ee 100644 --- a/l1-contracts/src.ts/diamondCut.ts +++ b/l1-contracts/src.ts/diamondCut.ts @@ -1,10 +1,10 @@ import * as hardhat from "hardhat"; import type { Interface } from "ethers/lib/utils"; import "@nomiclabs/hardhat-ethers"; -import type { Wallet } from "ethers"; +import type { Wallet, BigNumberish } from "ethers"; import { ethers } from "ethers"; -import { IZkSyncFactory } from "../typechain/IZkSyncFactory"; -import { IBaseFactory } from "../typechain/IBaseFactory"; +import { IZkSyncStateTransitionFactory } from "../typechain/IZkSyncStateTransitionFactory"; +import { IZkSyncStateTransitionBaseFactory } from "../typechain/IZkSyncStateTransitionBaseFactory"; // Some of the facets are to be removed with the upcoming upgrade. const UNCONDITIONALLY_REMOVED_FACETS = ["DiamondCutFacet", "GovernanceFacet"]; @@ -28,6 +28,19 @@ export interface DiamondCut { initCalldata: string; } +export interface InitializeData { + bridgehub: BigNumberish; + verifier: BigNumberish; + admin: BigNumberish; + genesisBatchHash: string; + genesisIndexRepeatedStorageChanges: BigNumberish; + genesisBatchCommitment: string; + allowList: BigNumberish; + l2BootloaderBytecodeHash: string; + l2DefaultAccountBytecodeHash: string; + priorityTxMaxGasLimit: BigNumberish; +} + export function facetCut(address: string, contract: Interface, action: Action, isFreezable: boolean): FacetCut { return { facet: address, @@ -61,7 +74,7 @@ export async function getCurrentFacetCutsForAdd( ) { const facetsCuts = {}; // Some facets should always be available regardless of freezing: upgradability system, getters, etc. - // And for some facets there are should be possibility to freeze them by the governor if we found a bug inside. + // And for some facets there are should be possibility to freeze them by the admin if we found a bug inside. if (adminAddress) { // Should be unfreezable. The function to unfreeze contract is located on the admin facet. // That means if the admin facet will be freezable, the proxy can NEVER be unfrozen. @@ -74,6 +87,7 @@ export async function getCurrentFacetCutsForAdd( facetsCuts["GettersFacet"] = facetCut(getters.address, getters.interface, Action.Add, false); } // These contracts implement the logic without which we can get out of the freeze. + // These contracts implement the logic without which we can get out of the freeze. if (mailboxAddress) { const mailbox = await hardhat.ethers.getContractAt("MailboxFacet", mailboxAddress); facetsCuts["MailboxFacet"] = facetCut(mailbox.address, mailbox.interface, Action.Add, true); @@ -87,12 +101,12 @@ export async function getCurrentFacetCutsForAdd( } export async function getDeployedFacetCutsForRemove(wallet: Wallet, zkSyncAddress: string, updatedFaceNames: string[]) { - const mainContract = IZkSyncFactory.connect(zkSyncAddress, wallet); + const mainContract = IZkSyncStateTransitionFactory.connect(zkSyncAddress, wallet); const diamondCutFacets = await mainContract.facets(); // We don't care about freezing, because we are removing the facets. const result = []; for (const { addr, selectors } of diamondCutFacets) { - const facet = IBaseFactory.connect(addr, wallet); + const facet = IZkSyncStateTransitionBaseFactory.connect(addr, wallet); const facetName = await facet.getName(); if (updatedFaceNames.includes(facetName)) { result.push({ @@ -103,6 +117,7 @@ export async function getDeployedFacetCutsForRemove(wallet: Wallet, zkSyncAddres }); } } + return result; } @@ -112,10 +127,14 @@ export async function getFacetCutsForUpgrade( adminAddress: string, gettersAddress: string, mailboxAddress: string, - executorAddress: string + executorAddress: string, + namesOfFacetsToBeRemoved?: string[] ) { const newFacetCuts = await getCurrentFacetCutsForAdd(adminAddress, gettersAddress, mailboxAddress, executorAddress); - const namesOfFacetsToBeRemoved = [...UNCONDITIONALLY_REMOVED_FACETS, ...Object.keys(newFacetCuts)]; + namesOfFacetsToBeRemoved = namesOfFacetsToBeRemoved || [ + ...UNCONDITIONALLY_REMOVED_FACETS, + ...Object.keys(newFacetCuts), + ]; const oldFacetCuts = await getDeployedFacetCutsForRemove(wallet, zkSyncAddress, namesOfFacetsToBeRemoved); return [...oldFacetCuts, ...Object.values(newFacetCuts)]; } diff --git a/l1-contracts/src.ts/hyperchain-upgrade.ts b/l1-contracts/src.ts/hyperchain-upgrade.ts new file mode 100644 index 000000000000..9934a021134f --- /dev/null +++ b/l1-contracts/src.ts/hyperchain-upgrade.ts @@ -0,0 +1,225 @@ +// hardhat import should be the first import in the file +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import * as hardhat from "hardhat"; + +import "@nomiclabs/hardhat-ethers"; + +import type { BigNumberish } from "ethers"; +import { BigNumber, ethers } from "ethers"; + +import type { DiamondCut } from "./diamondCut"; +import { getFacetCutsForUpgrade } from "./diamondCut"; + +import { getTokens } from "./deploy-token"; +import { EraLegacyChainId } from "./deploy"; +import type { Deployer } from "./deploy"; + +import { Interface } from "ethers/lib/utils"; +import type { L2CanonicalTransaction, ProposedUpgrade, VerifierParams } from "./utils"; + +import { REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT } from "zksync-web3/build/src/utils"; +import { ETH_ADDRESS_IN_CONTRACTS } from "zksync-ethers/build/src/utils"; + +const SYSTEM_UPGRADE_TX_TYPE = 254; +const FORCE_DEPLOYER_ADDRESS = "0x0000000000000000000000000000000000008007"; +// const CONTRACT_DEPLOYER_ADDRESS = "0x0000000000000000000000000000000000008006"; +// const COMPLEX_UPGRADE_ADDRESS = "0x000000000000000000000000000000000000800f"; + +export async function upgradeToHyperchains( + deployer: Deployer, + gasPrice: BigNumberish, + create2Salt?: string, + nonce?: number +) { + // does not interfere with existing system + if (deployer.verbose) { + console.log("Deploying new contracts"); + } + await deployNewContracts(deployer, gasPrice, create2Salt, nonce); + + // upgrading system contracts on Era only adds setChainId in systemContext, does not interfere with anything + // we first upgrade the DiamondProxy. the Mailbox is backwards compatible, so the L1ERC20 and other bridges should still work. + // but this requires the sharedBridge to be deployed. + // kl to: (is this needed?) disable shared bridge deposits until L2Bridge is upgraded. + if (deployer.verbose) { + console.log("Integrating Era into Bridgehub and upgrading L2 system contract"); + } + await integrateEraIntoBridgehubAndUpgradeL2SystemContract(deployer, gasPrice); + + // the L2Bridge and L1ERC20Bridge should be updated relatively in sync, as new messages might not be parsed correctly by the old bridge. + // however new bridges can parse old messages. L1->L2 messages are faster, so L2 side is upgraded first. + // until we integrate Era into the Bridgehub, txs will not work. + if (deployer.verbose) { + console.log("Upgrading L2 bridge"); + } + await upgradeL2Bridge(deployer); + // kl todo add both bridge address to L2Bridge, so that it can receive txs from both bridges + // kl todo: enable L1SharedBridge deposits if disabled. + if (deployer.verbose) { + console.log("Upgrading L1 ERC20 bridge"); + } + await upgradeL1ERC20Bridge(deployer); + // // note, withdrawals will not work until this step, but deposits will + if (deployer.verbose) { + console.log("Migrating assets from L1 ERC20 bridge and ChainBalance"); + } + await migrateAssets(deployer); +} + +async function deployNewContracts(deployer: Deployer, gasPrice: BigNumberish, create2Salt?: string, nonce?: number) { + nonce = nonce || (await deployer.deployWallet.getTransactionCount()); + create2Salt = create2Salt || ethers.utils.hexlify(ethers.utils.randomBytes(32)); + + // Create2 factory already deployed + + await deployer.deployGenesisUpgrade(create2Salt, { + gasPrice, + nonce, + }); + nonce++; + + await deployer.deployValidatorTimelock(create2Salt, { gasPrice, nonce }); + nonce++; + + // kl todo check if this needs to be redeployed + await deployer.deployDefaultUpgrade(create2Salt, { + gasPrice, + nonce, + }); + nonce++; + + // kl todo: we will need to deploy the proxyAdmin on mainnet, here it is already deployed + // await deployer.deployTransparentProxyAdmin(create2Salt, { gasPrice }); + await deployer.deployBridgehubContract(create2Salt, gasPrice); + + await deployer.deployStateTransitionManagerContract(create2Salt, [], gasPrice); + await deployer.setStateTransitionManagerInValidatorTimelock({ gasPrice }); + + await deployer.deploySharedBridgeContracts(create2Salt, gasPrice); + await deployer.deployERC20BridgeImplementation(create2Salt, { gasPrice }); +} + +async function integrateEraIntoBridgehubAndUpgradeL2SystemContract(deployer: Deployer, gasPrice: BigNumberish) { + // era facet cut + const defaultUpgrade = new Interface(hardhat.artifacts.readArtifactSync("DefaultUpgrade").abi); + const newProtocolVersion = 24; + const toAddress: string = ethers.constants.AddressZero; + const calldata: string = ethers.constants.HashZero; + const l2ProtocolUpgradeTx: L2CanonicalTransaction = { + txType: SYSTEM_UPGRADE_TX_TYPE, + from: FORCE_DEPLOYER_ADDRESS, + to: toAddress, + gasLimit: 72_000_000, + gasPerPubdataByteLimit: REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, + maxFeePerGas: 0, + maxPriorityFeePerGas: 0, + paymaster: 0, + nonce: newProtocolVersion, + value: 0, + reserved: [0, 0, 0, 0], + data: calldata, + signature: "0x", + factoryDeps: [], + paymasterInput: "0x", + reservedDynamic: "0x", + }; + const upgradeTimestamp = BigNumber.from(100); + const verifierParams: VerifierParams = { + recursionNodeLevelVkHash: ethers.constants.HashZero, + recursionLeafLevelVkHash: ethers.constants.HashZero, + recursionCircuitsSetVksHash: ethers.constants.HashZero, + }; + const proposedUpgrade: ProposedUpgrade = { + l2ProtocolUpgradeTx, + factoryDeps: [], + bootloaderHash: ethers.constants.HashZero, + defaultAccountHash: ethers.constants.HashZero, + verifier: ethers.constants.AddressZero, + verifierParams: verifierParams, + l1ContractsUpgradeCalldata: ethers.constants.HashZero, + postUpgradeCalldata: ethers.constants.HashZero, + upgradeTimestamp: upgradeTimestamp, + newProtocolVersion: 24, + }; + const defaultUpgradeData = defaultUpgrade.encodeFunctionData("upgrade", [proposedUpgrade]); + + const facetCuts = await getFacetCutsForUpgrade( + deployer.deployWallet, + deployer.addresses.StateTransition.DiamondProxy, + deployer.addresses.StateTransition.AdminFacet, + deployer.addresses.StateTransition.GettersFacet, + deployer.addresses.StateTransition.MailboxFacet, + deployer.addresses.StateTransition.ExecutorFacet + ); //.concat(extraFacets ?? []); + const diamondCut: DiamondCut = { + facetCuts, + initAddress: deployer.addresses.StateTransition.DefaultUpgrade, + initCalldata: defaultUpgradeData, + }; + // console.log('kl todo', facetCuts) + const adminFacet = new Interface(hardhat.artifacts.readArtifactSync("DummyAdminFacet").abi); + // to test this remove modifier from executeUpgrade + const data = adminFacet.encodeFunctionData("executeUpgrade2", [diamondCut]); // kl todo calldata might not be "0x" + await deployer.executeUpgrade(deployer.addresses.StateTransition.DiamondProxy, 0, data); + + // register Era in Bridgehub, STM + const stateTrasitionManager = deployer.stateTransitionManagerContract(deployer.deployWallet); + + const tx0 = await stateTrasitionManager.registerAlreadyDeployedStateTransition( + EraLegacyChainId, + deployer.addresses.StateTransition.DiamondProxy + ); + await tx0.wait(); + const bridgehub = deployer.bridgehubContract(deployer.deployWallet); + const tx = await bridgehub.createNewChain( + EraLegacyChainId, + deployer.addresses.StateTransition.StateTransitionProxy, + ETH_ADDRESS_IN_CONTRACTS, + ethers.constants.HashZero, + deployer.addresses.Governance, + ethers.constants.HashZero, + { gasPrice } + ); + + await tx.wait(); +} + +async function upgradeL2Bridge(deployer: Deployer) { + // upgrade L2 bridge contract, we do this directly via the L2 + // set initializeChainGovernance in L1SharedBridge + deployer; +} + +async function upgradeL1ERC20Bridge(deployer: Deployer) { + // upgrade old contracts + await deployer.upgradeL1ERC20Bridge(true); +} + +async function migrateAssets(deployer: Deployer) { + // migrate assets from L1 ERC20 bridge + if (deployer.verbose) { + console.log("transferring Eth"); + } + const sharedBridge = deployer.defaultSharedBridge(deployer.deployWallet); + const ethTransferData = sharedBridge.interface.encodeFunctionData("transferFundsFromLegacy", [ + ETH_ADDRESS_IN_CONTRACTS, + deployer.addresses.StateTransition.DiamondProxy, + deployer.chainId, + ]); + ethTransferData; + // await deployer.executeUpgrade(deployer.addresses.Bridges.SharedBridgeProxy, 0, ethTransferData); + + if (deployer.verbose) { + console.log("transferring Dai"); + } + + const tokens = getTokens(); + const altTokenAddress = tokens.find((token: { symbol: string }) => token.symbol == "DAI")!.address; + const daiTransferData = sharedBridge.interface.encodeFunctionData("transferFundsFromLegacy", [ + altTokenAddress, + deployer.addresses.Bridges.ERC20BridgeProxy, + deployer.chainId, + ]); + daiTransferData; + // await deployer.executeUpgrade(deployer.addresses.Bridges.SharedBridgeProxy, 0, daiTransferData); +} diff --git a/l1-contracts/src.ts/utils.ts b/l1-contracts/src.ts/utils.ts new file mode 100644 index 000000000000..191ca915cf93 --- /dev/null +++ b/l1-contracts/src.ts/utils.ts @@ -0,0 +1,175 @@ +// hardhat import should be the first import in the file +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import * as hardhat from "hardhat"; + +import type { BytesLike, BigNumberish } from "ethers"; +import { ethers } from "ethers"; +import * as fs from "fs"; +import * as path from "path"; + +export const testConfigPath = process.env.ZKSYNC_ENV + ? path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant") + : "./test/test_config/constant"; +export const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); + +// eslint-disable-next-line @typescript-eslint/no-var-requires +export const REQUIRED_L2_GAS_PRICE_PER_PUBDATA = require("../../SystemConfig.json").REQUIRED_L2_GAS_PRICE_PER_PUBDATA; + +export const SYSTEM_UPGRADE_L2_TX_TYPE = 254; +export const ADDRESS_ONE = "0x0000000000000000000000000000000000000001"; +export const L1_TO_L2_ALIAS_OFFSET = "0x1111000000000000000000000000000000001111"; +const CREATE2_PREFIX = ethers.utils.solidityKeccak256(["string"], ["zksyncCreate2"]); + +const ADDRESS_MODULO = ethers.BigNumber.from(2).pow(160); +export const DIAMOND_CUT_DATA_ABI_STRING = + "tuple(tuple(address facet, uint8 action, bool isFreezable, bytes4[] selectors)[] facetCuts, address initAddress, bytes initCalldata)"; + +export function applyL1ToL2Alias(address: string): string { + return ethers.utils.hexlify(ethers.BigNumber.from(address).add(L1_TO_L2_ALIAS_OFFSET).mod(ADDRESS_MODULO)); +} + +export function readBytecode(path: string, fileName: string) { + return JSON.parse(fs.readFileSync(`${path}/${fileName}.sol/${fileName}.json`, { encoding: "utf-8" })).bytecode; +} + +export function readInterface(path: string, fileName: string) { + const abi = JSON.parse(fs.readFileSync(`${path}/${fileName}.sol/${fileName}.json`, { encoding: "utf-8" })).abi; + return new ethers.utils.Interface(abi); +} + +export function hashL2Bytecode(bytecode: ethers.BytesLike): Uint8Array { + // For getting the consistent length we first convert the bytecode to UInt8Array + const bytecodeAsArray = ethers.utils.arrayify(bytecode); + + if (bytecodeAsArray.length % 32 != 0) { + throw new Error("The bytecode length in bytes must be divisible by 32"); + } + + const hashStr = ethers.utils.sha256(bytecodeAsArray); + const hash = ethers.utils.arrayify(hashStr); + + // Note that the length of the bytecode + // should be provided in 32-byte words. + const bytecodeLengthInWords = bytecodeAsArray.length / 32; + if (bytecodeLengthInWords % 2 == 0) { + throw new Error("Bytecode length in 32-byte words must be odd"); + } + const bytecodeLength = ethers.utils.arrayify(bytecodeAsArray.length / 32); + if (bytecodeLength.length > 2) { + throw new Error("Bytecode length must be less than 2^16 bytes"); + } + // The bytecode should always take the first 2 bytes of the bytecode hash, + // so we pad it from the left in case the length is smaller than 2 bytes. + const bytecodeLengthPadded = ethers.utils.zeroPad(bytecodeLength, 2); + + const codeHashVersion = new Uint8Array([1, 0]); + hash.set(codeHashVersion, 0); + hash.set(bytecodeLengthPadded, 2); + + return hash; +} + +export function computeL2Create2Address( + deployWallet: string, + bytecode: BytesLike, + constructorInput: BytesLike, + create2Salt: BytesLike +) { + const senderBytes = ethers.utils.hexZeroPad(deployWallet, 32); + const bytecodeHash = hashL2Bytecode(bytecode); + + const constructorInputHash = ethers.utils.keccak256(constructorInput); + + const data = ethers.utils.keccak256( + ethers.utils.concat([CREATE2_PREFIX, senderBytes, create2Salt, bytecodeHash, constructorInputHash]) + ); + + return ethers.utils.hexDataSlice(data, 12); +} + +export function getAddressFromEnv(envName: string): string { + const address = process.env[envName]; + if (!/^0x[a-fA-F0-9]{40}$/.test(address)) { + throw new Error(`Incorrect address format hash in ${envName} env: ${address}`); + } + return address; +} + +export function getHashFromEnv(envName: string): string { + const hash = process.env[envName]; + if (!/^0x[a-fA-F0-9]{64}$/.test(hash)) { + throw new Error(`Incorrect hash format hash in ${envName} env: ${hash}`); + } + return hash; +} + +export function getNumberFromEnv(envName: string): string { + const number = process.env[envName]; + if (!/^([1-9]\d*|0)$/.test(number)) { + throw new Error(`Incorrect number format number in ${envName} env: ${number}`); + } + return number; +} + +export enum PubdataPricingMode { + Rollup, + Validium, +} + +export interface FeeParams { + pubdataPricingMode: PubdataPricingMode; + batchOverheadL1Gas: number; + maxPubdataPerBatch: number; + maxL2GasPerBatch: number; + priorityTxMaxPubdata: number; + minimalL2GasPrice: BigNumberish; +} + +export interface ProposedUpgrade { + // The tx for the upgrade call to the l2 system upgrade contract + l2ProtocolUpgradeTx: L2CanonicalTransaction; + factoryDeps: BytesLike[]; + bootloaderHash: BytesLike; + defaultAccountHash: BytesLike; + verifier: string; + verifierParams: VerifierParams; + l1ContractsUpgradeCalldata: BytesLike; + postUpgradeCalldata: BytesLike; + upgradeTimestamp: ethers.BigNumber; + newProtocolVersion: BigNumberish; +} + +export interface VerifierParams { + recursionNodeLevelVkHash: BytesLike; + recursionLeafLevelVkHash: BytesLike; + recursionCircuitsSetVksHash: BytesLike; +} + +export interface L2CanonicalTransaction { + txType: BigNumberish; + from: BigNumberish; + to: BigNumberish; + gasLimit: BigNumberish; + gasPerPubdataByteLimit: BigNumberish; + maxFeePerGas: BigNumberish; + maxPriorityFeePerGas: BigNumberish; + paymaster: BigNumberish; + nonce: BigNumberish; + value: BigNumberish; + // In the future, we might want to add some + // new fields to the struct. The `txData` struct + // is to be passed to account and any changes to its structure + // would mean a breaking change to these accounts. In order to prevent this, + // we should keep some fields as "reserved". + // It is also recommended that their length is fixed, since + // it would allow easier proof integration (in case we will need + // some special circuit for preprocessing transactions). + reserved: [BigNumberish, BigNumberish, BigNumberish, BigNumberish]; + data: BytesLike; + signature: BytesLike; + factoryDeps: BigNumberish[]; + paymasterInput: BytesLike; + // Reserved dynamic type for the future use-case. Using it should be avoided, + // But it is still here, just in case we want to enable some additional functionality. + reservedDynamic: BytesLike; +} diff --git a/l1-contracts/test/foundry/unit/concrete/AddressAliasHelper/_AddressAliasHelper_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/AddressAliasHelper/_AddressAliasHelper_Shared.t.sol new file mode 100644 index 000000000000..034baeb10eb7 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/AddressAliasHelper/_AddressAliasHelper_Shared.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {AddressAliasHelperTest} from "solpp/dev-contracts/test/AddressAliasHelperTest.sol"; + +contract AddressAliasHelperSharedTest is Test { + AddressAliasHelperTest addressAliasHelper; + + function setUp() public { + addressAliasHelper = new AddressAliasHelperTest(); + } + + // add this to be excluded from coverage report + function test() internal virtual {} +} diff --git a/l1-contracts/test/foundry/unit/concrete/AddressAliasHelper/applyL1ToL2Alias.t.sol b/l1-contracts/test/foundry/unit/concrete/AddressAliasHelper/applyL1ToL2Alias.t.sol new file mode 100644 index 000000000000..8916b46aa918 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/AddressAliasHelper/applyL1ToL2Alias.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {AddressAliasHelperSharedTest} from "./_AddressAliasHelper_Shared.t.sol"; + +contract applyL1ToL2AliasTest is AddressAliasHelperSharedTest { + function testL1toL2AddressConversion() public { + address[2] memory l1Addresses = [ + 0xEEeEfFfffffFffFFFFffFFffFfFfFfffFfFFEEeE, + 0x0000000000000000000000000000081759a874B3 + ]; + address[2] memory l2ExpectedAddresses = [ + 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF, + 0x1111000000000000000000000000081759a885c4 + ]; + + for (uint i; i < l1Addresses.length; i++) { + address l2Address = addressAliasHelper.applyL1ToL2Alias(l1Addresses[i]); + + assertEq(l2Address, l2ExpectedAddresses[i], "L1 to L2 address conversion is not correct"); + } + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/AddressAliasHelper/undoL1ToL2Alias.t.sol b/l1-contracts/test/foundry/unit/concrete/AddressAliasHelper/undoL1ToL2Alias.t.sol new file mode 100644 index 000000000000..1833acf107e0 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/AddressAliasHelper/undoL1ToL2Alias.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {AddressAliasHelperSharedTest} from "./_AddressAliasHelper_Shared.t.sol"; + +contract undoL1ToL2AliasTest is AddressAliasHelperSharedTest { + function testL2toL1AddressConversion() public { + address[2] memory l2Addresses = [ + 0x1111000000000000000000000000000000001110, + 0x1111000000000000000000000000081759a885c4 + ]; + address[2] memory l1ExpectedAddresses = [ + 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF, + 0x0000000000000000000000000000081759a874B3 + ]; + + for (uint i; i < l2Addresses.length; i++) { + address l1Address = addressAliasHelper.undoL1ToL2Alias(l2Addresses[i]); + + assertEq(l1Address, l1ExpectedAddresses[i], "L2 to L1 address conversion is not correct"); + } + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Admin/Authorization.t.sol b/l1-contracts/test/foundry/unit/concrete/Admin/Authorization.t.sol deleted file mode 100644 index 3a6343f7c051..000000000000 --- a/l1-contracts/test/foundry/unit/concrete/Admin/Authorization.t.sol +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -import {AdminTest} from "./_Admin_Shared.t.sol"; -import {FeeParams, PubdataPricingMode} from "solpp/zksync/Storage.sol"; - -contract AuthorizationTest is AdminTest { - function test_SetPendingAdmin_RevertWhen_AdminNotGovernanceOwner() public { - address newAdmin = address(0x1337); - vm.prank(owner); - vm.expectRevert(bytes.concat("1g")); - proxyAsAdmin.setPendingAdmin(newAdmin); - } - - function test_changeFeeParams() public { - FeeParams memory newParams = FeeParams({ - pubdataPricingMode: PubdataPricingMode.Rollup, - batchOverheadL1Gas: 1_000, - maxPubdataPerBatch: 1_000, - maxL2GasPerBatch: 80_000_000, - priorityTxMaxPubdata: 99, - minimalL2GasPrice: 500_000_000 - }); - vm.prank(governor); - proxyAsAdmin.changeFeeParams(newParams); - - bytes32 correctNewFeeParamsHash = keccak256(abi.encode(newParams)); - bytes32 currentFeeParamsHash = keccak256(abi.encode(proxyAsGettersMock.getFeeParams())); - - require(currentFeeParamsHash == correctNewFeeParamsHash, "Fee params were not changed correctly"); - } - - function test_changeFeeParams_RevertWhen_PriorityTxMaxPubdataHigherThanMaxPubdataPerBatch() public { - FeeParams memory newParams = FeeParams({ - pubdataPricingMode: PubdataPricingMode.Rollup, - batchOverheadL1Gas: 1_000, - maxPubdataPerBatch: 1_000, - maxL2GasPerBatch: 80_000_000, - priorityTxMaxPubdata: 1_001, - minimalL2GasPrice: 500_000_000 - }); - vm.prank(governor); - vm.expectRevert(bytes.concat("n6")); - proxyAsAdmin.changeFeeParams(newParams); - } -} diff --git a/l1-contracts/test/foundry/unit/concrete/Admin/_Admin_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/Admin/_Admin_Shared.t.sol deleted file mode 100644 index 56f86063bd28..000000000000 --- a/l1-contracts/test/foundry/unit/concrete/Admin/_Admin_Shared.t.sol +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.20; - -import {Test} from "forge-std/Test.sol"; -import {DiamondProxy} from "solpp/zksync/DiamondProxy.sol"; -import {DiamondInit} from "solpp/zksync/DiamondInit.sol"; -import {VerifierParams, FeeParams, PubdataPricingMode} from "solpp/zksync/Storage.sol"; -import {Diamond} from "solpp/zksync/libraries/Diamond.sol"; -import {AdminFacet} from "solpp/zksync/facets/Admin.sol"; -import {Base} from "solpp/zksync/facets/Base.sol"; -import {Governance} from "solpp/governance/Governance.sol"; -import {IVerifier} from "../../../../../cache/solpp-generated-contracts/zksync/interfaces/IVerifier.sol"; - -contract GettersMock is Base { - function getFeeParams() public returns (FeeParams memory) { - return s.feeParams; - } -} - -contract AdminTest is Test { - DiamondProxy internal diamondProxy; - address internal owner; - address internal securityCouncil; - address internal governor; - AdminFacet internal adminFacet; - AdminFacet internal proxyAsAdmin; - GettersMock internal proxyAsGettersMock; - - function getAdminSelectors() private view returns (bytes4[] memory) { - bytes4[] memory dcSelectors = new bytes4[](11); - dcSelectors[0] = adminFacet.setPendingGovernor.selector; - dcSelectors[1] = adminFacet.acceptGovernor.selector; - dcSelectors[2] = adminFacet.setPendingAdmin.selector; - dcSelectors[3] = adminFacet.acceptAdmin.selector; - dcSelectors[4] = adminFacet.setValidator.selector; - dcSelectors[5] = adminFacet.setPorterAvailability.selector; - dcSelectors[6] = adminFacet.setPriorityTxMaxGasLimit.selector; - dcSelectors[7] = adminFacet.executeUpgrade.selector; - dcSelectors[8] = adminFacet.freezeDiamond.selector; - dcSelectors[9] = adminFacet.unfreezeDiamond.selector; - dcSelectors[10] = adminFacet.changeFeeParams.selector; - return dcSelectors; - } - - function getGettersMockSelectors() private view returns (bytes4[] memory) { - bytes4[] memory dcSelectors = new bytes4[](1); - dcSelectors[0] = proxyAsGettersMock.getFeeParams.selector; - return dcSelectors; - } - - function setUp() public { - owner = makeAddr("owner"); - securityCouncil = makeAddr("securityCouncil"); - governor = makeAddr("governor"); - DiamondInit diamondInit = new DiamondInit(); - - VerifierParams memory dummyVerifierParams = VerifierParams({ - recursionNodeLevelVkHash: 0, - recursionLeafLevelVkHash: 0, - recursionCircuitsSetVksHash: 0 - }); - - DiamondInit.InitializeData memory params = DiamondInit.InitializeData({ - verifier: IVerifier(0x03752D8252d67f99888E741E3fB642803B29B155), // verifier - governor: governor, - admin: owner, - genesisBatchHash: 0x02c775f0a90abf7a0e8043f2fdc38f0580ca9f9996a895d05a501bfeaa3b2e21, - genesisIndexRepeatedStorageChanges: 0, - genesisBatchCommitment: bytes32(0), - verifierParams: dummyVerifierParams, - zkPorterIsAvailable: false, - l2BootloaderBytecodeHash: 0x0100000000000000000000000000000000000000000000000000000000000000, - l2DefaultAccountBytecodeHash: 0x0100000000000000000000000000000000000000000000000000000000000000, - priorityTxMaxGasLimit: 500000, // priority tx max L2 gas limit - initialProtocolVersion: 0, - feeParams: FeeParams({ - pubdataPricingMode: PubdataPricingMode.Rollup, - batchOverheadL1Gas: 1_000_000, - maxPubdataPerBatch: 110_000, - maxL2GasPerBatch: 80_000_000, - priorityTxMaxPubdata: 99_000, - minimalL2GasPrice: 250_000_000 - }), - blobVersionedHashRetriever: address(0) - }); - - adminFacet = new AdminFacet(); - GettersMock gettersMock = new GettersMock(); - - bytes memory diamondInitCalldata = abi.encodeWithSelector(diamondInit.initialize.selector, params); - - Diamond.FacetCut[] memory facetCuts = new Diamond.FacetCut[](2); - facetCuts[0] = Diamond.FacetCut({ - facet: address(adminFacet), - action: Diamond.Action.Add, - isFreezable: false, - selectors: getAdminSelectors() - }); - facetCuts[1] = Diamond.FacetCut({ - facet: address(gettersMock), - action: Diamond.Action.Add, - isFreezable: false, - selectors: getGettersMockSelectors() - }); - - Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ - facetCuts: facetCuts, - initAddress: address(diamondInit), - initCalldata: diamondInitCalldata - }); - - diamondProxy = new DiamondProxy(block.chainid, diamondCutData); - proxyAsAdmin = AdminFacet(address(diamondProxy)); - proxyAsGettersMock = GettersMock(address(diamondProxy)); - } -} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/ClaimFailedDeposit.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/ClaimFailedDeposit.t.sol deleted file mode 100644 index ac6fe4af97ce..000000000000 --- a/l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/ClaimFailedDeposit.t.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -import {L1WethBridgeTest} from "./_L1WethBridge_Shared.t.sol"; - -contract ClaimFailedDepositTest is L1WethBridgeTest { - function test_RevertWhen_Claiming() public { - vm.expectRevert("Method not supported. Failed deposit funds are sent to the L2 refund recipient address."); - bridgeProxy.claimFailedDeposit(address(0), address(0), bytes32(0), 0, 0, 0, new bytes32[](0)); - } -} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/Deposit.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/Deposit.t.sol deleted file mode 100644 index fc326d95b5e3..000000000000 --- a/l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/Deposit.t.sol +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -import {L1WethBridgeTest} from "./_L1WethBridge_Shared.t.sol"; -import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA} from "../../../../../../cache/solpp-generated-contracts/zksync/Config.sol"; - -contract DepositTest is L1WethBridgeTest { - function test_RevertWhen_ReceivedL1TokenIsNotL1WethAddress() public { - vm.expectRevert("Invalid L1 token address"); - bridgeProxy.deposit(randomSigner, makeAddr("invalidL1TokenAddress"), 0, 0, 0, address(0)); - } - - function test_RevertWhen_DepositingZeroWETH() public { - bytes memory depositCallData = abi.encodeWithSelector( - bridgeProxy.deposit.selector, - randomSigner, - bridgeProxy.l1WethAddress(), - 0, - 0, - 0, - address(0) - ); - - vm.expectRevert("Amount cannot be zero"); - // solhint-disable-next-line avoid-low-level-calls - (bool revertAsExpected, ) = address(bridgeProxy).call(depositCallData); - assertTrue(revertAsExpected, "expectRevert: call did not revert"); - } - - function test_DepositSuccessfully() public { - uint256 amount = 100; - - hoax(randomSigner); - l1Weth.deposit{value: amount}(); - - hoax(randomSigner); - l1Weth.approve(address(bridgeProxy), amount); - - bytes memory depositCallData = abi.encodeWithSelector( - bridgeProxy.deposit.selector, - randomSigner, - bridgeProxy.l1WethAddress(), - amount, - 1000000, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - randomSigner - ); - - hoax(randomSigner); - (bool success, ) = address(bridgeProxy).call{value: 1000000000000000000}(depositCallData); - assertTrue(success, "call did not succeed"); - } - - function test_DepositSuccessfullyIfRefundRecipientIsNotSpecified() public { - uint256 amount = 100; - - hoax(randomSigner); - l1Weth.deposit{value: amount}(); - - hoax(randomSigner); - l1Weth.approve(address(bridgeProxy), amount); - - bytes memory depositCallData = abi.encodeWithSelector( - bridgeProxy.deposit.selector, - randomSigner, - bridgeProxy.l1WethAddress(), - amount, - 1000000, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - address(0) - ); - - hoax(randomSigner); - (bool success, ) = address(bridgeProxy).call{value: 1000000000000000000}(depositCallData); - assertTrue(success, "call did not succeed"); - } -} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/FinalizeWithdrawal.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/FinalizeWithdrawal.t.sol deleted file mode 100644 index 0f1f6c9bde5c..000000000000 --- a/l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/FinalizeWithdrawal.t.sol +++ /dev/null @@ -1,184 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -import {L1WethBridgeTest} from "./_L1WethBridge_Shared.t.sol"; -import {Utils} from "../../Utils/Utils.sol"; - -contract FinalizeWithdrawalTest is L1WethBridgeTest { - function test_RevertWhen_FinalizingWithdrawalWithWrongMessageLength() public { - vm.expectRevert(bytes.concat("Incorrect ETH message with additional data length")); - bridgeProxy.finalizeWithdrawal(0, 0, 0, bytes(""), new bytes32[](0)); - } - - function test_RevertWhen_FinalizingWithdrawalWithWrongFunctionSelector() public { - bytes memory message = abi.encodePacked( - bytes4(Utils.randomBytes32("functionSignature")), // function selector - 4 bytes - bytes20(Utils.randomBytes32("l1EthWithdrawReceiver")), // l1 eth withdraw receiver - 20 bytes - bytes32(Utils.randomBytes32("ethAmount")), // eth amount - 32 bytes - bytes20(Utils.randomBytes32("l2Sender")), // l2 sender - 20 bytes - bytes20(Utils.randomBytes32("l1WethReceiver")) // l1 weth receiver - 20 bytes - ); - - vm.expectRevert(bytes.concat("Incorrect ETH message function selector")); - bridgeProxy.finalizeWithdrawal(0, 0, 0, message, new bytes32[](0)); - } - - function test_RevertWhen_FinalizingWithdrawalWithWrongReceiver() public { - bytes memory message = abi.encodePacked( - functionSignature, // function selector - 4 bytes - bytes20(Utils.randomBytes32("l1EthWithdrawReceiver")), // l1 eth withdraw receiver - 20 bytes - bytes32(Utils.randomBytes32("ethAmount")), // eth amount - 32 bytes - bytes20(Utils.randomBytes32("l2Sender")), // l2 sender - 20 bytes - bytes20(Utils.randomBytes32("l1WethReceiver")) // l1 weth receiver - 20 bytes - ); - vm.expectRevert(bytes.concat(bytes.concat("Wrong L1 ETH withdraw receiver"))); - bridgeProxy.finalizeWithdrawal(0, 0, 0, message, new bytes32[](0)); - } - - function test_RevertWhen_FinalizingWithdrawalWithWrongL2Sender() public { - bytes memory message = abi.encodePacked( - functionSignature, // function selector - 4 bytes - address(bridgeProxy), // l1 eth withdraw receiver - 20 bytes - bytes32(Utils.randomBytes32("ethAmount")), // eth amount - 32 bytes - bytes20(Utils.randomBytes32("l2Sender")), // l2 sender - 20 bytes - bytes20(Utils.randomBytes32("l1WethReceiver")) // l1 weth receiver - 20 bytes - ); - vm.expectRevert(bytes.concat("The withdrawal was not initiated by L2 bridge")); - bridgeProxy.finalizeWithdrawal(0, 0, 0, message, new bytes32[](0)); - } - - function test_RevertWhen_FinalisedAndUnsuccessfulProve() public { - // finalised - vm.mockCall( - address(bridgeProxy.zkSync()), - abi.encodeWithSelector(bridgeProxy.zkSync().isEthWithdrawalFinalized.selector), - abi.encode(true) - ); - // prove is unsuccessful - vm.mockCall( - address(bridgeProxy.zkSync()), - abi.encodeWithSelector(bridgeProxy.zkSync().proveL2MessageInclusion.selector), - abi.encode(false) - ); - bytes memory message = abi.encodePacked( - functionSignature, // function selector - 4 bytes - address(bridgeProxy), // l1 eth withdraw receiver - 20 bytes - Utils.randomBytes32("eth amount"), // eth amount - 32 bytes - address(bridgeProxy.l2Bridge()), // l2 sender - 20 bytes - bytes20(Utils.randomBytes32("l1 weth receiver")) // l1 weth receiver - 20 bytes - ); - - vm.expectRevert(bytes.concat("vq")); - bridgeProxy.finalizeWithdrawal(0, 0, 0, message, new bytes32[](0)); - } - - function test_SuccessfulAlreadyFinalizedAndSuccessfulProve() public { - // finalised - vm.mockCall( - address(bridgeProxy.zkSync()), - abi.encodeWithSelector(bridgeProxy.zkSync().isEthWithdrawalFinalized.selector), - abi.encode(true) - ); - // prove is successful - vm.mockCall( - address(bridgeProxy.zkSync()), - abi.encodeWithSelector(bridgeProxy.zkSync().proveL2MessageInclusion.selector), - abi.encode(true) - ); - uint256 amount = 10000; - - uint256 l2BatchNumber = 0; - uint256 l2MessageIndex = 0; - uint16 l2TxNumberInBatch = 0; - bytes memory message = abi.encodePacked( - functionSignature, // function selector - 4 bytes - address(bridgeProxy), // l1 eth withdraw receiver - 20 bytes - bytes32(amount), // eth amount - 32 bytes - address(bridgeProxy.l2Bridge()), // l2 sender - 20 bytes - bytes20(Utils.randomBytes32("l1 weth receiver")) // l1 weth receiver - 20 bytes - ); - bytes32[] memory merkleProof = new bytes32[](0); - - // set the bridge's balance so it will be able to transfer funds - vm.deal(address(bridgeProxy), amount); - - bridgeProxy.finalizeWithdrawal(l2BatchNumber, l2MessageIndex, l2TxNumberInBatch, message, merkleProof); - bool isFinalised = bridgeProxy.isWithdrawalFinalized(l2BatchNumber, l2MessageIndex); - assertTrue(isFinalised, "Withdrawal should be finalised"); - } - - function test_SuccessfulNotYetFinalized() public { - // not yet finalized - vm.mockCall( - address(bridgeProxy.zkSync()), - abi.encodeWithSelector(bridgeProxy.zkSync().isEthWithdrawalFinalized.selector), - abi.encode(false) - ); - // finalizeEthWithdrawal mock - vm.mockCall( - address(bridgeProxy.zkSync()), - abi.encodeWithSelector(bridgeProxy.zkSync().finalizeEthWithdrawal.selector), - abi.encode(true) - ); - uint256 amount = 10000; - - uint256 l2BatchNumber = 0; - uint256 l2MessageIndex = 0; - uint16 l2TxNumberInBatch = 0; - bytes memory message = abi.encodePacked( - functionSignature, // function selector - 4 bytes - address(bridgeProxy), // l1 eth withdraw receiver - 20 bytes - bytes32(amount), // eth amount - 32 bytes - address(bridgeProxy.l2Bridge()), // l2 sender - 20 bytes - bytes20(Utils.randomBytes32("l1 weth receiver")) // l1 weth receiver - 20 bytes - ); - bytes32[] memory merkleProof = new bytes32[](0); - - // set the bridge's balance so it will be able to transfer funds - vm.deal(address(bridgeProxy), amount); - - bridgeProxy.finalizeWithdrawal(l2BatchNumber, l2MessageIndex, l2TxNumberInBatch, message, merkleProof); - - bool isFinalised = bridgeProxy.isWithdrawalFinalized(l2BatchNumber, l2MessageIndex); - assertTrue(isFinalised, "Withdrawal should be finalised"); - } - - function test_RevertWhen_AlreadyFinalized() public { - // running finalizeWithdrawal twice should revert - // not yet finalized - vm.mockCall( - address(bridgeProxy.zkSync()), - abi.encodeWithSelector(bridgeProxy.zkSync().isEthWithdrawalFinalized.selector), - abi.encode(false) - ); - // finalizeEthWithdrawal mock - vm.mockCall( - address(bridgeProxy.zkSync()), - abi.encodeWithSelector(bridgeProxy.zkSync().finalizeEthWithdrawal.selector), - abi.encode(true) - ); - uint256 amount = 10000; - - uint256 l2BatchNumber = 0; - uint256 l2MessageIndex = 0; - uint16 l2TxNumberInBatch = 0; - bytes memory message = abi.encodePacked( - functionSignature, // function selector - 4 bytes - address(bridgeProxy), // l1 eth withdraw receiver - 20 bytes - bytes32(amount), // eth amount - 32 bytes - address(bridgeProxy.l2Bridge()), // l2 sender - 20 bytes - bytes20(Utils.randomBytes32("l1 weth receiver")) // l1 weth receiver - 20 bytes - ); - bytes32[] memory merkleProof = new bytes32[](0); - - // set the bridge's balance so it will be able to transfer funds - vm.deal(address(bridgeProxy), amount); - - bridgeProxy.finalizeWithdrawal(l2BatchNumber, l2MessageIndex, l2TxNumberInBatch, message, merkleProof); - - // trying to finalize it again should result in a revert - vm.expectRevert(bytes.concat("Withdrawal is already finalized")); - bridgeProxy.finalizeWithdrawal(l2BatchNumber, l2MessageIndex, l2TxNumberInBatch, message, merkleProof); - } -} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/L2TokenAddress.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/L2TokenAddress.t.sol deleted file mode 100644 index 08ddf1fc3316..000000000000 --- a/l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/L2TokenAddress.t.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -import {L1WethBridgeTest} from "./_L1WethBridge_Shared.t.sol"; - -contract L2TokenAddressTest is L1WethBridgeTest { - function test_l1TokenSameAsL1WethAddress() public { - address l1Token = address(l1Weth); - - address l2Token = bridgeProxy.l2TokenAddress(l1Token); - - address expectedAddress = bridgeProxy.l2WethAddress(); - bool isSameAddress = l2Token == expectedAddress; - assertTrue(isSameAddress, "l2TokenAddress != l2WethAddress"); - } - - function test_l1TokenNotSameAsL1WethAddress() public { - address l1Token = makeAddr("l1Token"); - - address l2Token = bridgeProxy.l2TokenAddress(l1Token); - - address expectedAddress = address(0); - bool isSameAddress = l2Token == expectedAddress; - assertTrue(isSameAddress, "l2TokenAddress != address(0)"); - } -} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/Receive.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/Receive.t.sol deleted file mode 100644 index 93c11a4b3edd..000000000000 --- a/l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/Receive.t.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -import {Vm} from "forge-std/Test.sol"; -import {L1WethBridgeTest} from "./_L1WethBridge_Shared.t.sol"; - -contract ReceiveTest is L1WethBridgeTest { - function test_ReceiveEthFromL1WethAddress() public { - uint256 amount = 10000; - - hoax(address(l1Weth)); - - vm.recordLogs(); - - (bool success, ) = payable(address(bridgeProxy)).call{value: amount}(""); - require(success, "received unexpected revert"); - - Vm.Log[] memory entries = vm.getRecordedLogs(); - assertEq(entries.length, 1); - assertEq(entries[0].topics.length, 1); - assertEq(entries[0].topics[0], keccak256("EthReceived(uint256)")); - assertEq(abi.decode(entries[0].data, (uint256)), amount); - } - - function test_ReceiveEthFromZkSyncAddress() public { - uint256 amount = 10000; - - hoax(address(bridgeProxy.zkSync())); - - vm.recordLogs(); - - (bool success, ) = payable(address(bridgeProxy)).call{value: amount}(""); - require(success, "received unexpected revert"); - - Vm.Log[] memory entries = vm.getRecordedLogs(); - assertEq(entries.length, 1); - assertEq(entries[0].topics.length, 1); - assertEq(entries[0].topics[0], keccak256("EthReceived(uint256)")); - assertEq(abi.decode(entries[0].data, (uint256)), amount); - } - - function test_RevertWhen_ReceiveEthFromRandomAddress() public { - uint256 amount = 10000; - - hoax(randomSigner); - - vm.expectRevert(bytes.concat("pn")); - (bool revertAsExpected, ) = payable(address(bridgeProxy)).call{value: amount}(""); - assertTrue(revertAsExpected, "expectRevert: call did not revert"); - } -} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/_L1WethBridge_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/_L1WethBridge_Shared.t.sol deleted file mode 100644 index 73defbf6b8a5..000000000000 --- a/l1-contracts/test/foundry/unit/concrete/Bridge/L1WethBridge/_L1WethBridge_Shared.t.sol +++ /dev/null @@ -1,122 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -import {Test} from "forge-std/Test.sol"; -import {L1WethBridge} from "../../../../../../cache/solpp-generated-contracts/bridge/L1WethBridge.sol"; -import {WETH9} from "../../../../../../cache/solpp-generated-contracts/dev-contracts/WETH9.sol"; -import {GettersFacet} from "../../../../../../cache/solpp-generated-contracts/zksync/facets/Getters.sol"; -import {MailboxFacet} from "../../../../../../cache/solpp-generated-contracts/zksync/facets/Mailbox.sol"; -import {DiamondInit} from "../../../../../../cache/solpp-generated-contracts/zksync/DiamondInit.sol"; -import {VerifierParams, FeeParams, PubdataPricingMode} from "../../../../../../cache/solpp-generated-contracts/zksync/Storage.sol"; -import {Diamond} from "../../../../../../cache/solpp-generated-contracts/zksync/libraries/Diamond.sol"; -import {DiamondProxy} from "../../../../../../cache/solpp-generated-contracts/zksync/DiamondProxy.sol"; -import {Utils} from "../../Utils/Utils.sol"; -import {IZkSync} from "../../../../../../cache/solpp-generated-contracts/zksync/interfaces/IZkSync.sol"; -import {DiamondInit} from "../../../../../../cache/solpp-generated-contracts/zksync/DiamondInit.sol"; -import {IVerifier} from "../../../../../../cache/solpp-generated-contracts/zksync/interfaces/IVerifier.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; - -contract L1WethBridgeTest is Test { - address internal owner; - address internal randomSigner; - L1WethBridge internal bridgeProxy; - WETH9 internal l1Weth; - bytes4 internal functionSignature = 0x6c0960f9; - - function defaultFeeParams() private pure returns (FeeParams memory feeParams) { - feeParams = FeeParams({ - pubdataPricingMode: PubdataPricingMode.Rollup, - batchOverheadL1Gas: 1_000_000, - maxPubdataPerBatch: 110_000, - maxL2GasPerBatch: 80_000_000, - priorityTxMaxPubdata: 99_000, - minimalL2GasPrice: 250_000_000 - }); - } - - function setUp() public { - owner = makeAddr("owner"); - randomSigner = makeAddr("randomSigner"); - - GettersFacet gettersFacet = new GettersFacet(); - MailboxFacet mailboxFacet = new MailboxFacet(); - DiamondInit diamondInit = new DiamondInit(); - - bytes8 dummyHash = 0x1234567890123456; - address dummyAddress = makeAddr("dummyAddress"); - - DiamondInit.InitializeData memory params = DiamondInit.InitializeData({ - verifier: IVerifier(dummyAddress), // verifier - governor: owner, - admin: owner, - genesisBatchHash: bytes32(0), - genesisIndexRepeatedStorageChanges: 0, - genesisBatchCommitment: bytes32(0), - verifierParams: VerifierParams({ - recursionNodeLevelVkHash: 0, - recursionLeafLevelVkHash: 0, - recursionCircuitsSetVksHash: 0 - }), - zkPorterIsAvailable: false, - l2BootloaderBytecodeHash: dummyHash, - l2DefaultAccountBytecodeHash: dummyHash, - priorityTxMaxGasLimit: 10000000, - initialProtocolVersion: 0, - feeParams: defaultFeeParams(), - blobVersionedHashRetriever: address(0) - }); - - bytes memory diamondInitData = abi.encodeWithSelector(diamondInit.initialize.selector, params); - - Diamond.FacetCut[] memory facetCuts = new Diamond.FacetCut[](2); - facetCuts[0] = Diamond.FacetCut({ - facet: address(gettersFacet), - action: Diamond.Action.Add, - isFreezable: false, - selectors: Utils.getGettersSelectors() - }); - facetCuts[1] = Diamond.FacetCut({ - facet: address(mailboxFacet), - action: Diamond.Action.Add, - isFreezable: true, - selectors: Utils.getMailboxSelectors() - }); - - Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ - facetCuts: facetCuts, - initAddress: address(diamondInit), - initCalldata: diamondInitData - }); - - uint256 chainId = block.chainid; - DiamondProxy diamondProxy = new DiamondProxy(chainId, diamondCutData); - - l1Weth = new WETH9(); - - IZkSync zkSync = IZkSync(address(diamondProxy)); - - L1WethBridge bridge = new L1WethBridge(payable(address(l1Weth)), zkSync); - - bytes memory garbageBytecode = abi.encodePacked( - bytes32(0x1111111111111111111111111111111111111111111111111111111111111111) - ); - address garbageAddress = makeAddr("garbageAddress"); - - bytes[] memory factoryDeps = new bytes[](2); - factoryDeps[0] = garbageBytecode; - factoryDeps[1] = garbageBytecode; - bytes memory bridgeInitData = abi.encodeWithSelector( - bridge.initialize.selector, - factoryDeps, - garbageAddress, - owner, - 1000000000000000000, - 1000000000000000000 - ); - - ERC1967Proxy x = new ERC1967Proxy{value: 2000000000000000000}(address(bridge), bridgeInitData); - - bridgeProxy = L1WethBridge(payable(address(x))); - } -} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/Deposit.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/Deposit.t.sol new file mode 100644 index 000000000000..463a100db234 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/Deposit.t.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {BridgehubMailboxTest} from "./_BridgehubMailbox_Shared.t.sol"; + +contract DepositTest is BridgehubMailboxTest { + // function test_RevertWhen_CalledByNonChainContract() public { + // address nonChainContract = makeAddr("nonChainContract"); + // vm.expectRevert(abi.encodePacked("12c")); + // vm.startPrank(nonChainContract); + // bridgehub.deposit(chainId); + // } + // function test_SuccessfullIfCalledByChainContract() public { + // address chainContract = bridgehub.getStateTransition(chainId); + // vm.startPrank(chainContract); + // bridgehub.deposit(chainId); + // } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/FinalizeEthWithdrawal.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/FinalizeEthWithdrawal.t.sol new file mode 100644 index 000000000000..aac63725e7b1 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/FinalizeEthWithdrawal.t.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {BridgehubMailboxTest} from "./_BridgehubMailbox_Shared.t.sol"; + +import {IMailbox} from "solpp/state-transition/chain-interfaces/IMailbox.sol"; + +contract FinalizeEthWithdrawalTest is BridgehubMailboxTest { + // uint256 internal l2BlockNumber; + // uint256 internal l2MessageIndex; + // uint16 internal l2TxNumberInBlock; + // bytes internal message; + // bytes32[] internal merkleProof; + // address internal msgSender; + // function setUp() public { + // l2BlockNumber = 3456789; + // l2MessageIndex = 234567890; + // l2TxNumberInBlock = 12345; + // message = "message"; + // merkleProof = new bytes32[](1); + // msgSender = makeAddr("msgSender"); + // } + // function test_RevertWhen_InternalCallReverts() public { + // bytes memory revertMessage = "random revert"; + // vm.mockCallRevert( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector( + // IMailbox.finalizeEthWithdrawalBridgehub.selector, + // msgSender, + // l2BlockNumber, + // l2MessageIndex, + // l2TxNumberInBlock, + // message, + // merkleProof + // ), + // revertMessage + // ); + // vm.expectCall( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector( + // IMailbox.finalizeEthWithdrawalBridgehub.selector, + // msgSender, + // l2BlockNumber, + // l2MessageIndex, + // l2TxNumberInBlock, + // message, + // merkleProof + // ) + // ); + // vm.expectRevert(revertMessage); + // vm.startPrank(msgSender); + // bridgehub.finalizeEthWithdrawal( + // chainId, + // l2BlockNumber, + // l2MessageIndex, + // l2TxNumberInBlock, + // message, + // merkleProof + // ); + // } + // function test_ShouldReturnReceivedCanonicalTxHash() public { + // vm.mockCall( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector( + // IMailbox.finalizeEthWithdrawalBridgehub.selector, + // msgSender, + // l2BlockNumber, + // l2MessageIndex, + // l2TxNumberInBlock, + // message, + // merkleProof + // ), + // "" + // ); + // vm.expectCall( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector( + // IMailbox.finalizeEthWithdrawalBridgehub.selector, + // msgSender, + // l2BlockNumber, + // l2MessageIndex, + // l2TxNumberInBlock, + // message, + // merkleProof + // ) + // ); + // vm.startPrank(msgSender); + // bridgehub.finalizeEthWithdrawal( + // chainId, + // l2BlockNumber, + // l2MessageIndex, + // l2TxNumberInBlock, + // message, + // merkleProof + // ); + // } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/IsEthWithdrawalFinalized.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/IsEthWithdrawalFinalized.t.sol new file mode 100644 index 000000000000..2d3c06848507 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/IsEthWithdrawalFinalized.t.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {BridgehubMailboxTest} from "./_BridgehubMailbox_Shared.t.sol"; + +import {IMailbox} from "solpp/state-transition/chain-interfaces/IMailbox.sol"; + +contract IsEthWithdrawalFinalizedTest is BridgehubMailboxTest { + // uint256 internal l2MessageIndex; + // uint256 internal l2TxNumberInBlock; + // function setUp() public { + // l2MessageIndex = 123456789; + // l2TxNumberInBlock = 23456; + // } + // function test_WhenChainContractReturnsTrue() public { + // vm.mockCall( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector(IMailbox.isEthWithdrawalFinalized.selector, l2MessageIndex, l2TxNumberInBlock), + // abi.encode(true) + // ); + // vm.expectCall( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector(IMailbox.isEthWithdrawalFinalized.selector, l2MessageIndex, l2TxNumberInBlock) + // ); + // bool res = bridgehub.isEthWithdrawalFinalized(chainId, l2MessageIndex, l2TxNumberInBlock); + // assertEq(res, true, "ETH withdrawal should be finalized"); + // } + // function test_WhenChainContractReturnsFalse() public { + // vm.mockCall( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector(IMailbox.isEthWithdrawalFinalized.selector, l2MessageIndex, l2TxNumberInBlock), + // abi.encode(false) + // ); + // vm.expectCall( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector(IMailbox.isEthWithdrawalFinalized.selector, l2MessageIndex, l2TxNumberInBlock) + // ); + // bool res = bridgehub.isEthWithdrawalFinalized(chainId, l2MessageIndex, l2TxNumberInBlock); + // assertEq(res, false, "ETH withdrawal should not be finalized"); + // } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/L2TransactionBaseCost.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/L2TransactionBaseCost.t.sol new file mode 100644 index 000000000000..8800a12139c1 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/L2TransactionBaseCost.t.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {BridgehubMailboxTest} from "./_BridgehubMailbox_Shared.t.sol"; + +import {IMailbox} from "solpp/state-transition/chain-interfaces/IMailbox.sol"; + +contract L2TransactionBaseCostTest is BridgehubMailboxTest { + // uint256 internal gasPrice; + // uint256 internal l2GasLimit; + // uint256 internal l2GasPerPubdataByteLimit; + // function setUp() public { + // gasPrice = 123456789; + // l2GasLimit = 234567890; + // l2GasPerPubdataByteLimit = 345678901; + // } + // function test_RevertWhen_InternalCallReverts() public { + // bytes memory revertMessage = "random revert"; + // vm.mockCallRevert( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector( + // IMailbox.l2TransactionBaseCost.selector, + // gasPrice, + // l2GasLimit, + // l2GasPerPubdataByteLimit + // ), + // revertMessage + // ); + // vm.expectCall( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector( + // IMailbox.l2TransactionBaseCost.selector, + // gasPrice, + // l2GasLimit, + // l2GasPerPubdataByteLimit + // ) + // ); + // vm.expectRevert(revertMessage); + // bridgehub.l2TransactionBaseCost(chainId, gasPrice, l2GasLimit, l2GasPerPubdataByteLimit); + // } + // function test_ShouldReturnReceivedCanonicalTxHash() public { + // uint256 expectedBaseCost = 123456789; + // vm.mockCall( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector( + // IMailbox.l2TransactionBaseCost.selector, + // gasPrice, + // l2GasLimit, + // l2GasPerPubdataByteLimit + // ), + // abi.encode(expectedBaseCost) + // ); + // vm.expectCall( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector( + // IMailbox.l2TransactionBaseCost.selector, + // gasPrice, + // l2GasLimit, + // l2GasPerPubdataByteLimit + // ) + // ); + // uint256 baseCost = bridgehub.l2TransactionBaseCost(chainId, gasPrice, l2GasLimit, l2GasPerPubdataByteLimit); + // assertEq(baseCost, expectedBaseCost); + // } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/ProveL1ToL2TransactionStatus.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/ProveL1ToL2TransactionStatus.t.sol new file mode 100644 index 000000000000..f3710f05441f --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/ProveL1ToL2TransactionStatus.t.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {BridgehubMailboxTest} from "./_BridgehubMailbox_Shared.t.sol"; + +import {IMailbox} from "solpp/state-transition/chain-interfaces/IMailbox.sol"; +import {TxStatus} from "solpp/common/Messaging.sol"; + +contract ProveL1ToL2TransactionStatusTest is BridgehubMailboxTest { + // uint256 internal blockNumber; + // bytes32 internal l2TxHash; + // uint256 internal l2BlockNumber; + // uint256 internal l2MessageIndex; + // uint16 internal l2TxNumberInBlock; + // bytes32[] internal merkleProof; + // TxStatus internal status; + // function setUp() public { + // l2TxHash = bytes32(uint256(123456789)); + // l2BlockNumber = 3456789; + // l2MessageIndex = 234567890; + // l2TxNumberInBlock = 12345; + // merkleProof = new bytes32[](1); + // status = TxStatus.Success; + // } + // function test_WhenChainContractReturnsTrue() public { + // vm.mockCall( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector( + // IMailbox.proveL1ToL2TransactionStatus.selector, + // l2TxHash, + // l2BlockNumber, + // l2MessageIndex, + // l2TxNumberInBlock, + // merkleProof, + // status + // ), + // abi.encode(true) + // ); + // vm.expectCall( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector( + // IMailbox.proveL1ToL2TransactionStatus.selector, + // l2TxHash, + // l2BlockNumber, + // l2MessageIndex, + // l2TxNumberInBlock, + // merkleProof, + // status + // ) + // ); + // bool res = bridgehub.proveL1ToL2TransactionStatus( + // chainId, + // l2TxHash, + // l2BlockNumber, + // l2MessageIndex, + // l2TxNumberInBlock, + // merkleProof, + // status + // ); + // assertEq(res, true, "L1 to L2 transaction status should be proven"); + // } + // function test_WhenChainContractReturnsFalse() public { + // vm.mockCall( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector( + // IMailbox.proveL1ToL2TransactionStatus.selector, + // l2TxHash, + // l2BlockNumber, + // l2MessageIndex, + // l2TxNumberInBlock, + // merkleProof, + // status + // ), + // abi.encode(false) + // ); + // vm.expectCall( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector( + // IMailbox.proveL1ToL2TransactionStatus.selector, + // l2TxHash, + // l2BlockNumber, + // l2MessageIndex, + // l2TxNumberInBlock, + // merkleProof, + // status + // ) + // ); + // bool res = bridgehub.proveL1ToL2TransactionStatus( + // chainId, + // l2TxHash, + // l2BlockNumber, + // l2MessageIndex, + // l2TxNumberInBlock, + // merkleProof, + // status + // ); + // assertEq(res, false, "L1 to L2 transaction status should not be proven"); + // } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/ProveL2LogInclusion.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/ProveL2LogInclusion.t.sol new file mode 100644 index 000000000000..a9beb2bda699 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/ProveL2LogInclusion.t.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {BridgehubMailboxTest} from "./_BridgehubMailbox_Shared.t.sol"; + +import {IMailbox} from "solpp/state-transition/chain-interfaces/IMailbox.sol"; +import {L2Log} from "solpp/common/Messaging.sol"; + +contract ProveL2LogInclusionTest is BridgehubMailboxTest { + // uint256 internal blockNumber; + // uint256 internal index; + // L2Log internal l2Log; + // bytes32[] internal proof; + // function setUp() public { + // blockNumber = 3456789; + // index = 234567890; + // proof = new bytes32[](1); + // uint8 l2ShardId = 0; + // bool isService = false; + // uint16 txNumberInBlock = 12345; + // address sender = makeAddr("sender"); + // bytes32 key = "key"; + // bytes32 value = "value"; + // l2Log = L2Log(l2ShardId, isService, txNumberInBlock, sender, key, value); + // } + // function test_WhenChainContractReturnsTrue() public { + // vm.mockCall( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector(IMailbox.proveL2LogInclusion.selector, blockNumber, index, l2Log, proof), + // abi.encode(true) + // ); + // vm.expectCall( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector(IMailbox.proveL2LogInclusion.selector, blockNumber, index, l2Log, proof) + // ); + // bool res = bridgehub.proveL2LogInclusion(chainId, blockNumber, index, l2Log, proof); + // assertEq(res, true, "L2 log should be included"); + // } + // function test_WhenChainContractReturnsFalse() public { + // vm.mockCall( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector(IMailbox.proveL2LogInclusion.selector, blockNumber, index, l2Log, proof), + // abi.encode(false) + // ); + // vm.expectCall( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector(IMailbox.proveL2LogInclusion.selector, blockNumber, index, l2Log, proof) + // ); + // bool res = bridgehub.proveL2LogInclusion(chainId, blockNumber, index, l2Log, proof); + // assertEq(res, false, "L2 log should not be included"); + // } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/ProveL2MessageInclusion.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/ProveL2MessageInclusion.t.sol new file mode 100644 index 000000000000..80afe442bb15 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/ProveL2MessageInclusion.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {BridgehubMailboxTest} from "./_BridgehubMailbox_Shared.t.sol"; + +import {IMailbox} from "solpp/state-transition/chain-interfaces/IMailbox.sol"; +import {L2Message} from "solpp/common/Messaging.sol"; + +contract ProveL2MessageInclusionTest is BridgehubMailboxTest { + // uint256 internal blockNumber; + // uint256 internal index; + // L2Message internal message; + // bytes32[] internal proof; + // function setUp() public { + // blockNumber = 3456789; + // index = 234567890; + // proof = new bytes32[](1); + // uint16 txNumberInBlock = 12345; + // address sender = makeAddr("sender"); + // bytes memory data = "data"; + // message = L2Message(txNumberInBlock, sender, data); + // } + // function test_WhenChainContractReturnsTrue() public { + // vm.mockCall( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector(IMailbox.proveL2MessageInclusion.selector, blockNumber, index, message, proof), + // abi.encode(true) + // ); + // vm.expectCall( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector(IMailbox.proveL2MessageInclusion.selector, blockNumber, index, message, proof) + // ); + // bool res = bridgehub.proveL2MessageInclusion(chainId, blockNumber, index, message, proof); + // assertEq(res, true, "L2 message should be included"); + // } + // function test_WhenChainContractReturnsFalse() public { + // vm.mockCall( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector(IMailbox.proveL2MessageInclusion.selector, blockNumber, index, message, proof), + // abi.encode(false) + // ); + // vm.expectCall( + // bridgehub.getStateTransition(chainId), + // abi.encodeWithSelector(IMailbox.proveL2MessageInclusion.selector, blockNumber, index, message, proof) + // ); + // bool res = bridgehub.proveL2MessageInclusion(chainId, blockNumber, index, message, proof); + // assertEq(res, false, "L2 message should not be included"); + // } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/RequestL2Transaction.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/RequestL2Transaction.t.sol new file mode 100644 index 000000000000..40e3a51b40b8 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/RequestL2Transaction.t.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {BridgehubMailboxTest} from "./_BridgehubMailbox_Shared.t.sol"; + +import {IMailbox} from "solpp/state-transition/chain-interfaces/IMailbox.sol"; + +contract RequestL2TransactionTest is BridgehubMailboxTest { + // address internal contractL2; + // uint256 internal l2Value; + // bytes internal calldataBytes; + // uint256 internal l2GasLimit; + // uint256 internal l2GasPerPubdataByteLimit; + // bytes[] internal factoryDeps; + // address internal refundRecipient; + // address internal msgSender; + // uint256 internal msgValue; + // function setUp() public { + // contractL2 = makeAddr("contractL2"); + // l2Value = 123456789; + // calldataBytes = "calldataBytes"; + // l2GasLimit = 234567890; + // l2GasPerPubdataByteLimit = 345678901; + // factoryDeps = new bytes[](1); + // refundRecipient = makeAddr("refundRecipient"); + // msgSender = makeAddr("msgSender"); + // vm.deal(msgSender, 100 ether); + // msgValue = 456789012; + // } + // function test_RevertWhen_InternalCallReverts() public { + // bytes memory revertMessage = "random revert"; + // vm.mockCallRevert( + // bridgehub.getStateTransition(chainId), + // msgValue, + // abi.encodeWithSelector( + // IMailbox.requestL2TransactionBridgehub.selector, + // msgSender, + // contractL2, + // l2Value, + // calldataBytes, + // l2GasLimit, + // l2GasPerPubdataByteLimit, + // factoryDeps, + // refundRecipient + // ), + // revertMessage + // ); + // vm.expectCall( + // bridgehub.getStateTransition(chainId), + // msgValue, + // abi.encodeWithSelector( + // IMailbox.requestL2TransactionBridgehub.selector, + // msgSender, + // contractL2, + // l2Value, + // calldataBytes, + // l2GasLimit, + // l2GasPerPubdataByteLimit, + // factoryDeps, + // refundRecipient + // ) + // ); + // vm.expectRevert(revertMessage); + // vm.startPrank(msgSender); + // bridgehub.requestL2TransactionDirect{value: msgValue}( + // chainId, + // contractL2, + // l2Value, + // calldataBytes, + // l2GasLimit, + // l2GasPerPubdataByteLimit, + // factoryDeps, + // refundRecipient + // ); + // } + // function test_ShouldReturnReceivedCanonicalTxHash() public { + // bytes32 expectedCanonicalTxHash = bytes32(uint256(123456789)); + // vm.mockCall( + // bridgehub.getStateTransition(chainId), + // msgValue, + // abi.encodeWithSelector( + // IMailbox.requestL2TransactionBridgehub.selector, + // msgSender, + // contractL2, + // l2Value, + // calldataBytes, + // l2GasLimit, + // l2GasPerPubdataByteLimit, + // factoryDeps, + // refundRecipient + // ), + // abi.encode(expectedCanonicalTxHash) + // ); + // vm.expectCall( + // bridgehub.getStateTransition(chainId), + // msgValue, + // abi.encodeWithSelector( + // IMailbox.requestL2TransactionBridgehub.selector, + // msgSender, + // contractL2, + // l2Value, + // calldataBytes, + // l2GasLimit, + // l2GasPerPubdataByteLimit, + // factoryDeps, + // refundRecipient + // ) + // ); + // vm.startPrank(msgSender); + // bytes32 canonicalTxHash = bridgehub.requestL2TransactionDirect{value: msgValue}( + // chainId, + // contractL2, + // l2Value, + // calldataBytes, + // l2GasLimit, + // l2GasPerPubdataByteLimit, + // factoryDeps, + // refundRecipient + // ); + // assertEq(canonicalTxHash, expectedCanonicalTxHash, "Canonical transaction hash should be returned"); + // } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/WithdrawFunds.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/WithdrawFunds.t.sol new file mode 100644 index 000000000000..25da62a364e7 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/WithdrawFunds.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {BridgehubMailboxTest} from "./_BridgehubMailbox_Shared.t.sol"; + +contract WithdrawFundsTest is BridgehubMailboxTest { + // address internal to; + // uint256 internal amount; + // function setUp() public { + // to = makeAddr("to"); + // amount = 123456789; + // } + // function test_RevertWhen_CalledByNonChainContract() public { + // address nonChainContract = makeAddr("nonChainContract"); + // vm.expectRevert(abi.encodePacked("12c")); + // vm.startPrank(nonChainContract); + // bridgehub.withdrawFunds(chainId, to, amount); + // } + // function test_RevertWhen_NotEnoughBalance() public { + // address chainContract = bridgehub.getStateTransition(chainId); + // // setting the balance of the bridgehub to 0 and trying to withdraw 1 ether + // vm.deal(chainContract, 0 ether); + // amount = 1 ether; + // vm.expectRevert(abi.encodePacked("pz")); + // vm.startPrank(chainContract); + // bridgehub.withdrawFunds(chainId, to, amount); + // } + // function test_SuccessfulWithdraw() public { + // address chainContract = bridgehub.getStateTransition(chainId); + // vm.deal(address(bridgehub), 100 ether); + // amount = 10 ether; + // vm.startPrank(chainContract); + // bridgehub.withdrawFunds(chainId, to, amount); + // } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/_BridgehubMailbox_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/_BridgehubMailbox_Shared.t.sol new file mode 100644 index 000000000000..0a5365a6baba --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridgehub/BridgehubMailbox/_BridgehubMailbox_Shared.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {BridgehubTest} from "../_Bridgehub_Shared.t.sol"; +import {IStateTransitionManager} from "solpp/state-transition/IStateTransitionManager.sol"; + +contract BridgehubMailboxTest is BridgehubTest { + uint256 internal chainId; + address internal chainStateTransition; + address internal chainGovernor; + + // constructor() { + // chainId = 987654321; + // chainStateTransition = makeAddr("chainStateTransition"); + // chainGovernor = makeAddr("chainGovernor"); + // // vm.mockCall( + // // bridgehub.getChainImplementation(), + // // abi.encodeWithSelector(IBridgehubChain.initialize.selector), + // // "" + // // ); + // vm.mockCall(chainStateTransition, abi.encodeWithSelector(IStateTransitionManager.newChain.selector), ""); + // vm.startPrank(GOVERNOR); + // bridgehub.addStateTransition(chainStateTransition); + // bridgehub.createNewChain(chainId, chainStateTransition, chainGovernor, getDiamondCutData()); + // } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/Initialize.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridgehub/Initialize.t.sol new file mode 100644 index 000000000000..a79f3e82ea33 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridgehub/Initialize.t.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {BridgehubTest} from "./_Bridgehub_Shared.t.sol"; + +import {DiamondInit} from "solpp/state-transition/chain-deps/DiamondInit.sol"; + +contract InitializeTest is BridgehubTest { + address internal governor; + address internal chainImplementation; + address internal chainProxyAdmin; + uint256 internal priorityTxMaxGasLimit; + + function setUp() public { + bridgehubDiamondInit = new DiamondInit(); + + governor = GOVERNOR; + chainImplementation = makeAddr("chainImplementation"); + chainProxyAdmin = makeAddr("chainProxyAdmin"); + priorityTxMaxGasLimit = 1090193; + } + + // add this to be excluded from coverage report + function test() internal override {} + + // function test_RevertWhen_AlreadyInitialized() public { + // bridgehub.initialize(governor, chainImplementation, chainProxyAdmin, allowList, priorityTxMaxGasLimit); + + // vm.expectRevert(bytes.concat("bridgehub1")); + // bridgehub.initialize(governor, chainImplementation, chainProxyAdmin, allowList, priorityTxMaxGasLimit); + // } + + // function test_InitializeSuccessfully() public { + // bridgehub.initialize(governor, chainImplementation, chainProxyAdmin, allowList, priorityTxMaxGasLimit); + + // assertEq(bridgehub.governor(), governor); + // assertEq(bridgehub.getChainImplementation(), chainImplementation); + // assertEq(bridgehub.getChainProxyAdmin(), chainProxyAdmin); + // assertEq(address(bridgehub.getAllowList()), address(allowList)); + // assertEq(bridgehub.getPriorityTxMaxGasLimit(), priorityTxMaxGasLimit); + // } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/Registry/NewChain.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridgehub/Registry/NewChain.t.sol new file mode 100644 index 000000000000..c0a44e2f377a --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridgehub/Registry/NewChain.t.sol @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {Vm} from "forge-std/Test.sol"; + +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import {RegistryTest} from "./_Registry_Shared.t.sol"; + +import {IStateTransitionManager} from "solpp/state-transition/IStateTransitionManager.sol"; + +contract NewChainTest is RegistryTest { + uint256 internal chainId; + address internal governorAddress; + + // function setUp() public { + // chainId = 838383838383; + // stateTransitionAddress = makeAddr("chainStateTransitionAddress"); + // governorAddress = makeAddr("governorAddress"); + // allowList = new AllowList(governorAddress); + + // vm.prank(GOVERNOR); + // bridgehub.addStateTransition(stateTransitionAddress); + // } + + // function getStateTransitionAddress() internal returns (address chainContractAddress) { + // // vm.mockCall( + // // bridgehub.getChainImplementation(), + // // abi.encodeWithSelector(IBridgehubChain.initialize.selector), + // // "" + // // ); + + // uint256 snapshot = vm.snapshot(); + // vm.startPrank(address(bridgehub)); + + // // bytes memory data = abi.encodeWithSelector( + // // IBridgehubChain.initialize.selector, + // // chainId, + // // stateTransitionAddress, + // // governorAddress, + // // allowList, + // // bridgehub.getPriorityTxMaxGasLimit() + // // ); + // // TransparentUpgradeableProxy transparentUpgradeableProxy = new TransparentUpgradeableProxy( + // // bridgehub.getChainImplementation(), + // // bridgehub.getChainProxyAdmin(), + // // data + // // ); + // // chainContractAddress = address(transparentUpgradeableProxy); + + // vm.stopPrank(); + // vm.revertTo(snapshot); + // } + + // function test_RevertWhen_NonGovernor() public { + // vm.startPrank(NON_GOVERNOR); + // vm.expectRevert(bytes.concat("12g")); + // bridgehub.createNewChain(chainId, stateTransitionAddress, governorAddress, allowList, getDiamondCutData()); + // } + + // function test_RevertWhen_StateTransitionIsNotInStorage() public { + // address nonExistentStateTransitionAddress = address(0x3030303); + + // vm.startPrank(GOVERNOR); + // vm.expectRevert(bytes.concat("r19")); + // bridgehub.createNewChain( + // chainId, + // nonExistentStateTransitionAddress, + // governorAddress, + // allowList, + // getDiamondCutData() + // ); + // } + + // function test_RevertWhen_ChainIdIsAlreadyInUse() public { + // // vm.mockCall( + // // bridgehub.getChainImplementation(), + // // abi.encodeWithSelector(IBridgehubChain.initialize.selector), + // // "" + // // ); + // vm.mockCall(stateTransitionAddress, abi.encodeWithSelector(IStateTransitionManager.newChain.selector), ""); + + // vm.startPrank(GOVERNOR); + // bridgehub.createNewChain(chainId, stateTransitionAddress, governorAddress, allowList, getDiamondCutData()); + + // vm.expectRevert(bytes.concat("r20")); + // bridgehub.createNewChain(chainId, stateTransitionAddress, governorAddress, allowList, getDiamondCutData()); + // } + + // function test_NewChainSuccessfullyWithNonZeroChainId() public { + // // === Shared variables === + // address chainContractAddress = getStateTransitionAddress(); + + // // === Mocking === + // // vm.mockCall( + // // bridgehub.getChainImplementation(), + // // abi.encodeWithSelector(IBridgehubChain.initialize.selector), + // // "" + // // ); + // vm.mockCall(stateTransitionAddress, abi.encodeWithSelector(IStateTransitionManager.newChain.selector), ""); + + // // === Internal call checks === + // // vm.expectCall( + // // bridgehub.getChainImplementation(), + // // abi.encodeWithSelector( + // // IBridgehubChain.initialize.selector, + // // chainId, + // // stateTransitionAddress, + // // governorAddress, + // // allowList, + // // bridgehub.getPriorityTxMaxGasLimit() + // // ), + // // 1 + // // ); + // vm.expectCall( + // stateTransitionAddress, + // abi.encodeWithSelector( + // IStateTransitionManager.newChain.selector, + // chainId, + // chainContractAddress, + // governorAddress, + // getDiamondCutData() + // ), + // 1 + // ); + + // // === Function call === + // vm.recordLogs(); + // vm.startPrank(GOVERNOR); + + // uint256 resChainId = bridgehub.createNewChain( + // chainId, + // stateTransitionAddress, + // governorAddress, + // allowList, + // getDiamondCutData() + // ); + + // vm.stopPrank(); + // Vm.Log[] memory entries = vm.getRecordedLogs(); + + // // === Emitted event checks === + // assertEq(entries[2].topics[0], IRegistry.NewChain.selector, "NewChain event not emitted"); + // assertEq(entries[2].topics.length, 4, "NewChain event should have 4 topics"); + + // uint16 eventChainId = abi.decode(abi.encode(entries[2].topics[1]), (uint16)); + // address eventChainContract = abi.decode(abi.encode(entries[2].topics[2]), (address)); + // address eventChainGovernnance = abi.decode(abi.encode(entries[2].topics[3]), (address)); + // address eventStateTransition = abi.decode(entries[2].data, (address)); + + // assertEq(eventChainId, uint16(chainId), "NewChain.chainId is wrong"); + // assertEq(eventChainContract, chainContractAddress, "NewChain.chainContract is wrong"); + // assertEq(eventChainGovernnance, GOVERNOR, "NewChain.chainGovernance is wrong"); + // assertEq(eventStateTransition, stateTransitionAddress, "NewChain.stateTransition is wrong"); + + // // === Storage checks === + // assertEq(bridgehub.getStateTransition(chainId), stateTransitionAddress, "saved chainStateTransition is wrong"); + // assertEq(bridgehub.getTotalChains(), 1, "saved totalChains is wrong"); + // assertEq(bridgehub.getStateTransition(chainId), chainContractAddress, "saved chainContract address is wrong"); + // assertEq(resChainId, chainId, "returned chainId is wrong"); + // } + + // function test_NewChainSuccessfullyWithZeroChainId() public { + // // === Shared variables === + // address chainContractAddress = getStateTransitionAddress(); + // uint256 inputChainId = 0; + // chainId = uint16( + // uint256( + // keccak256( + // abi.encodePacked( + // "CHAIN_ID", + // block.chainid, + // address(bridgehub), + // stateTransitionAddress, + // block.timestamp, + // GOVERNOR + // ) + // ) + // ) + // ); + + // // === Mocking === + // // vm.mockCall( + // // bridgehub.getChainImplementation(), + // // abi.encodeWithSelector(IBridgehubChain.initialize.selector), + // // "" + // // ); + // vm.mockCall(stateTransitionAddress, abi.encodeWithSelector(IStateTransitionManager.newChain.selector), ""); + + // // === Internal call checks === + // // vm.expectCall( + // // bridgehub.getChainImplementation(), + // // abi.encodeWithSelector( + // // IBridgehubChain.initialize.selector, + // // chainId, + // // stateTransitionAddress, + // // governorAddress, + // // allowList, + // // bridgehub.getPriorityTxMaxGasLimit() + // // ), + // // 1 + // // ); + // vm.expectCall( + // stateTransitionAddress, + // abi.encodeWithSelector( + // IStateTransitionManager.newChain.selector, + // chainId, + // chainContractAddress, + // governorAddress, + // getDiamondCutData() + // ), + // 1 + // ); + + // // === Function call === + // vm.recordLogs(); + // vm.startPrank(GOVERNOR); + + // uint256 resChainId = bridgehub.createNewChain( + // inputChainId, + // stateTransitionAddress, + // governorAddress, + // allowList, + // getDiamondCutData() + // ); + + // vm.stopPrank(); + // Vm.Log[] memory entries = vm.getRecordedLogs(); + + // // === Emitted event checks === + // assertEq(entries[2].topics[0], IRegistry.NewChain.selector, "NewChain event not emitted"); + // assertEq(entries[2].topics.length, 4, "NewChain event should have 4 topics"); + + // uint16 eventChainId = abi.decode(abi.encode(entries[2].topics[1]), (uint16)); + // address eventChainContract = abi.decode(abi.encode(entries[2].topics[2]), (address)); + // address eventChainGovernnance = abi.decode(abi.encode(entries[2].topics[3]), (address)); + // address eventStateTransition = abi.decode(entries[2].data, (address)); + + // assertEq(eventChainId, uint16(chainId), "NewChain.chainId is wrong"); + // assertEq(eventChainContract, chainContractAddress, "NewChain.chainContract is wrong"); + // assertEq(eventChainGovernnance, GOVERNOR, "NewChain.chainGovernance is wrong"); + // assertEq(eventStateTransition, stateTransitionAddress, "NewChain.stateTransition is wrong"); + + // // === Storage checks === + // assertEq(bridgehub.getStateTransition(chainId), stateTransitionAddress, "saved chainStateTransition is wrong"); + // assertEq(bridgehub.getTotalChains(), 1, "saved totalChains is wrong"); + // assertEq(bridgehub.getStateTransition(chainId), chainContractAddress, "saved chainContract address is wrong"); + // assertEq(resChainId, chainId, "returned chainId is wrong"); + // } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/Registry/NewProofSystem.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridgehub/Registry/NewProofSystem.t.sol new file mode 100644 index 000000000000..cb29e7d88a61 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridgehub/Registry/NewProofSystem.t.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {RegistryTest} from "./_Registry_Shared.t.sol"; + +contract NewStateTransitionTest is RegistryTest { + function setUp() public { + stateTransitionAddress = makeAddr("stateTransitionAddress"); + } + + // add this to be excluded from coverage report + function test() internal override {} + + // function test_RevertWhen_NonGovernor() public { + // vm.prank(NON_GOVERNOR); + // vm.expectRevert(bytes.concat("12g")); + // bridgehub.addStateTransition(stateTransitionAddress); + // } + + // function test_RevertWhen_StateTransitionAlreadyExists() public { + // vm.prank(GOVERNOR); + // bridgehub.addStateTransition(stateTransitionAddress); + + // vm.prank(GOVERNOR); + // vm.expectRevert(bytes.concat("r35")); + // bridgehub.addStateTransition(stateTransitionAddress); + // } + + // function test_NewStateTransitionSuccessful() public { + // vm.prank(GOVERNOR); + // bridgehub.addStateTransition(stateTransitionAddress); + + // assertEq(bridgehub.getIsStateTransition(stateTransitionAddress), true, "should be true"); + // assertEq(bridgehub.getTotaStateTransitions(), 1, "should be exactly 1 proof system"); + // } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/Registry/_Registry_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridgehub/Registry/_Registry_Shared.t.sol new file mode 100644 index 000000000000..9487a2f835f0 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridgehub/Registry/_Registry_Shared.t.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {BridgehubTest} from "../_Bridgehub_Shared.t.sol"; + +contract RegistryTest is BridgehubTest { + address internal stateTransitionAddress; +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/_Bridgehub_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridgehub/_Bridgehub_Shared.t.sol new file mode 100644 index 000000000000..4fcc8d872cf0 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridgehub/_Bridgehub_Shared.t.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {Test} from "forge-std/Test.sol"; + +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; +import {DiamondInit} from "solpp/state-transition/chain-deps/DiamondInit.sol"; +import {DiamondProxy} from "solpp/state-transition/chain-deps/DiamondProxy.sol"; +import {IDiamondInit} from "solpp/state-transition/chain-interfaces/IDiamondInit.sol"; + +contract BridgehubTest is Test { + DiamondProxy internal bridgehub; + IDiamondInit internal bridgehubDiamondInit; + address internal constant GOVERNOR = address(0x101010101010101010101); + address internal constant NON_GOVERNOR = address(0x202020202020202020202); + + constructor() { + vm.chainId(31337); + bridgehubDiamondInit = new DiamondInit(); + + bridgehub = new DiamondProxy(block.chainid, getDiamondCutData(address(bridgehubDiamondInit))); + } + + function getDiamondCutData(address diamondInit) internal pure returns (Diamond.DiamondCutData memory) { + address governor = GOVERNOR; + + bytes memory initCalldata = abi.encodeWithSelector(IDiamondInit.initialize.selector, governor); + + return + Diamond.DiamondCutData({ + facetCuts: new Diamond.FacetCut[](0), + initAddress: diamondInit, + initCalldata: initCalldata + }); + } + + // add this to be excluded from coverage report + function test() internal virtual {} +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol new file mode 100644 index 000000000000..d5e727d1a336 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol @@ -0,0 +1,1047 @@ +//SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.20; + +import {stdStorage, StdStorage, Test} from "forge-std/Test.sol"; + +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; +import {TestnetERC20Token} from "solpp/dev-contracts/TestnetERC20Token.sol"; +import {IBridgehub, Bridgehub} from "solpp/bridgehub/Bridgehub.sol"; +import {L2TransactionRequestDirect, L2TransactionRequestTwoBridgesOuter} from "solpp/bridgehub/IBridgehub.sol"; +import {DummyStateTransitionManagerWBH} from "solpp/dev-contracts/test/DummyStateTransitionManagerWithBridgeHubAddress.sol"; +import {DummyStateTransition} from "solpp/dev-contracts/test/DummyStateTransition.sol"; +import {DummySharedBridge} from "solpp/dev-contracts/test/DummySharedBridge.sol"; +import {IL1SharedBridge} from "solpp/bridge/interfaces/IL1SharedBridge.sol"; +import {TransactionValidator} from "solpp/state-transition/libraries/TransactionValidator.sol"; + +import {L2Message, L2Log, TxStatus, BridgehubL2TransactionRequest} from "solpp/common/Messaging.sol"; +import {ETH_TOKEN_ADDRESS, REQUIRED_L2_GAS_PRICE_PER_PUBDATA, MAX_NEW_FACTORY_DEPS} from "solpp/common/Config.sol"; + +contract ExperimentalBridgeTest is Test { + using stdStorage for StdStorage; + + Bridgehub bridgeHub; + address public bridgeOwner; + DummyStateTransitionManagerWBH mockSTM; + DummyStateTransition mockChainContract; + DummySharedBridge mockSharedBridge; + DummySharedBridge mockSecondSharedBridge; + TestnetERC20Token testToken; + + function setUp() public { + bridgeHub = new Bridgehub(); + bridgeOwner = makeAddr("BRIDGE_OWNER"); + mockSTM = new DummyStateTransitionManagerWBH(address(bridgeHub)); + mockChainContract = new DummyStateTransition(address(bridgeHub)); + mockSharedBridge = new DummySharedBridge(keccak256("0xabc")); + mockSecondSharedBridge = new DummySharedBridge(keccak256("0xdef")); + testToken = new TestnetERC20Token("ZKSTT", "ZkSync Test Token", 18); + + // test if the ownership of the bridgeHub is set correctly or not + address defaultOwner = bridgeHub.owner(); + + // The defaultOwner should be the same as this contract address, since this is the one deploying the bridgehub contract + assertEq(defaultOwner, address(this)); + + // Now, the `reentrancyGuardInitializer` should prevent anyone from calling `initialize` since we have called the constructor of the contract + vm.expectRevert(bytes("1B")); + bridgeHub.initialize(bridgeOwner); + + vm.store( + address(mockChainContract), + 0x8e94fed44239eb2314ab7a406345e6c5a8f0ccedf3b600de3d004e672c33abf4, + bytes32(uint256(1)) + ); + bytes32 bridgehubLocation = bytes32(uint256(36)); + vm.store(address(mockChainContract), bridgehubLocation, bytes32(uint256(uint160(address(bridgeHub))))); + bytes32 baseTokenGasPriceNominatorLocation = bytes32(uint256(40)); + vm.store(address(mockChainContract), baseTokenGasPriceNominatorLocation, bytes32(uint256(1))); + bytes32 baseTokenGasPriceDenominatorLocation = bytes32(uint256(41)); + vm.store(address(mockChainContract), baseTokenGasPriceDenominatorLocation, bytes32(uint256(1))); + // The ownership can only be transfered by the current owner to a new owner via the two-step approach + + // Default owner calls transferOwnership + bridgeHub.transferOwnership(bridgeOwner); + + // bridgeOwner calls acceptOwnership + vm.prank(bridgeOwner); + bridgeHub.acceptOwnership(); + + // Ownership should have changed + assertEq(bridgeHub.owner(), bridgeOwner); + } + + function test_onlyOwnerCanSetDeployer(address randomDeployer) public { + assertEq(address(0), bridgeHub.admin()); + vm.prank(bridgeHub.owner()); + bridgeHub.setPendingAdmin(randomDeployer); + vm.prank(randomDeployer); + bridgeHub.acceptAdmin(); + + assertEq(randomDeployer, bridgeHub.admin()); + } + + function test_randomCallerCannotSetDeployer(address randomCaller, address randomDeployer) public { + if (randomCaller != bridgeHub.owner() && randomCaller != bridgeHub.admin()) { + vm.prank(randomCaller); + vm.expectRevert(bytes("Bridgehub: not owner or admin")); + bridgeHub.setPendingAdmin(randomDeployer); + + // The deployer shouldn't have changed. + assertEq(address(0), bridgeHub.admin()); + } + } + + function test_addStateTransitionManager(address randomAddressWithoutTheCorrectInterface) public { + bool isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(!isSTMRegistered); + + vm.prank(bridgeOwner); + bridgeHub.addStateTransitionManager(randomAddressWithoutTheCorrectInterface); + + isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(isSTMRegistered); + + // An address that has already been registered, cannot be registered again (atleast not before calling `removeStateTransitionManager`). + vm.prank(bridgeOwner); + vm.expectRevert(bytes("Bridgehub: state transition already registered")); + bridgeHub.addStateTransitionManager(randomAddressWithoutTheCorrectInterface); + + isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(isSTMRegistered); + } + + function test_addStateTransitionManager_cannotBeCalledByRandomAddress( + address randomCaller, + address randomAddressWithoutTheCorrectInterface + ) public { + bool isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(!isSTMRegistered); + + if (randomCaller != bridgeOwner) { + vm.prank(randomCaller); + vm.expectRevert(bytes("Ownable: caller is not the owner")); + + bridgeHub.addStateTransitionManager(randomAddressWithoutTheCorrectInterface); + } + + vm.prank(bridgeOwner); + bridgeHub.addStateTransitionManager(randomAddressWithoutTheCorrectInterface); + + isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(isSTMRegistered); + + // An address that has already been registered, cannot be registered again (atleast not before calling `removeStateTransitionManager`). + vm.prank(bridgeOwner); + vm.expectRevert(bytes("Bridgehub: state transition already registered")); + bridgeHub.addStateTransitionManager(randomAddressWithoutTheCorrectInterface); + + // Definitely not by a random caller + if (randomCaller != bridgeOwner) { + vm.prank(randomCaller); + vm.expectRevert("Ownable: caller is not the owner"); + bridgeHub.addStateTransitionManager(randomAddressWithoutTheCorrectInterface); + } + + isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(isSTMRegistered); + } + + function test_removeStateTransitionManager(address randomAddressWithoutTheCorrectInterface) public { + bool isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(!isSTMRegistered); + + // A non-existent STM cannot be removed + vm.prank(bridgeOwner); + vm.expectRevert(bytes("Bridgehub: state transition not registered yet")); + bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); + + // Let's first register our particular stateTransitionManager + vm.prank(bridgeOwner); + bridgeHub.addStateTransitionManager(randomAddressWithoutTheCorrectInterface); + + isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(isSTMRegistered); + + // Only an address that has already been registered, can be removed. + vm.prank(bridgeOwner); + bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); + + isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(!isSTMRegistered); + + // An already removed STM cannot be removed again + vm.prank(bridgeOwner); + vm.expectRevert(bytes("Bridgehub: state transition not registered yet")); + bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); + } + + function test_removeStateTransitionManager_cannotBeCalledByRandomAddress( + address randomAddressWithoutTheCorrectInterface, + address randomCaller + ) public { + bool isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(!isSTMRegistered); + + if (randomCaller != bridgeOwner) { + vm.prank(randomCaller); + vm.expectRevert(bytes("Ownable: caller is not the owner")); + + bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); + } + + // A non-existent STM cannot be removed + vm.prank(bridgeOwner); + vm.expectRevert(bytes("Bridgehub: state transition not registered yet")); + bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); + + // Let's first register our particular stateTransitionManager + vm.prank(bridgeOwner); + bridgeHub.addStateTransitionManager(randomAddressWithoutTheCorrectInterface); + + isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(isSTMRegistered); + + // Only an address that has already been registered, can be removed. + vm.prank(bridgeOwner); + bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); + + isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(!isSTMRegistered); + + // An already removed STM cannot be removed again + vm.prank(bridgeOwner); + vm.expectRevert(bytes("Bridgehub: state transition not registered yet")); + bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); + + // Not possible by a randomcaller as well + if (randomCaller != bridgeOwner) { + vm.prank(randomCaller); + vm.expectRevert(bytes("Ownable: caller is not the owner")); + bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); + } + } + + function test_addToken(address, address randomAddress) public { + assertTrue(!bridgeHub.tokenIsRegistered(randomAddress), "This random address is not registered as a token"); + + vm.prank(bridgeOwner); + bridgeHub.addToken(randomAddress); + + assertTrue( + bridgeHub.tokenIsRegistered(randomAddress), + "after call from the bridgeowner, this randomAddress should be a registered token" + ); + + if (randomAddress != address(testToken)) { + // Testing to see if an actual ERC20 implementation can also be added or not + vm.prank(bridgeOwner); + bridgeHub.addToken(address(testToken)); + + assertTrue(bridgeHub.tokenIsRegistered(address(testToken))); + } + + // An already registered token cannot be registered again + vm.prank(bridgeOwner); + vm.expectRevert("Bridgehub: token already registered"); + bridgeHub.addToken(randomAddress); + } + + function test_addToken_cannotBeCalledByRandomAddress(address randomAddress, address randomCaller) public { + if (randomCaller != bridgeOwner) { + vm.prank(randomCaller); + vm.expectRevert(bytes("Ownable: caller is not the owner")); + bridgeHub.addToken(randomAddress); + } + + assertTrue(!bridgeHub.tokenIsRegistered(randomAddress), "This random address is not registered as a token"); + + vm.prank(bridgeOwner); + bridgeHub.addToken(randomAddress); + + assertTrue( + bridgeHub.tokenIsRegistered(randomAddress), + "after call from the bridgeowner, this randomAddress should be a registered token" + ); + + if (randomAddress != address(testToken)) { + // Testing to see if an actual ERC20 implementation can also be added or not + vm.prank(bridgeOwner); + bridgeHub.addToken(address(testToken)); + + assertTrue(bridgeHub.tokenIsRegistered(address(testToken))); + } + + // An already registered token cannot be registered again by randomCaller + if (randomCaller != bridgeOwner) { + vm.prank(bridgeOwner); + vm.expectRevert("Bridgehub: token already registered"); + bridgeHub.addToken(randomAddress); + } + } + + function test_setSharedBridge(address randomAddress) public { + assertTrue( + bridgeHub.sharedBridge() == IL1SharedBridge(address(0)), + "This random address is not registered as sharedBridge" + ); + + vm.prank(bridgeOwner); + bridgeHub.setSharedBridge(randomAddress); + + assertTrue( + bridgeHub.sharedBridge() == IL1SharedBridge(randomAddress), + "after call from the bridgeowner, this randomAddress should be the registered sharedBridge" + ); + } + + function test_setSharedBridge_cannotBeCalledByRandomAddress(address randomCaller, address randomAddress) public { + if (randomCaller != bridgeOwner) { + vm.prank(randomCaller); + vm.expectRevert(bytes("Ownable: caller is not the owner")); + bridgeHub.setSharedBridge(randomAddress); + } + + assertTrue( + bridgeHub.sharedBridge() == IL1SharedBridge(address(0)), + "This random address is not registered as sharedBridge" + ); + + vm.prank(bridgeOwner); + bridgeHub.setSharedBridge(randomAddress); + + assertTrue( + bridgeHub.sharedBridge() == IL1SharedBridge(randomAddress), + "after call from the bridgeowner, this randomAddress should be the registered sharedBridge" + ); + } + + uint256 newChainId; + address admin; + + function test_createNewChain( + address randomCaller, + uint256 chainId, + bool isFreezable, + bytes4[] memory mockSelectors, + address mockInitAddress, + bytes memory mockInitCalldata + ) public { + address deployerAddress = makeAddr("DEPLOYER_ADDRESS"); + admin = makeAddr("NEW_CHAIN_ADMIN"); + // Diamond.DiamondCutData memory dcData; + + vm.prank(bridgeOwner); + bridgeHub.setPendingAdmin(deployerAddress); + vm.prank(deployerAddress); + bridgeHub.acceptAdmin(); + vm.startPrank(bridgeOwner); + bridgeHub.addStateTransitionManager(address(mockSTM)); + bridgeHub.addToken(address(testToken)); + bridgeHub.setSharedBridge(address(mockSharedBridge)); + vm.stopPrank(); + + if (randomCaller != deployerAddress && randomCaller != bridgeOwner) { + vm.prank(randomCaller); + vm.expectRevert(bytes("Bridgehub: not owner or admin")); + bridgeHub.createNewChain(chainId, address(mockSTM), address(testToken), uint256(123), admin, bytes("")); + } + + chainId = bound(chainId, 1, type(uint48).max); + bytes memory _newChainInitData = _createNewChainInitData( + isFreezable, + mockSelectors, + mockInitAddress, + mockInitCalldata + ); + + // bridgeHub.createNewChain => stateTransitionManager.createNewChain => this function sets the stateTransition mapping + // of `chainId`, let's emulate that using foundry cheatcodes or let's just use the extra function we introduced in our mockSTM + mockSTM.setStateTransition(chainId, address(mockChainContract)); + assertTrue(mockSTM.stateTransition(chainId) == address(mockChainContract)); + + vm.startPrank(deployerAddress); + vm.mockCall( + address(mockSTM), + abi.encodeWithSelector( + mockSTM.createNewChain.selector, + chainId, + address(testToken), + address(mockSharedBridge), + admin, + _newChainInitData + ), + bytes("") + ); + + newChainId = bridgeHub.createNewChain( + chainId, + address(mockSTM), + address(testToken), + uint256(chainId * 2), + admin, + _newChainInitData + ); + vm.stopPrank(); + vm.clearMockedCalls(); + + assertTrue(bridgeHub.stateTransitionManager(newChainId) == address(mockSTM)); + assertTrue(bridgeHub.baseToken(newChainId) == address(testToken)); + } + + function test_getStateTransition(uint256 mockChainId) public { + mockChainId = _setUpStateTransitionForChainId(mockChainId); + + // Now the following statements should be true as well: + assertTrue(bridgeHub.stateTransitionManager(mockChainId) == address(mockSTM)); + address returnedStateTransition = bridgeHub.getStateTransition(mockChainId); + + assertEq(returnedStateTransition, address(mockChainContract)); + } + + function test_proveL2MessageInclusion( + uint256 mockChainId, + uint256 mockBatchNumber, + uint256 mockIndex, + bytes32[] memory mockProof, + uint16 randomTxNumInBatch, + address randomSender, + bytes memory randomData + ) public { + mockChainId = _setUpStateTransitionForChainId(mockChainId); + + // Now the following statements should be true as well: + assertTrue(bridgeHub.stateTransitionManager(mockChainId) == address(mockSTM)); + assertTrue(bridgeHub.getStateTransition(mockChainId) == address(mockChainContract)); + + // Creating a random L2Message::l2Message so that we pass the correct parameters to `proveL2MessageInclusion` + L2Message memory l2Message = _createMockL2Message(randomTxNumInBatch, randomSender, randomData); + + // Since we have used random data for the `bridgeHub.proveL2MessageInclusion` function which basically forwards the call + // to the same function in the mailbox, we will mock the call to the mailbox to return true and see if it works. + vm.mockCall( + address(mockChainContract), + abi.encodeWithSelector( + mockChainContract.proveL2MessageInclusion.selector, + mockBatchNumber, + mockIndex, + l2Message, + mockProof + ), + abi.encode(true) + ); + + assertTrue(bridgeHub.proveL2MessageInclusion(mockChainId, mockBatchNumber, mockIndex, l2Message, mockProof)); + vm.clearMockedCalls(); + } + + function test_proveL2LogInclusion( + uint256 mockChainId, + uint256 mockBatchNumber, + uint256 mockIndex, + bytes32[] memory mockProof, + uint8 randomL2ShardId, + bool randomIsService, + uint16 randomTxNumInBatch, + address randomSender, + bytes32 randomKey, + bytes32 randomValue + ) public { + mockChainId = _setUpStateTransitionForChainId(mockChainId); + + // Now the following statements should be true as well: + assertTrue(bridgeHub.stateTransitionManager(mockChainId) == address(mockSTM)); + assertTrue(bridgeHub.getStateTransition(mockChainId) == address(mockChainContract)); + + // Creating a random L2Log::l2Log so that we pass the correct parameters to `proveL2LogInclusion` + L2Log memory l2Log = _createMockL2Log( + randomL2ShardId, + randomIsService, + randomTxNumInBatch, + randomSender, + randomKey, + randomValue + ); + + // Since we have used random data for the `bridgeHub.proveL2LogInclusion` function which basically forwards the call + // to the same function in the mailbox, we will mock the call to the mailbox to return true and see if it works. + vm.mockCall( + address(mockChainContract), + abi.encodeWithSelector( + mockChainContract.proveL2LogInclusion.selector, + mockBatchNumber, + mockIndex, + l2Log, + mockProof + ), + abi.encode(true) + ); + + assertTrue(bridgeHub.proveL2LogInclusion(mockChainId, mockBatchNumber, mockIndex, l2Log, mockProof)); + vm.clearMockedCalls(); + } + + function test_proveL1ToL2TransactionStatus( + uint256 randomChainId, + bytes32 randomL2TxHash, + uint256 randomL2BatchNumber, + uint256 randomL2MessageIndex, + uint16 randomL2TxNumberInBatch, + bytes32[] memory randomMerkleProof, + bool randomResultantBool, + bool txStatusBool + ) public { + randomChainId = _setUpStateTransitionForChainId(randomChainId); + + TxStatus txStatus; + + if (txStatusBool) { + txStatus = TxStatus.Failure; + } else { + txStatus = TxStatus.Success; + } + + vm.mockCall( + address(mockChainContract), + abi.encodeWithSelector( + mockChainContract.proveL1ToL2TransactionStatus.selector, + randomL2TxHash, + randomL2BatchNumber, + randomL2MessageIndex, + randomL2TxNumberInBatch, + randomMerkleProof, + txStatus + ), + abi.encode(randomResultantBool) + ); + + assertTrue( + bridgeHub.proveL1ToL2TransactionStatus( + randomChainId, + randomL2TxHash, + randomL2BatchNumber, + randomL2MessageIndex, + randomL2TxNumberInBatch, + randomMerkleProof, + txStatus + ) == randomResultantBool + ); + } + + function test_l2TransactionBaseCost( + uint256 mockChainId, + uint256 mockGasPrice, + uint256 mockL2GasLimit, + uint256 mockL2GasPerPubdataByteLimit, + uint256 mockL2TxnCost + ) public { + mockChainId = _setUpStateTransitionForChainId(mockChainId); + + vm.mockCall( + address(mockChainContract), + abi.encodeWithSelector( + mockChainContract.l2TransactionBaseCost.selector, + mockGasPrice, + mockL2GasLimit, + mockL2GasPerPubdataByteLimit + ), + abi.encode(mockL2TxnCost) + ); + + assertTrue( + bridgeHub.l2TransactionBaseCost(mockChainId, mockGasPrice, mockL2GasLimit, mockL2GasPerPubdataByteLimit) == + mockL2TxnCost + ); + vm.clearMockedCalls(); + } + + function test_requestL2TransactionDirect_ETHCase( + uint256 mockChainId, + uint256 mockMintValue, + address mockL2Contract, + uint256 mockL2Value, + bytes memory mockL2Calldata, + uint256 mockL2GasLimit, + uint256 mockL2GasPerPubdataByteLimit, + bytes[] memory mockFactoryDeps, + address mockRefundRecipient, + bytes[] memory mockRefundRecipientBH + ) public { + if (mockFactoryDeps.length > MAX_NEW_FACTORY_DEPS) { + mockFactoryDeps = _restrictArraySize(mockFactoryDeps, MAX_NEW_FACTORY_DEPS); + } + + L2TransactionRequestDirect memory l2TxnReqDirect = _createMockL2TransactionRequestDirect( + mockChainId, + mockMintValue, + mockL2Contract, + mockL2Value, + mockL2Calldata, + mockL2GasLimit, + mockL2GasPerPubdataByteLimit, + mockFactoryDeps, + mockRefundRecipient + ); + + l2TxnReqDirect.chainId = _setUpStateTransitionForChainId(l2TxnReqDirect.chainId); + + assertTrue(!(bridgeHub.baseToken(l2TxnReqDirect.chainId) == ETH_TOKEN_ADDRESS)); + _setUpBaseTokenForChainId(l2TxnReqDirect.chainId, true); + assertTrue(bridgeHub.baseToken(l2TxnReqDirect.chainId) == ETH_TOKEN_ADDRESS); + + _setUpSharedBridge(); + + address randomCaller = makeAddr("RANDOM_CALLER"); + vm.deal(randomCaller, l2TxnReqDirect.mintValue); + + assertTrue(bridgeHub.getStateTransition(l2TxnReqDirect.chainId) == address(mockChainContract)); + bytes32 canonicalHash = keccak256(abi.encode("CANONICAL_TX_HASH")); + //BridgehubL2TransactionRequest memory bhL2TxnRequest = + _createBhL2TxnRequest(mockRefundRecipientBH); + + vm.mockCall( + address(mockChainContract), + abi.encodeWithSelector(mockChainContract.bridgehubRequestL2Transaction.selector), + abi.encode(canonicalHash) + ); + + mockChainContract.setFeeParams(); + mockChainContract.setBaseTokenGasMultiplierPrice(uint128(1), uint128(1)); + mockChainContract.setBridgeHubAddress(address(bridgeHub)); + assertTrue(mockChainContract.getBridgeHubAddress() == address(bridgeHub)); + + vm.txGasPrice(0.05 ether); + + vm.prank(randomCaller); + bytes32 resultantHash = bridgeHub.requestL2TransactionDirect{value: randomCaller.balance}(l2TxnReqDirect); + + assertTrue(resultantHash == canonicalHash); + } + + function test_requestL2TransactionDirect_NonETHCase( + uint256 mockChainId, + uint256 mockMintValue, + address mockL2Contract, + uint256 mockL2Value, + bytes memory mockL2Calldata, + uint256 mockL2GasLimit, + uint256 mockL2GasPerPubdataByteLimit, + bytes[] memory mockFactoryDeps, + address mockRefundRecipient + ) public { + if (mockFactoryDeps.length > MAX_NEW_FACTORY_DEPS) { + mockFactoryDeps = _restrictArraySize(mockFactoryDeps, MAX_NEW_FACTORY_DEPS); + } + + L2TransactionRequestDirect memory l2TxnReqDirect = _createMockL2TransactionRequestDirect( + mockChainId, + mockMintValue, + mockL2Contract, + mockL2Value, + mockL2Calldata, + mockL2GasLimit, + mockL2GasPerPubdataByteLimit, + mockFactoryDeps, + mockRefundRecipient + ); + + l2TxnReqDirect.chainId = _setUpStateTransitionForChainId(l2TxnReqDirect.chainId); + + _setUpBaseTokenForChainId(l2TxnReqDirect.chainId, false); + _setUpSharedBridge(); + + assertTrue(bridgeHub.getStateTransition(l2TxnReqDirect.chainId) == address(mockChainContract)); + bytes32 canonicalHash = keccak256(abi.encode("CANONICAL_TX_HASH")); + + vm.mockCall( + address(mockChainContract), + abi.encodeWithSelector(mockChainContract.bridgehubRequestL2Transaction.selector), + abi.encode(canonicalHash) + ); + + mockChainContract.setFeeParams(); + mockChainContract.setBaseTokenGasMultiplierPrice(uint128(1), uint128(1)); + mockChainContract.setBridgeHubAddress(address(bridgeHub)); + assertTrue(mockChainContract.getBridgeHubAddress() == address(bridgeHub)); + + vm.txGasPrice(0.05 ether); + + address randomCaller = makeAddr("RANDOM_CALLER"); + vm.deal(randomCaller, 1 ether); + + vm.prank(randomCaller); + vm.expectRevert("Bridgehub: non-eth bridge with msg.value"); + bytes32 resultantHash = bridgeHub.requestL2TransactionDirect{value: randomCaller.balance}(l2TxnReqDirect); + + // Now, let's call the same function with zero msg.value + testToken.mint(randomCaller, l2TxnReqDirect.mintValue); + assertEq(testToken.balanceOf(randomCaller), l2TxnReqDirect.mintValue); + + vm.prank(randomCaller); + testToken.transfer(address(this), l2TxnReqDirect.mintValue); + assertEq(testToken.balanceOf(address(this)), l2TxnReqDirect.mintValue); + testToken.approve(address(mockSharedBridge), l2TxnReqDirect.mintValue); + + resultantHash = bridgeHub.requestL2TransactionDirect(l2TxnReqDirect); + + assertEq(canonicalHash, resultantHash); + } + + function test_requestL2TransactionTwoBridges_ETHCase( + uint256 chainId, + uint256 mintValue, + uint256 l2Value, + uint256 l2GasLimit, + uint256 l2GasPerPubdataByteLimit, + address refundRecipient, + uint256 secondBridgeValue, + bytes memory secondBridgeCalldata + ) public { + L2TransactionRequestTwoBridgesOuter memory l2TxnReq2BridgeOut = _createMockL2TransactionRequestTwoBridgesOuter( + chainId, + mintValue, + l2Value, + l2GasLimit, + l2GasPerPubdataByteLimit, + refundRecipient, + secondBridgeValue, + secondBridgeCalldata + ); + + l2TxnReq2BridgeOut.chainId = _setUpStateTransitionForChainId(l2TxnReq2BridgeOut.chainId); + + _setUpBaseTokenForChainId(l2TxnReq2BridgeOut.chainId, true); + assertTrue(bridgeHub.baseToken(l2TxnReq2BridgeOut.chainId) == ETH_TOKEN_ADDRESS); + + _setUpSharedBridge(); + assertTrue(bridgeHub.getStateTransition(l2TxnReq2BridgeOut.chainId) == address(mockChainContract)); + + uint256 callerMsgValue = l2TxnReq2BridgeOut.mintValue + l2TxnReq2BridgeOut.secondBridgeValue; + address randomCaller = makeAddr("RANDOM_CALLER"); + vm.deal(randomCaller, callerMsgValue); + + mockChainContract.setBridgeHubAddress(address(bridgeHub)); + + bytes32 canonicalHash = keccak256(abi.encode("CANONICAL_TX_HASH")); + + vm.mockCall( + address(mockChainContract), + abi.encodeWithSelector(mockChainContract.bridgehubRequestL2Transaction.selector), + abi.encode(canonicalHash) + ); + + vm.prank(randomCaller); + //bytes32 resultantHash = + bridgeHub.requestL2TransactionTwoBridges{value: randomCaller.balance}(l2TxnReq2BridgeOut); + + assertTrue(true); + } + + ///////////////////////////////////////////////////////// + // INTERNAL UTILITY FUNCTIONS + ///////////////////////////////////////////////////////// + + function _createMockL2TransactionRequestTwoBridgesOuter( + uint256 chainId, + uint256 mintValue, + uint256 l2Value, + uint256 l2GasLimit, + uint256 l2GasPerPubdataByteLimit, + address refundRecipient, + uint256 secondBridgeValue, + bytes memory secondBridgeCalldata + ) internal view returns (L2TransactionRequestTwoBridgesOuter memory) { + L2TransactionRequestTwoBridgesOuter memory l2Req; + + // Don't let the mintValue + secondBridgeValue go beyond type(uint256).max since that calculation is required to be done by our test: test_requestL2TransactionTwoBridges_ETHCase + mintValue = bound(mintValue, 1, (type(uint256).max) / 2); + secondBridgeValue = bound(secondBridgeValue, 1, (type(uint256).max) / 2); + + l2Req.chainId = chainId; + l2Req.mintValue = mintValue; + l2Req.l2Value = l2Value; + l2Req.l2GasLimit = l2GasLimit; + l2Req.l2GasPerPubdataByteLimit = l2GasPerPubdataByteLimit; + l2Req.refundRecipient = refundRecipient; + l2Req.secondBridgeAddress = address(mockSecondSharedBridge); + l2Req.secondBridgeValue = secondBridgeValue; + l2Req.secondBridgeCalldata = secondBridgeCalldata; + + return l2Req; + } + + function _createMockL2Message( + uint16 randomTxNumInBatch, + address randomSender, + bytes memory randomData + ) internal pure returns (L2Message memory) { + L2Message memory l2Message; + + l2Message.txNumberInBatch = randomTxNumInBatch; + l2Message.sender = randomSender; + l2Message.data = randomData; + + return l2Message; + } + + function _createMockL2Log( + uint8 randomL2ShardId, + bool randomIsService, + uint16 randomTxNumInBatch, + address randomSender, + bytes32 randomKey, + bytes32 randomValue + ) internal pure returns (L2Log memory) { + L2Log memory l2Log; + + l2Log.l2ShardId = randomL2ShardId; + l2Log.isService = randomIsService; + l2Log.txNumberInBatch = randomTxNumInBatch; + l2Log.sender = randomSender; + l2Log.key = randomKey; + l2Log.value = randomValue; + + return l2Log; + } + + function _createNewChainInitData( + bool isFreezable, + bytes4[] memory mockSelectors, + address, //mockInitAddress, + bytes memory //mockInitCalldata + ) internal returns (bytes memory) { + bytes4[] memory singleSelector = new bytes4[](1); + singleSelector[0] = bytes4(0xabcdef12); + + Diamond.FacetCut memory facetCut; + Diamond.DiamondCutData memory diamondCutData; + + facetCut.facet = address(this); // for a random address, it will fail the check of _facet.code.length > 0 + facetCut.action = Diamond.Action.Add; + facetCut.isFreezable = isFreezable; + if (mockSelectors.length == 0) { + mockSelectors = singleSelector; + } + facetCut.selectors = mockSelectors; + + Diamond.FacetCut[] memory facetCuts = new Diamond.FacetCut[](1); + facetCuts[0] = facetCut; + + diamondCutData.facetCuts = facetCuts; + diamondCutData.initAddress = address(0); + diamondCutData.initCalldata = ""; + + mockSTM.setInitialCutHash(diamondCutData); + + return abi.encode(diamondCutData); + } + + function _setUpStateTransitionForChainId(uint256 mockChainId) internal returns (uint256 mockChainIdInRange) { + mockChainId = bound(mockChainId, 2, type(uint48).max); + mockChainIdInRange = mockChainId; + vm.prank(bridgeOwner); + bridgeHub.addStateTransitionManager(address(mockSTM)); + + // We need to set the stateTransitionManager of the mockChainId to mockSTM + // There is no function to do that in the bridgeHub + // So, perhaps we will have to manually set the values in the stateTransitionManager mapping via a foundry cheatcode + assertTrue(!(bridgeHub.stateTransitionManager(mockChainId) == address(mockSTM))); + + stdstore.target(address(bridgeHub)).sig("stateTransitionManager(uint256)").with_key(mockChainId).checked_write( + address(mockSTM) + ); + + // Now in the StateTransitionManager that has been set for our mockChainId, we set the stateTransition contract as our mockChainContract + mockSTM.setStateTransition(mockChainId, address(mockChainContract)); + } + + function _setUpBaseTokenForChainId(uint256 mockChainId, bool tokenIsETH) internal { + address baseToken = tokenIsETH ? ETH_TOKEN_ADDRESS : address(testToken); + + stdstore.target(address(bridgeHub)).sig("baseToken(uint256)").with_key(mockChainId).checked_write(baseToken); + } + + function _setUpSharedBridge() internal { + vm.prank(bridgeOwner); + bridgeHub.setSharedBridge(address(mockSharedBridge)); + } + + function _createMockL2TransactionRequestDirect( + uint256 mockChainId, + uint256 mockMintValue, + address mockL2Contract, + uint256 mockL2Value, + bytes memory mockL2Calldata, + uint256 mockL2GasLimit, + uint256, //mockL2GasPerPubdataByteLimit, + bytes[] memory mockFactoryDeps, + address mockRefundRecipient + ) internal pure returns (L2TransactionRequestDirect memory) { + L2TransactionRequestDirect memory l2TxnReqDirect; + + l2TxnReqDirect.chainId = mockChainId; + l2TxnReqDirect.mintValue = mockMintValue; + l2TxnReqDirect.l2Contract = mockL2Contract; + l2TxnReqDirect.l2Value = mockL2Value; + l2TxnReqDirect.l2Calldata = mockL2Calldata; + l2TxnReqDirect.l2GasLimit = mockL2GasLimit; + l2TxnReqDirect.l2GasPerPubdataByteLimit = REQUIRED_L2_GAS_PRICE_PER_PUBDATA; + l2TxnReqDirect.factoryDeps = mockFactoryDeps; + l2TxnReqDirect.refundRecipient = mockRefundRecipient; + + return l2TxnReqDirect; + } + + function _createBhL2TxnRequest( + bytes[] memory mockFactoryDepsBH + ) internal returns (BridgehubL2TransactionRequest memory) { + BridgehubL2TransactionRequest memory bhL2TxnRequest; + + bhL2TxnRequest.sender = makeAddr("BH_L2_REQUEST_SENDER"); + bhL2TxnRequest.contractL2 = makeAddr("BH_L2_REQUEST_CONTRACT"); + bhL2TxnRequest.mintValue = block.timestamp; + bhL2TxnRequest.l2Value = block.timestamp * 2; + bhL2TxnRequest.l2Calldata = abi.encode("mock L2 Calldata"); + bhL2TxnRequest.l2GasLimit = block.timestamp * 3; + bhL2TxnRequest.l2GasPerPubdataByteLimit = block.timestamp * 4; + bhL2TxnRequest.factoryDeps = mockFactoryDepsBH; + bhL2TxnRequest.refundRecipient = makeAddr("BH_L2_REQUEST_REFUND_RECIPIENT"); + + return bhL2TxnRequest; + } + + function _restrictArraySize(bytes[] memory longArray, uint256 newSize) internal pure returns (bytes[] memory) { + bytes[] memory shortArray = new bytes[](newSize); + + for (uint i; i < newSize; i++) { + shortArray[i] = longArray[i]; + } + + return shortArray; + } + + ///////////////////////////////////////////////////////// + // OLDER (HIGH-LEVEL MOCKED) TESTS + //////////////////////////////////////////////////////// + + function test_proveL2MessageInclusion_old( + uint256 mockChainId, + uint256 mockBatchNumber, + uint256 mockIndex, + bytes32[] memory mockProof, + uint16 randomTxNumInBatch, + address randomSender, + bytes memory randomData + ) public { + vm.startPrank(bridgeOwner); + bridgeHub.addStateTransitionManager(address(mockSTM)); + vm.stopPrank(); + + L2Message memory l2Message = _createMockL2Message(randomTxNumInBatch, randomSender, randomData); + + vm.mockCall( + address(bridgeHub), + abi.encodeWithSelector( + bridgeHub.proveL2MessageInclusion.selector, + mockChainId, + mockBatchNumber, + mockIndex, + l2Message, + mockProof + ), + abi.encode(true) + ); + + assertTrue(bridgeHub.proveL2MessageInclusion(mockChainId, mockBatchNumber, mockIndex, l2Message, mockProof)); + } + + function test_proveL2LogInclusion_old( + uint256 mockChainId, + uint256 mockBatchNumber, + uint256 mockIndex, + bytes32[] memory mockProof, + uint8 randomL2ShardId, + bool randomIsService, + uint16 randomTxNumInBatch, + address randomSender, + bytes32 randomKey, + bytes32 randomValue + ) public { + vm.startPrank(bridgeOwner); + bridgeHub.addStateTransitionManager(address(mockSTM)); + vm.stopPrank(); + + L2Log memory l2Log = _createMockL2Log( + randomL2ShardId, + randomIsService, + randomTxNumInBatch, + randomSender, + randomKey, + randomValue + ); + + vm.mockCall( + address(bridgeHub), + abi.encodeWithSelector( + bridgeHub.proveL2LogInclusion.selector, + mockChainId, + mockBatchNumber, + mockIndex, + l2Log, + mockProof + ), + abi.encode(true) + ); + + assertTrue(bridgeHub.proveL2LogInclusion(mockChainId, mockBatchNumber, mockIndex, l2Log, mockProof)); + } + + function test_proveL1ToL2TransactionStatus_old( + uint256 randomChainId, + bytes32 randomL2TxHash, + uint256 randomL2BatchNumber, + uint256 randomL2MessageIndex, + uint16 randomL2TxNumberInBatch, + bytes32[] memory randomMerkleProof, + bool randomResultantBool + ) public { + vm.startPrank(bridgeOwner); + bridgeHub.addStateTransitionManager(address(mockSTM)); + vm.stopPrank(); + + TxStatus txStatus; + + if (randomChainId % 2 == 0) { + txStatus = TxStatus.Failure; + } else { + txStatus = TxStatus.Success; + } + + vm.mockCall( + address(bridgeHub), + abi.encodeWithSelector( + bridgeHub.proveL1ToL2TransactionStatus.selector, + randomChainId, + randomL2TxHash, + randomL2BatchNumber, + randomL2MessageIndex, + randomL2TxNumberInBatch, + randomMerkleProof, + txStatus + ), + abi.encode(randomResultantBool) + ); + + assertTrue( + bridgeHub.proveL1ToL2TransactionStatus( + randomChainId, + randomL2TxHash, + randomL2BatchNumber, + randomL2MessageIndex, + randomL2TxNumberInBatch, + randomMerkleProof, + txStatus + ) == randomResultantBool + ); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/ClaimFailedDeposit.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/ClaimFailedDeposit.t.sol new file mode 100644 index 000000000000..3dff333c5fb9 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/ClaimFailedDeposit.t.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {L1Erc20BridgeTest} from "./_L1Erc20Bridge_Shared.t.sol"; +import {StdStorage, stdStorage} from "forge-std/Test.sol"; + +contract ClaimFailedDepositTest is L1Erc20BridgeTest { + using stdStorage for StdStorage; + + event ClaimedFailedDeposit(address indexed to, address indexed l1Token, uint256 amount); + + function test_RevertWhen_ClaimAmountIsZero() public { + vm.expectRevert(bytes("2T")); + bytes32[] memory merkleProof; + bridge.claimFailedDeposit(randomSigner, address(token), dummyL2DepositTxHash, 0, 0, 0, merkleProof); + } + + function test_claimFailedDepositSuccessfully() public { + uint256 depositedAmountBefore = bridge.depositAmount(alice, address(token), dummyL2DepositTxHash); + assertEq(depositedAmountBefore, 0); + + uint256 amount = 16; + stdstore + .target(address(bridge)) + .sig("depositAmount(address,address,bytes32)") + .with_key(alice) + .with_key(address(token)) + .with_key(dummyL2DepositTxHash) + .checked_write(amount); + + uint256 depositedAmountAfterDeposit = bridge.depositAmount(alice, address(token), dummyL2DepositTxHash); + assertEq(depositedAmountAfterDeposit, amount); + + vm.prank(alice); + vm.expectEmit(true, true, true, true, address(bridge)); + emit ClaimedFailedDeposit(alice, address(token), amount); + bytes32[] memory merkleProof; + bridge.claimFailedDeposit(alice, address(token), dummyL2DepositTxHash, 0, 0, 0, merkleProof); + + uint256 depositedAmountAfterWithdrawal = bridge.depositAmount(alice, address(token), dummyL2DepositTxHash); + assertEq(depositedAmountAfterWithdrawal, 0); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Deposit.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Deposit.t.sol new file mode 100644 index 000000000000..941e224bb246 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Deposit.t.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {L1Erc20BridgeTest} from "./_L1Erc20Bridge_Shared.t.sol"; + +contract DepositTest is L1Erc20BridgeTest { + event DepositInitiated( + bytes32 indexed l2DepositTxHash, + address indexed from, + address indexed to, + address l1Token, + uint256 amount + ); + + function test_RevertWhen_depositAmountIsZero() public { + vm.expectRevert(bytes("0T")); + bridge.deposit(randomSigner, address(token), 0, 0, 0, address(0)); + } + + function test_RevertWhen_legacyDepositAmountIsZero() public { + vm.expectRevert(bytes("0T")); + bridge.deposit(randomSigner, address(token), 0, 0, 0); + } + + function test_RevertWhen_depositTokenIsNotContract() public { + vm.expectRevert(); + bridge.deposit(randomSigner, makeAddr("EOA"), 1, 0, 0); + } + + function test_RevertWhen_legacyDepositTokenIsNotContract() public { + vm.expectRevert(); + bridge.deposit(randomSigner, makeAddr("EOA"), 1, 0, 0); + } + + function test_RevertWhen_depositTokenTransferFailed() public { + vm.expectRevert("ERC20: insufficient allowance"); + bridge.deposit(randomSigner, address(token), 1, 0, 0); + } + + function test_RevertWhen_legacyDepositTokenTransferFailed() public { + vm.expectRevert("ERC20: insufficient allowance"); + bridge.deposit(randomSigner, address(token), 1, 0, 0); + } + + function test_RevertWhen_depositTransferAmountIsDifferent() public { + uint256 amount = 2; + vm.prank(alice); + feeOnTransferToken.approve(address(bridge), amount); + vm.expectRevert(bytes("3T")); + vm.prank(alice); + bridge.deposit(randomSigner, address(feeOnTransferToken), amount, 0, 0); + } + + function test_RevertWhen_legacyDepositTransferAmountIsDifferent() public { + uint256 amount = 4; + vm.prank(alice); + feeOnTransferToken.approve(address(bridge), amount); + vm.expectRevert(bytes("3T")); + vm.prank(alice); + bridge.deposit(randomSigner, address(feeOnTransferToken), amount, 0, 0); + } + + function test_depositSuccessfully() public { + uint256 amount = 8; + vm.prank(alice); + token.approve(address(bridge), amount); + vm.prank(alice); + vm.expectEmit(true, true, true, true, address(bridge)); + emit DepositInitiated(dummyL2DepositTxHash, alice, randomSigner, address(token), amount); + bytes32 txHash = bridge.deposit(randomSigner, address(token), amount, 0, 0, address(0)); + assertEq(txHash, dummyL2DepositTxHash); + + uint256 depositedAmount = bridge.depositAmount(alice, address(token), dummyL2DepositTxHash); + assertEq(amount, depositedAmount); + } + + function test_legacyDepositSuccessfully() public { + uint256 depositedAmountBefore = bridge.depositAmount(alice, address(token), dummyL2DepositTxHash); + assertEq(depositedAmountBefore, 0); + + uint256 amount = 8; + vm.prank(alice); + token.approve(address(bridge), amount); + vm.prank(alice); + vm.expectEmit(true, true, true, true, address(bridge)); + emit DepositInitiated(dummyL2DepositTxHash, alice, randomSigner, address(token), amount); + bytes32 txHash = bridge.deposit(randomSigner, address(token), amount, 0, 0); + assertEq(txHash, dummyL2DepositTxHash); + + uint256 depositedAmount = bridge.depositAmount(alice, address(token), dummyL2DepositTxHash); + assertEq(amount, depositedAmount); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/FinalizeWithdrawal.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/FinalizeWithdrawal.sol new file mode 100644 index 000000000000..7abc330134bc --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/FinalizeWithdrawal.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {L1Erc20BridgeTest} from "./_L1Erc20Bridge_Shared.t.sol"; +import {StdStorage, stdStorage} from "forge-std/Test.sol"; + +contract FinalizeWithdrawalTest is L1Erc20BridgeTest { + using stdStorage for StdStorage; + + event WithdrawalFinalized(address indexed to, address indexed l1Token, uint256 amount); + + function test_RevertWhen_withdrawalFinalized() public { + uint256 l2BatchNumber = 0; + uint256 l2MessageIndex = 1; + stdstore + .target(address(bridge)) + .sig("isWithdrawalFinalized(uint256,uint256)") + .with_key(l2BatchNumber) + .with_key(l2MessageIndex) + .checked_write(true); + + assertTrue(bridge.isWithdrawalFinalized(l2BatchNumber, l2MessageIndex)); + + vm.expectRevert(bytes("pw")); + bytes32[] memory merkleProof; + bridge.finalizeWithdrawal(l2BatchNumber, l2MessageIndex, 0, "", merkleProof); + } + + function test_finalizeWithdrawalSuccessfully() public { + uint256 l2BatchNumber = 3; + uint256 l2MessageIndex = 4; + uint256 amount = 999; + + assertFalse(bridge.isWithdrawalFinalized(l2BatchNumber, l2MessageIndex)); + + dummySharedBridge.setDataToBeReturnedInFinalizeWithdrawal(alice, address(token), amount); + + vm.prank(alice); + vm.expectEmit(true, true, true, true, address(bridge)); + emit WithdrawalFinalized(alice, address(token), amount); + bytes32[] memory merkleProof; + bridge.finalizeWithdrawal(l2BatchNumber, l2MessageIndex, 0, "", merkleProof); + + // withdrawal finalization should be handled in the shared bridge, so it shouldn't + // change in the L1 ERC20 bridge after finalization. + assertFalse(bridge.isWithdrawalFinalized(l2BatchNumber, l2MessageIndex)); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Getters.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Getters.t.sol new file mode 100644 index 000000000000..f0a86f197f79 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Getters.t.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {L1Erc20BridgeTest} from "./_L1Erc20Bridge_Shared.t.sol"; +import {StdStorage, stdStorage} from "forge-std/Test.sol"; + +contract GettersTest is L1Erc20BridgeTest { + using stdStorage for StdStorage; + + function test_l2TokenAddress() public { + address daiOnEthereum = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + address daiOnEra = 0x4B9eb6c0b6ea15176BBF62841C6B2A8a398cb656; + + stdstore.target(address(bridge)).sig("l2Bridge()").checked_write( + address(0x11f943b2c77b743AB90f4A0Ae7d5A4e7FCA3E102) + ); + + stdstore.target(address(bridge)).sig("l2TokenBeacon()").checked_write( + address(0x1Eb710030273e529A6aD7E1e14D4e601765Ba3c6) + ); + + stdstore.target(address(bridge)).sig("l2TokenProxyBytecodeHash()").checked_write( + bytes32(0x01000121a363b3fbec270986067c1b553bf540c30a6f186f45313133ff1a1019) + ); + + address token = bridge.l2TokenAddress(daiOnEthereum); + assertEq(token, daiOnEra); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Initialization.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Initialization.t.sol new file mode 100644 index 000000000000..3fb03da8f523 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Initialization.t.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {L1Erc20BridgeTest} from "./_L1Erc20Bridge_Shared.t.sol"; + +contract InitializationTest is L1Erc20BridgeTest { + function test_RevertWhen_DoubleInitialization() public { + vm.expectRevert(bytes("1B")); + bridge.initialize(); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Reentrancy.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Reentrancy.t.sol new file mode 100644 index 000000000000..2fcb7e4281aa --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Reentrancy.t.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {StdStorage, stdStorage} from "forge-std/Test.sol"; +import {L1Erc20BridgeTest} from "./_L1Erc20Bridge_Shared.t.sol"; +import {ReenterL1ERC20Bridge} from "solpp/dev-contracts/test/ReenterL1ERC20Bridge.sol"; + +contract ReentrancyTest is L1Erc20BridgeTest { + using stdStorage for StdStorage; + + function _depositExpectRevertOnReentrancy() internal { + uint256 amount = 8; + vm.prank(alice); + token.approve(address(bridgeReenterItself), amount); + + vm.prank(alice); + vm.expectRevert(bytes("r1")); + bridgeReenterItself.deposit(randomSigner, address(token), amount, 0, 0, address(0)); + } + + function _legacyDepositExpectRevertOnReentrancy() internal { + uint256 amount = 8; + vm.prank(alice); + token.approve(address(bridgeReenterItself), amount); + + vm.prank(alice); + vm.expectRevert(bytes("r1")); + bridgeReenterItself.deposit(randomSigner, address(token), amount, 0, 0); + } + + function _claimFailedDepositExpectRevertOnReentrancy() internal { + uint256 amount = 16; + stdstore + .target(address(bridgeReenterItself)) + .sig("depositAmount(address,address,bytes32)") + .with_key(alice) + .with_key(address(token)) + .with_key(dummyL2DepositTxHash) + .checked_write(amount); + + vm.prank(alice); + bytes32[] memory merkleProof; + vm.expectRevert(bytes("r1")); + bridgeReenterItself.claimFailedDeposit(alice, address(token), dummyL2DepositTxHash, 0, 0, 0, merkleProof); + } + + function _finalizeWithdrawalExpectRevertOnReentrancy() internal { + uint256 l2BatchNumber = 3; + uint256 l2MessageIndex = 4; + + vm.prank(alice); + vm.expectRevert(bytes("r1")); + bytes32[] memory merkleProof; + bridgeReenterItself.finalizeWithdrawal(l2BatchNumber, l2MessageIndex, 0, "", merkleProof); + } + + function test_depositReenterDeposit() public { + reenterL1ERC20Bridge.setFunctionToCall(ReenterL1ERC20Bridge.FunctionToCall.Deposit); + _depositExpectRevertOnReentrancy(); + } + + function test_depositReenterLegacyDeposit() public { + reenterL1ERC20Bridge.setFunctionToCall(ReenterL1ERC20Bridge.FunctionToCall.LegacyDeposit); + _depositExpectRevertOnReentrancy(); + } + + function test_depositReenterFinalizeWithdrawal() public { + reenterL1ERC20Bridge.setFunctionToCall(ReenterL1ERC20Bridge.FunctionToCall.FinalizeWithdrawal); + _depositExpectRevertOnReentrancy(); + } + + function test_depositReenterClaimFailedDeposit() public { + reenterL1ERC20Bridge.setFunctionToCall(ReenterL1ERC20Bridge.FunctionToCall.ClaimFailedDeposit); + _depositExpectRevertOnReentrancy(); + } + + function test_legacyDepositReenterDeposit() public { + reenterL1ERC20Bridge.setFunctionToCall(ReenterL1ERC20Bridge.FunctionToCall.Deposit); + _legacyDepositExpectRevertOnReentrancy(); + } + + function test_legacyDepositReenterLegacyDeposit() public { + reenterL1ERC20Bridge.setFunctionToCall(ReenterL1ERC20Bridge.FunctionToCall.LegacyDeposit); + _legacyDepositExpectRevertOnReentrancy(); + } + + function test_legacyDepositReenterFinalizeWithdrawal() public { + reenterL1ERC20Bridge.setFunctionToCall(ReenterL1ERC20Bridge.FunctionToCall.FinalizeWithdrawal); + _legacyDepositExpectRevertOnReentrancy(); + } + + function test_legacyDepositReenterClaimFailedDeposit() public { + reenterL1ERC20Bridge.setFunctionToCall(ReenterL1ERC20Bridge.FunctionToCall.ClaimFailedDeposit); + _legacyDepositExpectRevertOnReentrancy(); + } + + function test_claimFailedDepositReenterDeposit() public { + reenterL1ERC20Bridge.setFunctionToCall(ReenterL1ERC20Bridge.FunctionToCall.Deposit); + _claimFailedDepositExpectRevertOnReentrancy(); + } + + function test_claimFailedDepositReenterLegacyDeposit() public { + reenterL1ERC20Bridge.setFunctionToCall(ReenterL1ERC20Bridge.FunctionToCall.LegacyDeposit); + _claimFailedDepositExpectRevertOnReentrancy(); + } + + function test_claimFailedDepositReenterFinalizeWithdrawal() public { + reenterL1ERC20Bridge.setFunctionToCall(ReenterL1ERC20Bridge.FunctionToCall.FinalizeWithdrawal); + _claimFailedDepositExpectRevertOnReentrancy(); + } + + function test_claimFailedDepositReenterClaimFailedDeposit() public { + reenterL1ERC20Bridge.setFunctionToCall(ReenterL1ERC20Bridge.FunctionToCall.ClaimFailedDeposit); + _claimFailedDepositExpectRevertOnReentrancy(); + } + + function test_finalizeWithdrawalReenterDeposit() public { + reenterL1ERC20Bridge.setFunctionToCall(ReenterL1ERC20Bridge.FunctionToCall.Deposit); + _finalizeWithdrawalExpectRevertOnReentrancy(); + } + + function test_finalizeWithdrawalReenterLegacyDeposit() public { + reenterL1ERC20Bridge.setFunctionToCall(ReenterL1ERC20Bridge.FunctionToCall.LegacyDeposit); + _finalizeWithdrawalExpectRevertOnReentrancy(); + } + + function test_finalizeWithdrawalReenterFinalizeWithdrawal() public { + reenterL1ERC20Bridge.setFunctionToCall(ReenterL1ERC20Bridge.FunctionToCall.FinalizeWithdrawal); + _finalizeWithdrawalExpectRevertOnReentrancy(); + } + + function test_finalizeWithdrawalReenterClaimFailedDeposit() public { + reenterL1ERC20Bridge.setFunctionToCall(ReenterL1ERC20Bridge.FunctionToCall.ClaimFailedDeposit); + _finalizeWithdrawalExpectRevertOnReentrancy(); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/_L1Erc20Bridge_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/_L1Erc20Bridge_Shared.t.sol new file mode 100644 index 000000000000..d2e37e11524a --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/_L1Erc20Bridge_Shared.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {Test} from "forge-std/Test.sol"; + +import {L1ERC20Bridge} from "solpp/bridge/L1ERC20Bridge.sol"; +import {IL1SharedBridge} from "solpp/bridge/interfaces/IL1SharedBridge.sol"; +import {TestnetERC20Token} from "solpp/dev-contracts/TestnetERC20Token.sol"; +import {FeeOnTransferToken} from "solpp/dev-contracts/FeeOnTransferToken.sol"; +import {DummySharedBridge} from "solpp/dev-contracts/test/DummySharedBridge.sol"; +import {ReenterL1ERC20Bridge} from "solpp/dev-contracts/test/ReenterL1ERC20Bridge.sol"; +import {Forwarder} from "solpp/dev-contracts/Forwarder.sol"; +import {Utils} from "../../Utils/Utils.sol"; + +contract L1Erc20BridgeTest is Test { + L1ERC20Bridge internal bridge; + DummySharedBridge internal dummySharedBridge; + + ReenterL1ERC20Bridge internal reenterL1ERC20Bridge; + L1ERC20Bridge internal bridgeReenterItself; + + TestnetERC20Token internal token; + TestnetERC20Token internal feeOnTransferToken; + address internal randomSigner; + address internal alice; + bytes32 internal dummyL2DepositTxHash; + + constructor() { + randomSigner = makeAddr("randomSigner"); + dummyL2DepositTxHash = Utils.randomBytes32("dummyL2DepositTxHash"); + alice = makeAddr("alice"); + + dummySharedBridge = new DummySharedBridge(dummyL2DepositTxHash); + bridge = new L1ERC20Bridge(IL1SharedBridge(address(dummySharedBridge))); + + reenterL1ERC20Bridge = new ReenterL1ERC20Bridge(); + bridgeReenterItself = new L1ERC20Bridge(IL1SharedBridge(address(reenterL1ERC20Bridge))); + reenterL1ERC20Bridge.setBridge(bridgeReenterItself); + + token = new TestnetERC20Token("TestnetERC20Token", "TET", 18); + feeOnTransferToken = new FeeOnTransferToken("FeeOnTransferToken", "FOT", 18); + token.mint(alice, type(uint256).max); + feeOnTransferToken.mint(alice, type(uint256).max); + } + + // add this to be excluded from coverage report + function test() internal virtual {} +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridge.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridge.t.sol new file mode 100644 index 000000000000..a6df18723213 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridge.t.sol @@ -0,0 +1,639 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import {L1SharedBridge} from "solpp/bridge/L1SharedBridge.sol"; +import {Bridgehub} from "solpp/bridgehub/Bridgehub.sol"; +import {L1ERC20Bridge} from "solpp/bridge/L1ERC20Bridge.sol"; +import {ETH_TOKEN_ADDRESS} from "solpp/common/Config.sol"; +import {IBridgehub, L2TransactionRequestTwoBridgesInner} from "solpp/bridgehub/IBridgehub.sol"; +import {L2Message, TxStatus} from "solpp/common/Messaging.sol"; +import {IMailbox} from "solpp/state-transition/chain-interfaces/IMailbox.sol"; +import {IL1ERC20Bridge} from "solpp/bridge/interfaces/IL1ERC20Bridge.sol"; +import {IL1SharedBridge} from "solpp/bridge/interfaces/IL1SharedBridge.sol"; +import {TestnetERC20Token} from "solpp/dev-contracts/TestnetERC20Token.sol"; +import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR} from "solpp/common/L2ContractAddresses.sol"; +import {ERA_CHAIN_ID, ERA_DIAMOND_PROXY} from "solpp/common/Config.sol"; +import {IGetters} from "solpp/state-transition/chain-interfaces/IGetters.sol"; + +// import "forge-std/console.sol"; + +contract L1SharedBridgeTest is Test { + event BridgehubDepositBaseTokenInitiated( + uint256 indexed chainId, + address indexed from, + address l1Token, + uint256 amount + ); + + event BridgehubDepositInitiated( + uint256 indexed chainId, + bytes32 indexed txDataHash, + address indexed from, + address to, + address l1Token, + uint256 amount + ); + + event BridgehubDepositFinalized( + uint256 indexed chainId, + bytes32 indexed txDataHash, + bytes32 indexed l2DepositTxHash + ); + + event WithdrawalFinalizedSharedBridge( + uint256 indexed chainId, + address indexed to, + address indexed l1Token, + uint256 amount + ); + + event ClaimedFailedDepositSharedBridge( + uint256 indexed chainId, + address indexed to, + address indexed l1Token, + uint256 amount + ); + + event LegacyDepositInitiated( + uint256 indexed chainId, + bytes32 indexed l2DepositTxHash, + address indexed from, + address to, + address l1Token, + uint256 amount + ); + + L1SharedBridge sharedBridgeImpl; + L1SharedBridge sharedBridge; + address bridgehubAddress; + address l1ERC20BridgeAddress; + address l2SharedBridge; + TestnetERC20Token token; + uint256 eraFirstPostUpgradeBatch; + + address owner; + address admin; + address zkSync; + address alice; + address bob; + uint256 chainId; + uint256 amount = 100; + bytes32 txHash; + + uint256 l2BatchNumber; + uint256 l2MessageIndex; + uint16 l2TxNumberInBatch; + bytes32[] merkleProof; + + // storing depoistHappend[chainId][l2TxHash] = txDataHash. DepositHappened is 3rd so 3 -1 + dependency storage slots + uint256 depositLocationInStorage = uint256(3 - 1 + 1 + 1); + uint256 chainBalanceLocationInStorage = uint256(6 - 1 + 1 + 1); + + function setUp() public { + owner = makeAddr("owner"); + admin = makeAddr("admin"); + // zkSync = makeAddr("zkSync"); + bridgehubAddress = makeAddr("bridgehub"); + alice = makeAddr("alice"); + // bob = makeAddr("bob"); + address l1WethAddress = makeAddr("weth"); + l1ERC20BridgeAddress = makeAddr("l1ERC20Bridge"); + l2SharedBridge = makeAddr("l2SharedBridge"); + + txHash = bytes32(uint256(uint160(makeAddr("txHash")))); + l2BatchNumber = uint256(uint160(makeAddr("l2BatchNumber"))); + l2MessageIndex = uint256(uint160(makeAddr("l2MessageIndex"))); + l2TxNumberInBatch = uint16(uint160(makeAddr("l2TxNumberInBatch"))); + merkleProof = new bytes32[](1); + eraFirstPostUpgradeBatch = 1; + + chainId = 1; + + token = new TestnetERC20Token("TestnetERC20Token", "TET", 18); + sharedBridgeImpl = new L1SharedBridge( + l1WethAddress, + IBridgehub(bridgehubAddress), + IL1ERC20Bridge(l1ERC20BridgeAddress) + ); + TransparentUpgradeableProxy sharedBridgeProxy = new TransparentUpgradeableProxy( + address(sharedBridgeImpl), + admin, + abi.encodeWithSelector(L1SharedBridge.initialize.selector, owner, eraFirstPostUpgradeBatch) + ); + sharedBridge = L1SharedBridge(payable(sharedBridgeProxy)); + vm.prank(owner); + sharedBridge.initializeChainGovernance(chainId, l2SharedBridge); + vm.prank(owner); + sharedBridge.initializeChainGovernance(ERA_CHAIN_ID, l2SharedBridge); + } + + function test_bridgehubDepositBaseToken_Eth() public { + vm.deal(bridgehubAddress, amount); + vm.prank(bridgehubAddress); + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit BridgehubDepositBaseTokenInitiated(chainId, alice, ETH_TOKEN_ADDRESS, amount); + sharedBridge.bridgehubDepositBaseToken{value: amount}(chainId, alice, ETH_TOKEN_ADDRESS, amount); + } + + function test_bridgehubDepositBaseToken_Erc() public { + token.mint(alice, amount); + vm.prank(alice); + token.approve(address(sharedBridge), amount); + vm.prank(bridgehubAddress); + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit BridgehubDepositBaseTokenInitiated(chainId, alice, address(token), amount); + sharedBridge.bridgehubDepositBaseToken(chainId, alice, address(token), amount); + } + + function test_bridgehubDeposit_Eth() public { + vm.deal(bridgehubAddress, amount); + vm.prank(bridgehubAddress); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(address(token)) + ); + bytes32 txDataHash = keccak256(abi.encode(alice, ETH_TOKEN_ADDRESS, amount)); + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit BridgehubDepositInitiated(chainId, txDataHash, alice, zkSync, ETH_TOKEN_ADDRESS, amount); + L2TransactionRequestTwoBridgesInner memory output = sharedBridge.bridgehubDeposit{value: amount}( + chainId, + alice, + 0, + abi.encode(ETH_TOKEN_ADDRESS, 0, bob) + ); + } + + function test_bridgehubDeposit_Erc() public { + token.mint(alice, amount); + vm.prank(alice); + token.approve(address(sharedBridge), amount); + vm.prank(bridgehubAddress); + vm.expectEmit(true, true, true, true, address(sharedBridge)); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(ETH_TOKEN_ADDRESS) + ); + bytes32 txDataHash = keccak256(abi.encode(alice, address(token), amount)); + emit BridgehubDepositInitiated(chainId, txDataHash, alice, zkSync, address(token), amount); + L2TransactionRequestTwoBridgesInner memory output = sharedBridge.bridgehubDeposit( + chainId, + alice, + 0, + abi.encode(address(token), amount, bob) + ); + } + + function test_bridgehubConfirmL2Transaction() public { + vm.expectEmit(true, true, true, true, address(sharedBridge)); + bytes32 txDataHash = keccak256(abi.encode(alice, address(token), amount)); + emit BridgehubDepositFinalized(chainId, txDataHash, txHash); + vm.prank(bridgehubAddress); + sharedBridge.bridgehubConfirmL2Transaction(chainId, txDataHash, txHash); + } + + function test_claimFailedDeposit_Erc() public { + token.mint(address(sharedBridge), amount); + + bytes32 txDataHash = keccak256(abi.encode(alice, address(token), amount)); + vm.store( + address(sharedBridge), + keccak256(abi.encode(txHash, keccak256(abi.encode(chainId, depositLocationInStorage)))), + txDataHash + ); + require(sharedBridge.depositHappened(chainId, txHash) == txDataHash, "Deposit not set"); + + vm.store( + address(sharedBridge), + keccak256( + abi.encode( + uint256(uint160(address(token))), + keccak256(abi.encode(chainId, chainBalanceLocationInStorage)) + ) + ), + bytes32(amount) + ); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL1ToL2TransactionStatus.selector, + chainId, + txHash, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + merkleProof, + TxStatus.Failure + ), + abi.encode(true) + ); + + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit ClaimedFailedDepositSharedBridge(chainId, alice, address(token), amount); + sharedBridge.claimFailedDeposit( + chainId, + alice, + address(token), + amount, + txHash, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + merkleProof + ); + } + + function test_claimFailedDeposit_Eth() public { + vm.deal(address(sharedBridge), amount); + + bytes32 txDataHash = keccak256(abi.encode(alice, ETH_TOKEN_ADDRESS, amount)); + vm.store( + address(sharedBridge), + keccak256(abi.encode(txHash, keccak256(abi.encode(chainId, depositLocationInStorage)))), + txDataHash + ); + require(sharedBridge.depositHappened(chainId, txHash) == txDataHash, "Deposit not set"); + + vm.store( + address(sharedBridge), + keccak256( + abi.encode( + uint256(uint160(ETH_TOKEN_ADDRESS)), + keccak256(abi.encode(chainId, chainBalanceLocationInStorage)) + ) + ), + bytes32(amount) + ); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL1ToL2TransactionStatus.selector, + chainId, + txHash, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + merkleProof, + TxStatus.Failure + ), + abi.encode(true) + ); + + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit ClaimedFailedDepositSharedBridge(chainId, alice, ETH_TOKEN_ADDRESS, amount); + sharedBridge.claimFailedDeposit( + chainId, + alice, + ETH_TOKEN_ADDRESS, + amount, + txHash, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + merkleProof + ); + } + + function test_finalizeWithdrawal_EthOnEth() public { + vm.deal(address(sharedBridge), amount); + + vm.store( + address(sharedBridge), + keccak256( + abi.encode( + uint256(uint160(ETH_TOKEN_ADDRESS)), + keccak256(abi.encode(chainId, chainBalanceLocationInStorage)) + ) + ), + bytes32(amount) + ); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(ETH_TOKEN_ADDRESS) + ); + + bytes memory message = abi.encodePacked(IMailbox.finalizeEthWithdrawal.selector, alice, amount); + L2Message memory l2ToL1Message = L2Message({ + txNumberInBatch: l2TxNumberInBatch, + sender: L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, + data: message + }); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL2MessageInclusion.selector, + chainId, + l2BatchNumber, + l2MessageIndex, + l2ToL1Message, + merkleProof + ), + abi.encode(true) + ); + + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit WithdrawalFinalizedSharedBridge(chainId, alice, ETH_TOKEN_ADDRESS, amount); + sharedBridge.finalizeWithdrawal( + chainId, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + message, + merkleProof + ); + } + + function test_finalizeWithdrawal_ErcOnEth() public { + token.mint(address(sharedBridge), amount); + + vm.store( + address(sharedBridge), + keccak256( + abi.encode( + uint256(uint160(address(token))), + keccak256(abi.encode(chainId, chainBalanceLocationInStorage)) + ) + ), + bytes32(amount) + ); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(ETH_TOKEN_ADDRESS) + ); + + bytes memory message = abi.encodePacked( + IL1ERC20Bridge.finalizeWithdrawal.selector, + alice, + address(token), + amount + ); + L2Message memory l2ToL1Message = L2Message({ + txNumberInBatch: l2TxNumberInBatch, + sender: l2SharedBridge, + data: message + }); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL2MessageInclusion.selector, + chainId, + l2BatchNumber, + l2MessageIndex, + l2ToL1Message, + merkleProof + ), + abi.encode(true) + ); + + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit WithdrawalFinalizedSharedBridge(chainId, alice, address(token), amount); + sharedBridge.finalizeWithdrawal( + chainId, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + message, + merkleProof + ); + } + + function test_finalizeWithdrawal_EthOnErc() public { + vm.deal(address(sharedBridge), amount); + + vm.store( + address(sharedBridge), + keccak256( + abi.encode( + uint256(uint160(ETH_TOKEN_ADDRESS)), + keccak256(abi.encode(chainId, chainBalanceLocationInStorage)) + ) + ), + bytes32(amount) + ); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(address(token)) + ); + + bytes memory message = abi.encodePacked( + IL1ERC20Bridge.finalizeWithdrawal.selector, + alice, + ETH_TOKEN_ADDRESS, + amount + ); + L2Message memory l2ToL1Message = L2Message({ + txNumberInBatch: l2TxNumberInBatch, + sender: l2SharedBridge, + data: message + }); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL2MessageInclusion.selector, + chainId, + l2BatchNumber, + l2MessageIndex, + l2ToL1Message, + merkleProof + ), + abi.encode(true) + ); + + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit WithdrawalFinalizedSharedBridge(chainId, alice, ETH_TOKEN_ADDRESS, amount); + sharedBridge.finalizeWithdrawal( + chainId, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + message, + merkleProof + ); + } + + function test_finalizeWithdrawal_BaseErcOnErc() public { + token.mint(address(sharedBridge), amount); + + vm.store( + address(sharedBridge), + keccak256( + abi.encode( + uint256(uint160(address(token))), + keccak256(abi.encode(chainId, chainBalanceLocationInStorage)) + ) + ), + bytes32(amount) + ); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(address(token)) + ); + + bytes memory message = abi.encodePacked( + IL1ERC20Bridge.finalizeWithdrawal.selector, + alice, + address(token), + amount + ); + L2Message memory l2ToL1Message = L2Message({ + txNumberInBatch: l2TxNumberInBatch, + sender: L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, + data: message + }); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL2MessageInclusion.selector, + chainId, + l2BatchNumber, + l2MessageIndex, + l2ToL1Message, + merkleProof + ), + abi.encode(true) + ); + + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit WithdrawalFinalizedSharedBridge(chainId, alice, address(token), amount); + sharedBridge.finalizeWithdrawal( + chainId, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + message, + merkleProof + ); + } + + function test_finalizeWithdrawal_NonBaseErcOnErc() public { + token.mint(address(sharedBridge), amount); + + vm.store( + address(sharedBridge), + keccak256( + abi.encode( + uint256(uint160(address(token))), + keccak256(abi.encode(chainId, chainBalanceLocationInStorage)) + ) + ), + bytes32(amount) + ); + + bytes memory message = abi.encodePacked( + IL1ERC20Bridge.finalizeWithdrawal.selector, + alice, + address(token), + amount + ); + vm.mockCall(bridgehubAddress, abi.encodeWithSelector(IBridgehub.baseToken.selector), abi.encode(address(2))); //alt base token + L2Message memory l2ToL1Message = L2Message({ + txNumberInBatch: l2TxNumberInBatch, + sender: l2SharedBridge, + data: message + }); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL2MessageInclusion.selector, + chainId, + l2BatchNumber, + l2MessageIndex, + l2ToL1Message, + merkleProof + ), + abi.encode(true) + ); + + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit WithdrawalFinalizedSharedBridge(chainId, alice, address(token), amount); + sharedBridge.finalizeWithdrawal( + chainId, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + message, + merkleProof + ); + } + + function test_finalizeWithdrawal_EthOnEth_LegacyTx() public { + vm.deal(address(sharedBridge), amount); + uint256 legacyBatchNumber = 0; + + vm.mockCall( + l1ERC20BridgeAddress, + abi.encodeWithSelector(IL1ERC20Bridge.isWithdrawalFinalized.selector), + abi.encode(false) + ); + + vm.mockCall( + ERA_DIAMOND_PROXY, + abi.encodeWithSelector(IGetters.isEthWithdrawalFinalized.selector), + abi.encode(false) + ); + + vm.store( + address(sharedBridge), + keccak256( + abi.encode( + uint256(uint160(ETH_TOKEN_ADDRESS)), + keccak256(abi.encode(ERA_CHAIN_ID, chainBalanceLocationInStorage)) + ) + ), + bytes32(amount) + ); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(ETH_TOKEN_ADDRESS) + ); + + bytes memory message = abi.encodePacked(IMailbox.finalizeEthWithdrawal.selector, alice, amount); + L2Message memory l2ToL1Message = L2Message({ + txNumberInBatch: l2TxNumberInBatch, + sender: L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, + data: message + }); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL2MessageInclusion.selector, + ERA_CHAIN_ID, + legacyBatchNumber, + l2MessageIndex, + l2ToL1Message, + merkleProof + ), + abi.encode(true) + ); + + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit WithdrawalFinalizedSharedBridge(ERA_CHAIN_ID, alice, ETH_TOKEN_ADDRESS, amount); + sharedBridge.finalizeWithdrawal( + ERA_CHAIN_ID, + legacyBatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + message, + merkleProof + ); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol new file mode 100644 index 000000000000..4f82c4100da0 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol @@ -0,0 +1,832 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import {L1SharedBridge} from "solpp/bridge/L1SharedBridge.sol"; +import {Bridgehub} from "solpp/bridgehub/Bridgehub.sol"; +import {L1ERC20Bridge} from "solpp/bridge/L1ERC20Bridge.sol"; +import {ETH_TOKEN_ADDRESS} from "solpp/common/Config.sol"; +import {IBridgehub, L2TransactionRequestTwoBridgesInner} from "solpp/bridgehub/IBridgehub.sol"; +import {L2Message, TxStatus} from "solpp/common/Messaging.sol"; +import {IMailbox} from "solpp/state-transition/chain-interfaces/IMailbox.sol"; +import {IL1ERC20Bridge} from "solpp/bridge/interfaces/IL1ERC20Bridge.sol"; +import {IL1SharedBridge} from "solpp/bridge/interfaces/IL1SharedBridge.sol"; +import {TestnetERC20Token} from "solpp/dev-contracts/TestnetERC20Token.sol"; +import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR} from "solpp/common/L2ContractAddresses.sol"; +import {ERA_CHAIN_ID, ERA_DIAMOND_PROXY} from "solpp/common/Config.sol"; +import {IGetters} from "solpp/state-transition/chain-interfaces/IGetters.sol"; + +// import "forge-std/console.sol"; + +/// We are testing all the specifici revert and require cases. +contract L1SharedBridgeFailTest is Test { + event BridgehubDepositBaseTokenInitiated( + uint256 indexed chainId, + address indexed from, + address l1Token, + uint256 amount + ); + + event BridgehubDepositInitiated( + uint256 indexed chainId, + bytes32 indexed txDataHash, + address indexed from, + address to, + address l1Token, + uint256 amount + ); + + event BridgehubDepositFinalized( + uint256 indexed chainId, + bytes32 indexed txDataHash, + bytes32 indexed l2DepositTxHash + ); + + event WithdrawalFinalizedSharedBridge( + uint256 indexed chainId, + address indexed to, + address indexed l1Token, + uint256 amount + ); + + event ClaimedFailedDepositSharedBridge( + uint256 indexed chainId, + address indexed to, + address indexed l1Token, + uint256 amount + ); + + event LegacyDepositInitiated( + uint256 indexed chainId, + bytes32 indexed l2DepositTxHash, + address indexed from, + address to, + address l1Token, + uint256 amount + ); + + L1SharedBridge sharedBridgeImpl; + L1SharedBridge sharedBridge; + address bridgehubAddress; + address l1ERC20BridgeAddress; + address l1WethAddress; + address l2SharedBridge; + TestnetERC20Token token; + uint256 eraFirstPostUpgradeBatch; + + address owner; + address admin; + address zkSync; + address alice; + address bob; + uint256 chainId; + uint256 amount = 100; + bytes32 txHash; + + uint256 l2BatchNumber; + uint256 l2MessageIndex; + uint16 l2TxNumberInBatch; + bytes32[] merkleProof; + + // storing depoistHappend[chainId][l2TxHash] = txDataHash. DepositHappened is 3rd so 3 -1 + dependency storage slots + uint256 depositLocationInStorage = uint256(3 - 1 + 1 + 1); + uint256 chainBalanceLocationInStorage = uint256(6 - 1 + 1 + 1); + uint256 isWithdrawalFinalizedStorageLocation = uint256(4 - 1 + 1 + 1); + + function setUp() public { + owner = makeAddr("owner"); + admin = makeAddr("admin"); + // zkSync = makeAddr("zkSync"); + bridgehubAddress = makeAddr("bridgehub"); + alice = makeAddr("alice"); + // bob = makeAddr("bob"); + l1WethAddress = makeAddr("weth"); + l1ERC20BridgeAddress = makeAddr("l1ERC20Bridge"); + l2SharedBridge = makeAddr("l2SharedBridge"); + + txHash = bytes32(uint256(uint160(makeAddr("txHash")))); + l2BatchNumber = uint256(uint160(makeAddr("l2BatchNumber"))); + l2MessageIndex = uint256(uint160(makeAddr("l2MessageIndex"))); + l2TxNumberInBatch = uint16(uint160(makeAddr("l2TxNumberInBatch"))); + merkleProof = new bytes32[](1); + eraFirstPostUpgradeBatch = 1; + + chainId = 1; + + token = new TestnetERC20Token("TestnetERC20Token", "TET", 18); + sharedBridgeImpl = new L1SharedBridge( + l1WethAddress, + IBridgehub(bridgehubAddress), + IL1ERC20Bridge(l1ERC20BridgeAddress) + ); + TransparentUpgradeableProxy sharedBridgeProxy = new TransparentUpgradeableProxy( + address(sharedBridgeImpl), + admin, + abi.encodeWithSelector(L1SharedBridge.initialize.selector, owner, eraFirstPostUpgradeBatch) + ); + sharedBridge = L1SharedBridge(payable(sharedBridgeProxy)); + vm.prank(owner); + sharedBridge.initializeChainGovernance(chainId, l2SharedBridge); + vm.prank(owner); + sharedBridge.initializeChainGovernance(ERA_CHAIN_ID, l2SharedBridge); + } + + function test_initialize_wrongOwner() public { + vm.expectRevert("ShB owner 0"); + new TransparentUpgradeableProxy( + address(sharedBridgeImpl), + admin, + abi.encodeWithSelector(L1SharedBridge.initialize.selector, address(0), eraFirstPostUpgradeBatch) + ); + } + + function test_bridgehubDepositBaseToken_EthwrongMsgValue() public { + vm.deal(bridgehubAddress, amount); + vm.prank(bridgehubAddress); + vm.expectRevert("L1SharedBridge: msg.value not equal to amount"); + sharedBridge.bridgehubDepositBaseToken(chainId, alice, ETH_TOKEN_ADDRESS, amount); + } + + function test_bridgehubDepositBaseToken_ErcWrongMsgValue() public { + vm.deal(bridgehubAddress, amount); + token.mint(alice, amount); + vm.prank(alice); + token.approve(address(sharedBridge), amount); + vm.prank(bridgehubAddress); + vm.expectRevert("ShB m.v > 0 b d.it"); + sharedBridge.bridgehubDepositBaseToken{value: amount}(chainId, alice, address(token), amount); + } + + function test_bridgehubDepositBaseToken_ErcWrongErcDepositAmount() public { + token.mint(alice, amount); + vm.prank(alice); + token.approve(address(sharedBridge), amount); + + vm.mockCall(address(token), abi.encodeWithSelector(IERC20.balanceOf.selector), abi.encode(10)); + + bytes memory message = bytes("3T"); + vm.expectRevert(message); + vm.prank(bridgehubAddress); + sharedBridge.bridgehubDepositBaseToken(chainId, alice, address(token), amount); + } + + function test_bridgehubDeposit_Eth_l2BridgeNotDeployed() public { + vm.prank(owner); + sharedBridge.initializeChainGovernance(chainId, address(0)); + vm.deal(bridgehubAddress, amount); + vm.prank(bridgehubAddress); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(address(token)) + ); + vm.expectRevert("ShB l2 bridge not deployed"); + L2TransactionRequestTwoBridgesInner memory output = sharedBridge.bridgehubDeposit{value: amount}( + chainId, + alice, + 0, + abi.encode(ETH_TOKEN_ADDRESS, 0, bob) + ); + } + + function test_bridgehubDeposit_Erc_weth() public { + vm.prank(bridgehubAddress); + vm.expectRevert("ShB: WETH deposit not supported"); + L2TransactionRequestTwoBridgesInner memory output = sharedBridge.bridgehubDeposit( + chainId, + alice, + 0, + abi.encode(l1WethAddress, amount, bob) + ); + } + + function test_bridgehubDeposit_Eth_baseToken() public { + vm.prank(bridgehubAddress); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(ETH_TOKEN_ADDRESS) + ); + vm.expectRevert("ShB: baseToken deposit not supported"); + L2TransactionRequestTwoBridgesInner memory output = sharedBridge.bridgehubDeposit( + chainId, + alice, + 0, + abi.encode(ETH_TOKEN_ADDRESS, 0, bob) + ); + } + + function test_bridgehubDeposit_Eth_wrongDepositAmount() public { + token.mint(alice, amount); + vm.prank(alice); + token.approve(address(sharedBridge), amount); + vm.prank(bridgehubAddress); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(address(token)) + ); + vm.expectRevert("ShB wrong withdraw amount"); + L2TransactionRequestTwoBridgesInner memory output = sharedBridge.bridgehubDeposit( + chainId, + alice, + 0, + abi.encode(ETH_TOKEN_ADDRESS, amount, bob) + ); + } + + function test_bridgehubDeposit_Erc_msgValue() public { + vm.deal(bridgehubAddress, amount); + token.mint(alice, amount); + vm.prank(alice); + token.approve(address(sharedBridge), amount); + vm.prank(bridgehubAddress); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(ETH_TOKEN_ADDRESS) + ); + vm.expectRevert("ShB m.v > 0 for BH d.it 2"); + L2TransactionRequestTwoBridgesInner memory output = sharedBridge.bridgehubDeposit{value: amount}( + chainId, + alice, + 0, + abi.encode(address(token), amount, bob) + ); + } + + function test_bridgehubDeposit_Erc_wrongDepositAmount() public { + token.mint(alice, amount); + vm.prank(alice); + token.approve(address(sharedBridge), amount); + vm.prank(bridgehubAddress); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(ETH_TOKEN_ADDRESS) + ); + vm.mockCall(address(token), abi.encodeWithSelector(IERC20.balanceOf.selector), abi.encode(10)); + bytes memory message = bytes("5T"); + vm.expectRevert(message); + L2TransactionRequestTwoBridgesInner memory output = sharedBridge.bridgehubDeposit( + chainId, + alice, + 0, + abi.encode(address(token), amount, bob) + ); + } + + function test_bridgehubDeposit_Eth() public { + vm.prank(bridgehubAddress); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(address(token)) + ); + bytes memory message = bytes("6T"); + vm.expectRevert(message); + L2TransactionRequestTwoBridgesInner memory output = sharedBridge.bridgehubDeposit( + chainId, + alice, + 0, + abi.encode(ETH_TOKEN_ADDRESS, 0, bob) + ); + } + + function test_bridgehubConfirmL2Transaction_depositAlreadyHappened() public { + bytes32 txDataHash = keccak256(abi.encode(alice, address(token), amount)); + vm.store( + address(sharedBridge), + keccak256(abi.encode(txHash, keccak256(abi.encode(chainId, depositLocationInStorage)))), + txDataHash + ); + vm.prank(bridgehubAddress); + vm.expectRevert("ShB tx hap"); + sharedBridge.bridgehubConfirmL2Transaction(chainId, txDataHash, txHash); + } + + function test_claimFailedDeposit_proofInvalid() public { + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.proveL1ToL2TransactionStatus.selector), + abi.encode(address(0)) + ); + vm.prank(bridgehubAddress); + bytes memory message = bytes("yn"); + vm.expectRevert(message); + sharedBridge.claimFailedDeposit( + chainId, + alice, + ETH_TOKEN_ADDRESS, + amount, + txHash, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + merkleProof + ); + } + + function test_claimFailedDeposit_amountZero() public { + vm.deal(address(sharedBridge), amount); + + bytes32 txDataHash = keccak256(abi.encode(alice, ETH_TOKEN_ADDRESS, amount)); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL1ToL2TransactionStatus.selector, + chainId, + txHash, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + merkleProof, + TxStatus.Failure + ), + abi.encode(true) + ); + + bytes memory message = bytes("y1"); + vm.expectRevert(message); + sharedBridge.claimFailedDeposit( + chainId, + alice, + ETH_TOKEN_ADDRESS, + 0, + txHash, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + merkleProof + ); + } + + function test_claimFailedDeposit_depositDidNotHappen() public { + vm.deal(address(sharedBridge), amount); + + bytes32 txDataHash = keccak256(abi.encode(alice, ETH_TOKEN_ADDRESS, amount)); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL1ToL2TransactionStatus.selector, + chainId, + txHash, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + merkleProof, + TxStatus.Failure + ), + abi.encode(true) + ); + + vm.expectRevert("ShB: d.it not hap"); + sharedBridge.claimFailedDeposit( + chainId, + alice, + ETH_TOKEN_ADDRESS, + amount, + txHash, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + merkleProof + ); + } + + function test_claimFailedDeposit_chainBalanceLow() public { + vm.deal(address(sharedBridge), amount); + + bytes32 txDataHash = keccak256(abi.encode(alice, ETH_TOKEN_ADDRESS, amount)); + vm.store( + address(sharedBridge), + keccak256(abi.encode(txHash, keccak256(abi.encode(chainId, depositLocationInStorage)))), + txDataHash + ); + require(sharedBridge.depositHappened(chainId, txHash) == txDataHash, "Deposit not set"); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL1ToL2TransactionStatus.selector, + chainId, + txHash, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + merkleProof, + TxStatus.Failure + ), + abi.encode(true) + ); + + vm.expectRevert("ShB n funds"); + sharedBridge.claimFailedDeposit( + chainId, + alice, + ETH_TOKEN_ADDRESS, + amount, + txHash, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + merkleProof + ); + } + + // forge messes up low level calls + // function test_claimFailedDeposit_lowLevelCallFailed() public { + // vm.deal(address(sharedBridge), amount); + + // bytes32 txDataHash = keccak256(abi.encode(alice, ETH_TOKEN_ADDRESS, amount)); + // vm.store( + // address(sharedBridge), + // keccak256(abi.encode(txHash, keccak256(abi.encode(chainId, depositLocationInStorage)))), + // txDataHash + // ); + // require(sharedBridge.depositHappened(chainId, txHash) == txDataHash, "Deposit not set"); + + // vm.store( + // address(sharedBridge), + // keccak256( + // abi.encode( + // uint256(uint160(ETH_TOKEN_ADDRESS)), + // keccak256(abi.encode(chainId, chainBalanceLocationInStorage)) + // ) + // ), + // bytes32(amount) + // ); + + // vm.mockCall( + // bridgehubAddress, + // abi.encodeWithSelector( + // IBridgehub.proveL1ToL2TransactionStatus.selector, + // chainId, + // txHash, + // l2BatchNumber, + // l2MessageIndex, + // l2TxNumberInBatch, + // merkleProof, + // TxStatus.Failure + // ), + // abi.encode(true) + // ); + + // vm.mockCall(alice, bytes(""), abi.encode(0)); + // vm.expectRevert(bytes("ShB: claimFailedDeposit failed")); + // sharedBridge.claimFailedDeposit( + // chainId, + // alice, + // ETH_TOKEN_ADDRESS, + // amount, + // txHash, + // l2BatchNumber, + // l2MessageIndex, + // l2TxNumberInBatch, + // merkleProof + // ); + // // assertTrue(revertsAsExpected, "expectRevert: call did not revert"); + + // } + + function test_finalizeWithdrawal_EthOnEth_LegacyTxFinalizedInERC20Bridge() public { + vm.deal(address(sharedBridge), amount); + uint256 legacyBatchNumber = 0; + + vm.mockCall( + l1ERC20BridgeAddress, + abi.encodeWithSelector(IL1ERC20Bridge.isWithdrawalFinalized.selector), + abi.encode(true) + ); + + bytes memory message = abi.encodePacked( + IL1ERC20Bridge.finalizeWithdrawal.selector, + alice, + address(token), + amount + ); + + vm.expectRevert("ShB: legacy withdrawal"); + sharedBridge.finalizeWithdrawal( + ERA_CHAIN_ID, + legacyBatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + message, + merkleProof + ); + } + + function test_finalizeWithdrawal_EthOnEth_LegacyTxFinalizedInSharedBridge() public { + vm.deal(address(sharedBridge), amount); + uint256 legacyBatchNumber = 0; + + vm.mockCall( + l1ERC20BridgeAddress, + abi.encodeWithSelector(IL1ERC20Bridge.isWithdrawalFinalized.selector), + abi.encode(false) + ); + + vm.store( + address(sharedBridge), + keccak256( + abi.encode( + l2MessageIndex, + keccak256( + abi.encode( + legacyBatchNumber, + keccak256(abi.encode(ERA_CHAIN_ID, isWithdrawalFinalizedStorageLocation)) + ) + ) + ) + ), + bytes32(uint256(1)) + ); + + bytes memory message = abi.encodePacked( + IL1ERC20Bridge.finalizeWithdrawal.selector, + alice, + address(token), + amount + ); + + vm.expectRevert("Withdrawal is already finalized"); + sharedBridge.finalizeWithdrawal( + ERA_CHAIN_ID, + legacyBatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + message, + merkleProof + ); + } + + function test_finalizeWithdrawal_EthOnEth_LegacyTxFinalizedInDiamondProxy() public { + vm.deal(address(sharedBridge), amount); + uint256 legacyBatchNumber = 0; + + vm.mockCall( + l1ERC20BridgeAddress, + abi.encodeWithSelector(IL1ERC20Bridge.isWithdrawalFinalized.selector), + abi.encode(false) + ); + + vm.mockCall( + ERA_DIAMOND_PROXY, + abi.encodeWithSelector(IGetters.isEthWithdrawalFinalized.selector), + abi.encode(true) + ); + + bytes memory message = abi.encodePacked( + IL1ERC20Bridge.finalizeWithdrawal.selector, + alice, + address(token), + amount + ); + vm.expectRevert("Withdrawal is already finalized 2"); + + sharedBridge.finalizeWithdrawal( + ERA_CHAIN_ID, + legacyBatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + message, + merkleProof + ); + } + + function test_finalizeWithdrawal_chainBalance() public { + vm.deal(address(sharedBridge), amount); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(ETH_TOKEN_ADDRESS) + ); + + bytes memory message = abi.encodePacked(IMailbox.finalizeEthWithdrawal.selector, alice, amount); + L2Message memory l2ToL1Message = L2Message({ + txNumberInBatch: l2TxNumberInBatch, + sender: L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, + data: message + }); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL2MessageInclusion.selector, + chainId, + l2BatchNumber, + l2MessageIndex, + l2ToL1Message, + merkleProof + ), + abi.encode(true) + ); + + vm.expectRevert("ShB not enough funds 2"); + + sharedBridge.finalizeWithdrawal( + chainId, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + message, + merkleProof + ); + } + + function test_checkWithdrawal_wrongProof() public { + vm.deal(address(sharedBridge), amount); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(ETH_TOKEN_ADDRESS) + ); + + bytes memory message = abi.encodePacked(IMailbox.finalizeEthWithdrawal.selector, alice, amount); + L2Message memory l2ToL1Message = L2Message({ + txNumberInBatch: l2TxNumberInBatch, + sender: L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, + data: message + }); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL2MessageInclusion.selector, + chainId, + l2BatchNumber, + l2MessageIndex, + l2ToL1Message, + merkleProof + ), + abi.encode(false) + ); + + vm.expectRevert("ShB withd w proof"); + + sharedBridge.finalizeWithdrawal( + chainId, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + message, + merkleProof + ); + } + + function test_parseL2WithdrawalMessage_WrongMsgLength() public { + vm.deal(address(sharedBridge), amount); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(ETH_TOKEN_ADDRESS) + ); + + bytes memory message = abi.encodePacked(IMailbox.finalizeEthWithdrawal.selector); + L2Message memory l2ToL1Message = L2Message({ + txNumberInBatch: l2TxNumberInBatch, + sender: L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, + data: message + }); + + vm.expectRevert("ShB wrong msg len"); + sharedBridge.finalizeWithdrawal( + chainId, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + message, + merkleProof + ); + } + + function test_parseL2WithdrawalMessage_WrongMsgLength2() public { + vm.deal(address(sharedBridge), amount); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector, alice, amount), + abi.encode(ETH_TOKEN_ADDRESS) + ); + + bytes memory message = abi.encodePacked(IL1ERC20Bridge.finalizeWithdrawal.selector, alice, amount); /// should have more data here + L2Message memory l2ToL1Message = L2Message({ + txNumberInBatch: l2TxNumberInBatch, + sender: L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, + data: message + }); + + vm.expectRevert("ShB wrong msg len 2"); + sharedBridge.finalizeWithdrawal( + chainId, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + message, + merkleProof + ); + } + + function test_parseL2WithdrawalMessage_WrongSelector() public { + vm.deal(address(sharedBridge), amount); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(ETH_TOKEN_ADDRESS) + ); + + // notice that the selector is wrong + bytes memory message = abi.encodePacked(IMailbox.proveL2LogInclusion.selector, alice, amount); + L2Message memory l2ToL1Message = L2Message({ + txNumberInBatch: l2TxNumberInBatch, + sender: L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, + data: message + }); + + vm.expectRevert("ShB Incorrect message function selector"); + sharedBridge.finalizeWithdrawal( + chainId, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + message, + merkleProof + ); + } + + function test_depositLegacyERC20Bridge_l2BridgeNotDeployed() public { + uint256 l2TxGasLimit = 100000; + uint256 l2TxGasPerPubdataByte = 100; + address refundRecipient = address(0); + + vm.prank(owner); + sharedBridge.initializeChainGovernance(ERA_CHAIN_ID, address(0)); + + vm.expectRevert("ShB b. n dep"); + vm.prank(l1ERC20BridgeAddress); + sharedBridge.depositLegacyErc20Bridge( + alice, + bob, + address(token), + amount, + l2TxGasLimit, + l2TxGasPerPubdataByte, + refundRecipient + ); + } + + function test_depositLegacyERC20Bridge_weth() public { + uint256 l2TxGasLimit = 100000; + uint256 l2TxGasPerPubdataByte = 100; + address refundRecipient = address(0); + + vm.expectRevert("ShB: WETH deposit not supported 2"); + vm.prank(l1ERC20BridgeAddress); + sharedBridge.depositLegacyErc20Bridge( + alice, + bob, + l1WethAddress, + amount, + l2TxGasLimit, + l2TxGasPerPubdataByte, + refundRecipient + ); + } + + function test_depositLegacyERC20Bridge_refundRecipient() public { + uint256 l2TxGasLimit = 100000; + uint256 l2TxGasPerPubdataByte = 100; + address refundRecipient = address(0); + + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit LegacyDepositInitiated(ERA_CHAIN_ID, txHash, alice, bob, address(token), amount); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.requestL2TransactionDirect.selector), + abi.encode(txHash) + ); + + vm.prank(l1ERC20BridgeAddress); + sharedBridge.depositLegacyErc20Bridge( + alice, + bob, + address(token), + amount, + l2TxGasLimit, + l2TxGasPerPubdataByte, + address(1) + ); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol new file mode 100644 index 000000000000..2292014bd59d --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol @@ -0,0 +1,608 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import {L1SharedBridge} from "solpp/bridge/L1SharedBridge.sol"; +import {Bridgehub} from "solpp/bridgehub/Bridgehub.sol"; +import {L1ERC20Bridge} from "solpp/bridge/L1ERC20Bridge.sol"; +import {ETH_TOKEN_ADDRESS} from "solpp/common/Config.sol"; +import {IBridgehub, L2TransactionRequestTwoBridgesInner} from "solpp/bridgehub/IBridgehub.sol"; +import {L2Message, TxStatus} from "solpp/common/Messaging.sol"; +import {IMailbox} from "solpp/state-transition/chain-interfaces/IMailbox.sol"; +import {IL1ERC20Bridge} from "solpp/bridge/interfaces/IL1ERC20Bridge.sol"; +import {IL1SharedBridge} from "solpp/bridge/interfaces/IL1SharedBridge.sol"; +import {TestnetERC20Token} from "solpp/dev-contracts/TestnetERC20Token.sol"; +import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR} from "solpp/common/L2ContractAddresses.sol"; +import {ERA_CHAIN_ID} from "solpp/common/Config.sol"; + +// import "forge-std/console.sol"; + +// note, this should be the same as where hyper is disalbed +contract L1SharedBridgeHyperEnabledTest is Test { + event BridgehubDepositBaseTokenInitiated( + uint256 indexed chainId, + address indexed from, + address l1Token, + uint256 amount + ); + + event BridgehubDepositInitiated( + uint256 indexed chainId, + bytes32 indexed txDataHash, + address indexed from, + address to, + address l1Token, + uint256 amount + ); + + event BridgehubDepositFinalized( + uint256 indexed chainId, + bytes32 indexed txDataHash, + bytes32 indexed l2DepositTxHash + ); + + event WithdrawalFinalizedSharedBridge( + uint256 indexed chainId, + address indexed to, + address indexed l1Token, + uint256 amount + ); + + event ClaimedFailedDepositSharedBridge( + uint256 indexed chainId, + address indexed to, + address indexed l1Token, + uint256 amount + ); + + event LegacyDepositInitiated( + uint256 indexed chainId, + bytes32 indexed l2DepositTxHash, + address indexed from, + address to, + address l1Token, + uint256 amount + ); + + L1SharedBridge sharedBridgeImpl; + L1SharedBridge sharedBridge; + address bridgehubAddress; + address l1ERC20BridgeAddress; + address l2SharedBridge; + TestnetERC20Token token; + + address owner; + address admin; + address zkSync; + address alice; + address bob; + uint256 chainId; + uint256 amount = 100; + bytes32 txHash; + + uint256 l2BatchNumber; + uint256 l2MessageIndex; + uint16 l2TxNumberInBatch; + bytes32[] merkleProof; + + function setUp() public { + owner = makeAddr("owner"); + admin = makeAddr("admin"); + // zkSync = makeAddr("zkSync"); + bridgehubAddress = makeAddr("bridgehub"); + alice = makeAddr("alice"); + // bob = makeAddr("bob"); + address l1WethAddress = makeAddr("weth"); + l1ERC20BridgeAddress = makeAddr("l1ERC20Bridge"); + l2SharedBridge = makeAddr("l2SharedBridge"); + + txHash = bytes32(uint256(uint160(makeAddr("txHash")))); + l2BatchNumber = uint256(uint160(makeAddr("l2BatchNumber"))); + l2MessageIndex = uint256(uint160(makeAddr("l2MessageIndex"))); + l2TxNumberInBatch = uint16(uint160(makeAddr("l2TxNumberInBatch"))); + merkleProof = new bytes32[](1); + + chainId = 1; + + token = new TestnetERC20Token("TestnetERC20Token", "TET", 18); + sharedBridgeImpl = new L1SharedBridge( + l1WethAddress, + IBridgehub(bridgehubAddress), + IL1ERC20Bridge(l1ERC20BridgeAddress) + ); + TransparentUpgradeableProxy sharedBridgeProxy = new TransparentUpgradeableProxy( + address(sharedBridgeImpl), + admin, + abi.encodeWithSelector(L1SharedBridge.initialize.selector, owner, 0) + ); + sharedBridge = L1SharedBridge(payable(sharedBridgeProxy)); + vm.prank(owner); + sharedBridge.initializeChainGovernance(chainId, l2SharedBridge); + vm.prank(owner); + sharedBridge.initializeChainGovernance(ERA_CHAIN_ID, l2SharedBridge); + ///// NOTE: this is the only difference: enabling hyper bridging + uint256 hyperBridgingEnabledLocationInStorage = uint256(5 - 1 + 1 + 1); + vm.store( + address(sharedBridge), + keccak256( + abi.encode( + uint256(uint160(address(token))), + keccak256(abi.encode(chainId, hyperBridgingEnabledLocationInStorage)) + ) + ), + bytes32(uint256(1)) + ); + } + + function test_bridgehubDepositBaseToken_Eth() public { + vm.deal(bridgehubAddress, amount); + vm.prank(bridgehubAddress); + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit BridgehubDepositBaseTokenInitiated(chainId, alice, ETH_TOKEN_ADDRESS, amount); + sharedBridge.bridgehubDepositBaseToken{value: amount}(chainId, alice, ETH_TOKEN_ADDRESS, amount); + } + + function test_bridgehubDepositBaseToken_Erc() public { + token.mint(alice, amount); + vm.prank(alice); + token.approve(address(sharedBridge), amount); + vm.prank(bridgehubAddress); + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit BridgehubDepositBaseTokenInitiated(chainId, alice, address(token), amount); + sharedBridge.bridgehubDepositBaseToken(chainId, alice, address(token), amount); + } + + function test_bridgehubDeposit_Eth() public { + vm.deal(bridgehubAddress, amount); + vm.prank(bridgehubAddress); + vm.expectEmit(true, true, true, true, address(sharedBridge)); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(address(token)) + ); + bytes32 txDataHash = keccak256(abi.encode(alice, ETH_TOKEN_ADDRESS, amount)); + emit BridgehubDepositInitiated(chainId, txDataHash, alice, zkSync, ETH_TOKEN_ADDRESS, amount); + L2TransactionRequestTwoBridgesInner memory output = sharedBridge.bridgehubDeposit{value: amount}( + chainId, + alice, + 0, + abi.encode(ETH_TOKEN_ADDRESS, 0, bob) + ); + } + + function test_bridgehubDeposit_Erc() public { + token.mint(alice, amount); + vm.prank(alice); + token.approve(address(sharedBridge), amount); + vm.prank(bridgehubAddress); + vm.expectEmit(true, true, true, true, address(sharedBridge)); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(ETH_TOKEN_ADDRESS) + ); + bytes32 txDataHash = keccak256(abi.encode(alice, address(token), amount)); + emit BridgehubDepositInitiated(chainId, txDataHash, alice, zkSync, address(token), amount); + L2TransactionRequestTwoBridgesInner memory output = sharedBridge.bridgehubDeposit( + chainId, + alice, + 0, + abi.encode(address(token), amount, bob) + ); + } + + function test_bridgehubConfirmL2Transaction() public { + vm.expectEmit(true, true, true, true, address(sharedBridge)); + bytes32 txDataHash = keccak256(abi.encode(alice, address(token), amount)); + emit BridgehubDepositFinalized(chainId, txDataHash, txHash); + vm.prank(bridgehubAddress); + sharedBridge.bridgehubConfirmL2Transaction(chainId, txDataHash, txHash); + } + + function test_claimFailedDeposit_Erc() public { + token.mint(address(sharedBridge), amount); + + // storing depoistHappend[chainId][l2TxHash] = txDataHash. DepositHappened is 3rd so 3 -1 + dependency storage slots + uint256 depositLocationInStorage = uint256(3 - 1 + 1 + 1); + bytes32 txDataHash = keccak256(abi.encode(alice, address(token), amount)); + vm.store( + address(sharedBridge), + keccak256(abi.encode(txHash, keccak256(abi.encode(chainId, depositLocationInStorage)))), + txDataHash + ); + require(sharedBridge.depositHappened(chainId, txHash) == txDataHash, "Deposit not set"); + + uint256 chainBalanceLocationInStorage = uint256(6 - 1 + 1 + 1); + vm.store( + address(sharedBridge), + keccak256( + abi.encode( + uint256(uint160(address(token))), + keccak256(abi.encode(chainId, chainBalanceLocationInStorage)) + ) + ), + bytes32(amount) + ); + + // Bridgehub bridgehub = new Bridgehub(); + // vm.store(address(bridgehub), bytes32(uint256(5 +2)), bytes32(uint256(31337))); + // require(address(bridgehub.deployer()) == address(31337), "Bridgehub: deployer wrong"); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL1ToL2TransactionStatus.selector, + chainId, + txHash, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + merkleProof, + TxStatus.Failure + ), + abi.encode(true) + ); + + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit ClaimedFailedDepositSharedBridge(chainId, alice, address(token), amount); + vm.prank(bridgehubAddress); + sharedBridge.claimFailedDeposit( + chainId, + alice, + address(token), + amount, + txHash, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + merkleProof + ); + } + + function test_claimFailedDeposit_Eth() public { + vm.deal(address(sharedBridge), amount); + + // storing depoistHappend[chainId][l2TxHash] = txDataHash. DepositHappened is 3rd so 3 -1 + dependency storage slots + uint256 depositLocationInStorage = uint256(3 - 1 + 1 + 1); + bytes32 txDataHash = keccak256(abi.encode(alice, ETH_TOKEN_ADDRESS, amount)); + vm.store( + address(sharedBridge), + keccak256(abi.encode(txHash, keccak256(abi.encode(chainId, depositLocationInStorage)))), + txDataHash + ); + require(sharedBridge.depositHappened(chainId, txHash) == txDataHash, "Deposit not set"); + + /// storing chainBalance + uint256 chainBalanceLocationInStorage = uint256(6 - 1 + 1 + 1); + vm.store( + address(sharedBridge), + keccak256( + abi.encode( + uint256(uint160(ETH_TOKEN_ADDRESS)), + keccak256(abi.encode(chainId, chainBalanceLocationInStorage)) + ) + ), + bytes32(amount) + ); + + // Bridgehub bridgehub = new Bridgehub(); + // vm.store(address(bridgehub), bytes32(uint256(5 +2)), bytes32(uint256(31337))); + // require(address(bridgehub.deployer()) == address(31337), "Bridgehub: deployer wrong"); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL1ToL2TransactionStatus.selector, + chainId, + txHash, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + merkleProof, + TxStatus.Failure + ), + abi.encode(true) + ); + + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit ClaimedFailedDepositSharedBridge(chainId, alice, ETH_TOKEN_ADDRESS, amount); + vm.prank(bridgehubAddress); + sharedBridge.claimFailedDeposit( + chainId, + alice, + ETH_TOKEN_ADDRESS, + amount, + txHash, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + merkleProof + ); + } + + function test_finalizeWithdrawal_EthOnEth() public { + vm.deal(address(sharedBridge), amount); + + /// storing chainBalance + uint256 chainBalanceLocationInStorage = uint256(6 - 1 + 1 + 1); + vm.store( + address(sharedBridge), + keccak256( + abi.encode( + uint256(uint160(ETH_TOKEN_ADDRESS)), + keccak256(abi.encode(chainId, chainBalanceLocationInStorage)) + ) + ), + bytes32(amount) + ); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(ETH_TOKEN_ADDRESS) + ); + + bytes memory message = abi.encodePacked(IMailbox.finalizeEthWithdrawal.selector, alice, amount); + L2Message memory l2ToL1Message = L2Message({ + txNumberInBatch: l2TxNumberInBatch, + sender: L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, + data: message + }); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL2MessageInclusion.selector, + chainId, + l2BatchNumber, + l2MessageIndex, + l2ToL1Message, + merkleProof + ), + abi.encode(true) + ); + + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit WithdrawalFinalizedSharedBridge(chainId, alice, ETH_TOKEN_ADDRESS, amount); + sharedBridge.finalizeWithdrawal( + chainId, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + message, + merkleProof + ); + } + + function test_finalizeWithdrawal_ErcOnEth() public { + token.mint(address(sharedBridge), amount); + + /// storing chainBalance + uint256 chainBalanceLocationInStorage = uint256(6 - 1 + 1 + 1); + vm.store( + address(sharedBridge), + keccak256( + abi.encode( + uint256(uint160(address(token))), + keccak256(abi.encode(chainId, chainBalanceLocationInStorage)) + ) + ), + bytes32(amount) + ); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(ETH_TOKEN_ADDRESS) + ); + + bytes memory message = abi.encodePacked( + IL1ERC20Bridge.finalizeWithdrawal.selector, + alice, + address(token), + amount + ); + L2Message memory l2ToL1Message = L2Message({ + txNumberInBatch: l2TxNumberInBatch, + sender: l2SharedBridge, + data: message + }); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL2MessageInclusion.selector, + chainId, + l2BatchNumber, + l2MessageIndex, + l2ToL1Message, + merkleProof + ), + abi.encode(true) + ); + + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit WithdrawalFinalizedSharedBridge(chainId, alice, address(token), amount); + sharedBridge.finalizeWithdrawal( + chainId, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + message, + merkleProof + ); + } + + function test_finalizeWithdrawal_EthOnErc() public { + vm.deal(address(sharedBridge), amount); + + /// storing chainBalance + uint256 chainBalanceLocationInStorage = uint256(6 - 1 + 1 + 1); + vm.store( + address(sharedBridge), + keccak256( + abi.encode( + uint256(uint160(ETH_TOKEN_ADDRESS)), + keccak256(abi.encode(chainId, chainBalanceLocationInStorage)) + ) + ), + bytes32(amount) + ); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(address(token)) + ); + + bytes memory message = abi.encodePacked( + IL1ERC20Bridge.finalizeWithdrawal.selector, + alice, + ETH_TOKEN_ADDRESS, + amount + ); + L2Message memory l2ToL1Message = L2Message({ + txNumberInBatch: l2TxNumberInBatch, + sender: l2SharedBridge, + data: message + }); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL2MessageInclusion.selector, + chainId, + l2BatchNumber, + l2MessageIndex, + l2ToL1Message, + merkleProof + ), + abi.encode(true) + ); + + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit WithdrawalFinalizedSharedBridge(chainId, alice, ETH_TOKEN_ADDRESS, amount); + sharedBridge.finalizeWithdrawal( + chainId, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + message, + merkleProof + ); + } + + function test_finalizeWithdrawal_BaseErcOnErc() public { + token.mint(address(sharedBridge), amount); + + /// storing chainBalance + uint256 chainBalanceLocationInStorage = uint256(6 - 1 + 1 + 1); + vm.store( + address(sharedBridge), + keccak256( + abi.encode( + uint256(uint160(address(token))), + keccak256(abi.encode(chainId, chainBalanceLocationInStorage)) + ) + ), + bytes32(amount) + ); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(address(token)) + ); + + bytes memory message = abi.encodePacked( + IL1ERC20Bridge.finalizeWithdrawal.selector, + alice, + address(token), + amount + ); + L2Message memory l2ToL1Message = L2Message({ + txNumberInBatch: l2TxNumberInBatch, + sender: L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, + data: message + }); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL2MessageInclusion.selector, + chainId, + l2BatchNumber, + l2MessageIndex, + l2ToL1Message, + merkleProof + ), + abi.encode(true) + ); + + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit WithdrawalFinalizedSharedBridge(chainId, alice, address(token), amount); + sharedBridge.finalizeWithdrawal( + chainId, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + message, + merkleProof + ); + } + + function test_finalizeWithdrawal_NonBaseErcOnErc() public { + token.mint(address(sharedBridge), amount); + + /// storing chainBalance + uint256 chainBalanceLocationInStorage = uint256(6 - 1 + 1 + 1); + vm.store( + address(sharedBridge), + keccak256( + abi.encode( + uint256(uint160(address(token))), + keccak256(abi.encode(chainId, chainBalanceLocationInStorage)) + ) + ), + bytes32(amount) + ); + + bytes memory message = abi.encodePacked( + IL1ERC20Bridge.finalizeWithdrawal.selector, + alice, + address(token), + amount + ); + vm.mockCall(bridgehubAddress, abi.encodeWithSelector(IBridgehub.baseToken.selector), abi.encode(address(2))); //alt base token + L2Message memory l2ToL1Message = L2Message({ + txNumberInBatch: l2TxNumberInBatch, + sender: l2SharedBridge, + data: message + }); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL2MessageInclusion.selector, + chainId, + l2BatchNumber, + l2MessageIndex, + l2ToL1Message, + merkleProof + ), + abi.encode(true) + ); + + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit WithdrawalFinalizedSharedBridge(chainId, alice, address(token), amount); + sharedBridge.finalizeWithdrawal( + chainId, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + message, + merkleProof + ); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol new file mode 100644 index 000000000000..0c037ceccc3f --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import {L1SharedBridge} from "solpp/bridge/L1SharedBridge.sol"; +import {Bridgehub} from "solpp/bridgehub/Bridgehub.sol"; +import {L1ERC20Bridge} from "solpp/bridge/L1ERC20Bridge.sol"; +import {ETH_TOKEN_ADDRESS} from "solpp/common/Config.sol"; +import {IBridgehub, L2TransactionRequestTwoBridgesInner} from "solpp/bridgehub/IBridgehub.sol"; +import {L2Message, TxStatus} from "solpp/common/Messaging.sol"; +import {IMailbox} from "solpp/state-transition/chain-interfaces/IMailbox.sol"; +import {IL1ERC20Bridge} from "solpp/bridge/interfaces/IL1ERC20Bridge.sol"; +import {IL1SharedBridge} from "solpp/bridge/interfaces/IL1SharedBridge.sol"; +import {TestnetERC20Token} from "solpp/dev-contracts/TestnetERC20Token.sol"; +import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR} from "solpp/common/L2ContractAddresses.sol"; +import {ERA_CHAIN_ID} from "solpp/common/Config.sol"; + +contract L1SharedBridgeLegacyTest is Test { + event BridgehubDepositBaseTokenInitiated( + uint256 indexed chainId, + address indexed from, + address l1Token, + uint256 amount + ); + + event BridgehubDepositInitiated( + uint256 indexed chainId, + bytes32 indexed txDataHash, + address indexed from, + address to, + address l1Token, + uint256 amount + ); + + event BridgehubDepositFinalized( + uint256 indexed chainId, + bytes32 indexed txDataHash, + bytes32 indexed l2DepositTxHash + ); + + event WithdrawalFinalizedSharedBridge( + uint256 indexed chainId, + address indexed to, + address indexed l1Token, + uint256 amount + ); + + event ClaimedFailedDepositSharedBridge( + uint256 indexed chainId, + address indexed to, + address indexed l1Token, + uint256 amount + ); + + event LegacyDepositInitiated( + uint256 indexed chainId, + bytes32 indexed l2DepositTxHash, + address indexed from, + address to, + address l1Token, + uint256 amount + ); + + L1SharedBridge sharedBridgeImpl; + L1SharedBridge sharedBridge; + address bridgehubAddress; + address l1ERC20BridgeAddress; + address l2SharedBridge; + TestnetERC20Token token; + + address owner; + address admin; + address zkSync; + address alice; + address bob; + uint256 chainId; + uint256 amount = 100; + bytes32 txHash; + + uint256 l2BatchNumber; + uint256 l2MessageIndex; + uint16 l2TxNumberInBatch; + bytes32[] merkleProof; + + function setUp() public { + owner = makeAddr("owner"); + admin = makeAddr("admin"); + // zkSync = makeAddr("zkSync"); + bridgehubAddress = makeAddr("bridgehub"); + alice = makeAddr("alice"); + // bob = makeAddr("bob"); + address l1WethAddress = makeAddr("weth"); + l1ERC20BridgeAddress = makeAddr("l1ERC20Bridge"); + l2SharedBridge = makeAddr("l2SharedBridge"); + + txHash = bytes32(uint256(uint160(makeAddr("txHash")))); + l2BatchNumber = uint256(uint160(makeAddr("l2BatchNumber"))); + l2MessageIndex = uint256(uint160(makeAddr("l2MessageIndex"))); + l2TxNumberInBatch = uint16(uint160(makeAddr("l2TxNumberInBatch"))); + merkleProof = new bytes32[](1); + + chainId = 1; + + token = new TestnetERC20Token("TestnetERC20Token", "TET", 18); + sharedBridgeImpl = new L1SharedBridge( + l1WethAddress, + IBridgehub(bridgehubAddress), + IL1ERC20Bridge(l1ERC20BridgeAddress) + ); + TransparentUpgradeableProxy sharedBridgeProxy = new TransparentUpgradeableProxy( + address(sharedBridgeImpl), + admin, + abi.encodeWithSelector(L1SharedBridge.initialize.selector, owner, 0) + ); + sharedBridge = L1SharedBridge(payable(sharedBridgeProxy)); + vm.prank(owner); + sharedBridge.initializeChainGovernance(chainId, l2SharedBridge); + vm.prank(owner); + sharedBridge.initializeChainGovernance(ERA_CHAIN_ID, l2SharedBridge); + } + + function test_depositLegacyERC20Bridge() public { + uint256 l2TxGasLimit = 100000; + uint256 l2TxGasPerPubdataByte = 100; + address refundRecipient = address(0); + + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit LegacyDepositInitiated(ERA_CHAIN_ID, txHash, alice, bob, address(token), amount); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.requestL2TransactionDirect.selector), + abi.encode(txHash) + ); + + vm.prank(l1ERC20BridgeAddress); + sharedBridge.depositLegacyErc20Bridge( + alice, + bob, + address(token), + amount, + l2TxGasLimit, + l2TxGasPerPubdataByte, + refundRecipient + ); + } + + function test_finalizeWithdrawalLegacyErc20Bridge_EthOnEth() public { + vm.deal(address(sharedBridge), amount); + + /// storing chainBalance + uint256 chainBalanceLocationInStorage = uint256(6 - 1 + 1 + 1); + vm.store( + address(sharedBridge), + keccak256( + abi.encode( + uint256(uint160(ETH_TOKEN_ADDRESS)), + keccak256(abi.encode(ERA_CHAIN_ID, chainBalanceLocationInStorage)) + ) + ), + bytes32(amount) + ); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(ETH_TOKEN_ADDRESS) + ); + + bytes memory message = abi.encodePacked(IMailbox.finalizeEthWithdrawal.selector, alice, amount); + L2Message memory l2ToL1Message = L2Message({ + txNumberInBatch: l2TxNumberInBatch, + sender: L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, + data: message + }); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL2MessageInclusion.selector, + ERA_CHAIN_ID, + l2BatchNumber, + l2MessageIndex, + l2ToL1Message, + merkleProof + ), + abi.encode(true) + ); + + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit WithdrawalFinalizedSharedBridge(ERA_CHAIN_ID, alice, ETH_TOKEN_ADDRESS, amount); + vm.prank(l1ERC20BridgeAddress); + sharedBridge.finalizeWithdrawalLegacyErc20Bridge( + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + message, + merkleProof + ); + } + + function test_finalizeWithdrawalLegacyErc20Bridge_ErcOnEth() public { + token.mint(address(sharedBridge), amount); + + /// storing chainBalance + uint256 chainBalanceLocationInStorage = uint256(6 - 1 + 1 + 1); + vm.store( + address(sharedBridge), + keccak256( + abi.encode( + uint256(uint160(address(token))), + keccak256(abi.encode(ERA_CHAIN_ID, chainBalanceLocationInStorage)) + ) + ), + bytes32(amount) + ); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseToken.selector), + abi.encode(ETH_TOKEN_ADDRESS) + ); + + bytes memory message = abi.encodePacked( + IL1ERC20Bridge.finalizeWithdrawal.selector, + alice, + address(token), + amount + ); + L2Message memory l2ToL1Message = L2Message({ + txNumberInBatch: l2TxNumberInBatch, + sender: l2SharedBridge, + data: message + }); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL2MessageInclusion.selector, + ERA_CHAIN_ID, + l2BatchNumber, + l2MessageIndex, + l2ToL1Message, + merkleProof + ), + abi.encode(true) + ); + + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit WithdrawalFinalizedSharedBridge(ERA_CHAIN_ID, alice, address(token), amount); + vm.prank(l1ERC20BridgeAddress); + sharedBridge.finalizeWithdrawalLegacyErc20Bridge( + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + message, + merkleProof + ); + } + + function test_claimFailedDepositLegacyErc20Bridge_Erc() public { + token.mint(address(sharedBridge), amount); + + // storing depoistHappend[chainId][l2TxHash] = txDataHash. DepositHappened is 3rd so 3 -1 + dependency storage slots + uint256 depositLocationInStorage = uint256(3 - 1 + 1 + 1); + bytes32 txDataHash = keccak256(abi.encode(alice, address(token), amount)); + vm.store( + address(sharedBridge), + keccak256(abi.encode(txHash, keccak256(abi.encode(ERA_CHAIN_ID, depositLocationInStorage)))), + txDataHash + ); + require(sharedBridge.depositHappened(ERA_CHAIN_ID, txHash) == txDataHash, "Deposit not set"); + + uint256 chainBalanceLocationInStorage = uint256(6 - 1 + 1 + 1); + vm.store( + address(sharedBridge), + keccak256( + abi.encode( + uint256(uint160(address(token))), + keccak256(abi.encode(ERA_CHAIN_ID, chainBalanceLocationInStorage)) + ) + ), + bytes32(amount) + ); + + // Bridgehub bridgehub = new Bridgehub(); + // vm.store(address(bridgehub), bytes32(uint256(5 +2)), bytes32(uint256(31337))); + // require(address(bridgehub.deployer()) == address(31337), "Bridgehub: deployer wrong"); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector( + IBridgehub.proveL1ToL2TransactionStatus.selector, + ERA_CHAIN_ID, + txHash, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + merkleProof, + TxStatus.Failure + ), + abi.encode(true) + ); + + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit ClaimedFailedDepositSharedBridge(ERA_CHAIN_ID, alice, address(token), amount); + vm.prank(l1ERC20BridgeAddress); + sharedBridge.claimFailedDepositLegacyErc20Bridge( + alice, + address(token), + amount, + txHash, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + merkleProof + ); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacyHyperEnabled.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacyHyperEnabled.t.sol new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/l1-contracts/test/foundry/unit/concrete/DiamondCut/FacetCut.t.sol b/l1-contracts/test/foundry/unit/concrete/DiamondCut/FacetCut.t.sol index b2135a9ea9e7..a2bbeac5d13e 100644 --- a/l1-contracts/test/foundry/unit/concrete/DiamondCut/FacetCut.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/DiamondCut/FacetCut.t.sol @@ -1,17 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.20; -// solhint-disable max-line-length - -import {DiamondCutTest} from "./_DiamondCut_Shared.t.sol"; -import {DiamondCutTestContract} from "../../../../../cache/solpp-generated-contracts/dev-contracts/test/DiamondCutTestContract.sol"; -import {ExecutorFacet} from "../../../../../cache/solpp-generated-contracts/zksync/facets/Executor.sol"; -import {GettersFacet} from "../../../../../cache/solpp-generated-contracts/zksync/facets/Getters.sol"; -import {MailboxFacet} from "../../../../../cache/solpp-generated-contracts/zksync/facets/Mailbox.sol"; -import {Diamond} from "../../../../../cache/solpp-generated-contracts/zksync/libraries/Diamond.sol"; import {Utils} from "../Utils/Utils.sol"; +import {DiamondCutTest} from "./_DiamondCut_Shared.t.sol"; -// solhint-enable max-line-length +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; +import {DiamondCutTestContract} from "solpp/dev-contracts/test/DiamondCutTestContract.sol"; +import {ExecutorFacet} from "solpp/state-transition/chain-deps/facets/Executor.sol"; +import {GettersFacet} from "solpp/state-transition/chain-deps/facets/Getters.sol"; +import {MailboxFacet} from "solpp/state-transition/chain-deps/facets/Mailbox.sol"; contract FacetCutTest is DiamondCutTest { MailboxFacet private mailboxFacet; diff --git a/l1-contracts/test/foundry/unit/concrete/DiamondCut/Initialization.t.sol b/l1-contracts/test/foundry/unit/concrete/DiamondCut/Initialization.t.sol index 65ff23eccdd8..7a66cb17cc19 100644 --- a/l1-contracts/test/foundry/unit/concrete/DiamondCut/Initialization.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/DiamondCut/Initialization.t.sol @@ -1,15 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.20; -// solhint-disable max-line-length - import {DiamondCutTest} from "./_DiamondCut_Shared.t.sol"; -import {RevertFallback} from "../../../../../cache/solpp-generated-contracts/dev-contracts/RevertFallback.sol"; -import {ReturnSomething} from "../../../../../cache/solpp-generated-contracts/dev-contracts/ReturnSomething.sol"; -import {DiamondCutTestContract} from "../../../../../cache/solpp-generated-contracts/dev-contracts/test/DiamondCutTestContract.sol"; -import {Diamond} from "../../../../../cache/solpp-generated-contracts/zksync/libraries/Diamond.sol"; -// solhint-enable max-line-length +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; +import {DiamondCutTestContract} from "solpp/dev-contracts/test/DiamondCutTestContract.sol"; +import {ReturnSomething} from "solpp/dev-contracts/ReturnSomething.sol"; +import {RevertFallback} from "solpp/dev-contracts/RevertFallback.sol"; contract InitializationTest is DiamondCutTest { address private revertFallbackAddress; diff --git a/l1-contracts/test/foundry/unit/concrete/DiamondCut/UpgradeLogic.t.sol b/l1-contracts/test/foundry/unit/concrete/DiamondCut/UpgradeLogic.t.sol index ab066c0e2d56..23dc3bd5f8f1 100644 --- a/l1-contracts/test/foundry/unit/concrete/DiamondCut/UpgradeLogic.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/DiamondCut/UpgradeLogic.t.sol @@ -1,20 +1,20 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.20; -// solhint-disable max-line-length +import {Utils} from "../Utils/Utils.sol"; import {DiamondCutTest} from "./_DiamondCut_Shared.t.sol"; -import {DiamondCutTestContract} from "../../../../../cache/solpp-generated-contracts/dev-contracts/test/DiamondCutTestContract.sol"; -import {DiamondInit} from "../../../../../cache/solpp-generated-contracts/zksync/DiamondInit.sol"; -import {DiamondProxy} from "../../../../../cache/solpp-generated-contracts/zksync/DiamondProxy.sol"; -import {VerifierParams, FeeParams, PubdataPricingMode} from "../../../../../cache/solpp-generated-contracts/zksync/Storage.sol"; -import {AdminFacet} from "../../../../../cache/solpp-generated-contracts/zksync/facets/Admin.sol"; -import {GettersFacet} from "../../../../../cache/solpp-generated-contracts/zksync/facets/Getters.sol"; -import {IVerifier} from "../../../../../cache/solpp-generated-contracts/zksync/interfaces/IVerifier.sol"; -import {Diamond} from "../../../../../cache/solpp-generated-contracts/zksync/libraries/Diamond.sol"; -import {Utils} from "../Utils/Utils.sol"; -// solhint-enable max-line-length +import {AdminFacet} from "solpp/state-transition/chain-deps/facets/Admin.sol"; +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; +import {DiamondCutTestContract} from "solpp/dev-contracts/test/DiamondCutTestContract.sol"; +import {DiamondInit} from "solpp/state-transition/chain-deps/DiamondInit.sol"; +import {DiamondProxy} from "solpp/state-transition/chain-deps/DiamondProxy.sol"; +import {GettersFacet} from "solpp/state-transition/chain-deps/facets/Getters.sol"; +import {InitializeData} from "solpp/state-transition/chain-deps/DiamondInit.sol"; +import {IVerifier} from "solpp/state-transition/chain-interfaces/IVerifier.sol"; +import {VerifierParams, FeeParams, PubdataPricingMode} from "solpp/state-transition/chain-deps/ZkSyncStateTransitionStorage.sol"; +import {DummyStateTransitionManager} from "solpp/dev-contracts/test/DummyStateTransitionManager.sol"; contract UpgradeLogicTest is DiamondCutTest { DiamondProxy private diamondProxy; @@ -22,26 +22,29 @@ contract UpgradeLogicTest is DiamondCutTest { AdminFacet private adminFacet; AdminFacet private proxyAsAdmin; GettersFacet private proxyAsGetters; - address private governor; + address private admin; + address private stateTransitionManager; address private randomSigner; function getAdminSelectors() private view returns (bytes4[] memory) { - bytes4[] memory dcSelectors = new bytes4[](10); - dcSelectors[0] = adminFacet.setPendingGovernor.selector; - dcSelectors[1] = adminFacet.acceptGovernor.selector; - dcSelectors[2] = adminFacet.setPendingAdmin.selector; - dcSelectors[3] = adminFacet.acceptAdmin.selector; - dcSelectors[4] = adminFacet.setValidator.selector; - dcSelectors[5] = adminFacet.setPorterAvailability.selector; - dcSelectors[6] = adminFacet.setPriorityTxMaxGasLimit.selector; - dcSelectors[7] = adminFacet.executeUpgrade.selector; - dcSelectors[8] = adminFacet.freezeDiamond.selector; - dcSelectors[9] = adminFacet.unfreezeDiamond.selector; - return dcSelectors; + bytes4[] memory selectors = new bytes4[](11); + selectors[0] = adminFacet.setPendingAdmin.selector; + selectors[1] = adminFacet.acceptAdmin.selector; + selectors[2] = adminFacet.setValidator.selector; + selectors[3] = adminFacet.setPorterAvailability.selector; + selectors[4] = adminFacet.setPriorityTxMaxGasLimit.selector; + selectors[5] = adminFacet.changeFeeParams.selector; + selectors[6] = adminFacet.setTokenMultiplier.selector; + selectors[7] = adminFacet.upgradeChainFromVersion.selector; + selectors[8] = adminFacet.executeUpgrade.selector; + selectors[9] = adminFacet.freezeDiamond.selector; + selectors[10] = adminFacet.unfreezeDiamond.selector; + return selectors; } function setUp() public { - governor = makeAddr("governor"); + admin = makeAddr("admin"); + stateTransitionManager = address(new DummyStateTransitionManager()); randomSigner = makeAddr("randomSigner"); diamondCutTestContract = new DiamondCutTestContract(); @@ -69,19 +72,27 @@ contract UpgradeLogicTest is DiamondCutTest { recursionCircuitsSetVksHash: 0 }); - DiamondInit.InitializeData memory params = DiamondInit.InitializeData({ + InitializeData memory params = InitializeData({ + // TODO REVIEW + chainId: 1, + bridgehub: makeAddr("bridgehub"), + stateTransitionManager: stateTransitionManager, + protocolVersion: 0, + admin: admin, + validatorTimelock: makeAddr("validatorTimelock"), + baseToken: makeAddr("baseToken"), + baseTokenBridge: makeAddr("baseTokenBridge"), + storedBatchZero: bytes32(0), + // genesisBatchHash: 0x02c775f0a90abf7a0e8043f2fdc38f0580ca9f9996a895d05a501bfeaa3b2e21, + // genesisIndexRepeatedStorageChanges: 0, + // genesisBatchCommitment: bytes32(0), verifier: IVerifier(0x03752D8252d67f99888E741E3fB642803B29B155), // verifier - governor: governor, - admin: governor, - genesisBatchHash: 0x02c775f0a90abf7a0e8043f2fdc38f0580ca9f9996a895d05a501bfeaa3b2e21, - genesisIndexRepeatedStorageChanges: 0, - genesisBatchCommitment: bytes32(0), verifierParams: dummyVerifierParams, - zkPorterIsAvailable: false, + // zkPorterIsAvailable: false, l2BootloaderBytecodeHash: 0x0100000000000000000000000000000000000000000000000000000000000000, l2DefaultAccountBytecodeHash: 0x0100000000000000000000000000000000000000000000000000000000000000, priorityTxMaxGasLimit: 500000, // priority tx max L2 gas limit - initialProtocolVersion: 0, + // initialProtocolVersion: 0, feeParams: FeeParams({ pubdataPricingMode: PubdataPricingMode.Rollup, batchOverheadL1Gas: 1_000_000, @@ -109,12 +120,12 @@ contract UpgradeLogicTest is DiamondCutTest { function test_RevertWhen_EmergencyFreezeWhenUnauthurizedGovernor() public { vm.startPrank(randomSigner); - vm.expectRevert(abi.encodePacked("1g")); + vm.expectRevert(abi.encodePacked("StateTransition Chain: Only by admin or state transition manager")); proxyAsAdmin.freezeDiamond(); } function test_RevertWhen_DoubleFreezingByGovernor() public { - vm.startPrank(governor); + vm.startPrank(admin); proxyAsAdmin.freezeDiamond(); @@ -123,7 +134,7 @@ contract UpgradeLogicTest is DiamondCutTest { } function test_RevertWhen_UnfreezingWhenNotFrozen() public { - vm.startPrank(governor); + vm.startPrank(admin); vm.expectRevert(abi.encodePacked("a7")); proxyAsAdmin.unfreezeDiamond(); @@ -144,7 +155,7 @@ contract UpgradeLogicTest is DiamondCutTest { initCalldata: bytes("") }); - vm.startPrank(governor); + vm.startPrank(stateTransitionManager); proxyAsAdmin.executeUpgrade(diamondCutData); @@ -175,7 +186,7 @@ contract UpgradeLogicTest is DiamondCutTest { initCalldata: bytes("") }); - vm.startPrank(governor); + vm.startPrank(stateTransitionManager); proxyAsAdmin.executeUpgrade(diamondCutData); proxyAsAdmin.executeUpgrade(diamondCutData); diff --git a/l1-contracts/test/foundry/unit/concrete/DiamondCut/_DiamondCut_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/DiamondCut/_DiamondCut_Shared.t.sol index 178050202c10..9b706d8b1f51 100644 --- a/l1-contracts/test/foundry/unit/concrete/DiamondCut/_DiamondCut_Shared.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/DiamondCut/_DiamondCut_Shared.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.20; -// solhint-disable max-line-length - import {Test} from "forge-std/Test.sol"; -import {DiamondCutTestContract} from "../../../../../cache/solpp-generated-contracts/dev-contracts/test/DiamondCutTestContract.sol"; -import {GettersFacet} from "../../../../../cache/solpp-generated-contracts/zksync/facets/Getters.sol"; -// solhint-enable max-line-length +import {DiamondCutTestContract} from "solpp/dev-contracts/test/DiamondCutTestContract.sol"; +import {GettersFacet} from "solpp/state-transition/chain-deps/facets/Getters.sol"; contract DiamondCutTest is Test { DiamondCutTestContract internal diamondCutTestContract; diff --git a/l1-contracts/test/foundry/unit/concrete/Executor/Authorization.t.sol b/l1-contracts/test/foundry/unit/concrete/Executor/Authorization.t.sol index aa98e5f3223e..9d30471a4fed 100644 --- a/l1-contracts/test/foundry/unit/concrete/Executor/Authorization.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Executor/Authorization.t.sol @@ -1,9 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.20; -import {ExecutorTest} from "./_Executor_Shared.t.sol"; import {Utils} from "../Utils/Utils.sol"; -import {IExecutor} from "../../../../../cache/solpp-generated-contracts/zksync/interfaces/IExecutor.sol"; + +import {ExecutorTest} from "./_Executor_Shared.t.sol"; + +import {IExecutor} from "solpp/state-transition/chain-interfaces/IExecutor.sol"; contract AuthorizationTest is ExecutorTest { IExecutor.StoredBatchInfo private storedBatchInfo; @@ -41,7 +43,7 @@ contract AuthorizationTest is ExecutorTest { vm.prank(randomSigner); - vm.expectRevert(bytes.concat("1h")); + vm.expectRevert(bytes.concat("StateTransition Chain: not validator")); executor.commitBatches(storedBatchInfo, commitBatchInfoArray); } @@ -51,7 +53,7 @@ contract AuthorizationTest is ExecutorTest { vm.prank(owner); - vm.expectRevert(bytes.concat("1h")); + vm.expectRevert(bytes.concat("StateTransition Chain: not validator")); executor.proveBatches(storedBatchInfo, storedBatchInfoArray, proofInput); } @@ -61,7 +63,7 @@ contract AuthorizationTest is ExecutorTest { vm.prank(randomSigner); - vm.expectRevert(bytes.concat("1h")); + vm.expectRevert(bytes.concat("StateTransition Chain: not validator")); executor.executeBatches(storedBatchInfoArray); } } diff --git a/l1-contracts/test/foundry/unit/concrete/Executor/Committing.t.sol b/l1-contracts/test/foundry/unit/concrete/Executor/Committing.t.sol index f2bf0fce4992..4482856328b8 100644 --- a/l1-contracts/test/foundry/unit/concrete/Executor/Committing.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Executor/Committing.t.sol @@ -2,11 +2,12 @@ pragma solidity 0.8.20; import {Vm} from "forge-std/Test.sol"; -import {ExecutorTest} from "./_Executor_Shared.t.sol"; import {Utils, L2_BOOTLOADER_ADDRESS, L2_SYSTEM_CONTEXT_ADDRESS} from "../Utils/Utils.sol"; -import {IExecutor} from "solpp/zksync/interfaces/IExecutor.sol"; -import {SystemLogKey} from "solpp/zksync/interfaces/IExecutor.sol"; -import {POINT_EVALUATION_PRECOMPILE_ADDR} from "solpp/zksync/Config.sol"; +import {ExecutorTest} from "./_Executor_Shared.t.sol"; + +import {IExecutor} from "solpp/state-transition/chain-interfaces/IExecutor.sol"; +import {SystemLogKey} from "solpp/state-transition/chain-interfaces/IExecutor.sol"; +import {POINT_EVALUATION_PRECOMPILE_ADDR} from "solpp/common/Config.sol"; import {L2_PUBDATA_CHUNK_PUBLISHER_ADDR} from "solpp/common/L2ContractAddresses.sol"; contract CommittingTest is ExecutorTest { diff --git a/l1-contracts/test/foundry/unit/concrete/Executor/Executing.t.sol b/l1-contracts/test/foundry/unit/concrete/Executor/Executing.t.sol index ccae4dd2e2ea..e96e236a7230 100644 --- a/l1-contracts/test/foundry/unit/concrete/Executor/Executing.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Executor/Executing.t.sol @@ -1,16 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.20; -// solhint-disable max-line-length - import {Vm} from "forge-std/Test.sol"; -import {ExecutorTest} from "./_Executor_Shared.t.sol"; import {Utils, L2_SYSTEM_CONTEXT_ADDRESS} from "../Utils/Utils.sol"; import {L2_BOOTLOADER_ADDRESS} from "../../../../../cache/solpp-generated-contracts/common/L2ContractAddresses.sol"; -import {COMMIT_TIMESTAMP_NOT_OLDER, REQUIRED_L2_GAS_PRICE_PER_PUBDATA} from "../../../../../cache/solpp-generated-contracts/zksync/Config.sol"; -import {IExecutor, SystemLogKey} from "../../../../../cache/solpp-generated-contracts/zksync/interfaces/IExecutor.sol"; +import {COMMIT_TIMESTAMP_NOT_OLDER, REQUIRED_L2_GAS_PRICE_PER_PUBDATA} from "../../../../../cache/solpp-generated-contracts/common/Config.sol"; +import {IExecutor, SystemLogKey} from "../../../../../cache/solpp-generated-contracts/state-transition/chain-interfaces/IExecutor.sol"; + +import {ExecutorTest} from "./_Executor_Shared.t.sol"; -// solhint-enable max-line-length +import {COMMIT_TIMESTAMP_NOT_OLDER, REQUIRED_L2_GAS_PRICE_PER_PUBDATA} from "solpp/common/Config.sol"; +import {IExecutor} from "solpp/state-transition/chain-interfaces/IExecutor.sol"; +import {L2_BOOTLOADER_ADDRESS} from "solpp/common/L2ContractAddresses.sol"; contract ExecutingTest is ExecutorTest { function setUp() public { diff --git a/l1-contracts/test/foundry/unit/concrete/Executor/ExecutorProof.t.sol b/l1-contracts/test/foundry/unit/concrete/Executor/ExecutorProof.t.sol new file mode 100644 index 000000000000..c2dcb404a3a0 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Executor/ExecutorProof.t.sol @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {Utils} from "foundry-test/unit/concrete/Utils/Utils.sol"; +import {UtilsFacet} from "foundry-test/unit/concrete/Utils/UtilsFacet.sol"; + +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; +import {ExecutorFacet} from "solpp/state-transition/chain-deps/facets/Executor.sol"; +import {IExecutor, LogProcessingOutput} from "solpp/state-transition/chain-interfaces/IExecutor.sol"; +import {VerifierParams, PubdataPricingMode} from "solpp/state-transition/chain-deps/ZkSyncStateTransitionStorage.sol"; + +contract TestExecutorFacet is ExecutorFacet { + function createBatchCommitment( + CommitBatchInfo calldata _newBatchData, + bytes32 _stateDiffHash, + bytes32[] memory _blobCommitments, + bytes32[] memory _blobHashes + ) external view returns (bytes32) { + return _createBatchCommitment(_newBatchData, _stateDiffHash, _blobCommitments, _blobHashes); + } + + function getBatchProofPublicInput( + bytes32 _prevBatchCommitment, + bytes32 _currentBatchCommitment, + VerifierParams memory _verifierParams + ) external pure returns (uint256) { + return _getBatchProofPublicInput(_prevBatchCommitment, _currentBatchCommitment, _verifierParams); + } + + function processL2Logs( + CommitBatchInfo calldata _newBatch, + bytes32 _expectedSystemContractUpgradeTxHash + ) external pure returns (LogProcessingOutput memory logOutput) { + return _processL2Logs(_newBatch, _expectedSystemContractUpgradeTxHash); + } + + // add this to be excluded from coverage report + function test() internal virtual {} +} + +contract ExecutorProofTest is Test { + UtilsFacet internal utilsFacet; + TestExecutorFacet internal executor; + + function getTestExecutorFacetSelectors() private pure returns (bytes4[] memory) { + bytes4[] memory selectors = new bytes4[](3); + selectors[0] = TestExecutorFacet.createBatchCommitment.selector; + selectors[1] = TestExecutorFacet.getBatchProofPublicInput.selector; + selectors[2] = TestExecutorFacet.processL2Logs.selector; + return selectors; + } + + function setUp() public { + Diamond.FacetCut[] memory facetCuts = new Diamond.FacetCut[](2); + facetCuts[0] = Diamond.FacetCut({ + facet: address(new TestExecutorFacet()), + action: Diamond.Action.Add, + isFreezable: true, + selectors: getTestExecutorFacetSelectors() + }); + facetCuts[1] = Diamond.FacetCut({ + facet: address(new UtilsFacet()), + action: Diamond.Action.Add, + isFreezable: true, + selectors: Utils.getUtilsFacetSelectors() + }); + + address diamondProxy = Utils.makeDiamondProxy(facetCuts); + executor = TestExecutorFacet(diamondProxy); + utilsFacet = UtilsFacet(diamondProxy); + } + + /// This test is based on a block generated in a local system. + function test_Hashes() public { + utilsFacet.util_setL2DefaultAccountBytecodeHash( + 0x0100065d134a862a777e50059f5e0fbe68b583f3617a67820f7edda0d7f253a0 + ); + utilsFacet.util_setL2BootloaderBytecodeHash(0x010009416e909e0819593a9806bbc841d25c5cdfed3f4a1523497c6814e5194a); + utilsFacet.util_setZkPorterAvailability(false); + + IExecutor.CommitBatchInfo memory nextBatch = IExecutor.CommitBatchInfo({ + // ignored + batchNumber: 1, + // ignored + timestamp: 100, + indexRepeatedStorageChanges: 84, + newStateRoot: 0x9cf7bb72401a56039ca097cabed20a72221c944ed9b0e515c083c04663ab45a6, + // ignored + numberOfLayer1Txs: 10, + // ignored + priorityOperationsHash: 0x167f4ca80269c9520ad951eeeda28dd3deb0715e9e2917461e81a60120a14183, + bootloaderHeapInitialContentsHash: 0x540442e48142fa061a81822184f7790e7b69dea92153d38ef623802c6f0411c0, + eventsQueueStateHash: 0xda42ab7994d4695a25f4ea8a9a485a592b7a31c20d5dae6363828de86d8826ea, + systemLogs: abi.encodePacked( + hex"00000000000000000000000000000000000000000000800b000000000000000000000000000000000000000000000000000000000000000416914ac26bb9cafa0f1dfaeaab10745a9094e1b60c7076fedf21651d6a25b5740000000a000000000000000000000000000000000000800b0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000651bcde0000000000000000000000000651bcde20001000a00000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000005167f4ca80269c9520ad951eeeda28dd3deb0715e9e2917461e81a60120a141830001000a00000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0001000a00000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000000ee6ee8f50659bd8be3d86c32efb02baa5571cf3b46dd7ea3db733ae181747b8b0001000a0000000000000000000000000000000000008008000000000000000000000000000000000000000000000000000000000000000160fc5fb513ca8e6f6232a7410797954dcb6edbf9081768da24b483aca91c54db0001000a000000000000000000000000000000000000800800000000000000000000000000000000000000000000000000000000000000029a67073c2df8f53087fcfc32d82c98bba591da35df6ce1fb55a23b677d37f9fc0000000000000000000000000000000000000000000080110000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000801100000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000" + ), + pubdataCommitments: abi.encodePacked( + hex"000000000a000100000000000000000000000000000000000000008001760f6100ddbd86c4d5a58532923e7424d33ffb44145a26171d9b2595a349450b0000000000000000000000000000000000000000000000000000000000000001000100010000000000000000000000000000000000008001a789fe4e2a955eee45d44f408f86203c8f643910bf4888d1fd1465cdbc6376d800000000000000000000000000000000000000000000000000000000000000010001000200000000000000000000000000000000000080016ba43e7c7df11e5a655f22c9bce1b37434afd2bf8fcdb10100a460e6a2c0cc83000000000000000000000000000000000000000000000000000000000000000100010003000000000000000000000000000000000000800156e569838658c17c756aa9f6e40de8f1c41b1a67fea5214ec47869882ecda9bd0000000000000000000000000000000000000000000000000000000000000001000100040000000000000000000000000000000000008001ab5d064ba75c02635fd6e4de7fd8420eda54c4bda05bd61edabe201f2066d38f00000000000000000000000000000000000000000000000000000000000000010001000500000000000000000000000000000000000080015bcb6d7c735023e0884297db5016a6c704e3490ed0671417639313ecea86795b00000000000000000000000000000000000000000000000000000000000000010001000600000000000000000000000000000000000080015ee51b5b7d47fae5811a9f777174bb08d81d78098c8bd9430a7618756a0ceb8b00000000000000000000000000000000000000000000000000000000000000010001000700000000000000000000000000000000000080011ea63171021b9ab0846efbe0a06f7882d76e24a4900c74c14fa1e0bdf313ed560000000000000000000000000000000000000000000000000000000000000001000100080000000000000000000000000000000000008001574537f1665cd9c894d8d9834d32ed291f49ae1165a0e12a79a4937f2425bf70000000000000000000000000000000000000000000000000000000000000000100010009000000000000000000000000000000000000800190558033c8a3f7c20c81e613e00a9d0e678a7a14923e94e7cb99c8621c7918090000000000000000000000000000000000000000000000000000000000000001000000000000000001000c3104003d1291725c657fe486d0e626f562842175a705a9704c0980b40e3d716b95bbf9e8000100005dd96deb789fbc05264165795bf652190645bfae1ce253ce1db17087a898fb1e240ebf0d53563011198fddab33312923ba20f3c56cf1ba18ca5be9c053000100022bd65a924da61271d1dd5080fc640601185125830805e0ceb42f4185e5118fb454a12a3d9e0c1fbb89230f67044cc191e4f18459261233f659c9e2ba5e000100008b9feb52993729436da78b2863dd56d8d757e19c01a2cdcf1940e45ca9979941fa93f5f699afeab75e8b25cfea22004a8d2ea49f057741c2f2b910996d00010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4afee3cea48e96b9bddb544b4569e60736a1f1fe919e223fcc08f74acf3513be1200010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4a8755061217b6a78f5d5f8af6e326e482ebdc57f7144108662d122252ddcc27e7000100045dddc527887dc39b9cd189d6f183f16217393a5d3d3165fead2daeaf4f2d6916280c572561a809555de4a87d7a56d5bcca2c246a389dbb2a24c5639bdb0001000153c0f36532563ba2a10f52b865e558cd1a5eef9a9edd01c1cb23b74aa772beb4f3e3b784609f4e205a09863c0587e63b4b47664022cb34896a1711416b00010003e7842b0b4f4fd8e665883fe9c158ba8d38347840f1da0a75aca1fc284ce2428454b48df9f5551500fc50b63af4741b1cd21d4cfddc69aa46cb78eff45b00010000f183703a165afed04326ad5786316f6fc65b27f1cf17459a52bd1f57f27f896b7429e070ca76e3e33165ec75f6c9f439ee37f3b58822494b1251c8247500010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4a05ea3d0bb218598c42b2e25ae5f6cbc9369b273ee6610450cade89775646b2a08902000000000000000000000000000000008b71d4a184058d07fccac4348ae02a1f663403231b0a40fa2c8c0ff73bdca092890200000000000000000000000000000000ab63c4cebbd508a7d7184f0b9134453eea7a09ca749610d5576f8046241b9cde890200000000000000000000000000000000e58af14be53d8ac56f58ff3e5b07c239bfb549149f067597e9d028f35e3c2b77890200000000000000000000000000000000b78e94980fec3a5f68aa25d0d934084907688e537e82c2942af905aab21413ab890200000000000000000000000000000000c4db460819691e825328b532024bbecdc40394c74307a00bd245fc658b1bd34f0901908827f2052a14b24a10cae1f9e259ead06a89a1d74ff736a54f54ebcf05eeb30901d32d07305b87debd25698d4dfac4c2f986693a4e9d9baff7da37a7b5ca8d01cb0901e73042e5dacff2ce20a720c9c6d694576e4afa7bbbafdc4d409c63b7ca8027b70901760a7405795441aceea3be649a53d02785cb3487b7bd23e3b4888a935cee010d09011f3acf5d6d7bfeab8a7112771866e28c3714e0c315a81ec6a58ab4ad1c3d6eb10901c207b49d14deb3af9bc960d57074e27386285c73248abc5fa1d72aa6e8664fa40901644f0c4e15446d7e5ff363c944b55bd6801a1f38afd984c3427569530cb663210901743be0243628b8e7e8f04c00fc4f88efae001250a7482b31e6a0ec87ee3598e7090171e91721f9918576d760f02f03cac47c6f4003316031848e3c1d99e6e83a47434102d84e69f2f480002d5a6962cccee5d4adb48a36bbbf443a531721484381125937f3001ac5ff875b41022f496efbbdb2007b727eb806c926fb20c8ad087c57422977cebd06373e26d19b640e5fe32c85ff39a904faf736ce00a25420c1d9d705358c134cc601d9d184cb4dfdde7e1cac2bc3d4d38bf9ec44e6210ee6b280123bafc586f77764488cd24c6a77546e5a0fe8bdfb4fa203cfaffc36cce4dd5b8901000000000000000000000000651bcde08e7dd06ac5b73b473be6bc5a51030f4c7437657cb7b29bf376c564b8d1675a5e8903000000000000000000000000651bcde24ba84e1f37d041bc6e55ba396826cc494e84d4815b6db52690422eea7386314f00e8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244c3de2202ccb626ad387d70722e64fbe44562e2f231a290c08532b8d6aba402ff50025fe002039e87b424de2772b82d935f14e2b657429a1bcc04612391ea0330c90ebddefdda48eb2aa7f66ecf7940a280e9ef3fb2e95db0995538440a62af79861004434720529e816fd2e40f8031a8d7471ebcd00351db0787346bcfe8dfad8d2b479093588d0e847efa73a10ce20e4799fb1e46642d65617c7e5213fa04989d92d8903000000000000000000000000651bcde287ded247e1660f827071c7f1371934589751085384fc9f4462c1f1897c5c3eef890100000000000000000000000000000001911dd2ad743ff237d411648a0fe32c6d74eec060716a2a74352f6b1c435b5d670016914ac26bb9cafa0f1dfaeaab10745a9094e1b60c7076fedf21651d6a25b574686a068c708f1bdbefd9e6e454ac2b520fd41c8dcf23ecd4cee978c22f1c1f5f09ff974fe8b575175cefa919a5ba1c0ddf4409be4b16695dc7bd12f6701b99bd2e70a152312ad6f01657413b2eae9287f6b9adad93d5fed1a0dd5e13ec74ce1163146509bfe426f2315a69cb452bf388cccd321eca2746a1adf793b489e5c8f61c40688b7ef3e53defc56c78facf513e511f9f5ba0eb50dbcc745afea3b860da75b394d2d1627b6e2ef54fb7b187d0af61e4532c238f387ecf9f0b466f1d54414100018e519b65c8901b344a480638beadb923fbd3462e475d39acebe559d65ed5cb11a1b25279f1918477c35eec1332ff07001d3f85cf854b70d7552f93ba8e88d581064ca4c0df6ac456c00a0e83898ccd464c63e5008aa1a498cc0646b78eb216d9eeeec76ed0eb0ee6c352f35ca5f0b2edc2ca17d211cc5cb905ba10142f042a6ac836d9cef9a6916635c9a1c1d2dc62a9fe83e2230b506b98e0fded46249008fe28b813907a05ae0d773d8f31e330200e9336e0159034c137ed645fb67ccca8a152312ad6f01657413b2eae9287f6b9adad93d5fee5d8f810abde496ccbeb45a4f3c06af828975163a006257cbf18cefebbfb4cd409025f40404a3d37bba024799ce32d7c2a833aec8474288a26b246afa32b07b4a3ce00577261707065642045746865720000000000000000000000000000000000001a09cf14f266dfe87c4b33e6d934de01f8f7242199fa8783178117218fa033f7ab005745544800000000000000000000000000000000000000000000000000000008289026c5fa173652bd62774824698a6848c63031f853d0e275174552f35df33000577261707065642045746865720000000000000000000000000000000000001a1e59309944cbc900ae848855e10bc929f78e86c2179d6e96cf52bfd520f039200031000000000000000000000000000000000000000000000000000000000000021653a735395136e5494c5426ba972b45e34d36ebcb86ac104c724ab375fcce90a18580ba6aeebc6e6b89d226c79be8927257a436ad11d9c0305b18e9d78cab8f75a3aec2096302b67e3815939e29476fb36a0d8299a1b25279f1918477c35eec1332ff07001d3f85cf85688525f98e4859a9c6939f2d2f92e6b1950ed57e56137d717aca1ccf9754f719a1c7ebe9226d26524400a8959a08f411a727ae7bb68f8febecd89ffe9d84708d24544d452de3e22e62b3b2b872e430839a15115818a152312ad6f01657413b2eae9287f6b9adad93d5fe3fb60af355125687beeb90c066ace76c442b0f963a6afd0e3316fcdd673ad22c09ff30c8a03ec44e5337a1f9d66763cf1b319fdc6d8bc4981e1f47edbd86210614b909ff0cbdceb634b81192417b64d114d535ad3bdba97d6d7e90ee2a79bf1c132d3c2d09ff5cd85060f4ff26eb5b68a6687aee76c1b7a77575fdc86ba49b4faf5041377a79b14de8989f2385a6e23f6bd05a80e0d9231870c15a000142e50adc0d84bff439d0086d9fbab9984f8b27aa208935238a60cc62e7c9bb2ea1709e94c96366b3c40ea4854837c18733e5ac1193b8d8e4070d2eca4441b0378b572bd949ab764fd71c002b759613c3e29d425cf4000100012730c940a81021004e899c6ee4bec02f0667757b9d75a8f0714ce6c157f5940b7664e4f69f01fc530db36965e33599a1348629f07ae2d724007ac36a71a16baac84db583d88e0f3a8c082e3632fcc0e15757f0dcf5234b87af41fdee4c0999c4fe698a8d824415979ab839e6913a975a3055a152312ad6f01657413b2eae9287f6b9adad93d5fe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ) + }); + LogProcessingOutput memory logOutput = executor.processL2Logs( + nextBatch, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assertEq( + logOutput.stateDiffHash, + 0x9a67073c2df8f53087fcfc32d82c98bba591da35df6ce1fb55a23b677d37f9fc, + "stateDiffHash computation failed" + ); + + bytes32 nextCommitment = executor.createBatchCommitment( + nextBatch, + logOutput.stateDiffHash, + new bytes32[](2), + new bytes32[](2) + ); + assertEq( + nextCommitment, + 0x38ba669c30ce03475c4139fdab5d43fd8edd78f50ee3e99101cc55ad29b6efb0, + "nextCommitment computation failed" + ); + + bytes32 prevCommitment = 0x6ebf945305689a8c3ac993df7f002d41d311a762cd6bf39bb054ead8d1f54404; + VerifierParams memory verifierParams = VerifierParams({ + recursionNodeLevelVkHash: 0x5a3ef282b21e12fe1f4438e5bb158fc5060b160559c5158c6389d62d9fe3d080, + recursionLeafLevelVkHash: 0x72167c43a46cf38875b267d67716edc4563861364a3c03ab7aee73498421e828, + // ignored. + recursionCircuitsSetVksHash: 0x05dc05911af0aee6a0950ee36dad423981cf05a58cfdb479109bff3c2262eaac + }); + uint256 result = executor.getBatchProofPublicInput(prevCommitment, nextCommitment, verifierParams); + + assertEq(result, 0x3449f2e7025a7731ca98a7a232211f88ae4e5e8ae2ce41fd40866f7b, "getBatchProofPublicInput"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Executor/Proving.t.sol b/l1-contracts/test/foundry/unit/concrete/Executor/Proving.t.sol index 11e7b909750c..664760aa4fe7 100644 --- a/l1-contracts/test/foundry/unit/concrete/Executor/Proving.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Executor/Proving.t.sol @@ -2,10 +2,12 @@ pragma solidity 0.8.20; import {Vm} from "forge-std/Test.sol"; -import {ExecutorTest} from "./_Executor_Shared.t.sol"; import {Utils, L2_SYSTEM_CONTEXT_ADDRESS} from "../Utils/Utils.sol"; -import {COMMIT_TIMESTAMP_NOT_OLDER} from "../../../../../cache/solpp-generated-contracts/zksync/Config.sol"; -import {IExecutor, SystemLogKey} from "../../../../../cache/solpp-generated-contracts/zksync/interfaces/IExecutor.sol"; + +import {ExecutorTest} from "./_Executor_Shared.t.sol"; + +import {COMMIT_TIMESTAMP_NOT_OLDER} from "solpp/common/Config.sol"; +import {IExecutor, SystemLogKey} from "solpp/state-transition/chain-interfaces/IExecutor.sol"; contract ProvingTest is ExecutorTest { function setUp() public { diff --git a/l1-contracts/test/foundry/unit/concrete/Executor/Reverting.t.sol b/l1-contracts/test/foundry/unit/concrete/Executor/Reverting.t.sol index 450ebe9a8262..0a10b69ab1ea 100644 --- a/l1-contracts/test/foundry/unit/concrete/Executor/Reverting.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Executor/Reverting.t.sol @@ -2,10 +2,12 @@ pragma solidity 0.8.20; import {Vm} from "forge-std/Test.sol"; -import {ExecutorTest} from "./_Executor_Shared.t.sol"; import {Utils, L2_SYSTEM_CONTEXT_ADDRESS} from "../Utils/Utils.sol"; -import {COMMIT_TIMESTAMP_NOT_OLDER} from "../../../../../cache/solpp-generated-contracts/zksync/Config.sol"; -import {IExecutor, SystemLogKey} from "../../../../../cache/solpp-generated-contracts/zksync/interfaces/IExecutor.sol"; + +import {ExecutorTest} from "./_Executor_Shared.t.sol"; + +import {COMMIT_TIMESTAMP_NOT_OLDER} from "solpp/common/Config.sol"; +import {IExecutor, SystemLogKey} from "solpp/state-transition/chain-interfaces/IExecutor.sol"; contract RevertingTest is ExecutorTest { function setUp() public { diff --git a/l1-contracts/test/foundry/unit/concrete/Executor/_Executor_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/Executor/_Executor_Shared.t.sol index 6fed051f168a..bfc6b9442c3e 100644 --- a/l1-contracts/test/foundry/unit/concrete/Executor/_Executor_Shared.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Executor/_Executor_Shared.t.sol @@ -4,17 +4,21 @@ pragma solidity 0.8.20; import {Test} from "forge-std/Test.sol"; import {Utils, DEFAULT_L2_LOGS_TREE_ROOT_HASH} from "../Utils/Utils.sol"; -import {COMMIT_TIMESTAMP_NOT_OLDER} from "../../../../../cache/solpp-generated-contracts/zksync/Config.sol"; -import {DiamondInit} from "../../../../../cache/solpp-generated-contracts/zksync/DiamondInit.sol"; -import {DiamondProxy} from "../../../../../cache/solpp-generated-contracts/zksync/DiamondProxy.sol"; -import {VerifierParams, FeeParams, PubdataPricingMode} from "../../../../../cache/solpp-generated-contracts/zksync/Storage.sol"; -import {ExecutorFacet} from "../../../../../cache/solpp-generated-contracts/zksync/facets/Executor.sol"; -import {GettersFacet} from "../../../../../cache/solpp-generated-contracts/zksync/facets/Getters.sol"; -import {AdminFacet} from "../../../../../cache/solpp-generated-contracts/zksync/facets/Admin.sol"; -import {MailboxFacet} from "../../../../../cache/solpp-generated-contracts/zksync/facets/Mailbox.sol"; -import {IExecutor} from "../../../../../cache/solpp-generated-contracts/zksync/interfaces/IExecutor.sol"; -import {IVerifier} from "../../../../../cache/solpp-generated-contracts/zksync/interfaces/IVerifier.sol"; -import {Diamond} from "../../../../../cache/solpp-generated-contracts/zksync/libraries/Diamond.sol"; + +import {AdminFacet} from "solpp/state-transition/chain-deps/facets/Admin.sol"; +import {COMMIT_TIMESTAMP_NOT_OLDER, ETH_TOKEN_ADDRESS, ERA_CHAIN_ID} from "solpp/common/Config.sol"; +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; +import {DiamondInit} from "solpp/state-transition/chain-deps/DiamondInit.sol"; +import {DiamondProxy} from "solpp/state-transition/chain-deps/DiamondProxy.sol"; +import {ExecutorFacet} from "solpp/state-transition/chain-deps/facets/Executor.sol"; +import {GettersFacet} from "solpp/state-transition/chain-deps/facets/Getters.sol"; +import {IExecutor} from "solpp/state-transition/chain-interfaces/IExecutor.sol"; +import {InitializeData} from "solpp/state-transition/chain-deps/DiamondInit.sol"; +import {IVerifier} from "solpp/state-transition/chain-interfaces/IVerifier.sol"; +import {MailboxFacet} from "solpp/state-transition/chain-deps/facets/Mailbox.sol"; +import {VerifierParams, FeeParams, PubdataPricingMode} from "solpp/state-transition/chain-deps/ZkSyncStateTransitionStorage.sol"; +import {DummyStateTransitionManager} from "solpp/dev-contracts/test/DummyStateTransitionManager.sol"; +import {DummyEraBaseTokenBridge} from "solpp/dev-contracts/test/DummyEraBaseTokenBridge.sol"; contract ExecutorTest is Test { address internal owner; @@ -35,17 +39,18 @@ contract ExecutorTest is Test { IExecutor.ProofInput internal proofInput; function getAdminSelectors() private view returns (bytes4[] memory) { - bytes4[] memory selectors = new bytes4[](10); - selectors[0] = admin.setPendingGovernor.selector; - selectors[1] = admin.acceptGovernor.selector; - selectors[2] = admin.setPendingAdmin.selector; - selectors[3] = admin.acceptAdmin.selector; - selectors[4] = admin.setValidator.selector; - selectors[5] = admin.setPorterAvailability.selector; - selectors[6] = admin.setPriorityTxMaxGasLimit.selector; - selectors[7] = admin.executeUpgrade.selector; - selectors[8] = admin.freezeDiamond.selector; - selectors[9] = admin.unfreezeDiamond.selector; + bytes4[] memory selectors = new bytes4[](11); + selectors[0] = admin.setPendingAdmin.selector; + selectors[1] = admin.acceptAdmin.selector; + selectors[2] = admin.setValidator.selector; + selectors[3] = admin.setPorterAvailability.selector; + selectors[4] = admin.setPriorityTxMaxGasLimit.selector; + selectors[5] = admin.changeFeeParams.selector; + selectors[6] = admin.setTokenMultiplier.selector; + selectors[7] = admin.upgradeChainFromVersion.selector; + selectors[8] = admin.executeUpgrade.selector; + selectors[9] = admin.freezeDiamond.selector; + selectors[10] = admin.unfreezeDiamond.selector; return selectors; } @@ -61,8 +66,8 @@ contract ExecutorTest is Test { function getGettersSelectors() public view returns (bytes4[] memory) { bytes4[] memory selectors = new bytes4[](28); selectors[0] = getters.getVerifier.selector; - selectors[1] = getters.getGovernor.selector; - selectors[2] = getters.getPendingGovernor.selector; + selectors[1] = getters.getAdmin.selector; + selectors[2] = getters.getPendingAdmin.selector; selectors[3] = getters.getTotalBlocksCommitted.selector; selectors[4] = getters.getTotalBlocksVerified.selector; selectors[5] = getters.getTotalBlocksExecuted.selector; @@ -124,28 +129,44 @@ contract ExecutorTest is Test { getters = new GettersFacet(); mailbox = new MailboxFacet(); + DummyStateTransitionManager stateTransitionManager = new DummyStateTransitionManager(); + DiamondInit diamondInit = new DiamondInit(); bytes8 dummyHash = 0x1234567890123456; address dummyAddress = makeAddr("dummyAddress"); - DiamondInit.InitializeData memory params = DiamondInit.InitializeData({ - verifier: IVerifier(dummyAddress), // verifier - governor: owner, + genesisStoredBatchInfo = IExecutor.StoredBatchInfo({ + batchNumber: 0, + batchHash: bytes32(""), + indexRepeatedStorageChanges: 0, + numberOfLayer1Txs: 0, + priorityOperationsHash: keccak256(""), + l2LogsTreeRoot: DEFAULT_L2_LOGS_TREE_ROOT_HASH, + timestamp: 0, + commitment: bytes32("") + }); + + InitializeData memory params = InitializeData({ + // TODO REVIEW + chainId: ERA_CHAIN_ID, + bridgehub: makeAddr("bridgehub"), + stateTransitionManager: address(stateTransitionManager), + protocolVersion: 0, admin: owner, - genesisBatchHash: bytes32(0), - genesisIndexRepeatedStorageChanges: 0, - genesisBatchCommitment: bytes32(0), + validatorTimelock: validator, + baseToken: ETH_TOKEN_ADDRESS, + baseTokenBridge: address(new DummyEraBaseTokenBridge()), + storedBatchZero: keccak256(abi.encode(genesisStoredBatchInfo)), + verifier: IVerifier(dummyAddress), // verifier verifierParams: VerifierParams({ recursionNodeLevelVkHash: 0, recursionLeafLevelVkHash: 0, recursionCircuitsSetVksHash: 0 }), - zkPorterIsAvailable: false, l2BootloaderBytecodeHash: dummyHash, l2DefaultAccountBytecodeHash: dummyHash, priorityTxMaxGasLimit: 1000000, - initialProtocolVersion: 0, feeParams: defaultFeeParams(), blobVersionedHashRetriever: blobVersionedHashRetriever }); @@ -192,24 +213,14 @@ contract ExecutorTest is Test { mailbox = MailboxFacet(address(diamondProxy)); admin = AdminFacet(address(diamondProxy)); - vm.prank(owner); - admin.setValidator(validator, true); + // Initiate the token multiplier to enable L1 -> L2 transactions. + vm.prank(address(stateTransitionManager)); + admin.setTokenMultiplier(1, 1); uint256[] memory recursiveAggregationInput; uint256[] memory serializedProof; proofInput = IExecutor.ProofInput(recursiveAggregationInput, serializedProof); - genesisStoredBatchInfo = IExecutor.StoredBatchInfo({ - batchNumber: 0, - batchHash: bytes32(""), - indexRepeatedStorageChanges: 0, - numberOfLayer1Txs: 0, - priorityOperationsHash: keccak256(""), - l2LogsTreeRoot: DEFAULT_L2_LOGS_TREE_ROOT_HASH, - timestamp: 0, - commitment: bytes32("") - }); - // foundry's default value is 1 for the block's timestamp, it is expected // that block.timestamp > COMMIT_TIMESTAMP_NOT_OLDER + 1 vm.warp(COMMIT_TIMESTAMP_NOT_OLDER + 1 + 1); @@ -229,4 +240,7 @@ contract ExecutorTest is Test { pubdataCommitments: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }); } + + // add this to be excluded from coverage report + function test() internal virtual {} } diff --git a/l1-contracts/test/foundry/unit/concrete/Governance/Authorization.t.sol b/l1-contracts/test/foundry/unit/concrete/Governance/Authorization.t.sol index c81c0276f387..8ae8a30ca114 100644 --- a/l1-contracts/test/foundry/unit/concrete/Governance/Authorization.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Governance/Authorization.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.20; import {GovernanceTest} from "./_Governance_Shared.t.sol"; -import {IGovernance} from "../../../../../cache/solpp-generated-contracts/governance/IGovernance.sol"; +import {IGovernance} from "solpp/governance/IGovernance.sol"; contract Authorization is GovernanceTest { function test_RevertWhen_SchedulingByUnauthorisedAddress() public { diff --git a/l1-contracts/test/foundry/unit/concrete/Governance/Executing.t.sol b/l1-contracts/test/foundry/unit/concrete/Governance/Executing.t.sol index 9d369c2a9b15..88905db78a27 100644 --- a/l1-contracts/test/foundry/unit/concrete/Governance/Executing.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Governance/Executing.t.sol @@ -2,9 +2,11 @@ pragma solidity 0.8.20; import {StdStorage, stdStorage} from "forge-std/Test.sol"; -import {GovernanceTest} from "./_Governance_Shared.t.sol"; import {Utils} from "../Utils/Utils.sol"; -import {IGovernance} from "../../../../../cache/solpp-generated-contracts/governance/IGovernance.sol"; + +import {GovernanceTest} from "./_Governance_Shared.t.sol"; + +import {IGovernance} from "solpp/governance/IGovernance.sol"; contract ExecutingTest is GovernanceTest { using stdStorage for StdStorage; diff --git a/l1-contracts/test/foundry/unit/concrete/Governance/OperationStatus.t.sol b/l1-contracts/test/foundry/unit/concrete/Governance/OperationStatus.t.sol index a60608f295ab..d1ad7de2e079 100644 --- a/l1-contracts/test/foundry/unit/concrete/Governance/OperationStatus.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Governance/OperationStatus.t.sol @@ -1,9 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.20; -import {GovernanceTest} from "./_Governance_Shared.t.sol"; import {Utils} from "../Utils/Utils.sol"; -import {IGovernance} from "../../../../../cache/solpp-generated-contracts/governance/IGovernance.sol"; + +import {GovernanceTest} from "./_Governance_Shared.t.sol"; + +import {IGovernance} from "solpp/governance/IGovernance.sol"; contract OperationStatusTest is GovernanceTest { function test_RandomIdIsNotOperation() public { diff --git a/l1-contracts/test/foundry/unit/concrete/Governance/Reentrancy.t.sol b/l1-contracts/test/foundry/unit/concrete/Governance/Reentrancy.t.sol index 27b9e01c3a91..2f70bb20b21e 100644 --- a/l1-contracts/test/foundry/unit/concrete/Governance/Reentrancy.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Governance/Reentrancy.t.sol @@ -4,9 +4,11 @@ pragma solidity 0.8.20; // solhint-disable max-line-length import {StdStorage, stdStorage} from "forge-std/Test.sol"; + import {GovernanceTest} from "./_Governance_Shared.t.sol"; -import {IGovernance} from "../../../../../cache/solpp-generated-contracts/governance/IGovernance.sol"; -import {ReenterGovernance} from "../../../../../cache/solpp-generated-contracts/dev-contracts/test/ReenterGovernance.sol"; + +import {IGovernance} from "solpp/governance/IGovernance.sol"; +import {ReenterGovernance} from "solpp/dev-contracts/test/ReenterGovernance.sol"; contract ReentrancyTest is GovernanceTest { using stdStorage for StdStorage; diff --git a/l1-contracts/test/foundry/unit/concrete/Governance/SelfUpgrades.t.sol b/l1-contracts/test/foundry/unit/concrete/Governance/SelfUpgrades.t.sol index 20665aaec4a8..ef15882da922 100644 --- a/l1-contracts/test/foundry/unit/concrete/Governance/SelfUpgrades.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Governance/SelfUpgrades.t.sol @@ -1,9 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.20; -import {GovernanceTest} from "./_Governance_Shared.t.sol"; import {Utils} from "../Utils/Utils.sol"; -import {IGovernance} from "../../../../../cache/solpp-generated-contracts/governance/IGovernance.sol"; + +import {GovernanceTest} from "./_Governance_Shared.t.sol"; + +import {IGovernance} from "solpp/governance/IGovernance.sol"; contract SeflUpgradesTest is GovernanceTest { event ChangeSecurityCouncil(address _securityCouncilBefore, address _securityCouncilAfter); diff --git a/l1-contracts/test/foundry/unit/concrete/Governance/_Governance_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/Governance/_Governance_Shared.t.sol index 35a6e7fde3cd..12870a240622 100644 --- a/l1-contracts/test/foundry/unit/concrete/Governance/_Governance_Shared.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Governance/_Governance_Shared.t.sol @@ -3,11 +3,12 @@ pragma solidity 0.8.20; import {Test} from "forge-std/Test.sol"; -import {Governance} from "../../../../../cache/solpp-generated-contracts/governance/Governance.sol"; -import {IGovernance} from "../../../../../cache/solpp-generated-contracts/governance/IGovernance.sol"; -import {EventOnFallback} from "../../../../../cache/solpp-generated-contracts/dev-contracts/EventOnFallback.sol"; -import {Forwarder} from "../../../../../cache/solpp-generated-contracts/dev-contracts/Forwarder.sol"; -import {RevertFallback} from "../../../../../cache/solpp-generated-contracts/dev-contracts/RevertFallback.sol"; + +import {EventOnFallback} from "solpp/dev-contracts/EventOnFallback.sol"; +import {Forwarder} from "solpp/dev-contracts/Forwarder.sol"; +import {Governance} from "solpp/governance/Governance.sol"; +import {IGovernance} from "solpp/governance/IGovernance.sol"; +import {RevertFallback} from "solpp/dev-contracts/RevertFallback.sol"; contract GovernanceTest is Test, EventOnFallback { address internal owner; @@ -61,4 +62,7 @@ contract GovernanceTest is Test, EventOnFallback { calls[0] = IGovernance.Call({target: _target, value: _value, data: _data}); return IGovernance.Operation({calls: calls, salt: bytes32(0), predecessor: bytes32(0)}); } + + // add this to be excluded from coverage report + function test() internal override {} } diff --git a/l1-contracts/test/foundry/unit/concrete/UncheckedMath/_UncheckedMath_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/UncheckedMath/_UncheckedMath_Shared.t.sol deleted file mode 100644 index badc0233e7d6..000000000000 --- a/l1-contracts/test/foundry/unit/concrete/UncheckedMath/_UncheckedMath_Shared.t.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.20; - -import {Test} from "forge-std/Test.sol"; - -contract UncheckedMathTest is Test {} diff --git a/l1-contracts/test/foundry/unit/concrete/Utils/Utils.sol b/l1-contracts/test/foundry/unit/concrete/Utils/Utils.sol index f003f4b32e1f..d65b839d3280 100644 --- a/l1-contracts/test/foundry/unit/concrete/Utils/Utils.sol +++ b/l1-contracts/test/foundry/unit/concrete/Utils/Utils.sol @@ -1,9 +1,20 @@ // SPDX-License-Identifier: MIT + pragma solidity 0.8.20; -import {GettersFacet} from "../../../../../cache/solpp-generated-contracts/zksync/facets/Getters.sol"; -import {MailboxFacet} from "../../../../../cache/solpp-generated-contracts/zksync/facets/Mailbox.sol"; -import {IExecutor, SystemLogKey} from "solpp/zksync/interfaces/IExecutor.sol"; +import {UtilsFacet} from "../Utils/UtilsFacet.sol"; + +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; +import {DiamondInit} from "solpp/state-transition/chain-deps/DiamondInit.sol"; +import {DiamondProxy} from "solpp/state-transition/chain-deps/DiamondProxy.sol"; +import {AdminFacet} from "solpp/state-transition/chain-deps/facets/Admin.sol"; +import {ExecutorFacet} from "solpp/state-transition/chain-deps/facets/Executor.sol"; +import {GettersFacet} from "solpp/state-transition/chain-deps/facets/Getters.sol"; +import {MailboxFacet} from "solpp/state-transition/chain-deps/facets/Mailbox.sol"; +import {IVerifier, VerifierParams} from "solpp/state-transition/chain-deps/ZkSyncStateTransitionStorage.sol"; +import {FeeParams, PubdataPricingMode} from "solpp/state-transition/chain-deps/ZkSyncStateTransitionStorage.sol"; +import {InitializeData, InitializeDataNewChain} from "solpp/state-transition/chain-interfaces/IDiamondInit.sol"; +import {IExecutor, SystemLogKey} from "solpp/state-transition/chain-interfaces/IExecutor.sol"; bytes32 constant DEFAULT_L2_LOGS_TREE_ROOT_HASH = 0x0000000000000000000000000000000000000000000000000000000000000000; address constant L2_SYSTEM_CONTEXT_ADDRESS = 0x000000000000000000000000000000000000800B; @@ -83,6 +94,23 @@ library Utils { return logs; } + function createSystemLogsWithUpgradeTransaction( + bytes32 _expectedSystemContractUpgradeTxHash + ) public pure returns (bytes[] memory) { + bytes[] memory logsWithoutUpgradeTx = createSystemLogs(); + bytes[] memory logs = new bytes[](logsWithoutUpgradeTx.length + 1); + for (uint256 i = 0; i < logsWithoutUpgradeTx.length; i++) { + logs[i] = logsWithoutUpgradeTx[i]; + } + logs[logsWithoutUpgradeTx.length] = constructL2Log( + true, + L2_BOOTLOADER_ADDRESS, + uint256(SystemLogKey.EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY), + _expectedSystemContractUpgradeTxHash + ); + return logs; + } + function createStoredBatchInfo() public pure returns (IExecutor.StoredBatchInfo memory) { return IExecutor.StoredBatchInfo({ @@ -113,6 +141,17 @@ library Utils { }); } + function createProofInput() public view returns (IExecutor.ProofInput memory) { + uint256[] memory recursiveAggregationInput; + uint256[] memory serializedProof; + + return + IExecutor.ProofInput({ + recursiveAggregationInput: recursiveAggregationInput, + serializedProof: serializedProof + }); + } + function encodePacked(bytes[] memory data) public pure returns (bytes memory) { bytes memory result; for (uint256 i = 0; i < data.length; i++) { @@ -121,11 +160,36 @@ library Utils { return result; } + function getAdminSelectors() public view returns (bytes4[] memory) { + bytes4[] memory selectors = new bytes4[](11); + selectors[0] = AdminFacet.setPendingAdmin.selector; + selectors[1] = AdminFacet.acceptAdmin.selector; + selectors[2] = AdminFacet.setValidator.selector; + selectors[3] = AdminFacet.setPorterAvailability.selector; + selectors[4] = AdminFacet.setPriorityTxMaxGasLimit.selector; + selectors[5] = AdminFacet.changeFeeParams.selector; + selectors[6] = AdminFacet.setTokenMultiplier.selector; + selectors[7] = AdminFacet.upgradeChainFromVersion.selector; + selectors[8] = AdminFacet.executeUpgrade.selector; + selectors[9] = AdminFacet.freezeDiamond.selector; + selectors[10] = AdminFacet.unfreezeDiamond.selector; + return selectors; + } + + function getExecutorSelectors() public view returns (bytes4[] memory) { + bytes4[] memory selectors = new bytes4[](4); + selectors[0] = ExecutorFacet.commitBatches.selector; + selectors[1] = ExecutorFacet.proveBatches.selector; + selectors[2] = ExecutorFacet.executeBatches.selector; + selectors[3] = ExecutorFacet.revertBatches.selector; + return selectors; + } + function getGettersSelectors() public pure returns (bytes4[] memory) { bytes4[] memory selectors = new bytes4[](29); selectors[0] = GettersFacet.getVerifier.selector; - selectors[1] = GettersFacet.getGovernor.selector; - selectors[2] = GettersFacet.getPendingGovernor.selector; + selectors[1] = GettersFacet.getAdmin.selector; + selectors[2] = GettersFacet.getPendingAdmin.selector; selectors[3] = GettersFacet.getTotalBlocksCommitted.selector; selectors[4] = GettersFacet.getTotalBlocksVerified.selector; selectors[5] = GettersFacet.getTotalBlocksExecuted.selector; @@ -151,20 +215,185 @@ library Utils { selectors[25] = GettersFacet.getTotalBatchesCommitted.selector; selectors[26] = GettersFacet.getTotalBatchesVerified.selector; selectors[27] = GettersFacet.getTotalBatchesExecuted.selector; + selectors[28] = GettersFacet.getL2SystemContractsUpgradeTxHash.selector; return selectors; } function getMailboxSelectors() public pure returns (bytes4[] memory) { - bytes4[] memory selectors = new bytes4[](6); + bytes4[] memory selectors = new bytes4[](7); selectors[0] = MailboxFacet.proveL2MessageInclusion.selector; selectors[1] = MailboxFacet.proveL2LogInclusion.selector; selectors[2] = MailboxFacet.proveL1ToL2TransactionStatus.selector; selectors[3] = MailboxFacet.finalizeEthWithdrawal.selector; selectors[4] = MailboxFacet.requestL2Transaction.selector; - selectors[5] = MailboxFacet.l2TransactionBaseCost.selector; + selectors[5] = MailboxFacet.bridgehubRequestL2Transaction.selector; + selectors[6] = MailboxFacet.l2TransactionBaseCost.selector; return selectors; } + function getUtilsFacetSelectors() public pure returns (bytes4[] memory) { + bytes4[] memory selectors = new bytes4[](36); + selectors[0] = UtilsFacet.util_setChainId.selector; + selectors[1] = UtilsFacet.util_getChainId.selector; + selectors[2] = UtilsFacet.util_setBridgehub.selector; + selectors[3] = UtilsFacet.util_getBridgehub.selector; + selectors[4] = UtilsFacet.util_setBaseToken.selector; + selectors[5] = UtilsFacet.util_getBaseToken.selector; + selectors[6] = UtilsFacet.util_setBaseTokenBridge.selector; + selectors[7] = UtilsFacet.util_getBaseTokenBridge.selector; + selectors[8] = UtilsFacet.util_setVerifier.selector; + selectors[9] = UtilsFacet.util_getVerifier.selector; + selectors[10] = UtilsFacet.util_setStoredBatchHashes.selector; + selectors[11] = UtilsFacet.util_getStoredBatchHashes.selector; + selectors[12] = UtilsFacet.util_setVerifierParams.selector; + selectors[13] = UtilsFacet.util_getVerifierParams.selector; + selectors[14] = UtilsFacet.util_setL2BootloaderBytecodeHash.selector; + selectors[15] = UtilsFacet.util_getL2BootloaderBytecodeHash.selector; + selectors[16] = UtilsFacet.util_setL2DefaultAccountBytecodeHash.selector; + selectors[17] = UtilsFacet.util_getL2DefaultAccountBytecodeHash.selector; + selectors[18] = UtilsFacet.util_setPendingAdmin.selector; + selectors[19] = UtilsFacet.util_getPendingAdmin.selector; + selectors[20] = UtilsFacet.util_setAdmin.selector; + selectors[21] = UtilsFacet.util_getAdmin.selector; + selectors[22] = UtilsFacet.util_setValidator.selector; + selectors[23] = UtilsFacet.util_getValidator.selector; + selectors[24] = UtilsFacet.util_setZkPorterAvailability.selector; + selectors[25] = UtilsFacet.util_getZkPorterAvailability.selector; + selectors[26] = UtilsFacet.util_setStateTransitionManager.selector; + selectors[27] = UtilsFacet.util_getStateTransitionManager.selector; + selectors[28] = UtilsFacet.util_setPriorityTxMaxGasLimit.selector; + selectors[29] = UtilsFacet.util_getPriorityTxMaxGasLimit.selector; + selectors[30] = UtilsFacet.util_setFeeParams.selector; + selectors[31] = UtilsFacet.util_getFeeParams.selector; + selectors[32] = UtilsFacet.util_setProtocolVersion.selector; + selectors[33] = UtilsFacet.util_getProtocolVersion.selector; + selectors[34] = UtilsFacet.util_setIsFrozen.selector; + selectors[35] = UtilsFacet.util_getIsFrozen.selector; + return selectors; + } + + // function initial_deployment() public returns (address[] memory) { + // GettersFacet gettersFacet = new GettersFacet(); + // MailboxFacet mailboxFacet = new MailboxFacet(); + // // allowList = new AllowList(owner); + // DiamondInit diamondInit = new DiamondInit(); + + // bytes8 dummyHash = 0x1234567890123456; + // address dummyAddress = makeAddr("dummyAddress"); + // bytes memory diamondInitData = abi.encodeWithSelector( + // diamondInit.initialize.selector, + // dummyAddress, + // owner, + // owner, + // 0, + // 0, + // 0, + // allowList, + // VerifierParams({recursionNodeLevelVkHash: 0, recursionLeafLevelVkHash: 0, recursionCircuitsSetVksHash: 0}), + // false, + // dummyHash, + // dummyHash, + // 10000000 + // ); + + // Diamond.FacetCut[] memory facetCuts = new Diamond.FacetCut[](2); + // facetCuts[0] = Diamond.FacetCut({ + // facet: address(gettersFacet), + // action: Diamond.Action.Add, + // isFreezable: false, + // selectors: Utils.getGettersSelectors() + // }); + // facetCuts[1] = Diamond.FacetCut({ + // facet: address(mailboxFacet), + // action: Diamond.Action.Add, + // isFreezable: true, + // selectors: Utils.getMailboxSelectors() + // }); + + // Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ + // facetCuts: facetCuts, + // initAddress: address(diamondInit), + // initCalldata: diamondInitData + // }); + + // uint256 chainId = block.chainid; + // DiamondProxy diamondProxy = new DiamondProxy(chainId, diamondCutData); + + // vm.prank(owner); + // allowList.setAccessMode(address(diamondProxy), IAllowList.AccessMode.Public); + // } + + function makeVerifier() public pure returns (IVerifier) { + return IVerifier(address(0xdeadbeef)); + } + + function makeVerifierParams() public pure returns (VerifierParams memory) { + return + VerifierParams({recursionNodeLevelVkHash: 0, recursionLeafLevelVkHash: 0, recursionCircuitsSetVksHash: 0}); + } + + function makeFeeParams() public pure returns (FeeParams memory) { + return + FeeParams({ + pubdataPricingMode: PubdataPricingMode.Rollup, + batchOverheadL1Gas: 1_000_000, + maxPubdataPerBatch: 110_000, + maxL2GasPerBatch: 80_000_000, + priorityTxMaxPubdata: 99_000, + minimalL2GasPrice: 250_000_000 + }); + } + + function makeInitializeData() public pure returns (InitializeData memory) { + return + InitializeData({ + chainId: 1, + bridgehub: address(0x876543567890), + stateTransitionManager: address(0x1234567890876543567890), + protocolVersion: 0, + admin: address(0x32149872498357874258787), + validatorTimelock: address(0x85430237648403822345345), + baseToken: address(0x923645439232223445), + baseTokenBridge: address(0x23746765237749923040872834), + storedBatchZero: bytes32(0), + verifier: makeVerifier(), + verifierParams: makeVerifierParams(), + l2BootloaderBytecodeHash: 0x0100000000000000000000000000000000000000000000000000000000000000, + l2DefaultAccountBytecodeHash: 0x0100000000000000000000000000000000000000000000000000000000000000, + priorityTxMaxGasLimit: 500000, + feeParams: makeFeeParams(), + blobVersionedHashRetriever: address(0x23746765237749923040872834) + }); + } + + function makeInitializeDataForNewChain() public pure returns (InitializeDataNewChain memory) { + return + InitializeDataNewChain({ + verifier: makeVerifier(), + verifierParams: makeVerifierParams(), + l2BootloaderBytecodeHash: 0x0100000000000000000000000000000000000000000000000000000000000000, + l2DefaultAccountBytecodeHash: 0x0100000000000000000000000000000000000000000000000000000000000000, + priorityTxMaxGasLimit: 80000000, + feeParams: makeFeeParams(), + blobVersionedHashRetriever: address(0x23746765237749923040872834) + }); + } + + function makeDiamondProxy(Diamond.FacetCut[] memory facetCuts) public returns (address) { + DiamondInit diamondInit = new DiamondInit(); + bytes memory diamondInitData = abi.encodeWithSelector(diamondInit.initialize.selector, makeInitializeData()); + + Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ + facetCuts: facetCuts, + initAddress: address(diamondInit), + initCalldata: diamondInitData + }); + + uint256 chainId = block.chainid; + DiamondProxy diamondProxy = new DiamondProxy(chainId, diamondCutData); + return address(diamondProxy); + } + function createBatchCommitment( IExecutor.CommitBatchInfo calldata _newBatchData, bytes32 _stateDiffHash, @@ -221,4 +450,7 @@ library Utils { _blobCommitments[1] ); } + + // add this to be excluded from coverage report + function test() internal {} } diff --git a/l1-contracts/test/foundry/unit/concrete/Utils/Utils.t.sol b/l1-contracts/test/foundry/unit/concrete/Utils/Utils.t.sol index 187c00e904fc..a93f861fea7d 100644 --- a/l1-contracts/test/foundry/unit/concrete/Utils/Utils.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Utils/Utils.t.sol @@ -2,11 +2,9 @@ pragma solidity 0.8.20; -// solhint-disable max-line-length - import {Test} from "forge-std/Test.sol"; import {Utils, L2_TO_L1_MESSENGER, L2_SYSTEM_CONTEXT_ADDRESS, L2_BOOTLOADER_ADDRESS, PUBDATA_PUBLISHER_ADDRESS} from "./Utils.sol"; -import {SystemLogKey} from "solpp/zksync/interfaces/IExecutor.sol"; +import {SystemLogKey} from "solpp/state-transition/chain-interfaces/IExecutor.sol"; // solhint-enable max-line-length @@ -154,4 +152,7 @@ contract UtilsTest is Test { "log[8] should be correct" ); } + + // add this to be excluded from coverage report + function test() internal virtual {} } diff --git a/l1-contracts/test/foundry/unit/concrete/Utils/UtilsFacet.sol b/l1-contracts/test/foundry/unit/concrete/Utils/UtilsFacet.sol new file mode 100644 index 000000000000..abd0ccbc8a8b --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Utils/UtilsFacet.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {IVerifier, VerifierParams} from "solpp/state-transition/chain-deps/ZkSyncStateTransitionStorage.sol"; +import {FeeParams} from "solpp/state-transition/chain-deps/ZkSyncStateTransitionStorage.sol"; +import {ZkSyncStateTransitionBase} from "solpp/state-transition/chain-deps/facets/ZkSyncStateTransitionBase.sol"; +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; + +contract UtilsFacet is ZkSyncStateTransitionBase { + function util_setChainId(uint256 _chainId) external { + s.chainId = _chainId; + } + + function util_getChainId() external view returns (uint256) { + return s.chainId; + } + + function util_setBridgehub(address _bridgehub) external { + s.bridgehub = _bridgehub; + } + + function util_getBridgehub() external view returns (address) { + return s.bridgehub; + } + + function util_setBaseToken(address _baseToken) external { + s.baseToken = _baseToken; + } + + function util_getBaseToken() external view returns (address) { + return s.baseToken; + } + + function util_setBaseTokenBridge(address _baseTokenBridge) external { + s.baseTokenBridge = _baseTokenBridge; + } + + function util_getBaseTokenBridge() external view returns (address) { + return s.baseTokenBridge; + } + + function util_setVerifier(IVerifier _verifier) external { + s.verifier = _verifier; + } + + function util_getVerifier() external view returns (IVerifier) { + return s.verifier; + } + + function util_setStoredBatchHashes(uint32 _batchId, bytes32 _storedBatchHash) external { + s.storedBatchHashes[_batchId] = _storedBatchHash; + } + + function util_getStoredBatchHashes(uint32 _batchId) external view returns (bytes32) { + return s.storedBatchHashes[_batchId]; + } + + function util_setVerifierParams(VerifierParams calldata _verifierParams) external { + s.verifierParams = _verifierParams; + } + + function util_getVerifierParams() external view returns (VerifierParams memory) { + return s.verifierParams; + } + + function util_setL2BootloaderBytecodeHash(bytes32 _l2BootloaderBytecodeHash) external { + s.l2BootloaderBytecodeHash = _l2BootloaderBytecodeHash; + } + + function util_getL2BootloaderBytecodeHash() external view returns (bytes32) { + return s.l2BootloaderBytecodeHash; + } + + function util_setL2DefaultAccountBytecodeHash(bytes32 _l2DefaultAccountBytecodeHash) external { + s.l2DefaultAccountBytecodeHash = _l2DefaultAccountBytecodeHash; + } + + function util_getL2DefaultAccountBytecodeHash() external view returns (bytes32) { + return s.l2DefaultAccountBytecodeHash; + } + + function util_setPendingAdmin(address _pendingAdmin) external { + s.pendingAdmin = _pendingAdmin; + } + + function util_getPendingAdmin() external view returns (address) { + return s.pendingAdmin; + } + + function util_setAdmin(address _admin) external { + s.admin = _admin; + } + + function util_getAdmin() external view returns (address) { + return s.admin; + } + + function util_setValidator(address _validator, bool _active) external { + s.validators[_validator] = _active; + } + + function util_getValidator(address _validator) external view returns (bool) { + return s.validators[_validator]; + } + + function util_setZkPorterAvailability(bool _available) external { + s.zkPorterIsAvailable = _available; + } + + function util_getZkPorterAvailability() external view returns (bool) { + return s.zkPorterIsAvailable; + } + + function util_setStateTransitionManager(address _stateTransitionManager) external { + s.stateTransitionManager = _stateTransitionManager; + } + + function util_getStateTransitionManager() external view returns (address) { + return s.stateTransitionManager; + } + + function util_setPriorityTxMaxGasLimit(uint256 _priorityTxMaxGasLimit) external { + s.priorityTxMaxGasLimit = _priorityTxMaxGasLimit; + } + + function util_getPriorityTxMaxGasLimit() external view returns (uint256) { + return s.priorityTxMaxGasLimit; + } + + function util_setFeeParams(FeeParams calldata _feeParams) external { + s.feeParams = _feeParams; + } + + function util_getFeeParams() external view returns (FeeParams memory) { + return s.feeParams; + } + + function util_setProtocolVersion(uint256 _protocolVersion) external { + s.protocolVersion = _protocolVersion; + } + + function util_getProtocolVersion() external view returns (uint256) { + return s.protocolVersion; + } + + function util_setIsFrozen(bool _isFrozen) external { + Diamond.DiamondStorage storage s = Diamond.getDiamondStorage(); + s.isFrozen = _isFrozen; + } + + function util_getIsFrozen() external view returns (bool) { + Diamond.DiamondStorage storage s = Diamond.getDiamondStorage(); + return s.isFrozen; + } + + // add this to be excluded from coverage report + function test() internal virtual {} +} diff --git a/l1-contracts/test/foundry/unit/concrete/ValidatorTimelock.t.sol b/l1-contracts/test/foundry/unit/concrete/ValidatorTimelock.t.sol deleted file mode 100644 index 0ad11a2996d5..000000000000 --- a/l1-contracts/test/foundry/unit/concrete/ValidatorTimelock.t.sol +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.20; - -import {Test} from "forge-std/Test.sol"; -import {Utils} from "./Utils/Utils.sol"; -import {ValidatorTimelock, IExecutor} from "solpp/zksync/ValidatorTimelock.sol"; - -contract ValidatorTimelockTest is Test { - /// @notice Event emitted from ValidatorTimelock when new validator is added - event ValidatorAdded(address _addedValidator); - - /// @notice Event emitted from ValidatorTimelock when new validator is removed - event ValidatorRemoved(address _removedValidator); - - /// @notice Error for when an address is already a validator. - error AddressAlreadyValidator(); - - /// @notice Error for when an address is not a validator. - error ValidatorDoesNotExist(); - - ValidatorTimelock validator; - - address owner; - address zkSync; - address alice; - address bob; - - function setUp() public { - owner = makeAddr("owner"); - zkSync = makeAddr("zkSync"); - alice = makeAddr("alice"); - bob = makeAddr("bob"); - - address[] memory initValidators = new address[](1); - initValidators[0] = alice; - - validator = new ValidatorTimelock(owner, zkSync, 10, initValidators); - } - - function test_addValidator() public { - assert(validator.validators(bob) == false); - - vm.prank(owner); - vm.expectEmit(true, true, true, true, address(validator)); - emit ValidatorAdded(bob); - validator.addValidator(bob); - - assert(validator.validators(bob) == true); - } - - function test_removeValidator() public { - vm.prank(owner); - validator.addValidator(bob); - assert(validator.validators(bob) == true); - - vm.prank(owner); - vm.expectEmit(true, true, true, true, address(validator)); - emit ValidatorRemoved(bob); - validator.removeValidator(bob); - - assert(validator.validators(bob) == false); - } - - function test_validatorCanMakeCall() public { - // Setup Mock call to executor - vm.mockCall(zkSync, abi.encodeWithSelector(IExecutor.commitBatches.selector), ""); - - IExecutor.StoredBatchInfo memory storedBatch = Utils.createStoredBatchInfo(); - IExecutor.CommitBatchInfo memory batchToCommit = Utils.createCommitBatchInfo(); - - IExecutor.CommitBatchInfo[] memory batchesToCommit = new IExecutor.CommitBatchInfo[](1); - batchesToCommit[0] = batchToCommit; - - vm.prank(alice); - validator.commitBatches(storedBatch, batchesToCommit); - } - - function test_addValidator_revertWhenNotOwner() public { - assert(validator.validators(bob) == false); - - vm.expectRevert("Ownable: caller is not the owner"); - validator.addValidator(bob); - - assert(validator.validators(bob) == false); - } - - function test_removeValidator_revertWhenNotOwner() public { - assert(validator.validators(alice) == true); - - vm.expectRevert("Ownable: caller is not the owner"); - validator.removeValidator(alice); - - assert(validator.validators(alice) == true); - } - - function test_addValidator_revertWhenAddressAlreadyValidator() public { - assert(validator.validators(alice) == true); - - vm.prank(owner); - vm.expectRevert(AddressAlreadyValidator.selector); - validator.addValidator(alice); - } - - function test_removeValidator_revertWhenAddressNotValidator() public { - assert(validator.validators(bob) == false); - - vm.prank(owner); - vm.expectRevert(ValidatorDoesNotExist.selector); - validator.removeValidator(bob); - } - - function test_validatorCanMakeCall_revertWhenNotValidator() public { - IExecutor.StoredBatchInfo memory storedBatch = Utils.createStoredBatchInfo(); - IExecutor.CommitBatchInfo memory batchToCommit = Utils.createCommitBatchInfo(); - - IExecutor.CommitBatchInfo[] memory batchesToCommit = new IExecutor.CommitBatchInfo[](1); - batchesToCommit[0] = batchToCommit; - - vm.prank(bob); - vm.expectRevert(bytes("8h")); - validator.commitBatches(storedBatch, batchesToCommit); - } -} diff --git a/l1-contracts/test/foundry/unit/concrete/ValidatorTimelock/ValidatorTimelock.t.sol b/l1-contracts/test/foundry/unit/concrete/ValidatorTimelock/ValidatorTimelock.t.sol new file mode 100644 index 000000000000..0092e33fc3b4 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/ValidatorTimelock/ValidatorTimelock.t.sol @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {Utils} from "../Utils/Utils.sol"; +import {ValidatorTimelock, IExecutor} from "solpp/state-transition/ValidatorTimelock.sol"; +import {DummyStateTransitionManagerForValidatorTimelock} from "solpp/dev-contracts/test/DummyStateTransitionManagerForValidatorTimelock.sol"; +import {IStateTransitionManager} from "solpp/state-transition/IStateTransitionManager.sol"; +import {ERA_CHAIN_ID} from "solpp/common/Config.sol"; + +contract ValidatorTimelockTest is Test { + /// @notice A new validator has been added. + event ValidatorAdded(uint256 _chainId, address _addedValidator); + + /// @notice A validator has been removed. + event ValidatorRemoved(uint256 _chainId, address _removedValidator); + + /// @notice Error for when an address is already a validator. + error AddressAlreadyValidator(uint256 _chainId); + + /// @notice Error for when an address is not a validator. + error ValidatorDoesNotExist(uint256 _chainId); + + ValidatorTimelock validator; + DummyStateTransitionManagerForValidatorTimelock stateTransitionManager; + + address owner; + address zkSync; + address alice; + address bob; + address dan; + uint256 chainId; + uint256 lastBatchNumber; + uint32 executionDelay; + + function setUp() public { + owner = makeAddr("owner"); + zkSync = makeAddr("zkSync"); + alice = makeAddr("alice"); + bob = makeAddr("bob"); + dan = makeAddr("dan"); + chainId = 1; + lastBatchNumber = 123; + executionDelay = 10; + + stateTransitionManager = new DummyStateTransitionManagerForValidatorTimelock(owner, zkSync); + validator = new ValidatorTimelock(owner, executionDelay); + vm.prank(owner); + validator.setStateTransitionManager(IStateTransitionManager(address(stateTransitionManager))); + vm.prank(owner); + validator.addValidator(chainId, alice); + vm.prank(owner); + validator.addValidator(ERA_CHAIN_ID, dan); + } + + function test_addValidator() public { + assert(validator.validators(chainId, bob) == false); + + vm.prank(owner); + vm.expectEmit(true, true, true, true, address(validator)); + emit ValidatorAdded(chainId, bob); + validator.addValidator(chainId, bob); + + assert(validator.validators(chainId, bob) == true); + } + + function test_removeValidator() public { + vm.prank(owner); + validator.addValidator(chainId, bob); + assert(validator.validators(chainId, bob) == true); + + vm.prank(owner); + vm.expectEmit(true, true, true, true, address(validator)); + emit ValidatorRemoved(chainId, bob); + validator.removeValidator(chainId, bob); + + assert(validator.validators(chainId, bob) == false); + } + + function test_validatorCanMakeCall() public { + // Setup Mock call to executor + vm.mockCall(zkSync, abi.encodeWithSelector(IExecutor.commitBatchesSharedBridge.selector), ""); + + IExecutor.StoredBatchInfo memory storedBatch = Utils.createStoredBatchInfo(); + IExecutor.CommitBatchInfo memory batchToCommit = Utils.createCommitBatchInfo(); + + IExecutor.CommitBatchInfo[] memory batchesToCommit = new IExecutor.CommitBatchInfo[](1); + batchesToCommit[0] = batchToCommit; + + vm.prank(alice); + validator.commitBatchesSharedBridge(chainId, storedBatch, batchesToCommit); + } + + function test_setStateTransitionManager() public { + assert(validator.stateTransitionManager() == IStateTransitionManager(address(stateTransitionManager))); + + DummyStateTransitionManagerForValidatorTimelock newManager = new DummyStateTransitionManagerForValidatorTimelock( + bob, + zkSync + ); + vm.prank(owner); + validator.setStateTransitionManager(IStateTransitionManager(address(newManager))); + + assert(validator.stateTransitionManager() == IStateTransitionManager(address(newManager))); + } + + function test_setExecutionDelay() public { + assert(validator.executionDelay() == executionDelay); + + vm.prank(owner); + validator.setExecutionDelay(20); + + assert(validator.executionDelay() == 20); + } + + function test_getCommittedBatchTimestampEmpty() public { + assert(validator.getCommittedBatchTimestamp(chainId, lastBatchNumber) == 0); + } + + function test_getCommittedBatchTimestamp() public { + uint64 batchNumber = 10; + uint64 timestamp = 123456; + + vm.warp(timestamp); + vm.mockCall(zkSync, abi.encodeWithSelector(IExecutor.commitBatches.selector), abi.encode(ERA_CHAIN_ID)); + + IExecutor.StoredBatchInfo memory storedBatch = Utils.createStoredBatchInfo(); + IExecutor.CommitBatchInfo memory batchToCommit = Utils.createCommitBatchInfo(); + + batchToCommit.batchNumber = batchNumber; + IExecutor.CommitBatchInfo[] memory batchesToCommit = new IExecutor.CommitBatchInfo[](1); + batchesToCommit[0] = batchToCommit; + + vm.prank(dan); + validator.commitBatches(storedBatch, batchesToCommit); + + assert(validator.getCommittedBatchTimestamp(ERA_CHAIN_ID, batchNumber) == timestamp); + } + + function test_commitBatches() public { + vm.mockCall(zkSync, abi.encodeWithSelector(IExecutor.commitBatches.selector), abi.encode(chainId)); + + IExecutor.StoredBatchInfo memory storedBatch = Utils.createStoredBatchInfo(); + IExecutor.CommitBatchInfo memory batchToCommit = Utils.createCommitBatchInfo(); + + IExecutor.CommitBatchInfo[] memory batchesToCommit = new IExecutor.CommitBatchInfo[](1); + batchesToCommit[0] = batchToCommit; + + vm.prank(dan); + validator.commitBatches(storedBatch, batchesToCommit); + } + + function test_revertBatches() public { + vm.mockCall(zkSync, abi.encodeWithSelector(IExecutor.revertBatches.selector), abi.encode(lastBatchNumber)); + + vm.prank(dan); + validator.revertBatches(lastBatchNumber); + } + + function test_revertBatchesSharedBridge() public { + vm.mockCall(zkSync, abi.encodeWithSelector(IExecutor.revertBatches.selector), abi.encode(chainId)); + + vm.prank(alice); + validator.revertBatchesSharedBridge(chainId, lastBatchNumber); + } + + function test_proveBatches() public { + IExecutor.StoredBatchInfo memory prevBatch = Utils.createStoredBatchInfo(); + IExecutor.StoredBatchInfo memory batchToProve = Utils.createStoredBatchInfo(); + IExecutor.ProofInput memory proof = Utils.createProofInput(); + + IExecutor.StoredBatchInfo[] memory batchesToProve = new IExecutor.StoredBatchInfo[](1); + batchesToProve[0] = batchToProve; + + vm.mockCall( + zkSync, + abi.encodeWithSelector(IExecutor.proveBatches.selector), + abi.encode(prevBatch, batchesToProve, proof) + ); + vm.prank(dan); + validator.proveBatches(prevBatch, batchesToProve, proof); + } + + function test_proveBatchesSharedBridge() public { + IExecutor.StoredBatchInfo memory prevBatch = Utils.createStoredBatchInfo(); + IExecutor.StoredBatchInfo memory batchToProve = Utils.createStoredBatchInfo(); + IExecutor.ProofInput memory proof = Utils.createProofInput(); + + IExecutor.StoredBatchInfo[] memory batchesToProve = new IExecutor.StoredBatchInfo[](1); + batchesToProve[0] = batchToProve; + + vm.mockCall( + zkSync, + abi.encodeWithSelector(IExecutor.proveBatches.selector), + abi.encode(chainId, prevBatch, batchesToProve, proof) + ); + vm.prank(alice); + validator.proveBatchesSharedBridge(chainId, prevBatch, batchesToProve, proof); + } + + function test_executeBatches() public { + uint64 timestamp = 123456; + uint64 batchNumber = 123; + // Commit batches first to have the valid timestamp + vm.mockCall(zkSync, abi.encodeWithSelector(IExecutor.commitBatches.selector), abi.encode(chainId)); + + IExecutor.StoredBatchInfo memory storedBatch1 = Utils.createStoredBatchInfo(); + IExecutor.CommitBatchInfo memory batchToCommit = Utils.createCommitBatchInfo(); + + batchToCommit.batchNumber = batchNumber; + IExecutor.CommitBatchInfo[] memory batchesToCommit = new IExecutor.CommitBatchInfo[](1); + batchesToCommit[0] = batchToCommit; + + vm.prank(dan); + vm.warp(timestamp); + validator.commitBatches(storedBatch1, batchesToCommit); + + // Execute batches + IExecutor.StoredBatchInfo memory storedBatch2 = Utils.createStoredBatchInfo(); + storedBatch2.batchNumber = batchNumber; + IExecutor.StoredBatchInfo[] memory storedBatches = new IExecutor.StoredBatchInfo[](1); + storedBatches[0] = storedBatch2; + + vm.mockCall(zkSync, abi.encodeWithSelector(IExecutor.proveBatches.selector), abi.encode(storedBatches)); + + vm.prank(dan); + vm.warp(timestamp + executionDelay + 1); + validator.executeBatches(storedBatches); + } + + function test_executeBatchesSharedBridge() public { + uint64 timestamp = 123456; + uint64 batchNumber = 123; + // Commit batches first to have the valid timestamp + vm.mockCall(zkSync, abi.encodeWithSelector(IExecutor.commitBatches.selector), abi.encode(chainId)); + + IExecutor.StoredBatchInfo memory storedBatch1 = Utils.createStoredBatchInfo(); + IExecutor.CommitBatchInfo memory batchToCommit = Utils.createCommitBatchInfo(); + + batchToCommit.batchNumber = batchNumber; + IExecutor.CommitBatchInfo[] memory batchesToCommit = new IExecutor.CommitBatchInfo[](1); + batchesToCommit[0] = batchToCommit; + + vm.prank(alice); + vm.warp(timestamp); + validator.commitBatchesSharedBridge(chainId, storedBatch1, batchesToCommit); + + // Execute batches + IExecutor.StoredBatchInfo memory storedBatch2 = Utils.createStoredBatchInfo(); + storedBatch2.batchNumber = batchNumber; + IExecutor.StoredBatchInfo[] memory storedBatches = new IExecutor.StoredBatchInfo[](1); + storedBatches[0] = storedBatch2; + + vm.mockCall(zkSync, abi.encodeWithSelector(IExecutor.proveBatches.selector), abi.encode(storedBatches)); + + vm.prank(alice); + vm.warp(timestamp + executionDelay + 1); + validator.executeBatchesSharedBridge(chainId, storedBatches); + } + + function test_RevertWhen_setExecutionDelayNotOwner() public { + vm.expectRevert("Ownable: caller is not the owner"); + vm.prank(alice); + validator.setExecutionDelay(20); + } + + function test_RevertWhen_addValidatorNotAdmin() public { + assert(validator.validators(chainId, bob) == false); + + vm.expectRevert("ValidatorTimelock: only chain admin"); + validator.addValidator(chainId, bob); + + assert(validator.validators(chainId, bob) == false); + } + + function test_RevertWhen_removeValidatorNotAdmin() public { + assert(validator.validators(chainId, alice) == true); + + vm.expectRevert("ValidatorTimelock: only chain admin"); + validator.removeValidator(chainId, alice); + + assert(validator.validators(chainId, alice) == true); + } + + function test_RevertWhen_addValidatorAddressAlreadyValidator() public { + assert(validator.validators(chainId, alice) == true); + + vm.prank(owner); + vm.expectRevert(abi.encodePacked(AddressAlreadyValidator.selector, chainId)); + validator.addValidator(chainId, alice); + } + + function test_RevertWhen_removeValidatorAddressNotValidator() public { + assert(validator.validators(chainId, bob) == false); + + vm.prank(owner); + vm.expectRevert(abi.encodePacked(ValidatorDoesNotExist.selector, chainId)); + validator.removeValidator(chainId, bob); + } + + function test_RevertWhen_validatorCanMakeCallNotValidator() public { + IExecutor.StoredBatchInfo memory storedBatch = Utils.createStoredBatchInfo(); + IExecutor.CommitBatchInfo memory batchToCommit = Utils.createCommitBatchInfo(); + + IExecutor.CommitBatchInfo[] memory batchesToCommit = new IExecutor.CommitBatchInfo[](1); + batchesToCommit[0] = batchToCommit; + + vm.prank(bob); + vm.expectRevert(bytes("ValidatorTimelock: only validator")); + validator.commitBatches(storedBatch, batchesToCommit); + } + + function test_RevertWhen_setStateTransitionManagerNotOwner() public { + vm.expectRevert("Ownable: caller is not the owner"); + validator.setStateTransitionManager(IStateTransitionManager(address(stateTransitionManager))); + } + + function test_RevertWhen_revertBatchesNotValidator() public { + vm.expectRevert("ValidatorTimelock: only validator"); + validator.revertBatches(lastBatchNumber); + } + + function test_RevertWhen_revertBatchesSharedBridgeNotValidator() public { + vm.expectRevert("ValidatorTimelock: only validator"); + validator.revertBatchesSharedBridge(chainId, lastBatchNumber); + } + + function test_RevertWhen_proveBatchesNotValidator() public { + uint256[] memory recursiveAggregationInput; + uint256[] memory serializedProof; + IExecutor.StoredBatchInfo memory prevBatch = Utils.createStoredBatchInfo(); + IExecutor.StoredBatchInfo memory batchToProve = Utils.createStoredBatchInfo(); + IExecutor.ProofInput memory proof = Utils.createProofInput(); + + IExecutor.StoredBatchInfo[] memory batchesToProve = new IExecutor.StoredBatchInfo[](1); + batchesToProve[0] = batchToProve; + + vm.expectRevert("ValidatorTimelock: only validator"); + validator.proveBatches(prevBatch, batchesToProve, proof); + } + + function test_RevertWhen_proveBatchesSharedBridgeNotValidator() public { + IExecutor.StoredBatchInfo memory prevBatch = Utils.createStoredBatchInfo(); + IExecutor.StoredBatchInfo memory batchToProve = Utils.createStoredBatchInfo(); + IExecutor.ProofInput memory proof = Utils.createProofInput(); + + IExecutor.StoredBatchInfo[] memory batchesToProve = new IExecutor.StoredBatchInfo[](1); + batchesToProve[0] = batchToProve; + + vm.prank(bob); + vm.expectRevert("ValidatorTimelock: only validator"); + validator.proveBatchesSharedBridge(chainId, prevBatch, batchesToProve, proof); + } + + function test_RevertWhen_executeBatchesNotValidator() public { + IExecutor.StoredBatchInfo memory storedBatch = Utils.createStoredBatchInfo(); + + IExecutor.StoredBatchInfo[] memory storedBatches = new IExecutor.StoredBatchInfo[](1); + storedBatches[0] = storedBatch; + + vm.prank(bob); + vm.expectRevert("ValidatorTimelock: only validator"); + validator.executeBatches(storedBatches); + } + + function test_RevertWhen_executeBatchesSharedBridgeNotValidator() public { + IExecutor.StoredBatchInfo memory storedBatch = Utils.createStoredBatchInfo(); + + IExecutor.StoredBatchInfo[] memory storedBatches = new IExecutor.StoredBatchInfo[](1); + storedBatches[0] = storedBatch; + + vm.prank(bob); + vm.expectRevert("ValidatorTimelock: only validator"); + validator.executeBatchesSharedBridge(chainId, storedBatches); + } + + function test_RevertWhen_executeBatchesTooEarly() public { + uint64 timestamp = 123456; + uint64 batchNumber = 123; + // Prove batches first to have the valid timestamp + vm.mockCall(zkSync, abi.encodeWithSelector(IExecutor.commitBatches.selector), abi.encode(chainId)); + + IExecutor.StoredBatchInfo memory storedBatch1 = Utils.createStoredBatchInfo(); + IExecutor.CommitBatchInfo memory batchToCommit = Utils.createCommitBatchInfo(); + + batchToCommit.batchNumber = batchNumber; + IExecutor.CommitBatchInfo[] memory batchesToCommit = new IExecutor.CommitBatchInfo[](1); + batchesToCommit[0] = batchToCommit; + + vm.prank(dan); + vm.warp(timestamp); + validator.commitBatches(storedBatch1, batchesToCommit); + + // Execute batches + IExecutor.StoredBatchInfo memory storedBatch2 = Utils.createStoredBatchInfo(); + storedBatch2.batchNumber = batchNumber; + IExecutor.StoredBatchInfo[] memory storedBatches = new IExecutor.StoredBatchInfo[](1); + storedBatches[0] = storedBatch2; + + vm.prank(dan); + vm.warp(timestamp + executionDelay - 1); + vm.expectRevert(bytes("5c")); + validator.executeBatches(storedBatches); + } + + function test_RevertWhen_executeBatchesSharedBridgeTooEarly() public { + uint64 timestamp = 123456; + uint64 batchNumber = 123; + // Prove batches first to have the valid timestamp + vm.mockCall(zkSync, abi.encodeWithSelector(IExecutor.commitBatches.selector), abi.encode(chainId)); + + IExecutor.StoredBatchInfo memory storedBatch1 = Utils.createStoredBatchInfo(); + IExecutor.CommitBatchInfo memory batchToCommit = Utils.createCommitBatchInfo(); + + batchToCommit.batchNumber = batchNumber; + IExecutor.CommitBatchInfo[] memory batchesToCommit = new IExecutor.CommitBatchInfo[](1); + batchesToCommit[0] = batchToCommit; + + vm.prank(alice); + vm.warp(timestamp); + validator.commitBatchesSharedBridge(chainId, storedBatch1, batchesToCommit); + + // Execute batches + IExecutor.StoredBatchInfo memory storedBatch2 = Utils.createStoredBatchInfo(); + storedBatch2.batchNumber = batchNumber; + IExecutor.StoredBatchInfo[] memory storedBatches = new IExecutor.StoredBatchInfo[](1); + storedBatches[0] = storedBatch2; + + vm.prank(alice); + vm.warp(timestamp + executionDelay - 1); + vm.expectRevert(bytes("5c")); + validator.executeBatchesSharedBridge(chainId, storedBatches); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Verifier/Verifier.t.sol b/l1-contracts/test/foundry/unit/concrete/Verifier/Verifier.t.sol index 94dd208a70d8..c6c7cfa55aea 100644 --- a/l1-contracts/test/foundry/unit/concrete/Verifier/Verifier.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Verifier/Verifier.t.sol @@ -2,8 +2,9 @@ pragma solidity 0.8.20; import {Test} from "forge-std/Test.sol"; + +import {Verifier} from "solpp/state-transition/Verifier.sol"; import {VerifierTest} from "solpp/dev-contracts/test/VerifierTest.sol"; -import {Verifier} from "solpp/zksync/Verifier.sol"; contract VerifierTestTest is Test { uint256 Q_MOD = 21888242871839275222246405745257275088696311157297823662689037894645226208583; diff --git a/l1-contracts/test/foundry/unit/concrete/UncheckedMath/UncheckedAdd.t.sol b/l1-contracts/test/foundry/unit/concrete/common/libraries/UncheckedMath/UncheckedAdd.t.sol similarity index 59% rename from l1-contracts/test/foundry/unit/concrete/UncheckedMath/UncheckedAdd.t.sol rename to l1-contracts/test/foundry/unit/concrete/common/libraries/UncheckedMath/UncheckedAdd.t.sol index 847818a6540d..98f21cfaebc4 100644 --- a/l1-contracts/test/foundry/unit/concrete/UncheckedMath/UncheckedAdd.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/common/libraries/UncheckedMath/UncheckedAdd.t.sol @@ -1,16 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.20; -import {UncheckedMathTest} from "./_UncheckedMath_Shared.t.sol"; -import {UncheckedMath} from "solpp/common/libraries/UncheckedMath.sol"; - -contract UncheckedAddTest is UncheckedMathTest { - using UncheckedMath for uint256; +import {UncheckedMathSharedTest} from "./_UncheckedMath_Shared.t.sol"; +contract UncheckedAddTest is UncheckedMathSharedTest { function test_Add() public { uint256 a = 1234; uint256 b = 4321; - uint256 c = a.uncheckedAdd(b); + uint256 c = uncheckedMath.uncheckedAdd(a, b); assertEq(c, 5555); } @@ -19,7 +16,7 @@ contract UncheckedAddTest is UncheckedMathTest { uint256 b = 1; // uncheckedAdd does not fail - uint256 c = a.uncheckedAdd(b); + uint256 c = uncheckedMath.uncheckedAdd(a, b); assertEq(c, 0); // regular addition fails with overflow diff --git a/l1-contracts/test/foundry/unit/concrete/UncheckedMath/UncheckedInc.t.sol b/l1-contracts/test/foundry/unit/concrete/common/libraries/UncheckedMath/UncheckedInc.t.sol similarity index 56% rename from l1-contracts/test/foundry/unit/concrete/UncheckedMath/UncheckedInc.t.sol rename to l1-contracts/test/foundry/unit/concrete/common/libraries/UncheckedMath/UncheckedInc.t.sol index aa1a669f721b..9efc8a17a7b1 100644 --- a/l1-contracts/test/foundry/unit/concrete/UncheckedMath/UncheckedInc.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/common/libraries/UncheckedMath/UncheckedInc.t.sol @@ -1,15 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.20; -import {UncheckedMathTest} from "./_UncheckedMath_Shared.t.sol"; -import {UncheckedMath} from "solpp/common/libraries/UncheckedMath.sol"; - -contract UncheckedIncTest is UncheckedMathTest { - using UncheckedMath for uint256; +import {UncheckedMathSharedTest} from "./_UncheckedMath_Shared.t.sol"; +contract UncheckedIncTest is UncheckedMathSharedTest { function test_Inc() public { uint256 a = 1234; - uint256 c = a.uncheckedInc(); + uint256 c = uncheckedMath.uncheckedInc(a); assertEq(c, 1235); } @@ -17,7 +14,7 @@ contract UncheckedIncTest is UncheckedMathTest { uint256 a = type(uint256).max; // uncheckedInc does not fail - uint256 c = a.uncheckedInc(); + uint256 c = uncheckedMath.uncheckedInc(a); assertEq(c, 0); // regular addition fails with overflow diff --git a/l1-contracts/test/foundry/unit/concrete/common/libraries/UncheckedMath/_UncheckedMath_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/common/libraries/UncheckedMath/_UncheckedMath_Shared.t.sol new file mode 100644 index 000000000000..c39ca64e591b --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/common/libraries/UncheckedMath/_UncheckedMath_Shared.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {UncheckedMathTest} from "solpp/dev-contracts/test/UncheckedMathTest.sol"; + +contract UncheckedMathSharedTest is Test { + UncheckedMathTest uncheckedMath; + + function setUp() public { + uncheckedMath = new UncheckedMathTest(); + } + + // add this to be excluded from coverage report + function test() internal virtual {} +} diff --git a/l1-contracts/test/foundry/unit/concrete/UnsafeBytes/UnsafeBytes.t.sol b/l1-contracts/test/foundry/unit/concrete/common/libraries/UnsafeBytes/UnsafeBytes.t.sol similarity index 95% rename from l1-contracts/test/foundry/unit/concrete/UnsafeBytes/UnsafeBytes.t.sol rename to l1-contracts/test/foundry/unit/concrete/common/libraries/UnsafeBytes/UnsafeBytes.t.sol index 9bee54199ca2..084c93728c24 100644 --- a/l1-contracts/test/foundry/unit/concrete/UnsafeBytes/UnsafeBytes.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/common/libraries/UnsafeBytes/UnsafeBytes.t.sol @@ -3,7 +3,8 @@ pragma solidity 0.8.20; import {Test} from "forge-std/Test.sol"; -import {UnsafeBytesTest} from "../../../../../cache/solpp-generated-contracts/dev-contracts/test/UnsafeBytesTest.sol"; + +import {UnsafeBytesTest} from "solpp/dev-contracts/test/UnsafeBytesTest.sol"; contract UnsafeBytesTestTest is Test { UnsafeBytesTest private unsafeBytesTest; diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/CreateNewChain.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/CreateNewChain.t.sol new file mode 100644 index 000000000000..b3435cf0506b --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/CreateNewChain.t.sol @@ -0,0 +1,34 @@ +// // SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {StateTransitionManagerTest} from "./_StateTransitionManager_Shared.t.sol"; +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; +import {DiamondProxy} from "solpp/state-transition/chain-deps/DiamondProxy.sol"; + +contract createNewChainTest is StateTransitionManagerTest { + function test_RevertWhen_InitialDiamondCutHashMismatch() public { + Diamond.DiamondCutData memory initialDiamondCutData = getDiamondCutData(sharedBridge); + + vm.expectRevert(bytes("StateTransition: initial cutHash mismatch")); + + createNewChain(initialDiamondCutData); + } + + function test_RevertWhen_CalledNotByBridgehub() public { + Diamond.DiamondCutData memory initialDiamondCutData = getDiamondCutData(diamondInit); + + vm.expectRevert(bytes("StateTransition: only bridgehub")); + + chainContractAddress.createNewChain(chainId, baseToken, sharedBridge, admin, abi.encode(initialDiamondCutData)); + } + + function test_SuccessfulCreationOfNewChain() public { + createNewChain(getDiamondCutData(diamondInit)); + + address admin = chainContractAddress.getChainAdmin(chainId); + address newChainAddress = chainContractAddress.stateTransition(chainId); + + assertEq(newChainAdmin, admin); + assertNotEq(newChainAddress, address(0)); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/FreezeChain.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/FreezeChain.t.sol new file mode 100644 index 000000000000..b96f93081816 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/FreezeChain.t.sol @@ -0,0 +1,31 @@ +// // SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {StateTransitionManagerTest} from "./_StateTransitionManager_Shared.t.sol"; +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; +import {DiamondProxy} from "solpp/state-transition/chain-deps/DiamondProxy.sol"; +import {GettersFacet} from "solpp/state-transition/chain-deps/facets/Getters.sol"; + +contract freezeChainTest is StateTransitionManagerTest { + function test_FreezingChain() public { + createNewChain(getDiamondCutData(diamondInit)); + + address newChainAddress = chainContractAddress.stateTransition(chainId); + GettersFacet gettersFacet = GettersFacet(newChainAddress); + bool isChainFrozen = gettersFacet.isDiamondStorageFrozen(); + assertEq(isChainFrozen, false); + + vm.stopPrank(); + vm.startPrank(governor); + + chainContractAddress.freezeChain(block.chainid); + + // Repeated call should revert + vm.expectRevert(bytes.concat("q1")); // storage frozen + chainContractAddress.freezeChain(block.chainid); + + // Call fails as storage is frozen + vm.expectRevert(bytes.concat("q1")); + isChainFrozen = gettersFacet.isDiamondStorageFrozen(); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/RevertBatches.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/RevertBatches.t.sol new file mode 100644 index 000000000000..f1c23d582ef3 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/RevertBatches.t.sol @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {Vm} from "forge-std/Test.sol"; + +import {Utils, L2_SYSTEM_CONTEXT_ADDRESS} from "../../Utils/Utils.sol"; +import {StateTransitionManagerTest} from "./_StateTransitionManager_Shared.t.sol"; + +import {COMMIT_TIMESTAMP_NOT_OLDER, DEFAULT_L2_LOGS_TREE_ROOT_HASH, EMPTY_STRING_KECCAK} from "solpp/common/Config.sol"; +import {IExecutor, SystemLogKey} from "solpp/state-transition/chain-interfaces/IExecutor.sol"; +import {GettersFacet} from "solpp/state-transition/chain-deps/facets/Getters.sol"; +import {AdminFacet} from "solpp/state-transition/chain-deps/facets/Admin.sol"; +import {ExecutorFacet} from "solpp/state-transition/chain-deps/facets/Executor.sol"; +import {IExecutor} from "solpp/state-transition/chain-interfaces/IExecutor.sol"; +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; + +contract revertBatchesTest is StateTransitionManagerTest { + // Items for logs & commits + uint256 internal currentTimestamp; + IExecutor.CommitBatchInfo internal newCommitBatchInfo; + IExecutor.StoredBatchInfo internal newStoredBatchInfo; + IExecutor.StoredBatchInfo internal genesisStoredBatchInfo; + IExecutor.ProofInput internal proofInput; + + // Facets exposing the diamond + AdminFacet internal adminFacet; + ExecutorFacet internal executorFacet; + GettersFacet internal gettersFacet; + + function test_SuccessfulBatchReverting() public { + createNewChain(getDiamondCutData(diamondInit)); + + address newChainAddress = chainContractAddress.stateTransition(chainId); + + executorFacet = ExecutorFacet(address(newChainAddress)); + gettersFacet = GettersFacet(address(newChainAddress)); + adminFacet = AdminFacet(address(newChainAddress)); + + // Initital setup for loge & commits + vm.stopPrank(); + vm.startPrank(newChainAdmin); + + genesisStoredBatchInfo = IExecutor.StoredBatchInfo({ + batchNumber: 0, + batchHash: bytes32(""), + indexRepeatedStorageChanges: 0, + numberOfLayer1Txs: 0, + priorityOperationsHash: EMPTY_STRING_KECCAK, + l2LogsTreeRoot: DEFAULT_L2_LOGS_TREE_ROOT_HASH, + timestamp: 0, + commitment: bytes32("") + }); + + adminFacet.setTokenMultiplier(1, 1); + + uint256[] memory recursiveAggregationInput; + uint256[] memory serializedProof; + proofInput = IExecutor.ProofInput(recursiveAggregationInput, serializedProof); + + // foundry's default value is 1 for the block's timestamp, it is expected + // that block.timestamp > COMMIT_TIMESTAMP_NOT_OLDER + 1 + vm.warp(COMMIT_TIMESTAMP_NOT_OLDER + 1 + 1); + currentTimestamp = block.timestamp; + + bytes memory l2Logs = Utils.encodePacked(Utils.createSystemLogs()); + newCommitBatchInfo = IExecutor.CommitBatchInfo({ + batchNumber: 1, + timestamp: uint64(currentTimestamp), + indexRepeatedStorageChanges: 0, + newStateRoot: Utils.randomBytes32("newStateRoot"), + numberOfLayer1Txs: 0, + priorityOperationsHash: keccak256(""), + bootloaderHeapInitialContentsHash: Utils.randomBytes32("bootloaderHeapInitialContentsHash"), + eventsQueueStateHash: Utils.randomBytes32("eventsQueueStateHash"), + systemLogs: l2Logs, + pubdataCommitments: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + }); + + // Commit & prove batches + vm.warp(COMMIT_TIMESTAMP_NOT_OLDER + 1); + currentTimestamp = block.timestamp; + + bytes32 expectedSystemContractUpgradeTxHash = gettersFacet.getL2SystemContractsUpgradeTxHash(); + bytes[] memory correctL2Logs = Utils.createSystemLogsWithUpgradeTransaction( + expectedSystemContractUpgradeTxHash + ); + + correctL2Logs[uint256(uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY))] = Utils.constructL2Log( + true, + L2_SYSTEM_CONTEXT_ADDRESS, + uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY), + Utils.packBatchTimestampAndBlockTimestamp(currentTimestamp, currentTimestamp) + ); + + l2Logs = Utils.encodePacked(correctL2Logs); + newCommitBatchInfo.timestamp = uint64(currentTimestamp); + newCommitBatchInfo.systemLogs = l2Logs; + + IExecutor.CommitBatchInfo[] memory commitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + commitBatchInfoArray[0] = newCommitBatchInfo; + + vm.stopPrank(); + vm.startPrank(validator); + vm.recordLogs(); + executorFacet.commitBatches(genesisStoredBatchInfo, commitBatchInfoArray); + Vm.Log[] memory entries = vm.getRecordedLogs(); + + newStoredBatchInfo = IExecutor.StoredBatchInfo({ + batchNumber: 1, + batchHash: entries[0].topics[2], + indexRepeatedStorageChanges: 0, + numberOfLayer1Txs: 0, + priorityOperationsHash: keccak256(""), + l2LogsTreeRoot: 0, + timestamp: currentTimestamp, + commitment: entries[0].topics[3] + }); + + IExecutor.StoredBatchInfo[] memory storedBatchInfoArray = new IExecutor.StoredBatchInfo[](1); + storedBatchInfoArray[0] = newStoredBatchInfo; + + executorFacet.proveBatches(genesisStoredBatchInfo, storedBatchInfoArray, proofInput); + + // Test batch revert triggered from STM + vm.stopPrank(); + vm.startPrank(governor); + + uint256 totalBlocksCommittedBefore = gettersFacet.getTotalBlocksCommitted(); + assertEq(totalBlocksCommittedBefore, 1, "totalBlocksCommittedBefore"); + + uint256 totalBlocksVerifiedBefore = gettersFacet.getTotalBlocksVerified(); + assertEq(totalBlocksVerifiedBefore, 1, "totalBlocksVerifiedBefore"); + + chainContractAddress.revertBatches(chainId, 0); + + uint256 totalBlocksCommitted = gettersFacet.getTotalBlocksCommitted(); + assertEq(totalBlocksCommitted, 0, "totalBlocksCommitted"); + + uint256 totalBlocksVerified = gettersFacet.getTotalBlocksVerified(); + assertEq(totalBlocksVerified, 0, "totalBlocksVerified"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetInitialCutHash.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetInitialCutHash.t.sol new file mode 100644 index 000000000000..29e2e89e4c58 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetInitialCutHash.t.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {StateTransitionManagerTest} from "./_StateTransitionManager_Shared.t.sol"; +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; + +contract setInitialCutHashTest is StateTransitionManagerTest { + function test_SettingInitialCutHash() public { + bytes32 initialCutHash = keccak256(abi.encode(getDiamondCutData(address(diamondInit)))); + address randomDiamondInit = address(0x303030303030303030303); + + assertEq(chainContractAddress.initialCutHash(), initialCutHash, "Initial cut hash is not correct"); + + Diamond.DiamondCutData memory newDiamondCutData = getDiamondCutData(address(randomDiamondInit)); + bytes32 newCutHash = keccak256(abi.encode(newDiamondCutData)); + + chainContractAddress.setInitialCutHash(newDiamondCutData); + + assertEq(chainContractAddress.initialCutHash(), newCutHash, "Initial cut hash update was not successful"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetNewVersionUpgrade.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetNewVersionUpgrade.t.sol new file mode 100644 index 000000000000..a27ba2943f6b --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetNewVersionUpgrade.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {StateTransitionManagerTest} from "./_StateTransitionManager_Shared.t.sol"; +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; + +contract setNewVersionUpgradeTest is StateTransitionManagerTest { + function test_SettingNewVersionUpgrade() public { + assertEq(chainContractAddress.protocolVersion(), 0, "Initial protocol version is not correct"); + + address randomDiamondInit = address(0x303030303030303030303); + Diamond.DiamondCutData memory newDiamondCutData = getDiamondCutData(address(randomDiamondInit)); + bytes32 newCutHash = keccak256(abi.encode(newDiamondCutData)); + + chainContractAddress.setNewVersionUpgrade(newDiamondCutData, 0, 1); + + assertEq(chainContractAddress.upgradeCutHash(0), newCutHash, "Diamond cut upgrade was not successful"); + assertEq(chainContractAddress.protocolVersion(), 1, "New protocol version is not correct"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetUpgradeDiamondCut.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetUpgradeDiamondCut.t.sol new file mode 100644 index 000000000000..44b00ac1a380 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetUpgradeDiamondCut.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {StateTransitionManagerTest} from "./_StateTransitionManager_Shared.t.sol"; +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; + +contract setUpgradeDiamondCutTest is StateTransitionManagerTest { + function test_SettingUpgradeDiamondCut() public { + assertEq(chainContractAddress.protocolVersion(), 0, "Initial protocol version is not correct"); + + address randomDiamondInit = address(0x303030303030303030303); + Diamond.DiamondCutData memory newDiamondCutData = getDiamondCutData(address(randomDiamondInit)); + bytes32 newCutHash = keccak256(abi.encode(newDiamondCutData)); + + chainContractAddress.setUpgradeDiamondCut(newDiamondCutData, 0); + + assertEq(chainContractAddress.upgradeCutHash(0), newCutHash, "Diamond cut upgrade was not successful"); + assertEq(chainContractAddress.protocolVersion(), 0, "Protocol version should not change"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetValidatorTimelock.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetValidatorTimelock.t.sol new file mode 100644 index 000000000000..ee6bf4c58d6a --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetValidatorTimelock.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {StateTransitionManagerTest} from "./_StateTransitionManager_Shared.t.sol"; + +contract setValidatorTimelockTest is StateTransitionManagerTest { + function test_SettingValidatorTimelock() public { + assertEq( + chainContractAddress.validatorTimelock(), + validator, + "Initial validator timelock address is not correct" + ); + + address newValidatorTimelock = address(0x0000000000000000000000000000000000004235); + chainContractAddress.setValidatorTimelock(newValidatorTimelock); + + assertEq( + chainContractAddress.validatorTimelock(), + newValidatorTimelock, + "Validator timelock update was not successful" + ); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/StateTransitionGovernorZero.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/StateTransitionGovernorZero.t.sol new file mode 100644 index 000000000000..8ce1a16ecff4 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/StateTransitionGovernorZero.t.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {StateTransitionManagerTest} from "./_StateTransitionManager_Shared.t.sol"; +import {StateTransitionManager} from "solpp/state-transition/StateTransitionManager.sol"; +import {StateTransitionManagerInitializeData} from "solpp/state-transition/IStateTransitionManager.sol"; +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; + +contract initializingSTMGovernorZeroTest is StateTransitionManagerTest { + function test_InitializingSTMWithGovernorZeroShouldRevert() public { + StateTransitionManagerInitializeData memory stmInitializeDataNoGovernor = StateTransitionManagerInitializeData({ + governor: address(0), + validatorTimelock: validator, + genesisUpgrade: address(genesisUpgradeContract), + genesisBatchHash: bytes32(""), + genesisIndexRepeatedStorageChanges: 0, + genesisBatchCommitment: bytes32(""), + diamondCut: getDiamondCutData(address(diamondInit)), + protocolVersion: 0 + }); + + vm.expectRevert(bytes("StateTransition: governor zero")); + TransparentUpgradeableProxy transparentUpgradeableProxyReverting = new TransparentUpgradeableProxy( + address(stateTransitionManager), + admin, + abi.encodeCall(StateTransitionManager.initialize, stmInitializeDataNoGovernor) + ); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/_StateTransitionManager_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/_StateTransitionManager_Shared.t.sol new file mode 100644 index 000000000000..d4292ca22459 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/_StateTransitionManager_Shared.t.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import {Utils} from "foundry-test/unit/concrete/Utils/Utils.sol"; +import {UtilsFacet} from "foundry-test/unit/concrete/Utils/UtilsFacet.sol"; +import {AdminFacet} from "solpp/state-transition/chain-deps/facets/Admin.sol"; +import {ExecutorFacet} from "solpp/state-transition/chain-deps/facets/Executor.sol"; +import {GettersFacet} from "solpp/state-transition/chain-deps/facets/Getters.sol"; +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; +import {DiamondInit} from "solpp/state-transition/chain-deps/DiamondInit.sol"; +import {GenesisUpgrade} from "solpp/upgrades/GenesisUpgrade.sol"; +import {IDiamondInit} from "solpp/state-transition/chain-interfaces/IDiamondInit.sol"; +import {InitializeData, InitializeDataNewChain} from "solpp/state-transition/chain-interfaces/IDiamondInit.sol"; +import {IVerifier} from "solpp/state-transition/chain-interfaces/IVerifier.sol"; +import {StateTransitionManager} from "solpp/state-transition/StateTransitionManager.sol"; +import {StateTransitionManagerInitializeData} from "solpp/state-transition/IStateTransitionManager.sol"; + +contract StateTransitionManagerTest is Test { + StateTransitionManager internal stateTransitionManager; + StateTransitionManager internal chainContractAddress; + GenesisUpgrade internal genesisUpgradeContract; + address internal bridgehub; + address internal diamondInit; + address internal constant governor = address(0x1010101); + address internal constant admin = address(0x2020202); + address internal constant baseToken = address(0x3030303); + address internal constant sharedBridge = address(0x4040404); + address internal constant validator = address(0x5050505); + address internal newChainAdmin; + uint256 chainId = block.chainid; + + Diamond.FacetCut[] internal facetCuts; + + function setUp() public { + bridgehub = makeAddr("bridgehub"); + newChainAdmin = makeAddr("chainadmin"); + + vm.startPrank(bridgehub); + stateTransitionManager = new StateTransitionManager(bridgehub); + diamondInit = address(new DiamondInit()); + genesisUpgradeContract = new GenesisUpgrade(); + + facetCuts.push( + Diamond.FacetCut({ + facet: address(new UtilsFacet()), + action: Diamond.Action.Add, + isFreezable: true, + selectors: Utils.getUtilsFacetSelectors() + }) + ); + facetCuts.push( + Diamond.FacetCut({ + facet: address(new AdminFacet()), + action: Diamond.Action.Add, + isFreezable: true, + selectors: Utils.getAdminSelectors() + }) + ); + facetCuts.push( + Diamond.FacetCut({ + facet: address(new ExecutorFacet()), + action: Diamond.Action.Add, + isFreezable: true, + selectors: Utils.getExecutorSelectors() + }) + ); + facetCuts.push( + Diamond.FacetCut({ + facet: address(new GettersFacet()), + action: Diamond.Action.Add, + isFreezable: true, + selectors: Utils.getGettersSelectors() + }) + ); + + StateTransitionManagerInitializeData memory stmInitializeDataNoGovernor = StateTransitionManagerInitializeData({ + governor: address(0), + validatorTimelock: validator, + genesisUpgrade: address(genesisUpgradeContract), + genesisBatchHash: bytes32(""), + genesisIndexRepeatedStorageChanges: 0, + genesisBatchCommitment: bytes32(""), + diamondCut: getDiamondCutData(address(diamondInit)), + protocolVersion: 0 + }); + + vm.expectRevert(bytes.concat("StateTransition: governor zero")); + TransparentUpgradeableProxy transparentUpgradeableProxyReverting = new TransparentUpgradeableProxy( + address(stateTransitionManager), + admin, + abi.encodeCall(StateTransitionManager.initialize, stmInitializeDataNoGovernor) + ); + + StateTransitionManagerInitializeData memory stmInitializeData = StateTransitionManagerInitializeData({ + governor: governor, + validatorTimelock: validator, + genesisUpgrade: address(genesisUpgradeContract), + genesisBatchHash: bytes32(""), + genesisIndexRepeatedStorageChanges: 0, + genesisBatchCommitment: bytes32(""), + diamondCut: getDiamondCutData(address(diamondInit)), + protocolVersion: 0 + }); + + TransparentUpgradeableProxy transparentUpgradeableProxy = new TransparentUpgradeableProxy( + address(stateTransitionManager), + admin, + abi.encodeCall(StateTransitionManager.initialize, stmInitializeData) + ); + chainContractAddress = StateTransitionManager(address(transparentUpgradeableProxy)); + + vm.stopPrank(); + vm.startPrank(governor); + } + + function getDiamondCutData(address _diamondInit) internal view returns (Diamond.DiamondCutData memory) { + InitializeDataNewChain memory initializeData = Utils.makeInitializeDataForNewChain(); + + bytes memory initCalldata = abi.encode(initializeData); + + return Diamond.DiamondCutData({facetCuts: facetCuts, initAddress: _diamondInit, initCalldata: initCalldata}); + } + + function createNewChain(Diamond.DiamondCutData memory _diamondCut) internal { + vm.stopPrank(); + vm.startPrank(bridgehub); + + chainContractAddress.createNewChain(chainId, baseToken, sharedBridge, newChainAdmin, abi.encode(_diamondCut)); + } + + // add this to be excluded from coverage report + function test() internal virtual {} +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondInit/Initialize.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondInit/Initialize.t.sol new file mode 100644 index 000000000000..35d33149962b --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondInit/Initialize.t.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {DiamondInitTest} from "./_DiamondInit_Shared.t.sol"; +import {Utils} from "foundry-test/unit/concrete/Utils/Utils.sol"; +import {UtilsFacet} from "foundry-test/unit/concrete/Utils/UtilsFacet.sol"; + +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; +import {DiamondInit} from "solpp/state-transition/chain-deps/DiamondInit.sol"; +import {DiamondProxy} from "solpp/state-transition/chain-deps/DiamondProxy.sol"; +import {InitializeData} from "solpp/state-transition/chain-interfaces/IDiamondInit.sol"; +import {IVerifier} from "solpp/state-transition/chain-interfaces/IVerifier.sol"; +import {MAX_GAS_PER_TRANSACTION} from "solpp/common/Config.sol"; + +contract InitializeTest is DiamondInitTest { + function test_revertWhen_verifierIsZeroAddress() public { + InitializeData memory initializeData = Utils.makeInitializeData(); + initializeData.verifier = IVerifier(address(0)); + + Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ + facetCuts: facetCuts, + initAddress: address(new DiamondInit()), + initCalldata: abi.encodeWithSelector(DiamondInit.initialize.selector, initializeData) + }); + + vm.expectRevert(bytes.concat("vt")); + new DiamondProxy(block.chainid, diamondCutData); + } + + function test_revertWhen_governorIsZeroAddress() public { + InitializeData memory initializeData = Utils.makeInitializeData(); + initializeData.admin = address(0); + + Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ + facetCuts: facetCuts, + initAddress: address(new DiamondInit()), + initCalldata: abi.encodeWithSelector(DiamondInit.initialize.selector, initializeData) + }); + + vm.expectRevert(bytes.concat("vy")); + new DiamondProxy(block.chainid, diamondCutData); + } + + function test_revertWhen_validatorTimelockIsZeroAddress() public { + InitializeData memory initializeData = Utils.makeInitializeData(); + initializeData.validatorTimelock = address(0); + + Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ + facetCuts: facetCuts, + initAddress: address(new DiamondInit()), + initCalldata: abi.encodeWithSelector(DiamondInit.initialize.selector, initializeData) + }); + + vm.expectRevert(bytes.concat("hc")); + new DiamondProxy(block.chainid, diamondCutData); + } + + function test_revertWhen_priorityTxMaxGasLimitIsGreaterThanMaxGasPerTransaction() public { + InitializeData memory initializeData = Utils.makeInitializeData(); + initializeData.priorityTxMaxGasLimit = MAX_GAS_PER_TRANSACTION + 1; + + Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ + facetCuts: facetCuts, + initAddress: address(new DiamondInit()), + initCalldata: abi.encodeWithSelector(DiamondInit.initialize.selector, initializeData) + }); + + vm.expectRevert(bytes.concat("vu")); + new DiamondProxy(block.chainid, diamondCutData); + } + + function test_valuesCorrectWhenSuccessfulInit() public { + InitializeData memory initializeData = Utils.makeInitializeData(); + + Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ + facetCuts: facetCuts, + initAddress: address(new DiamondInit()), + initCalldata: abi.encodeWithSelector(DiamondInit.initialize.selector, initializeData) + }); + + DiamondProxy diamondProxy = new DiamondProxy(block.chainid, diamondCutData); + UtilsFacet utilsFacet = UtilsFacet(address(diamondProxy)); + + assertEq(utilsFacet.util_getChainId(), initializeData.chainId); + assertEq(utilsFacet.util_getBridgehub(), initializeData.bridgehub); + assertEq(utilsFacet.util_getStateTransitionManager(), initializeData.stateTransitionManager); + assertEq(utilsFacet.util_getBaseToken(), initializeData.baseToken); + assertEq(utilsFacet.util_getBaseTokenBridge(), initializeData.baseTokenBridge); + assertEq(utilsFacet.util_getProtocolVersion(), initializeData.protocolVersion); + + assertEq(address(utilsFacet.util_getVerifier()), address(initializeData.verifier)); + assertEq(utilsFacet.util_getAdmin(), initializeData.admin); + assertEq(utilsFacet.util_getValidator(initializeData.validatorTimelock), true); + + assertEq(utilsFacet.util_getStoredBatchHashes(0), initializeData.storedBatchZero); + assertEq( + keccak256(abi.encode(utilsFacet.util_getVerifierParams())), + keccak256(abi.encode(initializeData.verifierParams)) + ); + assertEq(utilsFacet.util_getL2BootloaderBytecodeHash(), initializeData.l2BootloaderBytecodeHash); + assertEq(utilsFacet.util_getL2DefaultAccountBytecodeHash(), initializeData.l2DefaultAccountBytecodeHash); + assertEq(utilsFacet.util_getPriorityTxMaxGasLimit(), initializeData.priorityTxMaxGasLimit); + assertEq( + keccak256(abi.encode(utilsFacet.util_getFeeParams())), + keccak256(abi.encode(initializeData.feeParams)) + ); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondInit/_DiamondInit_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondInit/_DiamondInit_Shared.t.sol new file mode 100644 index 000000000000..abf7cc4b8066 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondInit/_DiamondInit_Shared.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {Utils} from "foundry-test/unit/concrete/Utils/Utils.sol"; +import {UtilsFacet} from "foundry-test/unit/concrete/Utils/UtilsFacet.sol"; + +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; + +contract DiamondInitTest is Test { + Diamond.FacetCut[] internal facetCuts; + + function setUp() public virtual { + facetCuts.push( + Diamond.FacetCut({ + facet: address(new UtilsFacet()), + action: Diamond.Action.Add, + isFreezable: true, + selectors: Utils.getUtilsFacetSelectors() + }) + ); + } + + // add this to be excluded from coverage report + function test() internal virtual {} +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondProxy/DiamondProxy.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondProxy/DiamondProxy.t.sol new file mode 100644 index 000000000000..1fb2735ff8d5 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondProxy/DiamondProxy.t.sol @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {Utils} from "foundry-test/unit/concrete/Utils/Utils.sol"; +import {UtilsFacet} from "foundry-test/unit/concrete/Utils/UtilsFacet.sol"; + +import {InitializeData} from "solpp/state-transition/chain-interfaces/IDiamondInit.sol"; +import {DiamondInit} from "solpp/state-transition/chain-deps/DiamondInit.sol"; +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; +import {GettersFacet} from "solpp/state-transition/chain-deps/facets/Getters.sol"; +import {IVerifier, VerifierParams} from "solpp/state-transition/chain-interfaces/IVerifier.sol"; +import {FeeParams, PubdataPricingMode, ZkSyncStateTransitionStorage} from "solpp/state-transition/chain-deps/ZkSyncStateTransitionStorage.sol"; +import {DiamondProxy} from "solpp/state-transition/chain-deps/DiamondProxy.sol"; +import {MAX_GAS_PER_TRANSACTION} from "solpp/common/Config.sol"; +import {ZkSyncStateTransitionBase} from "solpp/state-transition/chain-deps/facets/ZkSyncStateTransitionBase.sol"; + +contract TestFacet is ZkSyncStateTransitionBase { + function func() public pure returns (bool) { + return true; + } + + // add this to be excluded from coverage report + function test() internal virtual {} +} + +contract DiamondProxyTest is Test { + Diamond.FacetCut[] internal facetCuts; + + function getTestFacetSelectors() public pure returns (bytes4[] memory selectors) { + selectors = new bytes4[](1); + selectors[0] = TestFacet.func.selector; + } + + function setUp() public virtual { + facetCuts.push( + Diamond.FacetCut({ + facet: address(new TestFacet()), + action: Diamond.Action.Add, + isFreezable: true, + selectors: getTestFacetSelectors() + }) + ); + facetCuts.push( + Diamond.FacetCut({ + facet: address(new UtilsFacet()), + action: Diamond.Action.Add, + isFreezable: true, + selectors: Utils.getUtilsFacetSelectors() + }) + ); + } + + function test_revertWhen_chainIdDiffersFromBlockChainId() public { + InitializeData memory initializeData = Utils.makeInitializeData(); + + Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ + facetCuts: facetCuts, + initAddress: address(new DiamondInit()), + initCalldata: abi.encodeWithSelector(DiamondInit.initialize.selector, initializeData) + }); + + vm.expectRevert(abi.encodePacked("pr")); + new DiamondProxy(block.chainid + 1, diamondCutData); + } + + function test_revertWhen_calledWithEmptyMsgData() public { + InitializeData memory initializeData = Utils.makeInitializeData(); + + Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ + facetCuts: facetCuts, + initAddress: address(new DiamondInit()), + initCalldata: abi.encodeWithSelector(DiamondInit.initialize.selector, initializeData) + }); + + DiamondProxy diamondProxy = new DiamondProxy(block.chainid, diamondCutData); + + vm.expectRevert(abi.encodePacked("Ut")); + (bool success, ) = address(diamondProxy).call(""); + assertEq(success, false); + } + + function test_revertWhen_calledWithFullSelectorInMsgData() public { + InitializeData memory initializeData = Utils.makeInitializeData(); + + Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ + facetCuts: facetCuts, + initAddress: address(new DiamondInit()), + initCalldata: abi.encodeWithSelector(DiamondInit.initialize.selector, initializeData) + }); + + DiamondProxy diamondProxy = new DiamondProxy(block.chainid, diamondCutData); + + vm.expectRevert(abi.encodePacked("Ut")); + (bool success, ) = address(diamondProxy).call(bytes.concat(bytes4(0xdeadbeef))); + assertEq(success, false); + } + + function test_revertWhen_proxyHasNoFacetForSelector() public { + InitializeData memory initializeData = Utils.makeInitializeData(); + + Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ + facetCuts: new Diamond.FacetCut[](0), + initAddress: address(new DiamondInit()), + initCalldata: abi.encodeWithSelector(DiamondInit.initialize.selector, initializeData) + }); + + DiamondProxy diamondProxy = new DiamondProxy(block.chainid, diamondCutData); + TestFacet testFacet = TestFacet(address(diamondProxy)); + + vm.expectRevert(abi.encodePacked("F")); + testFacet.func(); + } + + function test_revertWhenFacetIsFrozen() public { + InitializeData memory initializeData = Utils.makeInitializeData(); + + Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ + facetCuts: facetCuts, + initAddress: address(new DiamondInit()), + initCalldata: abi.encodeWithSelector(DiamondInit.initialize.selector, initializeData) + }); + + DiamondProxy diamondProxy = new DiamondProxy(block.chainid, diamondCutData); + TestFacet testFacet = TestFacet(address(diamondProxy)); + UtilsFacet utilsFacet = UtilsFacet(address(diamondProxy)); + + utilsFacet.util_setIsFrozen(true); + + vm.expectRevert(abi.encodePacked("q1")); + testFacet.func(); + } + + function test_successfulExecution() public { + InitializeData memory initializeData = Utils.makeInitializeData(); + + Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ + facetCuts: facetCuts, + initAddress: address(new DiamondInit()), + initCalldata: abi.encodeWithSelector(DiamondInit.initialize.selector, initializeData) + }); + + DiamondProxy diamondProxy = new DiamondProxy(block.chainid, diamondCutData); + TestFacet testFacet = TestFacet(address(diamondProxy)); + + assertEq(testFacet.func(), true); + } + + function test_revertWhen_removeFunctions() public { + Diamond.FacetCut[] memory cuts = new Diamond.FacetCut[](3); + cuts[0] = facetCuts[0]; + cuts[1] = facetCuts[1]; + cuts[2] = Diamond.FacetCut({ + facet: address(0), + action: Diamond.Action.Remove, + isFreezable: true, + selectors: getTestFacetSelectors() + }); + + InitializeData memory initializeData = Utils.makeInitializeData(); + + Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ + facetCuts: cuts, + initAddress: address(new DiamondInit()), + initCalldata: abi.encodeWithSelector(DiamondInit.initialize.selector, initializeData) + }); + + DiamondProxy diamondProxy = new DiamondProxy(block.chainid, diamondCutData); + TestFacet testFacet = TestFacet(address(diamondProxy)); + + vm.expectRevert(bytes("F")); + testFacet.func(); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/AcceptAdmin.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/AcceptAdmin.t.sol new file mode 100644 index 000000000000..647b94642472 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/AcceptAdmin.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {AdminTest} from "./_Admin_Shared.t.sol"; + +contract AcceptAdminTest is AdminTest { + event NewPendingAdmin(address indexed oldPendingAdmin, address indexed newPendingAdmin); + event NewAdmin(address indexed oldAdmin, address indexed newAdmin); + + function test_revertWhen_calledByNonPendingAdmin() public { + address nonPendingAdmin = makeAddr("nonPendingAdmin"); + + vm.expectRevert(bytes.concat("n4")); + + vm.startPrank(nonPendingAdmin); + adminFacet.acceptAdmin(); + } + + function test_successfulCall() public { + address pendingAdmin = utilsFacet.util_getPendingAdmin(); + address previousAdmin = utilsFacet.util_getAdmin(); + + vm.expectEmit(true, true, true, true, address(adminFacet)); + emit NewPendingAdmin(pendingAdmin, address(0)); + vm.expectEmit(true, true, true, true, address(adminFacet)); + emit NewAdmin(previousAdmin, pendingAdmin); + + vm.startPrank(pendingAdmin); + adminFacet.acceptAdmin(); + + assertEq(utilsFacet.util_getPendingAdmin(), address(0)); + assertEq(utilsFacet.util_getAdmin(), pendingAdmin); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/ChangeFeeParams.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/ChangeFeeParams.t.sol new file mode 100644 index 000000000000..e173ec409e32 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/ChangeFeeParams.t.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {AdminTest} from "./_Admin_Shared.t.sol"; +import {ERROR_ONLY_ADMIN_OR_STATE_TRANSITION_MANAGER} from "../Base/_Base_Shared.t.sol"; + +import {FeeParams, PubdataPricingMode} from "solpp/state-transition/chain-deps/ZkSyncStateTransitionStorage.sol"; + +contract ChangeFeeParamsTest is AdminTest { + event NewFeeParams(FeeParams oldFeeParams, FeeParams newFeeParams); + + function setUp() public override { + super.setUp(); + + utilsFacet.util_setFeeParams( + FeeParams({ + pubdataPricingMode: PubdataPricingMode.Rollup, + batchOverheadL1Gas: 1_000_000, + maxPubdataPerBatch: 110_000, + maxL2GasPerBatch: 80_000_000, + priorityTxMaxPubdata: 99_000, + minimalL2GasPrice: 250_000_000 + }) + ); + } + + function test_revertWhen_calledByNonStateTransitionManager() public { + address nonStateTransitionManager = makeAddr("nonStateTransitionManager"); + FeeParams memory newFeeParams = FeeParams({ + pubdataPricingMode: PubdataPricingMode.Rollup, + batchOverheadL1Gas: 1_000_000, + maxPubdataPerBatch: 110_000, + maxL2GasPerBatch: 80_000_000, + priorityTxMaxPubdata: 99_000, + minimalL2GasPrice: 250_000_000 + }); + + vm.startPrank(nonStateTransitionManager); + vm.expectRevert(ERROR_ONLY_ADMIN_OR_STATE_TRANSITION_MANAGER); + + adminFacet.changeFeeParams(newFeeParams); + } + + function test_revertWhen_newMaxPubdataPerBatchIsLessThanMaxPubdataPerTransaction() public { + address stateTransitionManager = utilsFacet.util_getStateTransitionManager(); + uint32 priorityTxMaxPubdata = 88_000; + uint32 maxPubdataPerBatch = priorityTxMaxPubdata - 1; + FeeParams memory newFeeParams = FeeParams({ + pubdataPricingMode: PubdataPricingMode.Rollup, + batchOverheadL1Gas: 1_000_000, + maxPubdataPerBatch: maxPubdataPerBatch, + maxL2GasPerBatch: 80_000_000, + priorityTxMaxPubdata: priorityTxMaxPubdata, + minimalL2GasPrice: 250_000_000 + }); + + vm.expectRevert(bytes.concat("n6")); + + vm.startPrank(stateTransitionManager); + adminFacet.changeFeeParams(newFeeParams); + } + + function test_successfulChange() public { + address stateTransitionManager = utilsFacet.util_getStateTransitionManager(); + FeeParams memory oldFeeParams = utilsFacet.util_getFeeParams(); + FeeParams memory newFeeParams = FeeParams({ + pubdataPricingMode: PubdataPricingMode.Rollup, + batchOverheadL1Gas: 2_000_000, + maxPubdataPerBatch: 220_000, + maxL2GasPerBatch: 100_000_000, + priorityTxMaxPubdata: 100_000, + minimalL2GasPrice: 450_000_000 + }); + + vm.expectEmit(true, true, true, true, address(adminFacet)); + emit NewFeeParams(oldFeeParams, newFeeParams); + + vm.startPrank(stateTransitionManager); + adminFacet.changeFeeParams(newFeeParams); + + bytes32 newFeeParamsHash = keccak256(abi.encode(newFeeParams)); + bytes32 currentFeeParamsHash = keccak256(abi.encode(utilsFacet.util_getFeeParams())); + require(currentFeeParamsHash == newFeeParamsHash, "Fee params were not changed correctly"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/ExecuteUpgrade.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/ExecuteUpgrade.t.sol new file mode 100644 index 000000000000..5e665193b5b5 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/ExecuteUpgrade.t.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {AdminTest} from "./_Admin_Shared.t.sol"; +import {ERROR_ONLY_STATE_TRANSITION_MANAGER} from "../Base/_Base_Shared.t.sol"; + +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; + +contract ExecuteUpgradeTest is AdminTest { + event ExecuteUpgrade(Diamond.DiamondCutData diamondCut); + + function test_revertWhen_calledByNonGovernorOrStateTransitionManager() public { + address nonStateTransitionManager = makeAddr("nonStateTransitionManager"); + Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ + facetCuts: new Diamond.FacetCut[](0), + initAddress: address(0), + initCalldata: new bytes(0) + }); + + vm.expectRevert(ERROR_ONLY_STATE_TRANSITION_MANAGER); + + vm.startPrank(nonStateTransitionManager); + adminFacet.executeUpgrade(diamondCutData); + } + + // TODO + // function test_successfulUpgrade() public { + // address stateTransitionManager = utilsFacet.util_getStateTransitionManager(); + // Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ + // facetCuts: new Diamond.FacetCut[](0), + // initAddress: address(0), + // initCalldata: new bytes(0) + // }); + // vm.mockCall(address(0), abi.encodeWithSelector(IDiamondLibrary.diamondCut.selector), abi.encode()); + // vm.expectEmit(true, true, true, true, address(adminFacet)); + // emit ExecuteUpgrade(diamondCutData); + // vm.startPrank(stateTransitionManager); + // adminFacet.executeUpgrade(diamondCutData); + // } +} + +interface IDiamondLibrary { + function diamondCut(Diamond.FacetCut[] memory _diamondCut, address _init, bytes memory _calldata) external; +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/FreezeDiamond.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/FreezeDiamond.t.sol new file mode 100644 index 000000000000..21084adf9de8 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/FreezeDiamond.t.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {AdminTest} from "./_Admin_Shared.t.sol"; +import {ERROR_ONLY_ADMIN_OR_STATE_TRANSITION_MANAGER} from "../Base/_Base_Shared.t.sol"; + +contract FreezeDiamondTest is AdminTest { + event Freeze(); + + function test_revertWhen_calledByNonAdminOrStateTransitionManager() public { + address nonAdminOrStateTransitionManager = makeAddr("nonAdminOrStateTransitionManager"); + + vm.expectRevert(ERROR_ONLY_ADMIN_OR_STATE_TRANSITION_MANAGER); + + vm.startPrank(nonAdminOrStateTransitionManager); + adminFacet.freezeDiamond(); + } + + // function test_revertWhen_diamondIsAlreadyFrozen() public { + // address governor = utilsFacet.util_getAdmin(); + + // utilsFacet.util_setIsFrozen(true); + + // vm.expectRevert(bytes.concat("a9")); + + // vm.startPrank(governor); + // adminFacet.freezeDiamond(); + // } + + // function test_successfulFreeze() public { + // address governor = utilsFacet.util_getAdmin(); + + // utilsFacet.util_setIsFrozen(false); + + // vm.expectEmit(true, true, true, true, address(adminFacet)); + // emit Freeze(); + + // vm.startPrank(governor); + // adminFacet.freezeDiamond(); + + // assertEq(utilsFacet.util_getIsFrozen(), true); + // } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetPendingGovernor.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetPendingGovernor.t.sol new file mode 100644 index 000000000000..0cba797d7768 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetPendingGovernor.t.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {AdminTest} from "./_Admin_Shared.t.sol"; +import {ERROR_ONLY_ADMIN} from "../Base/_Base_Shared.t.sol"; + +contract SetPendingAdminTest is AdminTest { + event NewPendingAdmin(address indexed oldPendingAdmin, address indexed newPendingAdmin); + + function test_revertWhen_calledByNonAdmin() public { + address nonAdmin = makeAddr("nonAdmin"); + address newPendingAdmin = makeAddr("newPendingAdmin"); + + vm.expectRevert(ERROR_ONLY_ADMIN); + + vm.startPrank(nonAdmin); + adminFacet.setPendingAdmin(newPendingAdmin); + } + + function test_successfulCall() public { + address admin = utilsFacet.util_getAdmin(); + address oldPendingAdmin = utilsFacet.util_getPendingAdmin(); + address newPendingAdmin = makeAddr("newPendingAdmin"); + + vm.expectEmit(true, true, true, true, address(adminFacet)); + emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin); + + vm.startPrank(admin); + adminFacet.setPendingAdmin(newPendingAdmin); + + assertEq(utilsFacet.util_getPendingAdmin(), newPendingAdmin); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetPorterAvailability.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetPorterAvailability.t.sol new file mode 100644 index 000000000000..2e3965f43a44 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetPorterAvailability.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {AdminTest} from "./_Admin_Shared.t.sol"; +import {ERROR_ONLY_STATE_TRANSITION_MANAGER} from "../Base/_Base_Shared.t.sol"; + +contract SetPorterAvailabilityTest is AdminTest { + event IsPorterAvailableStatusUpdate(bool isPorterAvailable); + + function test_revertWhen_calledByNonStateTransitionManager() public { + address nonStateTransitionManager = makeAddr("nonStateTransitionManager"); + bool isPorterAvailable = true; + + vm.expectRevert(ERROR_ONLY_STATE_TRANSITION_MANAGER); + + vm.startPrank(nonStateTransitionManager); + adminFacet.setPorterAvailability(isPorterAvailable); + } + + function test_setPorterAvailabilityToFalse() public { + address stateTransitionManager = utilsFacet.util_getStateTransitionManager(); + bool isPorterAvailable = false; + + utilsFacet.util_setZkPorterAvailability(true); + + vm.expectEmit(true, true, true, true, address(adminFacet)); + emit IsPorterAvailableStatusUpdate(isPorterAvailable); + + vm.startPrank(stateTransitionManager); + adminFacet.setPorterAvailability(isPorterAvailable); + + assertEq(utilsFacet.util_getZkPorterAvailability(), isPorterAvailable); + } + + function test_setPorterAvailabilityToTrue() public { + address stateTransitionManager = utilsFacet.util_getStateTransitionManager(); + bool isPorterAvailable = true; + + utilsFacet.util_setZkPorterAvailability(false); + + vm.expectEmit(true, true, true, true, address(adminFacet)); + emit IsPorterAvailableStatusUpdate(isPorterAvailable); + + vm.startPrank(stateTransitionManager); + adminFacet.setPorterAvailability(isPorterAvailable); + + assertEq(utilsFacet.util_getZkPorterAvailability(), isPorterAvailable); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetPriorityTxMaxGasLimit.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetPriorityTxMaxGasLimit.t.sol new file mode 100644 index 000000000000..4b80b613211e --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetPriorityTxMaxGasLimit.t.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {AdminTest} from "./_Admin_Shared.t.sol"; +import {ERROR_ONLY_STATE_TRANSITION_MANAGER} from "../Base/_Base_Shared.t.sol"; + +import {MAX_GAS_PER_TRANSACTION} from "solpp/common/Config.sol"; + +contract SetPriorityTxMaxGasLimitTest is AdminTest { + event NewPriorityTxMaxGasLimit(uint256 oldPriorityTxMaxGasLimit, uint256 newPriorityTxMaxGasLimit); + + function test_revertWhen_calledByNonStateTransitionManager() public { + address nonStateTransitionManager = makeAddr("nonStateTransitionManager"); + uint256 newPriorityTxMaxGasLimit = 100; + + vm.startPrank(nonStateTransitionManager); + vm.expectRevert(ERROR_ONLY_STATE_TRANSITION_MANAGER); + + adminFacet.setPriorityTxMaxGasLimit(newPriorityTxMaxGasLimit); + } + + function test_revertWhen_newPriorityTxMaxGasLimitIsGreaterThanMaxGasPerTransaction() public { + address stateTransitionManager = utilsFacet.util_getStateTransitionManager(); + uint256 newPriorityTxMaxGasLimit = MAX_GAS_PER_TRANSACTION + 1; + + vm.expectRevert(bytes.concat("n5")); + + vm.startPrank(stateTransitionManager); + adminFacet.setPriorityTxMaxGasLimit(newPriorityTxMaxGasLimit); + } + + function test_successfulSet() public { + address stateTransitionManager = utilsFacet.util_getStateTransitionManager(); + uint256 oldPriorityTxMaxGasLimit = utilsFacet.util_getPriorityTxMaxGasLimit(); + uint256 newPriorityTxMaxGasLimit = 100; + + vm.expectEmit(true, true, true, true, address(adminFacet)); + emit NewPriorityTxMaxGasLimit(oldPriorityTxMaxGasLimit, newPriorityTxMaxGasLimit); + + vm.startPrank(stateTransitionManager); + adminFacet.setPriorityTxMaxGasLimit(newPriorityTxMaxGasLimit); + + assertEq(utilsFacet.util_getPriorityTxMaxGasLimit(), newPriorityTxMaxGasLimit); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetValidator.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetValidator.t.sol new file mode 100644 index 000000000000..243f6e6a6baa --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetValidator.t.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {AdminTest} from "./_Admin_Shared.t.sol"; +import {ERROR_ONLY_STATE_TRANSITION_MANAGER} from "../Base/_Base_Shared.t.sol"; + +contract SetValidatorTest is AdminTest { + event ValidatorStatusUpdate(address indexed validatorAddress, bool isActive); + + function test_revertWhen_calledByNonStateTransitionManager() public { + address nonStateTransitionManager = makeAddr("nonStateTransitionManager"); + address validator = makeAddr("validator"); + bool isActive = true; + + vm.expectRevert(ERROR_ONLY_STATE_TRANSITION_MANAGER); + + vm.startPrank(nonStateTransitionManager); + adminFacet.setValidator(validator, isActive); + } + + function test_deactivateValidator() public { + address stateTransitionManager = utilsFacet.util_getStateTransitionManager(); + address validator = makeAddr("validator"); + bool isActive = false; + + utilsFacet.util_setValidator(validator, true); + + vm.expectEmit(true, true, true, true, address(adminFacet)); + emit ValidatorStatusUpdate(validator, isActive); + + vm.startPrank(stateTransitionManager); + adminFacet.setValidator(validator, isActive); + + assertEq(utilsFacet.util_getValidator(validator), isActive); + } + + function test_reactivateValidator() public { + address stateTransitionManager = utilsFacet.util_getStateTransitionManager(); + address validator = makeAddr("validator"); + bool isActive = true; + + utilsFacet.util_setValidator(validator, false); + + vm.expectEmit(true, true, true, true, address(adminFacet)); + emit ValidatorStatusUpdate(validator, isActive); + + vm.startPrank(stateTransitionManager); + adminFacet.setValidator(validator, isActive); + + assertEq(utilsFacet.util_getValidator(validator), isActive); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/UnfreezeDiamond.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/UnfreezeDiamond.t.sol new file mode 100644 index 000000000000..2bea73dfc339 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/UnfreezeDiamond.t.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {AdminTest} from "./_Admin_Shared.t.sol"; +import {ERROR_ONLY_ADMIN_OR_STATE_TRANSITION_MANAGER} from "../Base/_Base_Shared.t.sol"; + +contract UnfreezeDiamondTest is AdminTest { + event Unfreeze(); + + function test_revertWhen_calledByNonAdminOrStateTransitionManager() public { + address nonAdminOrStateTransitionManager = makeAddr("nonAdminOrStateTransitionManager"); + + vm.expectRevert(ERROR_ONLY_ADMIN_OR_STATE_TRANSITION_MANAGER); + + vm.startPrank(nonAdminOrStateTransitionManager); + adminFacet.unfreezeDiamond(); + } + + function test_revertWhen_diamondIsNotFrozen() public { + address admin = utilsFacet.util_getAdmin(); + + utilsFacet.util_setIsFrozen(false); + + vm.expectRevert(bytes.concat("a7")); + + vm.startPrank(admin); + adminFacet.unfreezeDiamond(); + } + + // function test_successfulUnfreeze() public { + // address governor = utilsFacet.util_getAdmin(); + + // utilsFacet.util_setIsFrozen(true); + + // vm.expectEmit(true, true, true, true, address(adminFacet)); + // emit Unfreeze(); + + // vm.startPrank(governor); + // adminFacet.unfreezeDiamond(); + + // assertEq(utilsFacet.util_getIsFrozen(), false); + // } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/UpgradeChainFromVersion.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/UpgradeChainFromVersion.t.sol new file mode 100644 index 000000000000..d01ea55a9ae5 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/UpgradeChainFromVersion.t.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {AdminTest} from "./_Admin_Shared.t.sol"; +import {ERROR_ONLY_ADMIN_OR_STATE_TRANSITION_MANAGER} from "../Base/_Base_Shared.t.sol"; + +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; +import {IStateTransitionManager} from "solpp/state-transition/IStateTransitionManager.sol"; + +contract UpgradeChainFromVersionTest is AdminTest { + event ExecuteUpgrade(Diamond.DiamondCutData diamondCut); + + function test_revertWhen_calledByNonAdminOrStateTransitionManager() public { + address nonAdminOrStateTransitionManager = makeAddr("nonAdminOrStateTransitionManager"); + uint256 oldProtocolVersion = 1; + Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ + facetCuts: new Diamond.FacetCut[](0), + initAddress: address(0), + initCalldata: new bytes(0) + }); + + vm.expectRevert(ERROR_ONLY_ADMIN_OR_STATE_TRANSITION_MANAGER); + + vm.startPrank(nonAdminOrStateTransitionManager); + adminFacet.upgradeChainFromVersion(oldProtocolVersion, diamondCutData); + } + + function test_revertWhen_cutHashMismatch() public { + address admin = utilsFacet.util_getAdmin(); + address stateTransitionManager = makeAddr("stateTransitionManager"); + + uint256 oldProtocolVersion = 1; + Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ + facetCuts: new Diamond.FacetCut[](0), + initAddress: address(0), + initCalldata: new bytes(0) + }); + + utilsFacet.util_setStateTransitionManager(stateTransitionManager); + + bytes32 cutHashInput = keccak256("random"); + vm.mockCall( + stateTransitionManager, + abi.encodeWithSelector(IStateTransitionManager.upgradeCutHash.selector), + abi.encode(cutHashInput) + ); + + vm.expectRevert("StateTransition: cutHash mismatch"); + + vm.startPrank(admin); + adminFacet.upgradeChainFromVersion(oldProtocolVersion, diamondCutData); + } + + function test_revertWhen_ProtocolVersionMismatchWhenUpgrading() public { + address admin = utilsFacet.util_getAdmin(); + address stateTransitionManager = makeAddr("stateTransitionManager"); + + uint256 oldProtocolVersion = 1; + Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ + facetCuts: new Diamond.FacetCut[](0), + initAddress: address(0), + initCalldata: new bytes(0) + }); + + utilsFacet.util_setProtocolVersion(oldProtocolVersion + 1); + utilsFacet.util_setStateTransitionManager(stateTransitionManager); + + bytes32 cutHashInput = keccak256(abi.encode(diamondCutData)); + vm.mockCall( + stateTransitionManager, + abi.encodeWithSelector(IStateTransitionManager.upgradeCutHash.selector), + abi.encode(cutHashInput) + ); + + vm.expectRevert("StateTransition: protocolVersion mismatch in STC when upgrading"); + + vm.startPrank(admin); + adminFacet.upgradeChainFromVersion(oldProtocolVersion, diamondCutData); + } + + function test_revertWhen_ProtocolVersionMismatchAfterUpgrading() public { + address admin = utilsFacet.util_getAdmin(); + address stateTransitionManager = makeAddr("stateTransitionManager"); + + uint256 oldProtocolVersion = 1; + Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ + facetCuts: new Diamond.FacetCut[](0), + initAddress: address(0), + initCalldata: new bytes(0) + }); + + utilsFacet.util_setProtocolVersion(oldProtocolVersion); + utilsFacet.util_setStateTransitionManager(stateTransitionManager); + + bytes32 cutHashInput = keccak256(abi.encode(diamondCutData)); + vm.mockCall( + stateTransitionManager, + abi.encodeWithSelector(IStateTransitionManager.upgradeCutHash.selector), + abi.encode(cutHashInput) + ); + + vm.expectRevert("StateTransition: protocolVersion mismatch in STC after upgrading"); + + vm.expectEmit(true, true, true, true, address(adminFacet)); + emit ExecuteUpgrade(diamondCutData); + + vm.startPrank(admin); + adminFacet.upgradeChainFromVersion(oldProtocolVersion, diamondCutData); + } + + // TODO + // function test_successfulUpgrade() public {} +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/_Admin_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/_Admin_Shared.t.sol new file mode 100644 index 000000000000..a06cac9484ff --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/_Admin_Shared.t.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {Utils} from "foundry-test/unit/concrete/Utils/Utils.sol"; +import {UtilsFacet} from "foundry-test/unit/concrete/Utils/UtilsFacet.sol"; + +import {AdminFacet} from "solpp/state-transition/chain-deps/facets/Admin.sol"; +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; +import {DiamondInit} from "solpp/state-transition/chain-deps/DiamondInit.sol"; +import {DiamondProxy} from "solpp/state-transition/chain-deps/DiamondProxy.sol"; +import {FeeParams} from "solpp/state-transition/chain-deps/ZkSyncStateTransitionStorage.sol"; +import {IAdmin} from "solpp/state-transition/chain-interfaces/IAdmin.sol"; + +contract AdminTest is Test { + IAdmin internal adminFacet; + UtilsFacet internal utilsFacet; + + function getAdminSelectors() public pure returns (bytes4[] memory) { + bytes4[] memory selectors = new bytes4[](11); + selectors[0] = IAdmin.setPendingAdmin.selector; + selectors[1] = IAdmin.acceptAdmin.selector; + selectors[2] = IAdmin.setValidator.selector; + selectors[3] = IAdmin.setPorterAvailability.selector; + selectors[4] = IAdmin.setPriorityTxMaxGasLimit.selector; + selectors[5] = IAdmin.changeFeeParams.selector; + selectors[6] = IAdmin.setTokenMultiplier.selector; + selectors[7] = IAdmin.upgradeChainFromVersion.selector; + selectors[8] = IAdmin.executeUpgrade.selector; + selectors[9] = IAdmin.freezeDiamond.selector; + selectors[10] = IAdmin.unfreezeDiamond.selector; + return selectors; + } + + function setUp() public virtual { + Diamond.FacetCut[] memory facetCuts = new Diamond.FacetCut[](2); + facetCuts[0] = Diamond.FacetCut({ + facet: address(new AdminFacet()), + action: Diamond.Action.Add, + isFreezable: true, + selectors: getAdminSelectors() + }); + facetCuts[1] = Diamond.FacetCut({ + facet: address(new UtilsFacet()), + action: Diamond.Action.Add, + isFreezable: true, + selectors: Utils.getUtilsFacetSelectors() + }); + + address diamondProxy = Utils.makeDiamondProxy(facetCuts); + adminFacet = IAdmin(diamondProxy); + utilsFacet = UtilsFacet(diamondProxy); + } + + // add this to be excluded from coverage report + function test() internal virtual {} +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyBridgehub.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyBridgehub.t.sol new file mode 100644 index 000000000000..1563de9a2c4d --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyBridgehub.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {ZkSyncStateTransitionBaseTest, ERROR_ONLY_BRIDGEHUB} from "./_Base_Shared.t.sol"; + +contract OnlyBridgehubTest is ZkSyncStateTransitionBaseTest { + function test_revertWhen_calledByNonBridgehub() public { + address nonBridgehub = makeAddr("nonBridgehub"); + + vm.expectRevert(ERROR_ONLY_BRIDGEHUB); + + vm.startPrank(nonBridgehub); + testBaseFacet.functionWithOnlyBridgehubModifier(); + } + + function test_successfulCall() public { + address bridgehub = utilsFacet.util_getBridgehub(); + + vm.startPrank(bridgehub); + testBaseFacet.functionWithOnlyBridgehubModifier(); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyGovernor.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyGovernor.t.sol new file mode 100644 index 000000000000..b882c386f088 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyGovernor.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {ZkSyncStateTransitionBaseTest, ERROR_ONLY_ADMIN} from "./_Base_Shared.t.sol"; + +contract OnlyAdminTest is ZkSyncStateTransitionBaseTest { + function test_revertWhen_calledByNonAdmin() public { + address nonAdmin = makeAddr("nonAdmin"); + + vm.expectRevert(ERROR_ONLY_ADMIN); + + vm.startPrank(nonAdmin); + testBaseFacet.functionWithOnlyAdminModifier(); + } + + function test_successfulCall() public { + address admin = utilsFacet.util_getAdmin(); + + vm.startPrank(admin); + testBaseFacet.functionWithOnlyAdminModifier(); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyGovernorOrStateTransitionManager.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyGovernorOrStateTransitionManager.t.sol new file mode 100644 index 000000000000..a6616fc21835 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyGovernorOrStateTransitionManager.t.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {ZkSyncStateTransitionBaseTest, ERROR_ONLY_ADMIN_OR_STATE_TRANSITION_MANAGER} from "./_Base_Shared.t.sol"; + +contract OnlyAdminOrStateTransitionManagerTest is ZkSyncStateTransitionBaseTest { + function test_revertWhen_calledByNonAdmin() public { + address nonAdmin = makeAddr("nonAdmin"); + + vm.expectRevert(ERROR_ONLY_ADMIN_OR_STATE_TRANSITION_MANAGER); + + vm.startPrank(nonAdmin); + testBaseFacet.functionWithOnlyAdminOrStateTransitionManagerModifier(); + } + + function test_revertWhen_calledByNonStateTranstionManager() public { + address nonStateTransitionManager = makeAddr("nonStateTransitionManager"); + + vm.expectRevert(ERROR_ONLY_ADMIN_OR_STATE_TRANSITION_MANAGER); + + vm.startPrank(nonStateTransitionManager); + testBaseFacet.functionWithOnlyAdminOrStateTransitionManagerModifier(); + } + + function test_successfulCallWhenCalledByAdmin() public { + address admin = utilsFacet.util_getAdmin(); + + vm.startPrank(admin); + testBaseFacet.functionWithOnlyAdminOrStateTransitionManagerModifier(); + } + + function test_successfulCallWhenCalledByStateTransitionManager() public { + address stateTransitionManager = utilsFacet.util_getStateTransitionManager(); + + vm.startPrank(stateTransitionManager); + testBaseFacet.functionWithOnlyAdminOrStateTransitionManagerModifier(); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyStateTransitionManager.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyStateTransitionManager.t.sol new file mode 100644 index 000000000000..8ed9dc2f3bcf --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyStateTransitionManager.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {ZkSyncStateTransitionBaseTest, ERROR_ONLY_STATE_TRANSITION_MANAGER} from "./_Base_Shared.t.sol"; + +contract OnlyStateTransitionManagerTest is ZkSyncStateTransitionBaseTest { + function test_revertWhen_calledByNonStateTransitionManager() public { + address nonStateTransitionManager = makeAddr("nonStateTransitionManager"); + + vm.expectRevert(ERROR_ONLY_STATE_TRANSITION_MANAGER); + + vm.startPrank(nonStateTransitionManager); + testBaseFacet.functionWithOnlyStateTransitionManagerModifier(); + } + + function test_successfulCall() public { + address stateTransitionManager = utilsFacet.util_getStateTransitionManager(); + + vm.startPrank(stateTransitionManager); + testBaseFacet.functionWithOnlyStateTransitionManagerModifier(); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyValidator.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyValidator.t.sol new file mode 100644 index 000000000000..75df204ff51d --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyValidator.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {ZkSyncStateTransitionBaseTest, ERROR_ONLY_VALIDATOR} from "./_Base_Shared.t.sol"; + +contract OnlyValidatorTest is ZkSyncStateTransitionBaseTest { + function test_revertWhen_calledByNonValidator() public { + address nonValidator = makeAddr("nonValidator"); + + utilsFacet.util_setValidator(nonValidator, false); + + vm.expectRevert(ERROR_ONLY_VALIDATOR); + + vm.startPrank(nonValidator); + testBaseFacet.functionWithOnlyValidatorModifier(); + } + + function test_successfulCall() public { + address validator = makeAddr("validator"); + utilsFacet.util_setValidator(validator, true); + + vm.startPrank(validator); + testBaseFacet.functionWithOnlyValidatorModifier(); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/_Base_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/_Base_Shared.t.sol new file mode 100644 index 000000000000..d29a9d614d70 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/_Base_Shared.t.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {Utils} from "foundry-test/unit/concrete/Utils/Utils.sol"; +import {UtilsFacet} from "foundry-test/unit/concrete/Utils/UtilsFacet.sol"; + +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; +import {ZkSyncStateTransitionBase} from "solpp/state-transition/chain-deps/facets/Admin.sol"; + +contract TestBaseFacet is ZkSyncStateTransitionBase { + function functionWithOnlyAdminModifier() external onlyAdmin {} + + function functionWithOnlyValidatorModifier() external onlyValidator {} + + function functionWithOnlyStateTransitionManagerModifier() external onlyStateTransitionManager {} + + function functionWithOnlyBridgehubModifier() external onlyBridgehub {} + + function functionWithOnlyAdminOrStateTransitionManagerModifier() external onlyAdminOrStateTransitionManager {} + + function functionWithonlyValidatorOrStateTransitionManagerModifier() + external + onlyValidatorOrStateTransitionManager + {} + + // add this to be excluded from coverage report + function test() internal virtual {} +} + +bytes constant ERROR_ONLY_ADMIN = "StateTransition Chain: not admin"; +bytes constant ERROR_ONLY_VALIDATOR = "StateTransition Chain: not validator"; +bytes constant ERROR_ONLY_STATE_TRANSITION_MANAGER = "StateTransition Chain: not state transition manager"; +bytes constant ERROR_ONLY_BRIDGEHUB = "StateTransition Chain: not bridgehub"; +bytes constant ERROR_ONLY_ADMIN_OR_STATE_TRANSITION_MANAGER = "StateTransition Chain: Only by admin or state transition manager"; +bytes constant ERROR_ONLY_VALIDATOR_OR_STATE_TRANSITION_MANAGER = "StateTransition Chain: Only by validator or state transition manager"; + +contract ZkSyncStateTransitionBaseTest is Test { + TestBaseFacet internal testBaseFacet; + UtilsFacet internal utilsFacet; + + function getTestBaseFacetSelectors() public pure returns (bytes4[] memory selectors) { + selectors = new bytes4[](6); + selectors[0] = TestBaseFacet.functionWithOnlyAdminModifier.selector; + selectors[1] = TestBaseFacet.functionWithOnlyValidatorModifier.selector; + selectors[2] = TestBaseFacet.functionWithOnlyStateTransitionManagerModifier.selector; + selectors[3] = TestBaseFacet.functionWithOnlyBridgehubModifier.selector; + selectors[4] = TestBaseFacet.functionWithOnlyAdminOrStateTransitionManagerModifier.selector; + selectors[5] = TestBaseFacet.functionWithonlyValidatorOrStateTransitionManagerModifier.selector; + } + + function setUp() public virtual { + Diamond.FacetCut[] memory facetCuts = new Diamond.FacetCut[](2); + facetCuts[0] = Diamond.FacetCut({ + facet: address(new TestBaseFacet()), + action: Diamond.Action.Add, + isFreezable: true, + selectors: getTestBaseFacetSelectors() + }); + facetCuts[1] = Diamond.FacetCut({ + facet: address(new UtilsFacet()), + action: Diamond.Action.Add, + isFreezable: true, + selectors: Utils.getUtilsFacetSelectors() + }); + + address diamondProxy = Utils.makeDiamondProxy(facetCuts); + testBaseFacet = TestBaseFacet(diamondProxy); + utilsFacet = UtilsFacet(diamondProxy); + } + + // add this to be excluded from coverage report + function test() internal virtual {} +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/FacetAddress.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/FacetAddress.t.sol new file mode 100644 index 000000000000..02c1edbbd265 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/FacetAddress.t.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; +import {IGetters} from "solpp/state-transition/chain-interfaces/IGetters.sol"; + +contract FacetAddressTest is GettersFacetTest { + function test() public { + address expected = makeAddr("facetAddress"); + bytes4 selector = bytes4("4321"); + + gettersFacetWrapper.util_setFacetAddress(selector, expected); + + address received = gettersFacet.facetAddress(selector); + + assertEq(expected, received, "Received Facet Address is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/FacetAddresses.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/FacetAddresses.t.sol new file mode 100644 index 000000000000..284255f8227c --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/FacetAddresses.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; +import {IGetters} from "solpp/state-transition/chain-interfaces/IGetters.sol"; + +contract FacetAddressesTest is GettersFacetTest { + function test() public { + address[] memory expected = new address[](2); + expected[0] = address(1); + expected[1] = address(2); + + gettersFacetWrapper.util_setFacetAddresses(expected); + + address[] memory received = gettersFacet.facetAddresses(); + + bytes32 expectedHash = keccak256(abi.encode(expected)); + bytes32 receivedHash = keccak256(abi.encode(received)); + assertEq(expectedHash, receivedHash, "Received Facet Addresses are incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/FacetFunctionSelectors.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/FacetFunctionSelectors.t.sol new file mode 100644 index 000000000000..e1cbc726bc79 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/FacetFunctionSelectors.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; +import {IGetters} from "solpp/state-transition/chain-interfaces/IGetters.sol"; + +contract FacetFunctionSelectorsTest is GettersFacetTest { + function test() public { + address facet = address(1); + bytes4[] memory expected = new bytes4[](2); + expected[0] = bytes4("1234"); + expected[1] = bytes4("4321"); + + gettersFacetWrapper.util_setFacetFunctionSelectors(facet, expected); + + bytes4[] memory received = gettersFacet.facetFunctionSelectors(facet); + + bytes32 expectedHash = keccak256(abi.encode(expected)); + bytes32 receivedHash = keccak256(abi.encode(received)); + assertEq(expectedHash, receivedHash, "Received Facet Function Selectors are incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/Facets.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/Facets.t.sol new file mode 100644 index 000000000000..243f77118b78 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/Facets.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; +import {IGetters} from "solpp/state-transition/chain-interfaces/IGetters.sol"; + +contract FacetsTest is GettersFacetTest { + function test() public { + IGetters.Facet[] memory expected = new IGetters.Facet[](2); + expected[0] = IGetters.Facet({addr: address(1), selectors: new bytes4[](1)}); + expected[0] = IGetters.Facet({addr: address(2), selectors: new bytes4[](1)}); + + gettersFacetWrapper.util_setFacets(expected); + + IGetters.Facet[] memory received = gettersFacet.facets(); + + bytes32 expectedHash = keccak256(abi.encode(expected)); + bytes32 receivedHash = keccak256(abi.encode(received)); + assertEq(expectedHash, receivedHash, "Received Facets are incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetAdmin.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetAdmin.t.sol new file mode 100644 index 000000000000..ede6a7ef7f12 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetAdmin.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetAdminrTest is GettersFacetTest { + function test() public { + address expected = makeAddr("admin"); + gettersFacetWrapper.util_setAdmin(expected); + + address received = gettersFacet.getAdmin(); + + assertEq(expected, received, "Admin address is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetBaseToken.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetBaseToken.t.sol new file mode 100644 index 000000000000..35695341dd83 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetBaseToken.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetBaseTokenTest is GettersFacetTest { + function test() public { + address expected = makeAddr("baseToken"); + gettersFacetWrapper.util_setBaseToken(expected); + + address received = gettersFacet.getBaseToken(); + + assertEq(expected, received, "BaseToken address is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetBaseTokenBridge.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetBaseTokenBridge.t.sol new file mode 100644 index 000000000000..6a8995043cf0 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetBaseTokenBridge.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetBaseTokenBridgeTest is GettersFacetTest { + function test() public { + address expected = makeAddr("baseTokenBride"); + gettersFacetWrapper.util_setBaseTokenBridge(expected); + + address received = gettersFacet.getBaseTokenBridge(); + + assertEq(expected, received, "BaseTokenBridge address is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetBridgehub.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetBridgehub.t.sol new file mode 100644 index 000000000000..2c14d8e385c3 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetBridgehub.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetBridgehubTest is GettersFacetTest { + function test() public { + address expected = makeAddr("bridgehub"); + gettersFacetWrapper.util_setBridgehub(expected); + + address received = gettersFacet.getBridgehub(); + + assertEq(expected, received, "Bridgehub address is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetFirstUnprocessedPriorityTx.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetFirstUnprocessedPriorityTx.t.sol new file mode 100644 index 000000000000..d14058fa9d2c --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetFirstUnprocessedPriorityTx.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetFirstUnprocessedPriorityTxTest is GettersFacetTest { + function test() public { + uint256 expected = 7865; + gettersFacetWrapper.util_setFirstUnprocessedPriorityTx(expected); + + uint256 received = gettersFacet.getFirstUnprocessedPriorityTx(); + + assertEq(expected, received, "First unprocessed priority tx is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2BootloaderBytecodeHash.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2BootloaderBytecodeHash.t.sol new file mode 100644 index 000000000000..a2e4a1a3a7dc --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2BootloaderBytecodeHash.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetL2BootloaderBytecodeHashTest is GettersFacetTest { + function test() public { + bytes32 expected = keccak256("L2 Bootloader Bytecode Hash"); + gettersFacetWrapper.util_setL2BootloaderBytecodeHash(expected); + + bytes32 received = gettersFacet.getL2BootloaderBytecodeHash(); + + assertEq(expected, received, "L2 Bootloader Bytecode Hash is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2DefaultAccountBytecodeHash.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2DefaultAccountBytecodeHash.t.sol new file mode 100644 index 000000000000..6b08a69cb98a --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2DefaultAccountBytecodeHash.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetL2DefaultAccountBytecodeHashTest is GettersFacetTest { + function test() public { + bytes32 expected = keccak256("L2 Default Account Bytecode Hash"); + gettersFacetWrapper.util_setL2DefaultAccountBytecodeHash(expected); + + bytes32 received = gettersFacet.getL2DefaultAccountBytecodeHash(); + + assertEq(expected, received, "L2 Default Account Bytecode Hash is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeBatchNumber.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeBatchNumber.t.sol new file mode 100644 index 000000000000..04a036b6e3f8 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeBatchNumber.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetL2SystemContractsUpgradeBatchNumberTest is GettersFacetTest { + function test() public { + uint256 expected = 56432; + gettersFacetWrapper.util_setL2SystemContractsUpgradeBatchNumber(expected); + + uint256 received = gettersFacet.getL2SystemContractsUpgradeBatchNumber(); + + assertEq(expected, received, "L2 System Contracts Upgrade Batch Number is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeBlockNumber.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeBlockNumber.t.sol new file mode 100644 index 000000000000..eb10bb679ee1 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeBlockNumber.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetL2SystemContractsUpgradeBlockNumberTest is GettersFacetTest { + function test() public { + uint256 expected = 56432; + gettersFacetWrapper.util_setL2SystemContractsUpgradeBatchNumber(expected); + + uint256 received = legacyGettersFacet.getL2SystemContractsUpgradeBlockNumber(); + + assertEq(expected, received, "L2 System Contracts Upgrade Batch Number is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeTxHash.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeTxHash.t.sol new file mode 100644 index 000000000000..ffdc9ab7dfcb --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeTxHash.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetL2SystemContractsUpgradeTxHashTest is GettersFacetTest { + function test() public { + bytes32 expected = keccak256("L2 System COntracts Upgrade Tx Hash"); + gettersFacetWrapper.util_setL2SystemContractsUpgradeTxHash(expected); + + bytes32 received = gettersFacet.getL2SystemContractsUpgradeTxHash(); + + assertEq(expected, received, "L2 System COntracts Upgrade Tx Hash"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetPendingAdmin.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetPendingAdmin.t.sol new file mode 100644 index 000000000000..7d1529e04bf9 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetPendingAdmin.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetPendingAdminTest is GettersFacetTest { + function test() public { + address expected = makeAddr("pendingAdmin"); + gettersFacetWrapper.util_setPendingAdmin(expected); + + address received = gettersFacet.getPendingAdmin(); + + assertEq(expected, received, "Pending governor address is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetPriorityQueueSize.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetPriorityQueueSize.t.sol new file mode 100644 index 000000000000..de0b22a3de74 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetPriorityQueueSize.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetPriorityQueueSizeTest is GettersFacetTest { + function test() public { + uint256 expected = 3456789; + gettersFacetWrapper.util_setPriorityQueueSize(expected); + + uint256 received = gettersFacet.getPriorityQueueSize(); + + assertEq(expected, received, "Priority queue size is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetPriorityTxMaxGasLimit.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetPriorityTxMaxGasLimit.t.sol new file mode 100644 index 000000000000..075781ef733d --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetPriorityTxMaxGasLimit.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetPriorityTxMaxGasLimitTest is GettersFacetTest { + function test() public { + uint256 expected = 3456789; + gettersFacetWrapper.util_setPriorityTxMaxGasLimit(expected); + + uint256 received = gettersFacet.getPriorityTxMaxGasLimit(); + + assertEq(expected, received, "Priority Tx Max Gas Limit is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetProtocolVersion.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetProtocolVersion.t.sol new file mode 100644 index 000000000000..924f1b874a1b --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetProtocolVersion.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetProtocolVersionTest is GettersFacetTest { + function test() public { + uint256 expected = 765456; + gettersFacetWrapper.util_setProtocolVersion(expected); + + uint256 received = gettersFacet.getProtocolVersion(); + + assertEq(expected, received, "Protocol version is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetStateTransitionManager.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetStateTransitionManager.t.sol new file mode 100644 index 000000000000..10cf67e270d9 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetStateTransitionManager.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetStateTransitionManagerTest is GettersFacetTest { + function test() public { + address expected = makeAddr("stateTranstionManager"); + gettersFacetWrapper.util_setStateTransitionManager(expected); + + address received = gettersFacet.getStateTransitionManager(); + + assertEq(expected, received, "StateTransitionManager address is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesCommitted.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesCommitted.t.sol new file mode 100644 index 000000000000..f644018cbc1c --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesCommitted.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetTotalBatchesCommittedTest is GettersFacetTest { + function test() public { + uint256 expected = 96544567876534567890; + gettersFacetWrapper.util_setTotalBatchesCommitted(expected); + + uint256 received = gettersFacet.getTotalBatchesCommitted(); + + assertEq(expected, received, "Total batches committed is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesExecuted.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesExecuted.t.sol new file mode 100644 index 000000000000..e87f93c72fdd --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesExecuted.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetTotalBatchesExecutedTest is GettersFacetTest { + function test() public { + uint256 expected = 4678097654; + gettersFacetWrapper.util_setTotalBatchesExecuted(expected); + + uint256 received = gettersFacet.getTotalBatchesExecuted(); + + assertEq(expected, received, "Total batches executed is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesVerified.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesVerified.t.sol new file mode 100644 index 000000000000..292670d5262b --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesVerified.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetTotalBatchesVerifiedTest is GettersFacetTest { + function test() public { + uint256 expected = 123456787654345789; + gettersFacetWrapper.util_setTotalBatchesVerified(expected); + + uint256 received = gettersFacet.getTotalBatchesVerified(); + + assertEq(expected, received, "Total batches verified is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksCommitted.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksCommitted.t.sol new file mode 100644 index 000000000000..9c131a8a1968 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksCommitted.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetTotalBlocksCommittedTest is GettersFacetTest { + function test() public { + uint256 expected = 96544567876534567890; + gettersFacetWrapper.util_setTotalBatchesCommitted(expected); + + uint256 received = legacyGettersFacet.getTotalBlocksCommitted(); + + assertEq(expected, received, "Total batches committed is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksExecuted.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksExecuted.t.sol new file mode 100644 index 000000000000..6bdf6df19b8a --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksExecuted.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetTotalBlocksExecutedTest is GettersFacetTest { + function test() public { + uint256 expected = 4678097654; + gettersFacetWrapper.util_setTotalBatchesExecuted(expected); + + uint256 received = legacyGettersFacet.getTotalBlocksExecuted(); + + assertEq(expected, received, "Total batches executed is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksVerified.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksVerified.t.sol new file mode 100644 index 000000000000..c6ed8d160723 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksVerified.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetTotalBlocksVerifiedTest is GettersFacetTest { + function test() public { + uint256 expected = 123456787654345789; + gettersFacetWrapper.util_setTotalBatchesVerified(expected); + + uint256 received = legacyGettersFacet.getTotalBlocksVerified(); + + assertEq(expected, received, "Total batches verified is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalPriorityTxs.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalPriorityTxs.t.sol new file mode 100644 index 000000000000..4ea98442b7a6 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalPriorityTxs.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetTotalPriorityTxsTest is GettersFacetTest { + function test() public { + uint256 expected = 345678333335; + gettersFacetWrapper.util_setTotalPriorityTxs(expected); + + uint256 received = gettersFacet.getTotalPriorityTxs(); + + assertEq(expected, received, "Total priority txs is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetVerifier.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetVerifier.t.sol new file mode 100644 index 000000000000..a1f364832491 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetVerifier.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetVerifierTest is GettersFacetTest { + function test() public { + address expected = makeAddr("verifier"); + gettersFacetWrapper.util_setVerifier(expected); + + address received = gettersFacet.getVerifier(); + + assertEq(expected, received, "Verifier address is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetVerifierParams.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetVerifierParams.t.sol new file mode 100644 index 000000000000..943cd0571f9a --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetVerifierParams.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; +import {VerifierParams} from "solpp/state-transition/chain-interfaces/IVerifier.sol"; + +contract GetVerifierParamsTest is GettersFacetTest { + function test() public { + VerifierParams memory expected = VerifierParams({ + recursionNodeLevelVkHash: keccak256("recursionNodeLevelVkHash"), + recursionLeafLevelVkHash: keccak256("recursionLeafLevelVkHash"), + recursionCircuitsSetVksHash: keccak256("recursionCircuitsSetVksHash") + }); + gettersFacetWrapper.util_setVerifierParams(expected); + + VerifierParams memory received = gettersFacet.getVerifierParams(); + + bytes32 expectedHash = keccak256(abi.encode(expected)); + bytes32 receivedHash = keccak256(abi.encode(received)); + assertEq(expectedHash, receivedHash, "Received Verifier Params is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsDiamondStorageFrozen.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsDiamondStorageFrozen.t.sol new file mode 100644 index 000000000000..40f5d373b0b2 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsDiamondStorageFrozen.t.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract IsDiamondStorageFrozenTest is GettersFacetTest { + function test() public { + gettersFacetWrapper.util_setIsDiamondStorageFrozen(true); + + bool received = gettersFacet.isDiamondStorageFrozen(); + + assertTrue(received, "Received DiamondStorageFrozen is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsEthWithdrawalFinalized.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsEthWithdrawalFinalized.t.sol new file mode 100644 index 000000000000..5047aed3d8fe --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsEthWithdrawalFinalized.t.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract IsEthWithdrawalFinalizedTest is GettersFacetTest { + function test() public { + uint256 l2BatchNumber = 123456789; + uint256 l2MessageIndex = 987654321; + gettersFacetWrapper.util_setIsEthWithdrawalFinalized(l2BatchNumber, l2MessageIndex, true); + + bool received = gettersFacet.isEthWithdrawalFinalized(l2BatchNumber, l2MessageIndex); + + assertTrue(received, "isEthWithdrawalFinalized is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsFacetFreezable.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsFacetFreezable.t.sol new file mode 100644 index 000000000000..cbbc6306346c --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsFacetFreezable.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract IsFacetFreezableTest is GettersFacetTest { + function test_noSelectors() public { + address facet = makeAddr("facet"); + bool received = gettersFacet.isFacetFreezable(facet); + + assertFalse(received, "Received isFacetFreezable is incorrect"); + } + + function test() public { + address facet = makeAddr("facet"); + gettersFacetWrapper.util_setIsFacetFreezable(facet, true); + + bool received = gettersFacet.isFacetFreezable(facet); + + assertTrue(received, "Received isFacetFreezable is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsFunctionFreezable.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsFunctionFreezable.t.sol new file mode 100644 index 000000000000..1f79b8f32a7d --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsFunctionFreezable.t.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract IsFunctionFreezableTest is GettersFacetTest { + function test_revertWhen_facetAddressIzZero() public { + bytes4 selector = bytes4(keccak256("asdfghfjtyhrewd")); + gettersFacetWrapper.util_setIsFunctionFreezable(selector, true); + + gettersFacetWrapper.util_setFacetAddress(selector, address(0)); + + vm.expectRevert(bytes.concat("g2")); + + gettersFacet.isFunctionFreezable(selector); + } + + function test() public { + bytes4 selector = bytes4(keccak256("asdfghfjtyhrewd")); + gettersFacetWrapper.util_setFacetAddress(selector, makeAddr("nonZeroAddress")); + gettersFacetWrapper.util_setIsFunctionFreezable(selector, true); + + bool received = gettersFacet.isFunctionFreezable(selector); + + assertTrue(received, "Received isFunctionFreezable is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsValidator.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsValidator.t.sol new file mode 100644 index 000000000000..d4ea133c96ae --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsValidator.t.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract IsValidatorTest is GettersFacetTest { + function test_validator() public { + address validator = makeAddr("validator"); + gettersFacetWrapper.util_setValidator(validator, true); + + bool received = gettersFacet.isValidator(validator); + + assertTrue(received, "Address should be validator"); + } + + function test_notValidator() public { + address validator = makeAddr("validator"); + gettersFacetWrapper.util_setValidator(validator, false); + + bool received = gettersFacet.isValidator(validator); + + assertFalse(received, "Address should not be validator"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/L2LogsRootHash.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/L2LogsRootHash.t.sol new file mode 100644 index 000000000000..8672025ac734 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/L2LogsRootHash.t.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract L2LogsRootHashTest is GettersFacetTest { + function test() public { + uint256 batchNumber = 42; + bytes32 expected = keccak256("L2 Logs Root Hash"); + gettersFacetWrapper.util_setL2LogsRootHash(batchNumber, expected); + + bytes32 received = gettersFacet.l2LogsRootHash(batchNumber); + + assertEq(expected, received, "L2 Logs Root Hash is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/PriorityQueueFrontOperation.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/PriorityQueueFrontOperation.t.sol new file mode 100644 index 000000000000..db08fc7c283a --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/PriorityQueueFrontOperation.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; +import {PriorityOperation} from "solpp/state-transition/libraries/PriorityQueue.sol"; + +contract GetPriorityQueueFrontOperationTest is GettersFacetTest { + function test_revertWhen_queueIsEmpty() public { + vm.expectRevert(bytes.concat("D")); + gettersFacet.priorityQueueFrontOperation(); + } + + function test() public { + PriorityOperation memory expected = PriorityOperation({ + canonicalTxHash: bytes32(uint256(1)), + expirationTimestamp: uint64(2), + layer2Tip: uint192(3) + }); + + gettersFacetWrapper.util_setPriorityQueueFrontOperation(expected); + + PriorityOperation memory received = gettersFacet.priorityQueueFrontOperation(); + + bytes32 expectedHash = keccak256(abi.encode(expected)); + bytes32 receivedHash = keccak256(abi.encode(received)); + assertEq(expectedHash, receivedHash, "Priority queue front operation is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/StoredBatchHash.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/StoredBatchHash.t.sol new file mode 100644 index 000000000000..d4ce37f27831 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/StoredBatchHash.t.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract StoredBatchHashTest is GettersFacetTest { + function test() public { + uint256 batchNumber = 42; + bytes32 expected = keccak256("Stored Batch Hash"); + gettersFacetWrapper.util_setStoredBatchHash(batchNumber, expected); + + bytes32 received = gettersFacet.storedBatchHash(batchNumber); + + assertEq(expected, received, "Stored Batch Hash is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/StoredBlockHash.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/StoredBlockHash.t.sol new file mode 100644 index 000000000000..c62b7dc8f796 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/StoredBlockHash.t.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract StoredBlockHashTest is GettersFacetTest { + function test() public { + uint256 batchNumber = 42; + bytes32 expected = keccak256("Stored Batch Hash"); + gettersFacetWrapper.util_setStoredBatchHash(batchNumber, expected); + + bytes32 received = legacyGettersFacet.storedBlockHash(batchNumber); + + assertEq(expected, received, "Stored Batch Hash is incorrect"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/_Getters_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/_Getters_Shared.t.sol new file mode 100644 index 000000000000..4d7cb5e47dc4 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/_Getters_Shared.t.sol @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {Diamond} from "solpp/state-transition/libraries/Diamond.sol"; +import {GettersFacet} from "solpp/state-transition/chain-deps/facets/Getters.sol"; +import {IGetters} from "solpp/state-transition/chain-interfaces/IGetters.sol"; +import {ILegacyGetters} from "solpp/state-transition/chain-interfaces/ILegacyGetters.sol"; +import {IVerifier} from "solpp/state-transition/chain-interfaces/IVerifier.sol"; +import {PriorityQueue, PriorityOperation} from "solpp/state-transition/libraries/PriorityQueue.sol"; +import {VerifierParams} from "solpp/state-transition/chain-interfaces/IVerifier.sol"; + +contract GettersFacetWrapper is GettersFacet { + function util_setVerifier(address _verifier) external { + s.verifier = IVerifier(_verifier); + } + + function util_setAdmin(address _admin) external { + s.admin = _admin; + } + + function util_setPendingAdmin(address _pendingAdmin) external { + s.pendingAdmin = _pendingAdmin; + } + + function util_setBridgehub(address _bridgehub) external { + s.bridgehub = _bridgehub; + } + + function util_setStateTransitionManager(address _stateTransitionManager) external { + s.stateTransitionManager = _stateTransitionManager; + } + + function util_setBaseToken(address _baseToken) external { + s.baseToken = _baseToken; + } + + function util_setBaseTokenBridge(address _baseTokenBridge) external { + s.baseTokenBridge = _baseTokenBridge; + } + + function util_setTotalBatchesCommitted(uint256 _totalBatchesCommitted) external { + s.totalBatchesCommitted = _totalBatchesCommitted; + } + + function util_setTotalBatchesVerified(uint256 _totalBatchesVerified) external { + s.totalBatchesVerified = _totalBatchesVerified; + } + + function util_setTotalBatchesExecuted(uint256 _totalBatchesExecuted) external { + s.totalBatchesExecuted = _totalBatchesExecuted; + } + + function util_setTotalPriorityTxs(uint256 _totalPriorityTxs) external { + s.priorityQueue.tail = _totalPriorityTxs; + } + + function util_setFirstUnprocessedPriorityTx(uint256 _firstUnprocessedPriorityTx) external { + s.priorityQueue.head = _firstUnprocessedPriorityTx; + } + + function util_setPriorityQueueSize(uint256 _priorityQueueSize) external { + s.priorityQueue.head = 0; + s.priorityQueue.tail = _priorityQueueSize; + } + + function util_setPriorityQueueFrontOperation(PriorityOperation memory _priorityQueueFrontOperation) external { + s.priorityQueue.data[s.priorityQueue.head] = _priorityQueueFrontOperation; + s.priorityQueue.tail = s.priorityQueue.head + 1; + } + + function util_setValidator(address _validator, bool _status) external { + s.validators[_validator] = _status; + } + + function util_setL2LogsRootHash(uint256 batchNumber, bytes32 _l2LogsRootHash) external { + s.l2LogsRootHashes[batchNumber] = _l2LogsRootHash; + } + + function util_setStoredBatchHash(uint256 batchNumber, bytes32 _storedBatchHash) external { + s.storedBatchHashes[batchNumber] = _storedBatchHash; + } + + function util_setL2BootloaderBytecodeHash(bytes32 _l2BootloaderBytecodeHash) external { + s.l2BootloaderBytecodeHash = _l2BootloaderBytecodeHash; + } + + function util_setL2DefaultAccountBytecodeHash(bytes32 _l2DefaultAccountBytecodeHash) external { + s.l2DefaultAccountBytecodeHash = _l2DefaultAccountBytecodeHash; + } + + function util_setVerifierParams(VerifierParams memory _verifierParams) external { + s.verifierParams = _verifierParams; + } + + function util_setProtocolVersion(uint256 _protocolVersion) external { + s.protocolVersion = _protocolVersion; + } + + function util_setL2SystemContractsUpgradeTxHash(bytes32 _l2SystemContractsUpgradeTxHash) external { + s.l2SystemContractsUpgradeTxHash = _l2SystemContractsUpgradeTxHash; + } + + function util_setL2SystemContractsUpgradeBatchNumber(uint256 _l2SystemContractsUpgradeBatchNumber) external { + s.l2SystemContractsUpgradeBatchNumber = _l2SystemContractsUpgradeBatchNumber; + } + + function util_setIsDiamondStorageFrozen(bool _isDiamondStorageFrozen) external { + Diamond.DiamondStorage storage ds = Diamond.getDiamondStorage(); + ds.isFrozen = _isDiamondStorageFrozen; + } + + function util_setIsFacetFreezable(address _facet, bool _isFacetFreezable) external { + Diamond.DiamondStorage storage ds = Diamond.getDiamondStorage(); + + ds.facetToSelectors[_facet].selectors = new bytes4[](1); + ds.facetToSelectors[_facet].selectors[0] = bytes4("1234"); + bytes4 selector0 = ds.facetToSelectors[_facet].selectors[0]; + + ds.selectorToFacet[selector0] = Diamond.SelectorToFacet({ + facetAddress: _facet, + selectorPosition: 0, + isFreezable: _isFacetFreezable + }); + } + + function util_setPriorityTxMaxGasLimit(uint256 _priorityTxMaxGasLimit) external { + s.priorityTxMaxGasLimit = _priorityTxMaxGasLimit; + } + + function util_setIsFunctionFreezable(bytes4 _selector, bool _isFreezable) external { + Diamond.DiamondStorage storage ds = Diamond.getDiamondStorage(); + + ds.selectorToFacet[_selector].isFreezable = _isFreezable; + } + + function util_setIsEthWithdrawalFinalized( + uint256 _l2Batchnumber, + uint256 _l2MessageIndex, + bool _isFinalized + ) external { + s.isEthWithdrawalFinalized[_l2Batchnumber][_l2MessageIndex] = _isFinalized; + } + + function util_setFacets(IGetters.Facet[] memory _facets) external { + Diamond.DiamondStorage storage ds = Diamond.getDiamondStorage(); + + ds.facets = new address[](_facets.length); + for (uint256 i = 0; i < ds.facets.length; i++) { + ds.facets[i] = _facets[i].addr; + ds.facetToSelectors[_facets[i].addr] = Diamond.FacetToSelectors({ + selectors: _facets[i].selectors, + facetPosition: uint16(i) + }); + } + } + + function util_setFacetFunctionSelectors(address _facet, bytes4[] memory _selectors) external { + Diamond.DiamondStorage storage ds = Diamond.getDiamondStorage(); + ds.facetToSelectors[_facet].selectors = _selectors; + } + + function util_setFacetAddresses(address[] memory _facets) external { + Diamond.DiamondStorage storage ds = Diamond.getDiamondStorage(); + ds.facets = _facets; + } + + function util_setFacetAddress(bytes4 _selector, address _facet) external { + Diamond.DiamondStorage storage ds = Diamond.getDiamondStorage(); + ds.selectorToFacet[_selector].facetAddress = _facet; + } +} + +contract GettersFacetTest is Test { + IGetters internal gettersFacet; + GettersFacetWrapper internal gettersFacetWrapper; + ILegacyGetters internal legacyGettersFacet; + + function setUp() public virtual { + gettersFacetWrapper = new GettersFacetWrapper(); + gettersFacet = IGetters(gettersFacetWrapper); + legacyGettersFacet = ILegacyGetters(gettersFacetWrapper); + } + + // add this to be excluded from coverage report + function testA() internal virtual {} +} diff --git a/l1-contracts/test/foundry/unit/concrete/Merkle/Merkle.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/Merkle/Merkle.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/Merkle/Merkle.t.sol rename to l1-contracts/test/foundry/unit/concrete/state-transition/libraries/Merkle/Merkle.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/Merkle/MerkleTreeNoSort.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/Merkle/MerkleTreeNoSort.sol similarity index 85% rename from l1-contracts/test/foundry/unit/concrete/Merkle/MerkleTreeNoSort.sol rename to l1-contracts/test/foundry/unit/concrete/state-transition/libraries/Merkle/MerkleTreeNoSort.sol index 3f4f64aa47a6..c62d4158ccd3 100644 --- a/l1-contracts/test/foundry/unit/concrete/Merkle/MerkleTreeNoSort.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/Merkle/MerkleTreeNoSort.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT + pragma solidity 0.8.20; import "murky/common/MurkyBase.sol"; @@ -16,4 +17,7 @@ contract MerkleTreeNoSort is MurkyBase { _hash := keccak256(0x0, 0x40) } } + + // add this to be excluded from coverage report + function test() internal virtual {} } diff --git a/l1-contracts/test/foundry/unit/concrete/PriorityQueue/OnEmptyQueue.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/PriorityQueue/OnEmptyQueue.sol similarity index 95% rename from l1-contracts/test/foundry/unit/concrete/PriorityQueue/OnEmptyQueue.sol rename to l1-contracts/test/foundry/unit/concrete/state-transition/libraries/PriorityQueue/OnEmptyQueue.sol index 98a0e3e22a82..231c0dd47eb6 100644 --- a/l1-contracts/test/foundry/unit/concrete/PriorityQueue/OnEmptyQueue.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/PriorityQueue/OnEmptyQueue.sol @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: MIT + pragma solidity 0.8.20; import {PriorityQueueSharedTest} from "./_PriorityQueue_Shared.t.sol"; diff --git a/l1-contracts/test/foundry/unit/concrete/PriorityQueue/PopOperations.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/PriorityQueue/PopOperations.sol similarity index 95% rename from l1-contracts/test/foundry/unit/concrete/PriorityQueue/PopOperations.sol rename to l1-contracts/test/foundry/unit/concrete/state-transition/libraries/PriorityQueue/PopOperations.sol index c5146838ecd6..739f2b7dc43d 100644 --- a/l1-contracts/test/foundry/unit/concrete/PriorityQueue/PopOperations.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/PriorityQueue/PopOperations.sol @@ -1,7 +1,9 @@ +// SPDX-License-Identifier: MIT + pragma solidity 0.8.20; import {PriorityQueueSharedTest} from "./_PriorityQueue_Shared.t.sol"; -import {PriorityOperation} from "../../../../../cache/solpp-generated-contracts/dev-contracts/test/PriorityQueueTest.sol"; +import {PriorityOperation} from "solpp/dev-contracts/test/PriorityQueueTest.sol"; contract PopOperationsTest is PriorityQueueSharedTest { uint public constant NUMBER_OPERATIONS = 10; diff --git a/l1-contracts/test/foundry/unit/concrete/PriorityQueue/PushOperations.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/PriorityQueue/PushOperations.sol similarity index 88% rename from l1-contracts/test/foundry/unit/concrete/PriorityQueue/PushOperations.sol rename to l1-contracts/test/foundry/unit/concrete/state-transition/libraries/PriorityQueue/PushOperations.sol index 0fb540d006d8..82fd052a50d5 100644 --- a/l1-contracts/test/foundry/unit/concrete/PriorityQueue/PushOperations.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/PriorityQueue/PushOperations.sol @@ -1,7 +1,10 @@ +// SPDX-License-Identifier: MIT + pragma solidity 0.8.20; import {PriorityQueueSharedTest} from "./_PriorityQueue_Shared.t.sol"; -import {PriorityOperation} from "../../../../../cache/solpp-generated-contracts/dev-contracts/test/PriorityQueueTest.sol"; + +import {PriorityOperation} from "solpp/dev-contracts/test/PriorityQueueTest.sol"; contract PushOperationsTest is PriorityQueueSharedTest { uint public constant NUMBER_OPERATIONS = 10; diff --git a/l1-contracts/test/foundry/unit/concrete/PriorityQueue/_PriorityQueue_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/PriorityQueue/_PriorityQueue_Shared.t.sol similarity index 89% rename from l1-contracts/test/foundry/unit/concrete/PriorityQueue/_PriorityQueue_Shared.t.sol rename to l1-contracts/test/foundry/unit/concrete/state-transition/libraries/PriorityQueue/_PriorityQueue_Shared.t.sol index a219aea1794c..4860bb7430fe 100644 --- a/l1-contracts/test/foundry/unit/concrete/PriorityQueue/_PriorityQueue_Shared.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/PriorityQueue/_PriorityQueue_Shared.t.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT + pragma solidity 0.8.20; import {Test} from "forge-std/Test.sol"; @@ -22,4 +23,7 @@ contract PriorityQueueSharedTest is Test { priorityQueue.pushBack(dummyOp); } } + + // add this to be excluded from coverage report + function test() internal virtual {} } diff --git a/l1-contracts/test/foundry/unit/concrete/TransactionValidator/ValidateL1L2Tx.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/TransactionValidator/ValidateL1L2Tx.t.sol similarity index 82% rename from l1-contracts/test/foundry/unit/concrete/TransactionValidator/ValidateL1L2Tx.t.sol rename to l1-contracts/test/foundry/unit/concrete/state-transition/libraries/TransactionValidator/ValidateL1L2Tx.t.sol index 2d357e79aa05..57a246ad2835 100644 --- a/l1-contracts/test/foundry/unit/concrete/TransactionValidator/ValidateL1L2Tx.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/TransactionValidator/ValidateL1L2Tx.t.sol @@ -1,18 +1,21 @@ +// SPDX-License-Identifier: MIT + pragma solidity 0.8.20; import {TransactionValidatorSharedTest} from "./_TransactionValidator_Shared.t.sol"; -import {IMailbox} from "solpp/zksync/interfaces/IMailbox.sol"; -import {TransactionValidator} from "solpp/zksync/libraries/TransactionValidator.sol"; + +import {L2CanonicalTransaction} from "solpp/common/Messaging.sol"; +import {TransactionValidator} from "solpp/state-transition/libraries/TransactionValidator.sol"; contract ValidateL1L2TxTest is TransactionValidatorSharedTest { function test_BasicRequestL1L2() public pure { - IMailbox.L2CanonicalTransaction memory testTx = createTestTransaction(); + L2CanonicalTransaction memory testTx = createTestTransaction(); testTx.gasLimit = 500000; validateL1ToL2Transaction(testTx, 500000, 100000); } function test_RevertWhen_GasLimitDoesntCoverOverhead() public { - IMailbox.L2CanonicalTransaction memory testTx = createTestTransaction(); + L2CanonicalTransaction memory testTx = createTestTransaction(); // The limit is so low, that it doesn't even cover the overhead testTx.gasLimit = 0; vm.expectRevert(bytes("my")); @@ -20,7 +23,7 @@ contract ValidateL1L2TxTest is TransactionValidatorSharedTest { } function test_RevertWhen_GasLimitHigherThanMax() public { - IMailbox.L2CanonicalTransaction memory testTx = createTestTransaction(); + L2CanonicalTransaction memory testTx = createTestTransaction(); // We should fail, if user asks for too much gas. // Notice, that we subtract the transaction overhead costs from the user's gas limit // before checking that it is below the max gas limit. @@ -31,7 +34,7 @@ contract ValidateL1L2TxTest is TransactionValidatorSharedTest { } function test_RevertWhen_TooMuchPubdata() public { - IMailbox.L2CanonicalTransaction memory testTx = createTestTransaction(); + L2CanonicalTransaction memory testTx = createTestTransaction(); // We should fail, if user's transaction could output too much pubdata. // We can allow only 99k of pubdata (otherwise we'd exceed the ethereum calldata limits). @@ -45,7 +48,7 @@ contract ValidateL1L2TxTest is TransactionValidatorSharedTest { } function test_RevertWhen_BelowMinimumCost() public { - IMailbox.L2CanonicalTransaction memory testTx = createTestTransaction(); + L2CanonicalTransaction memory testTx = createTestTransaction(); uint256 priorityTxMaxGasLimit = 500000; testTx.gasLimit = 200000; vm.expectRevert(bytes("up")); @@ -53,7 +56,7 @@ contract ValidateL1L2TxTest is TransactionValidatorSharedTest { } function test_RevertWhen_HugePubdata() public { - IMailbox.L2CanonicalTransaction memory testTx = createTestTransaction(); + L2CanonicalTransaction memory testTx = createTestTransaction(); uint256 priorityTxMaxGasLimit = 500000; testTx.gasLimit = 400000; // Setting huge pubdata limit should cause the panic. @@ -64,7 +67,7 @@ contract ValidateL1L2TxTest is TransactionValidatorSharedTest { function test_ShouldAllowLargeTransactions() public { // If the governance is fine with, the user can send a transaction with a huge gas limit. - IMailbox.L2CanonicalTransaction memory testTx = createTestTransaction(); + L2CanonicalTransaction memory testTx = createTestTransaction(); uint256 largeGasLimit = 2_000_000_000; diff --git a/l1-contracts/test/foundry/unit/concrete/TransactionValidator/ValidateUpgradeTransaction.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/TransactionValidator/ValidateUpgradeTransaction.t.sol similarity index 72% rename from l1-contracts/test/foundry/unit/concrete/TransactionValidator/ValidateUpgradeTransaction.t.sol rename to l1-contracts/test/foundry/unit/concrete/state-transition/libraries/TransactionValidator/ValidateUpgradeTransaction.t.sol index 14cd4c0eb745..d9a62ab94eed 100644 --- a/l1-contracts/test/foundry/unit/concrete/TransactionValidator/ValidateUpgradeTransaction.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/TransactionValidator/ValidateUpgradeTransaction.t.sol @@ -1,17 +1,19 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.8.20; import {TransactionValidatorSharedTest} from "./_TransactionValidator_Shared.t.sol"; -import {IMailbox} from "solpp/zksync/interfaces/IMailbox.sol"; -import {TransactionValidator} from "solpp/zksync/libraries/TransactionValidator.sol"; + +import {L2CanonicalTransaction} from "solpp/common/Messaging.sol"; +import {TransactionValidator} from "solpp/state-transition/libraries/TransactionValidator.sol"; contract ValidateUpgradeTxTest is TransactionValidatorSharedTest { function test_BasicRequest() public pure { - IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + L2CanonicalTransaction memory testTx = createUpgradeTransaction(); TransactionValidator.validateUpgradeTransaction(testTx); } function test_RevertWhen_RequestNotFromSystemContract() public { - IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + L2CanonicalTransaction memory testTx = createUpgradeTransaction(); // only system contracts (address < 2^16) are allowed to send upgrade transactions. testTx.from = uint256(1000000000); vm.expectRevert(bytes("ua")); @@ -19,7 +21,7 @@ contract ValidateUpgradeTxTest is TransactionValidatorSharedTest { } function test_RevertWhen_RequestNotToSystemContract() public { - IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + L2CanonicalTransaction memory testTx = createUpgradeTransaction(); // Now the 'to' address it too large. testTx.to = uint256(type(uint160).max) + 100; vm.expectRevert(bytes("ub")); @@ -27,7 +29,7 @@ contract ValidateUpgradeTxTest is TransactionValidatorSharedTest { } function test_RevertWhen_PaymasterIsNotZero() public { - IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + L2CanonicalTransaction memory testTx = createUpgradeTransaction(); // Paymaster must be 0 - otherwise we revert. testTx.paymaster = 1; vm.expectRevert(bytes("uc")); @@ -35,7 +37,7 @@ contract ValidateUpgradeTxTest is TransactionValidatorSharedTest { } function test_RevertWhen_ValueIsNotZero() public { - IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + L2CanonicalTransaction memory testTx = createUpgradeTransaction(); // Value must be 0 - otherwise we revert. testTx.value = 1; vm.expectRevert(bytes("ud")); @@ -43,7 +45,7 @@ contract ValidateUpgradeTxTest is TransactionValidatorSharedTest { } function test_RevertWhen_Reserved0IsNonZero() public { - IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + L2CanonicalTransaction memory testTx = createUpgradeTransaction(); // reserved 0 must be 0 - otherwise we revert. testTx.reserved[0] = 1; vm.expectRevert(bytes("ue")); @@ -51,7 +53,7 @@ contract ValidateUpgradeTxTest is TransactionValidatorSharedTest { } function test_RevertWhen_Reserved1IsTooLarge() public { - IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + L2CanonicalTransaction memory testTx = createUpgradeTransaction(); // reserved 1 must be a valid address testTx.reserved[1] = uint256(type(uint160).max) + 100; vm.expectRevert(bytes("uf")); @@ -59,7 +61,7 @@ contract ValidateUpgradeTxTest is TransactionValidatorSharedTest { } function test_RevertWhen_Reserved2IsNonZero() public { - IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + L2CanonicalTransaction memory testTx = createUpgradeTransaction(); // reserved 2 must be 0 - otherwise we revert. testTx.reserved[2] = 1; vm.expectRevert(bytes("ug")); @@ -67,7 +69,7 @@ contract ValidateUpgradeTxTest is TransactionValidatorSharedTest { } function test_RevertWhen_Reserved3IsNonZero() public { - IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + L2CanonicalTransaction memory testTx = createUpgradeTransaction(); // reserved 3 be 0 - otherwise we revert. testTx.reserved[3] = 1; vm.expectRevert(bytes("uo")); @@ -75,7 +77,7 @@ contract ValidateUpgradeTxTest is TransactionValidatorSharedTest { } function test_RevertWhen_NonZeroSignature() public { - IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + L2CanonicalTransaction memory testTx = createUpgradeTransaction(); // Signature must be 0 - otherwise we revert. testTx.signature = bytes("hello"); vm.expectRevert(bytes("uh")); @@ -83,15 +85,15 @@ contract ValidateUpgradeTxTest is TransactionValidatorSharedTest { } function test_RevertWhen_PaymasterInputNonZero() public { - IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + L2CanonicalTransaction memory testTx = createUpgradeTransaction(); // PaymasterInput must be 0 - otherwise we revert. testTx.paymasterInput = bytes("hi"); - vm.expectRevert(bytes("ul")); + vm.expectRevert(bytes("ul1")); TransactionValidator.validateUpgradeTransaction(testTx); } function test_RevertWhen_ReservedDynamicIsNonZero() public { - IMailbox.L2CanonicalTransaction memory testTx = createUpgradeTransaction(); + L2CanonicalTransaction memory testTx = createUpgradeTransaction(); // ReservedDynamic must be 0 - otherwise we revert. testTx.reservedDynamic = bytes("something"); vm.expectRevert(bytes("um")); diff --git a/l1-contracts/test/foundry/unit/concrete/TransactionValidator/_TransactionValidator_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/TransactionValidator/_TransactionValidator_Shared.t.sol similarity index 71% rename from l1-contracts/test/foundry/unit/concrete/TransactionValidator/_TransactionValidator_Shared.t.sol rename to l1-contracts/test/foundry/unit/concrete/state-transition/libraries/TransactionValidator/_TransactionValidator_Shared.t.sol index a301bf6651b7..fcb5376619cd 100644 --- a/l1-contracts/test/foundry/unit/concrete/TransactionValidator/_TransactionValidator_Shared.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/TransactionValidator/_TransactionValidator_Shared.t.sol @@ -1,16 +1,17 @@ // SPDX-License-Identifier: MIT + pragma solidity 0.8.20; import {Test} from "forge-std/Test.sol"; -import {IMailbox} from "solpp/zksync/interfaces/IMailbox.sol"; -//import {TransactionValidator} from "solpp/zksync/libraries/TransactionValidator.sol"; -import {TransactionValidator} from "cache/solpp-generated-contracts/zksync/libraries/TransactionValidator.sol"; + +import {L2CanonicalTransaction} from "solpp/common/Messaging.sol"; +import {TransactionValidator} from "solpp/state-transition/libraries/TransactionValidator.sol"; contract TransactionValidatorSharedTest is Test { constructor() {} - function createTestTransaction() public pure returns (IMailbox.L2CanonicalTransaction memory testTx) { - testTx = IMailbox.L2CanonicalTransaction({ + function createTestTransaction() public pure returns (L2CanonicalTransaction memory testTx) { + testTx = L2CanonicalTransaction({ txType: 0, from: uint256(uint160(1_000_000_000)), to: uint256(uint160(0)), @@ -30,14 +31,14 @@ contract TransactionValidatorSharedTest is Test { }); } - function createUpgradeTransaction() public pure returns (IMailbox.L2CanonicalTransaction memory testTx) { + function createUpgradeTransaction() public pure returns (L2CanonicalTransaction memory testTx) { testTx = createTestTransaction(); testTx.from = uint256(0x8001); testTx.to = uint256(0x8007); } function validateL1ToL2Transaction( - IMailbox.L2CanonicalTransaction memory _transaction, + L2CanonicalTransaction memory _transaction, uint256 _priorityTxMaxGasLimit, uint256 _priorityTxMaxPubdata ) public pure { @@ -52,4 +53,7 @@ contract TransactionValidatorSharedTest is Test { function getOverheadForTransaction(uint256 _encodingLength) public pure returns (uint256) { return TransactionValidator.getOverheadForTransaction(_encodingLength); } + + // add this to be excluded from coverage report + function test() internal virtual {} } diff --git a/l1-contracts/test/test_config/constant/addresses.json b/l1-contracts/test/test_config/constant/addresses.json new file mode 100644 index 000000000000..277f9a241d91 --- /dev/null +++ b/l1-contracts/test/test_config/constant/addresses.json @@ -0,0 +1,29 @@ +{ + "Bridgehub": { + "BridgehubProxy": "0x0000000000000000000000000000000000000000", + "BridgehubImplementation": "0x0000000000000000000000000000000000000000" + }, + "StateTransition": { + "StateTransitionProxy": "0x0000000000000000000000000000000000000000", + "StateTransitionImplementation": "0x0000000000000000000000000000000000000000", + "Verifier": "0x0000000000000000000000000000000000000000", + "GovernanceFacet": "0x0000000000000000000000000000000000000000", + "DiamondCutFacet": "0x0000000000000000000000000000000000000000", + "ExecutorFacet": "0x0000000000000000000000000000000000000000", + "GettersFacet": "0x0000000000000000000000000000000000000000", + "DiamondInit": "0x0000000000000000000000000000000000000000", + "DiamondUpgradeInit": "0x0000000000000000000000000000000000000000", + "DefaultUpgrade": "0x0000000000000000000000000000000000000000", + "DiamondProxy": "0x0000000000000000000000000000000000000000" + }, + "Bridges": { + "ERC20BridgeImplementation": "0x0000000000000000000000000000000000000000", + "ERC20BridgeProxy": "0x0000000000000000000000000000000000000000", + "WethBridgeImplementation": "0x0000000000000000000000000000000000000000", + "WethBridgeProxy": "0x0000000000000000000000000000000000000000" + }, + "TransparentProxyAdmin": "0x0000000000000000000000000000000000000000", + "AllowList": "0x0000000000000000000000000000000000000000", + "Create2Factory": "0x0000000000000000000000000000000000000000", + "ValidatorTimeLock": "0x0000000000000000000000000000000000000000" +} diff --git a/l1-contracts/test/test_config/constant/eth.json b/l1-contracts/test/test_config/constant/eth.json new file mode 100644 index 000000000000..b79f1d5fa4d9 --- /dev/null +++ b/l1-contracts/test/test_config/constant/eth.json @@ -0,0 +1,14 @@ +{ + "web3_url": "http://127.0.0.1:8545", + "test_mnemonic": "stuff slice staff easily soup parent arm payment cotton trade scatter struggle", + "test_mnemonic2": "pair glimpse monkey crisp eternal often favorite amused valley year prize inspire", + "test_mnemonic3": "jar coyote segment sign gate host device wonder mention hybrid spawn rifle", + "test_mnemonic4": "science rally wrestle destroy fly album manual virus cloud tortoise core adapt", + "test_mnemonic5": "process supply swift attack build mobile laundry keep regret just arrive pepper", + "test_mnemonic6": "age second sugar novel round mechanic settle garage peasant spell bus surprise", + "test_mnemonic7": "embody number total rice word lunch reject jungle merge siren elegant finger", + "test_mnemonic8": "race bring office flip tired subject region public about target effort aisle", + "test_mnemonic9": "regret bench until lesson fever two license garlic series rice odor tourist", + "test_mnemonic10": "misery lizard beach magic blue one genre damage excess police image become", + "mnemonic": "fine music test violin matrix prize squirrel panther purchase material script deal" +} diff --git a/l1-contracts/test/test_config/constant/hardhat.json b/l1-contracts/test/test_config/constant/hardhat.json new file mode 100644 index 000000000000..172451eb21fa --- /dev/null +++ b/l1-contracts/test/test_config/constant/hardhat.json @@ -0,0 +1,98 @@ +[ + { + "name": "DAI", + "symbol": "DAI", + "decimals": 18, + "address": "0x3c138Dd13EC52C1Ea7e73c8DD16EF6D0593bDcf3" + }, + { + "name": "wBTC", + "symbol": "wBTC", + "decimals": 8, + "address": "0x76258E6b9F71d45BCeAA0CC6514B910d7D8aC807" + }, + { + "name": "BAT", + "symbol": "BAT", + "decimals": 18, + "address": "0xc7fc371d3340337407bDfE48e2113085DD89FFe2" + }, + { + "name": "GNT", + "symbol": "GNT", + "decimals": 18, + "address": "0xAAFc93A3E06857a40d82fB61F038288dCDfcE7eF" + }, + { + "name": "MLTT", + "symbol": "MLTT", + "decimals": 18, + "address": "0x0F3934f734e62Daa87715B3E4640eFDB89246571" + }, + { + "name": "DAIK", + "symbol": "DAIK", + "decimals": 18, + "address": "0x421e18B8c90Fe0e4AA6f049d6D5fdbA3093b698C" + }, + { + "name": "wBTCK", + "symbol": "wBTCK", + "decimals": 8, + "address": "0x94f0779546Ed463dABBa2f71aB10410e9C8A839D" + }, + { + "name": "BATK", + "symbol": "BATS", + "decimals": 18, + "address": "0xE3D1be6c892D4f7b09c3940b67741104c2FFB34b" + }, + { + "name": "GNTK", + "symbol": "GNTS", + "decimals": 18, + "address": "0x632655228439172CC496A17c0ceA170917FebE18" + }, + { + "name": "MLTTK", + "symbol": "MLTTS", + "decimals": 18, + "address": "0x211e762c89e588AfB3ec50b0017Cd5250bBb111A" + }, + { + "name": "DAIL", + "symbol": "DAIL", + "decimals": 18, + "address": "0xEFc9af2B75706c223C1Af2e4dEaBDfeE46f8Bf85" + }, + { + "name": "wBTCL", + "symbol": "wBTCP", + "decimals": 8, + "address": "0x41a0577764aDdc7B86149884324e1E53A5EE82C6" + }, + { + "name": "BATL", + "symbol": "BATW", + "decimals": 18, + "address": "0xc21042CA11b84072229b91750805Fd611290ecF8" + }, + { + "name": "GNTL", + "symbol": "GNTW", + "decimals": 18, + "address": "0xe1630b33Dcad7d2376FDA96cd19F2A3A633E60ce" + }, + { + "name": "MLTTL", + "symbol": "MLTTW", + "decimals": 18, + "address": "0xE982e1ff2d8972B09aCF7728E8b5e23149bd74eb" + }, + { + "name": "Wrapped Ether", + "symbol": "WETH", + "decimals": 18, + "address": "0x6ca4A813AcAcfF0CA91746a759D0ffD4420eF160" + } +] diff --git a/l1-contracts/test/unit_tests/README.md b/l1-contracts/test/unit_tests/README.md new file mode 100644 index 000000000000..dd98a6f4bcb8 --- /dev/null +++ b/l1-contracts/test/unit_tests/README.md @@ -0,0 +1,4 @@ +# Tips + +- When testing, when using initialDeployment make deployer have verbose:true, this will print out deployment logs. +- diff --git a/l1-contracts/test/unit_tests/custom_base_token.spec.ts b/l1-contracts/test/unit_tests/custom_base_token.spec.ts new file mode 100644 index 000000000000..b0e9733a387c --- /dev/null +++ b/l1-contracts/test/unit_tests/custom_base_token.spec.ts @@ -0,0 +1,159 @@ +import { expect } from "chai"; +import * as hardhat from "hardhat"; +import { ethers, Wallet } from "ethers"; +import { Interface } from "ethers/lib/utils"; + +import type { TestnetERC20Token } from "../../typechain"; +import { TestnetERC20TokenFactory } from "../../typechain"; +import type { IBridgehub } from "../../typechain/IBridgehub"; +import { IBridgehubFactory } from "../../typechain/IBridgehubFactory"; +import type { IL1SharedBridge } from "../../typechain/IL1SharedBridge"; +import { IL1SharedBridgeFactory } from "../../typechain/IL1SharedBridgeFactory"; + +import { getTokens } from "../../src.ts/deploy-token"; +import type { Deployer } from "../../src.ts/deploy"; +import { ADDRESS_ONE, ethTestConfig } from "../../src.ts/utils"; +import { initialTestnetDeploymentProcess } from "../../src.ts/deploy-test-process"; + +import { getCallRevertReason, REQUIRED_L2_GAS_PRICE_PER_PUBDATA } from "./utils"; + +describe("Custom base token chain and bridge tests", () => { + let owner: ethers.Signer; + let randomSigner: ethers.Signer; + let deployWallet: Wallet; + let deployer: Deployer; + let l1SharedBridge: IL1SharedBridge; + let bridgehub: IBridgehub; + let baseToken: TestnetERC20Token; + let baseTokenAddress: string; + let altTokenAddress: string; + let altToken: TestnetERC20Token; + let chainId = process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID ? parseInt(process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID) : 270; + + before(async () => { + [owner, randomSigner] = await hardhat.ethers.getSigners(); + + deployWallet = Wallet.fromMnemonic(ethTestConfig.test_mnemonic4, "m/44'/60'/0'/0/1").connect(owner.provider); + const ownerAddress = await deployWallet.getAddress(); + + const gasPrice = await owner.provider.getGasPrice(); + + const tx = { + from: owner.getAddress(), + to: deployWallet.address, + value: ethers.utils.parseEther("1000"), + nonce: owner.getTransactionCount(), + gasLimit: 100000, + gasPrice: gasPrice, + }; + + await owner.sendTransaction(tx); + // note we can use initialDeployment so we don't go into deployment details here + deployer = await initialTestnetDeploymentProcess(deployWallet, ownerAddress, gasPrice, [], "BAT"); + chainId = deployer.chainId; + bridgehub = IBridgehubFactory.connect(deployer.addresses.Bridgehub.BridgehubProxy, deployWallet); + + const tokens = getTokens(); + baseTokenAddress = tokens.find((token: { symbol: string }) => token.symbol == "BAT")!.address; + baseToken = TestnetERC20TokenFactory.connect(baseTokenAddress, owner); + + altTokenAddress = tokens.find((token: { symbol: string }) => token.symbol == "DAI")!.address; + altToken = TestnetERC20TokenFactory.connect(altTokenAddress, owner); + + // prepare the bridge + l1SharedBridge = IL1SharedBridgeFactory.connect(deployer.addresses.Bridges.SharedBridgeProxy, deployWallet); + }); + + it("Should have correct base token", async () => { + // we should still be able to deploy the erc20 bridge + const baseTokenAddressInBridgehub = await bridgehub.baseToken(chainId); + expect(baseTokenAddress).equal(baseTokenAddressInBridgehub); + }); + + it("Check should initialize through governance", async () => { + const l1SharedBridgeInterface = new Interface(hardhat.artifacts.readArtifactSync("L1SharedBridge").abi); + const upgradeCall = l1SharedBridgeInterface.encodeFunctionData("initializeChainGovernance(uint256,address)", [ + chainId, + ADDRESS_ONE, + ]); + + const txHash = await deployer.executeUpgrade(l1SharedBridge.address, 0, upgradeCall); + + expect(txHash).not.equal(ethers.constants.HashZero); + }); + + it("Should not allow direct legacy deposits", async () => { + const revertReason = await getCallRevertReason( + l1SharedBridge + .connect(randomSigner) + .depositLegacyErc20Bridge( + await randomSigner.getAddress(), + await randomSigner.getAddress(), + baseTokenAddress, + 0, + 0, + 0, + ethers.constants.AddressZero + ) + ); + + expect(revertReason).equal("ShB not legacy bridge"); + }); + + it("Should deposit base token successfully direct via bridgehub", async () => { + await baseToken.connect(randomSigner).mint(await randomSigner.getAddress(), ethers.utils.parseUnits("800", 18)); + await ( + await baseToken.connect(randomSigner).approve(l1SharedBridge.address, ethers.utils.parseUnits("800", 18)) + ).wait(); + await bridgehub.connect(randomSigner).requestL2TransactionDirect({ + chainId, + l2Contract: await randomSigner.getAddress(), + mintValue: ethers.utils.parseUnits("800", 18), + l2Value: 1, + l2Calldata: "0x", + l2GasLimit: 10000000, + l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + factoryDeps: [], + refundRecipient: await randomSigner.getAddress(), + }); + }); + + it("Should deposit alternative token successfully twoBridges method", async () => { + const altTokenAmount = ethers.utils.parseUnits("800", 18); + const baseTokenAmount = ethers.utils.parseUnits("800", 18); + + await altToken.connect(randomSigner).mint(await randomSigner.getAddress(), altTokenAmount); + await (await altToken.connect(randomSigner).approve(l1SharedBridge.address, altTokenAmount)).wait(); + + await baseToken.connect(randomSigner).mint(await randomSigner.getAddress(), baseTokenAmount); + await (await baseToken.connect(randomSigner).approve(l1SharedBridge.address, baseTokenAmount)).wait(); + await bridgehub.connect(randomSigner).requestL2TransactionTwoBridges({ + chainId, + mintValue: baseTokenAmount, + l2Value: 1, + l2GasLimit: 10000000, + l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + refundRecipient: await randomSigner.getAddress(), + secondBridgeAddress: l1SharedBridge.address, + secondBridgeValue: 0, + secondBridgeCalldata: ethers.utils.defaultAbiCoder.encode( + ["address", "uint256", "address"], + [altTokenAddress, altTokenAmount, await randomSigner.getAddress()] + ), + }); + }); + + it("Should revert on finalizing a withdrawal with wrong message length", async () => { + const revertReason = await getCallRevertReason( + l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, "0x", []) + ); + expect(revertReason).equal("ShB wrong msg len"); + }); + + it("Should revert on finalizing a withdrawal with wrong function selector", async () => { + const revertReason = await getCallRevertReason( + l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, ethers.utils.randomBytes(96), []) + ); + expect(revertReason).equal("ShB Incorrect message function selector"); + }); +}); diff --git a/l1-contracts/test/unit_tests/erc20-bridge-upgrade.fork.ts b/l1-contracts/test/unit_tests/erc20-bridge-upgrade.fork.ts index 4559f835a7c0..b968b68da049 100644 --- a/l1-contracts/test/unit_tests/erc20-bridge-upgrade.fork.ts +++ b/l1-contracts/test/unit_tests/erc20-bridge-upgrade.fork.ts @@ -1,10 +1,9 @@ import { expect } from "chai"; import * as hardhat from "hardhat"; - import type { ethers } from "ethers"; + import type { L1ERC20Bridge } from "../../typechain"; import { L1ERC20BridgeTestFactory } from "../../typechain"; - import type { ITransparentUpgradeableProxy } from "../../typechain/ITransparentUpgradeableProxy"; import { ITransparentUpgradeableProxyFactory } from "../../typechain/ITransparentUpgradeableProxyFactory"; diff --git a/l1-contracts/test/unit_tests/executor_proof.spec.ts b/l1-contracts/test/unit_tests/executor_proof.spec.ts index c4992d7bfdcc..01ac20fc70ae 100644 --- a/l1-contracts/test/unit_tests/executor_proof.spec.ts +++ b/l1-contracts/test/unit_tests/executor_proof.spec.ts @@ -2,6 +2,7 @@ import * as hardhat from "hardhat"; import { expect } from "chai"; import type { ExecutorProvingTest } from "../../typechain"; import { ExecutorProvingTestFactory } from "../../typechain"; +// import { PubdataPricingMode } from "../../src.ts/utils"; describe("Executor test", function () { let executor: ExecutorProvingTest; @@ -13,59 +14,110 @@ describe("Executor test", function () { }); /// This test is based on a block generated in a local system. - it("Test hashes", async () => { + it("Test hashes (Rollup)", async () => { const bootloaderHash = "0x010009416e909e0819593a9806bbc841d25c5cdfed3f4a1523497c6814e5194a"; const aaHash = "0x0100065d134a862a777e50059f5e0fbe68b583f3617a67820f7edda0d7f253a0"; const setResult = await executor.setHashes(aaHash, bootloaderHash); const finish = await setResult.wait(); expect(finish.status == 1); - const nextBatch = { - // ignored - batchNumber: 1, - // ignored - timestamp: 100, - indexRepeatedStorageChanges: 84, - newStateRoot: "0x9cf7bb72401a56039ca097cabed20a72221c944ed9b0e515c083c04663ab45a6", - // ignored - numberOfLayer1Txs: 10, - // ignored - priorityOperationsHash: "0x167f4ca80269c9520ad951eeeda28dd3deb0715e9e2917461e81a60120a14183", - bootloaderHeapInitialContentsHash: "0x540442e48142fa061a81822184f7790e7b69dea92153d38ef623802c6f0411c0", - eventsQueueStateHash: "0xda42ab7994d4695a25f4ea8a9a485a592b7a31c20d5dae6363828de86d8826ea", - systemLogs: - "0x00000000000000000000000000000000000000000000800b000000000000000000000000000000000000000000000000000000000000000416914ac26bb9cafa0f1dfaeaab10745a9094e1b60c7076fedf21651d6a25b5740000000a000000000000000000000000000000000000800b0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000651bcde0000000000000000000000000651bcde20001000a00000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000005167f4ca80269c9520ad951eeeda28dd3deb0715e9e2917461e81a60120a141830001000a00000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0001000a00000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000000ee6ee8f50659bd8be3d86c32efb02baa5571cf3b46dd7ea3db733ae181747b8b0001000a0000000000000000000000000000000000008008000000000000000000000000000000000000000000000000000000000000000160fc5fb513ca8e6f6232a7410797954dcb6edbf9081768da24b483aca91c54db0001000a000000000000000000000000000000000000800800000000000000000000000000000000000000000000000000000000000000029a67073c2df8f53087fcfc32d82c98bba591da35df6ce1fb55a23b677d37f9fc0000000000000000000000000000000000000000000080110000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000801100000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000", - pubdataCommitments: - "0x000000000a000100000000000000000000000000000000000000008001760f6100ddbd86c4d5a58532923e7424d33ffb44145a26171d9b2595a349450b0000000000000000000000000000000000000000000000000000000000000001000100010000000000000000000000000000000000008001a789fe4e2a955eee45d44f408f86203c8f643910bf4888d1fd1465cdbc6376d800000000000000000000000000000000000000000000000000000000000000010001000200000000000000000000000000000000000080016ba43e7c7df11e5a655f22c9bce1b37434afd2bf8fcdb10100a460e6a2c0cc83000000000000000000000000000000000000000000000000000000000000000100010003000000000000000000000000000000000000800156e569838658c17c756aa9f6e40de8f1c41b1a67fea5214ec47869882ecda9bd0000000000000000000000000000000000000000000000000000000000000001000100040000000000000000000000000000000000008001ab5d064ba75c02635fd6e4de7fd8420eda54c4bda05bd61edabe201f2066d38f00000000000000000000000000000000000000000000000000000000000000010001000500000000000000000000000000000000000080015bcb6d7c735023e0884297db5016a6c704e3490ed0671417639313ecea86795b00000000000000000000000000000000000000000000000000000000000000010001000600000000000000000000000000000000000080015ee51b5b7d47fae5811a9f777174bb08d81d78098c8bd9430a7618756a0ceb8b00000000000000000000000000000000000000000000000000000000000000010001000700000000000000000000000000000000000080011ea63171021b9ab0846efbe0a06f7882d76e24a4900c74c14fa1e0bdf313ed560000000000000000000000000000000000000000000000000000000000000001000100080000000000000000000000000000000000008001574537f1665cd9c894d8d9834d32ed291f49ae1165a0e12a79a4937f2425bf70000000000000000000000000000000000000000000000000000000000000000100010009000000000000000000000000000000000000800190558033c8a3f7c20c81e613e00a9d0e678a7a14923e94e7cb99c8621c7918090000000000000000000000000000000000000000000000000000000000000001000000000000000001000c3104003d1291725c657fe486d0e626f562842175a705a9704c0980b40e3d716b95bbf9e8000100005dd96deb789fbc05264165795bf652190645bfae1ce253ce1db17087a898fb1e240ebf0d53563011198fddab33312923ba20f3c56cf1ba18ca5be9c053000100022bd65a924da61271d1dd5080fc640601185125830805e0ceb42f4185e5118fb454a12a3d9e0c1fbb89230f67044cc191e4f18459261233f659c9e2ba5e000100008b9feb52993729436da78b2863dd56d8d757e19c01a2cdcf1940e45ca9979941fa93f5f699afeab75e8b25cfea22004a8d2ea49f057741c2f2b910996d00010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4afee3cea48e96b9bddb544b4569e60736a1f1fe919e223fcc08f74acf3513be1200010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4a8755061217b6a78f5d5f8af6e326e482ebdc57f7144108662d122252ddcc27e7000100045dddc527887dc39b9cd189d6f183f16217393a5d3d3165fead2daeaf4f2d6916280c572561a809555de4a87d7a56d5bcca2c246a389dbb2a24c5639bdb0001000153c0f36532563ba2a10f52b865e558cd1a5eef9a9edd01c1cb23b74aa772beb4f3e3b784609f4e205a09863c0587e63b4b47664022cb34896a1711416b00010003e7842b0b4f4fd8e665883fe9c158ba8d38347840f1da0a75aca1fc284ce2428454b48df9f5551500fc50b63af4741b1cd21d4cfddc69aa46cb78eff45b00010000f183703a165afed04326ad5786316f6fc65b27f1cf17459a52bd1f57f27f896b7429e070ca76e3e33165ec75f6c9f439ee37f3b58822494b1251c8247500010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4a05ea3d0bb218598c42b2e25ae5f6cbc9369b273ee6610450cade89775646b2a08902000000000000000000000000000000008b71d4a184058d07fccac4348ae02a1f663403231b0a40fa2c8c0ff73bdca092890200000000000000000000000000000000ab63c4cebbd508a7d7184f0b9134453eea7a09ca749610d5576f8046241b9cde890200000000000000000000000000000000e58af14be53d8ac56f58ff3e5b07c239bfb549149f067597e9d028f35e3c2b77890200000000000000000000000000000000b78e94980fec3a5f68aa25d0d934084907688e537e82c2942af905aab21413ab890200000000000000000000000000000000c4db460819691e825328b532024bbecdc40394c74307a00bd245fc658b1bd34f0901908827f2052a14b24a10cae1f9e259ead06a89a1d74ff736a54f54ebcf05eeb30901d32d07305b87debd25698d4dfac4c2f986693a4e9d9baff7da37a7b5ca8d01cb0901e73042e5dacff2ce20a720c9c6d694576e4afa7bbbafdc4d409c63b7ca8027b70901760a7405795441aceea3be649a53d02785cb3487b7bd23e3b4888a935cee010d09011f3acf5d6d7bfeab8a7112771866e28c3714e0c315a81ec6a58ab4ad1c3d6eb10901c207b49d14deb3af9bc960d57074e27386285c73248abc5fa1d72aa6e8664fa40901644f0c4e15446d7e5ff363c944b55bd6801a1f38afd984c3427569530cb663210901743be0243628b8e7e8f04c00fc4f88efae001250a7482b31e6a0ec87ee3598e7090171e91721f9918576d760f02f03cac47c6f4003316031848e3c1d99e6e83a47434102d84e69f2f480002d5a6962cccee5d4adb48a36bbbf443a531721484381125937f3001ac5ff875b41022f496efbbdb2007b727eb806c926fb20c8ad087c57422977cebd06373e26d19b640e5fe32c85ff39a904faf736ce00a25420c1d9d705358c134cc601d9d184cb4dfdde7e1cac2bc3d4d38bf9ec44e6210ee6b280123bafc586f77764488cd24c6a77546e5a0fe8bdfb4fa203cfaffc36cce4dd5b8901000000000000000000000000651bcde08e7dd06ac5b73b473be6bc5a51030f4c7437657cb7b29bf376c564b8d1675a5e8903000000000000000000000000651bcde24ba84e1f37d041bc6e55ba396826cc494e84d4815b6db52690422eea7386314f00e8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244c3de2202ccb626ad387d70722e64fbe44562e2f231a290c08532b8d6aba402ff50025fe002039e87b424de2772b82d935f14e2b657429a1bcc04612391ea0330c90ebddefdda48eb2aa7f66ecf7940a280e9ef3fb2e95db0995538440a62af79861004434720529e816fd2e40f8031a8d7471ebcd00351db0787346bcfe8dfad8d2b479093588d0e847efa73a10ce20e4799fb1e46642d65617c7e5213fa04989d92d8903000000000000000000000000651bcde287ded247e1660f827071c7f1371934589751085384fc9f4462c1f1897c5c3eef890100000000000000000000000000000001911dd2ad743ff237d411648a0fe32c6d74eec060716a2a74352f6b1c435b5d670016914ac26bb9cafa0f1dfaeaab10745a9094e1b60c7076fedf21651d6a25b574686a068c708f1bdbefd9e6e454ac2b520fd41c8dcf23ecd4cee978c22f1c1f5f09ff974fe8b575175cefa919a5ba1c0ddf4409be4b16695dc7bd12f6701b99bd2e70a152312ad6f01657413b2eae9287f6b9adad93d5fed1a0dd5e13ec74ce1163146509bfe426f2315a69cb452bf388cccd321eca2746a1adf793b489e5c8f61c40688b7ef3e53defc56c78facf513e511f9f5ba0eb50dbcc745afea3b860da75b394d2d1627b6e2ef54fb7b187d0af61e4532c238f387ecf9f0b466f1d54414100018e519b65c8901b344a480638beadb923fbd3462e475d39acebe559d65ed5cb11a1b25279f1918477c35eec1332ff07001d3f85cf854b70d7552f93ba8e88d581064ca4c0df6ac456c00a0e83898ccd464c63e5008aa1a498cc0646b78eb216d9eeeec76ed0eb0ee6c352f35ca5f0b2edc2ca17d211cc5cb905ba10142f042a6ac836d9cef9a6916635c9a1c1d2dc62a9fe83e2230b506b98e0fded46249008fe28b813907a05ae0d773d8f31e330200e9336e0159034c137ed645fb67ccca8a152312ad6f01657413b2eae9287f6b9adad93d5fee5d8f810abde496ccbeb45a4f3c06af828975163a006257cbf18cefebbfb4cd409025f40404a3d37bba024799ce32d7c2a833aec8474288a26b246afa32b07b4a3ce00577261707065642045746865720000000000000000000000000000000000001a09cf14f266dfe87c4b33e6d934de01f8f7242199fa8783178117218fa033f7ab005745544800000000000000000000000000000000000000000000000000000008289026c5fa173652bd62774824698a6848c63031f853d0e275174552f35df33000577261707065642045746865720000000000000000000000000000000000001a1e59309944cbc900ae848855e10bc929f78e86c2179d6e96cf52bfd520f039200031000000000000000000000000000000000000000000000000000000000000021653a735395136e5494c5426ba972b45e34d36ebcb86ac104c724ab375fcce90a18580ba6aeebc6e6b89d226c79be8927257a436ad11d9c0305b18e9d78cab8f75a3aec2096302b67e3815939e29476fb36a0d8299a1b25279f1918477c35eec1332ff07001d3f85cf85688525f98e4859a9c6939f2d2f92e6b1950ed57e56137d717aca1ccf9754f719a1c7ebe9226d26524400a8959a08f411a727ae7bb68f8febecd89ffe9d84708d24544d452de3e22e62b3b2b872e430839a15115818a152312ad6f01657413b2eae9287f6b9adad93d5fe3fb60af355125687beeb90c066ace76c442b0f963a6afd0e3316fcdd673ad22c09ff30c8a03ec44e5337a1f9d66763cf1b319fdc6d8bc4981e1f47edbd86210614b909ff0cbdceb634b81192417b64d114d535ad3bdba97d6d7e90ee2a79bf1c132d3c2d09ff5cd85060f4ff26eb5b68a6687aee76c1b7a77575fdc86ba49b4faf5041377a79b14de8989f2385a6e23f6bd05a80e0d9231870c15a000142e50adc0d84bff439d0086d9fbab9984f8b27aa208935238a60cc62e7c9bb2ea1709e94c96366b3c40ea4854837c18733e5ac1193b8d8e4070d2eca4441b0378b572bd949ab764fd71c002b759613c3e29d425cf4000100012730c940a81021004e899c6ee4bec02f0667757b9d75a8f0714ce6c157f5940b7664e4f69f01fc530db36965e33599a1348629f07ae2d724007ac36a71a16baac84db583d88e0f3a8c082e3632fcc0e15757f0dcf5234b87af41fdee4c0999c4fe698a8d824415979ab839e6913a975a3055a152312ad6f01657413b2eae9287f6b9adad93d5fe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - }; + // const nextBatch = { + // // ignored + // batchNumber: 1, + // // ignored + // timestamp: 100, + // indexRepeatedStorageChanges: 84, + // newStateRoot: "0x9cf7bb72401a56039ca097cabed20a72221c944ed9b0e515c083c04663ab45a6", + // // ignored + // numberOfLayer1Txs: 10, + // // ignored + // priorityOperationsHash: "0x167f4ca80269c9520ad951eeeda28dd3deb0715e9e2917461e81a60120a14183", + // bootloaderHeapInitialContentsHash: "0x540442e48142fa061a81822184f7790e7b69dea92153d38ef623802c6f0411c0", + // eventsQueueStateHash: "0xda42ab7994d4695a25f4ea8a9a485a592b7a31c20d5dae6363828de86d8826ea", + // systemLogs: + // "0x00000000000000000000000000000000000000000000800b000000000000000000000000000000000000000000000000000000000000000416914ac26bb9cafa0f1dfaeaab10745a9094e1b60c7076fedf21651d6a25b5740000000a000000000000000000000000000000000000800b0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000651bcde0000000000000000000000000651bcde20001000a00000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000005167f4ca80269c9520ad951eeeda28dd3deb0715e9e2917461e81a60120a141830001000a00000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0001000a00000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000000ee6ee8f50659bd8be3d86c32efb02baa5571cf3b46dd7ea3db733ae181747b8b0001000a0000000000000000000000000000000000008008000000000000000000000000000000000000000000000000000000000000000160fc5fb513ca8e6f6232a7410797954dcb6edbf9081768da24b483aca91c54db0001000a000000000000000000000000000000000000800800000000000000000000000000000000000000000000000000000000000000029a67073c2df8f53087fcfc32d82c98bba591da35df6ce1fb55a23b677d37f9fc0000000000000000000000000000000000000000000080110000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000801100000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000", + // pubdataCommitments: + // "0x000000000a000100000000000000000000000000000000000000008001760f6100ddbd86c4d5a58532923e7424d33ffb44145a26171d9b2595a349450b0000000000000000000000000000000000000000000000000000000000000001000100010000000000000000000000000000000000008001a789fe4e2a955eee45d44f408f86203c8f643910bf4888d1fd1465cdbc6376d800000000000000000000000000000000000000000000000000000000000000010001000200000000000000000000000000000000000080016ba43e7c7df11e5a655f22c9bce1b37434afd2bf8fcdb10100a460e6a2c0cc83000000000000000000000000000000000000000000000000000000000000000100010003000000000000000000000000000000000000800156e569838658c17c756aa9f6e40de8f1c41b1a67fea5214ec47869882ecda9bd0000000000000000000000000000000000000000000000000000000000000001000100040000000000000000000000000000000000008001ab5d064ba75c02635fd6e4de7fd8420eda54c4bda05bd61edabe201f2066d38f00000000000000000000000000000000000000000000000000000000000000010001000500000000000000000000000000000000000080015bcb6d7c735023e0884297db5016a6c704e3490ed0671417639313ecea86795b00000000000000000000000000000000000000000000000000000000000000010001000600000000000000000000000000000000000080015ee51b5b7d47fae5811a9f777174bb08d81d78098c8bd9430a7618756a0ceb8b00000000000000000000000000000000000000000000000000000000000000010001000700000000000000000000000000000000000080011ea63171021b9ab0846efbe0a06f7882d76e24a4900c74c14fa1e0bdf313ed560000000000000000000000000000000000000000000000000000000000000001000100080000000000000000000000000000000000008001574537f1665cd9c894d8d9834d32ed291f49ae1165a0e12a79a4937f2425bf70000000000000000000000000000000000000000000000000000000000000000100010009000000000000000000000000000000000000800190558033c8a3f7c20c81e613e00a9d0e678a7a14923e94e7cb99c8621c7918090000000000000000000000000000000000000000000000000000000000000001000000000000000001000c3104003d1291725c657fe486d0e626f562842175a705a9704c0980b40e3d716b95bbf9e8000100005dd96deb789fbc05264165795bf652190645bfae1ce253ce1db17087a898fb1e240ebf0d53563011198fddab33312923ba20f3c56cf1ba18ca5be9c053000100022bd65a924da61271d1dd5080fc640601185125830805e0ceb42f4185e5118fb454a12a3d9e0c1fbb89230f67044cc191e4f18459261233f659c9e2ba5e000100008b9feb52993729436da78b2863dd56d8d757e19c01a2cdcf1940e45ca9979941fa93f5f699afeab75e8b25cfea22004a8d2ea49f057741c2f2b910996d00010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4afee3cea48e96b9bddb544b4569e60736a1f1fe919e223fcc08f74acf3513be1200010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4a8755061217b6a78f5d5f8af6e326e482ebdc57f7144108662d122252ddcc27e7000100045dddc527887dc39b9cd189d6f183f16217393a5d3d3165fead2daeaf4f2d6916280c572561a809555de4a87d7a56d5bcca2c246a389dbb2a24c5639bdb0001000153c0f36532563ba2a10f52b865e558cd1a5eef9a9edd01c1cb23b74aa772beb4f3e3b784609f4e205a09863c0587e63b4b47664022cb34896a1711416b00010003e7842b0b4f4fd8e665883fe9c158ba8d38347840f1da0a75aca1fc284ce2428454b48df9f5551500fc50b63af4741b1cd21d4cfddc69aa46cb78eff45b00010000f183703a165afed04326ad5786316f6fc65b27f1cf17459a52bd1f57f27f896b7429e070ca76e3e33165ec75f6c9f439ee37f3b58822494b1251c8247500010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4a05ea3d0bb218598c42b2e25ae5f6cbc9369b273ee6610450cade89775646b2a08902000000000000000000000000000000008b71d4a184058d07fccac4348ae02a1f663403231b0a40fa2c8c0ff73bdca092890200000000000000000000000000000000ab63c4cebbd508a7d7184f0b9134453eea7a09ca749610d5576f8046241b9cde890200000000000000000000000000000000e58af14be53d8ac56f58ff3e5b07c239bfb549149f067597e9d028f35e3c2b77890200000000000000000000000000000000b78e94980fec3a5f68aa25d0d934084907688e537e82c2942af905aab21413ab890200000000000000000000000000000000c4db460819691e825328b532024bbecdc40394c74307a00bd245fc658b1bd34f0901908827f2052a14b24a10cae1f9e259ead06a89a1d74ff736a54f54ebcf05eeb30901d32d07305b87debd25698d4dfac4c2f986693a4e9d9baff7da37a7b5ca8d01cb0901e73042e5dacff2ce20a720c9c6d694576e4afa7bbbafdc4d409c63b7ca8027b70901760a7405795441aceea3be649a53d02785cb3487b7bd23e3b4888a935cee010d09011f3acf5d6d7bfeab8a7112771866e28c3714e0c315a81ec6a58ab4ad1c3d6eb10901c207b49d14deb3af9bc960d57074e27386285c73248abc5fa1d72aa6e8664fa40901644f0c4e15446d7e5ff363c944b55bd6801a1f38afd984c3427569530cb663210901743be0243628b8e7e8f04c00fc4f88efae001250a7482b31e6a0ec87ee3598e7090171e91721f9918576d760f02f03cac47c6f4003316031848e3c1d99e6e83a47434102d84e69f2f480002d5a6962cccee5d4adb48a36bbbf443a531721484381125937f3001ac5ff875b41022f496efbbdb2007b727eb806c926fb20c8ad087c57422977cebd06373e26d19b640e5fe32c85ff39a904faf736ce00a25420c1d9d705358c134cc601d9d184cb4dfdde7e1cac2bc3d4d38bf9ec44e6210ee6b280123bafc586f77764488cd24c6a77546e5a0fe8bdfb4fa203cfaffc36cce4dd5b8901000000000000000000000000651bcde08e7dd06ac5b73b473be6bc5a51030f4c7437657cb7b29bf376c564b8d1675a5e8903000000000000000000000000651bcde24ba84e1f37d041bc6e55ba396826cc494e84d4815b6db52690422eea7386314f00e8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244c3de2202ccb626ad387d70722e64fbe44562e2f231a290c08532b8d6aba402ff50025fe002039e87b424de2772b82d935f14e2b657429a1bcc04612391ea0330c90ebddefdda48eb2aa7f66ecf7940a280e9ef3fb2e95db0995538440a62af79861004434720529e816fd2e40f8031a8d7471ebcd00351db0787346bcfe8dfad8d2b479093588d0e847efa73a10ce20e4799fb1e46642d65617c7e5213fa04989d92d8903000000000000000000000000651bcde287ded247e1660f827071c7f1371934589751085384fc9f4462c1f1897c5c3eef890100000000000000000000000000000001911dd2ad743ff237d411648a0fe32c6d74eec060716a2a74352f6b1c435b5d670016914ac26bb9cafa0f1dfaeaab10745a9094e1b60c7076fedf21651d6a25b574686a068c708f1bdbefd9e6e454ac2b520fd41c8dcf23ecd4cee978c22f1c1f5f09ff974fe8b575175cefa919a5ba1c0ddf4409be4b16695dc7bd12f6701b99bd2e70a152312ad6f01657413b2eae9287f6b9adad93d5fed1a0dd5e13ec74ce1163146509bfe426f2315a69cb452bf388cccd321eca2746a1adf793b489e5c8f61c40688b7ef3e53defc56c78facf513e511f9f5ba0eb50dbcc745afea3b860da75b394d2d1627b6e2ef54fb7b187d0af61e4532c238f387ecf9f0b466f1d54414100018e519b65c8901b344a480638beadb923fbd3462e475d39acebe559d65ed5cb11a1b25279f1918477c35eec1332ff07001d3f85cf854b70d7552f93ba8e88d581064ca4c0df6ac456c00a0e83898ccd464c63e5008aa1a498cc0646b78eb216d9eeeec76ed0eb0ee6c352f35ca5f0b2edc2ca17d211cc5cb905ba10142f042a6ac836d9cef9a6916635c9a1c1d2dc62a9fe83e2230b506b98e0fded46249008fe28b813907a05ae0d773d8f31e330200e9336e0159034c137ed645fb67ccca8a152312ad6f01657413b2eae9287f6b9adad93d5fee5d8f810abde496ccbeb45a4f3c06af828975163a006257cbf18cefebbfb4cd409025f40404a3d37bba024799ce32d7c2a833aec8474288a26b246afa32b07b4a3ce00577261707065642045746865720000000000000000000000000000000000001a09cf14f266dfe87c4b33e6d934de01f8f7242199fa8783178117218fa033f7ab005745544800000000000000000000000000000000000000000000000000000008289026c5fa173652bd62774824698a6848c63031f853d0e275174552f35df33000577261707065642045746865720000000000000000000000000000000000001a1e59309944cbc900ae848855e10bc929f78e86c2179d6e96cf52bfd520f039200031000000000000000000000000000000000000000000000000000000000000021653a735395136e5494c5426ba972b45e34d36ebcb86ac104c724ab375fcce90a18580ba6aeebc6e6b89d226c79be8927257a436ad11d9c0305b18e9d78cab8f75a3aec2096302b67e3815939e29476fb36a0d8299a1b25279f1918477c35eec1332ff07001d3f85cf85688525f98e4859a9c6939f2d2f92e6b1950ed57e56137d717aca1ccf9754f719a1c7ebe9226d26524400a8959a08f411a727ae7bb68f8febecd89ffe9d84708d24544d452de3e22e62b3b2b872e430839a15115818a152312ad6f01657413b2eae9287f6b9adad93d5fe3fb60af355125687beeb90c066ace76c442b0f963a6afd0e3316fcdd673ad22c09ff30c8a03ec44e5337a1f9d66763cf1b319fdc6d8bc4981e1f47edbd86210614b909ff0cbdceb634b81192417b64d114d535ad3bdba97d6d7e90ee2a79bf1c132d3c2d09ff5cd85060f4ff26eb5b68a6687aee76c1b7a77575fdc86ba49b4faf5041377a79b14de8989f2385a6e23f6bd05a80e0d9231870c15a000142e50adc0d84bff439d0086d9fbab9984f8b27aa208935238a60cc62e7c9bb2ea1709e94c96366b3c40ea4854837c18733e5ac1193b8d8e4070d2eca4441b0378b572bd949ab764fd71c002b759613c3e29d425cf4000100012730c940a81021004e899c6ee4bec02f0667757b9d75a8f0714ce6c157f5940b7664e4f69f01fc530db36965e33599a1348629f07ae2d724007ac36a71a16baac84db583d88e0f3a8c082e3632fcc0e15757f0dcf5234b87af41fdee4c0999c4fe698a8d824415979ab839e6913a975a3055a152312ad6f01657413b2eae9287f6b9adad93d5fe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + // }; - const processL2Logs = await executor.processL2Logs( - nextBatch, - "0x0000000000000000000000000000000000000000000000000000000000000000" - ); - expect(processL2Logs.stateDiffHash, "State diff hash computation failed").is.equal( - "0x9a67073c2df8f53087fcfc32d82c98bba591da35df6ce1fb55a23b677d37f9fc" - ); + // const processL2Logs = await executor.processL2Logs( + // nextBatch, + // "0x0000000000000000000000000000000000000000000000000000000000000000", + // PubdataPricingMode.Rollup + // ); + // expect(processL2Logs.stateDiffHash, "State diff hash computation failed").is.equal( + // "0x9a67073c2df8f53087fcfc32d82c98bba591da35df6ce1fb55a23b677d37f9fc" + // ); - const bytesZero = "0x0000000000000000000000000000000000000000000000000000000000000000"; - const nextCommitment = await executor.createBatchCommitment( - nextBatch, - processL2Logs.stateDiffHash, - [bytesZero, bytesZero], - [bytesZero, bytesZero] - ); - console.log("This block Commitment is : " + nextCommitment); - expect(nextCommitment, "Commitment computation failed").is.equal( - "0x38ba669c30ce03475c4139fdab5d43fd8edd78f50ee3e99101cc55ad29b6efb0" - ); + // const nextCommitment = await executor.createBatchCommitment(nextBatch, processL2Logs.stateDiffHash); + // console.log("This block Commitment is : " + nextCommitment); + // expect(nextCommitment, "Commitment computation failed").is.equal( + // "0xae36e9bed834f99d427adb8958935f38f46b6431c31c5711587d39cf2c93da90" + // ); - const prevCommitment = "0x6ebf945305689a8c3ac993df7f002d41d311a762cd6bf39bb054ead8d1f54404"; - const result = await executor.getBatchProofPublicInput(prevCommitment, nextCommitment, { - recursionNodeLevelVkHash: "0x5a3ef282b21e12fe1f4438e5bb158fc5060b160559c5158c6389d62d9fe3d080", - recursionLeafLevelVkHash: "0x72167c43a46cf38875b267d67716edc4563861364a3c03ab7aee73498421e828", - // ignored. - recursionCircuitsSetVksHash: "0x05dc05911af0aee6a0950ee36dad423981cf05a58cfdb479109bff3c2262eaac", - }); - expect(result.toHexString(), "").to.be.equal("0x3449f2e7025a7731ca98a7a232211f88ae4e5e8ae2ce41fd40866f7b"); + // const prevCommitment = "0x6ebf945305689a8c3ac993df7f002d41d311a762cd6bf39bb054ead8d1f54404"; + // const result = await executor.getBatchProofPublicInput(prevCommitment, nextCommitment, { + // recursionNodeLevelVkHash: "0x5a3ef282b21e12fe1f4438e5bb158fc5060b160559c5158c6389d62d9fe3d080", + // recursionLeafLevelVkHash: "0x72167c43a46cf38875b267d67716edc4563861364a3c03ab7aee73498421e828", + // // ignored. + // recursionCircuitsSetVksHash: "0x05dc05911af0aee6a0950ee36dad423981cf05a58cfdb479109bff3c2262eaac", + // }); + // expect(result.toHexString(), "").to.be.equal("0x66876e724acc551e35d48f5c091447a245efcc79d70bb840533ddf83"); }); + + // it("Test hashes (Validium)", async () => { + // const bootloaderHash = "0x010009416e909e0819593a9806bbc841d25c5cdfed3f4a1523497c6814e5194a"; + // const aaHash = "0x0100065d134a862a777e50059f5e0fbe68b583f3617a67820f7edda0d7f253a0"; + // const setResult = await executor.setHashes(aaHash, bootloaderHash); + // const finish = await setResult.wait(); + // expect(finish.status == 1); + + // const nextBatch = { + // // ignored + // batchNumber: 1, + // // ignored + // timestamp: 100, + // indexRepeatedStorageChanges: 84, + // newStateRoot: "0x9cf7bb72401a56039ca097cabed20a72221c944ed9b0e515c083c04663ab45a6", + // // ignored + // numberOfLayer1Txs: 10, + // // ignored + // priorityOperationsHash: "0x167f4ca80269c9520ad951eeeda28dd3deb0715e9e2917461e81a60120a14183", + // bootloaderHeapInitialContentsHash: "0x540442e48142fa061a81822184f7790e7b69dea92153d38ef623802c6f0411c0", + // eventsQueueStateHash: "0xda42ab7994d4695a25f4ea8a9a485a592b7a31c20d5dae6363828de86d8826ea", + // systemLogs: + // "0x00000000000000000000000000000000000000000000800b000000000000000000000000000000000000000000000000000000000000000416914ac26bb9cafa0f1dfaeaab10745a9094e1b60c7076fedf21651d6a25b5740000000a000000000000000000000000000000000000800b0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000651bcde0000000000000000000000000651bcde20001000a00000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000005167f4ca80269c9520ad951eeeda28dd3deb0715e9e2917461e81a60120a141830001000a00000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0001000a00000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000000ee6ee8f50659bd8be3d86c32efb02baa5571cf3b46dd7ea3db733ae181747b8b0001000a0000000000000000000000000000000000008008000000000000000000000000000000000000000000000000000000000000000160fc5fb513ca8e6f6232a7410797954dcb6edbf9081768da24b483aca91c54db0001000a000000000000000000000000000000000000800800000000000000000000000000000000000000000000000000000000000000029a67073c2df8f53087fcfc32d82c98bba591da35df6ce1fb55a23b677d37f9fc", + // totalL2ToL1Pubdata: "0x", + // }; + + // const processL2Logs = await executor.processL2Logs( + // nextBatch, + // "0x0000000000000000000000000000000000000000000000000000000000000000", + // PubdataPricingMode.Validium + // ); + // expect(processL2Logs.stateDiffHash, "State diff hash computation failed").is.equal( + // "0x9a67073c2df8f53087fcfc32d82c98bba591da35df6ce1fb55a23b677d37f9fc" + // ); + + // const bytesZero = "0x0000000000000000000000000000000000000000000000000000000000000000"; + // const nextCommitment = await executor.createBatchCommitment( + // nextBatch, + // processL2Logs.stateDiffHash, + // [bytesZero, bytesZero], + // [bytesZero, bytesZero] + // ); + // console.log("This block Commitment is : " + nextCommitment); + // expect(nextCommitment, "Commitment computation failed").is.equal( + // "0x38ba669c30ce03475c4139fdab5d43fd8edd78f50ee3e99101cc55ad29b6efb0" + // ); + + // const prevCommitment = "0x6ebf945305689a8c3ac993df7f002d41d311a762cd6bf39bb054ead8d1f54404"; + // const result = await executor.getBatchProofPublicInput(prevCommitment, nextCommitment, { + // recursionNodeLevelVkHash: "0x5a3ef282b21e12fe1f4438e5bb158fc5060b160559c5158c6389d62d9fe3d080", + // recursionLeafLevelVkHash: "0x72167c43a46cf38875b267d67716edc4563861364a3c03ab7aee73498421e828", + // // ignored. + // recursionCircuitsSetVksHash: "0x05dc05911af0aee6a0950ee36dad423981cf05a58cfdb479109bff3c2262eaac", + // }); + // expect(result.toHexString(), "").to.be.equal("0x3449f2e7025a7731ca98a7a232211f88ae4e5e8ae2ce41fd40866f7b"); + // }); }); diff --git a/l1-contracts/test/unit_tests/governance_test.spec.ts b/l1-contracts/test/unit_tests/governance_test.spec.ts index 292a7944e4ea..31736fb13650 100644 --- a/l1-contracts/test/unit_tests/governance_test.spec.ts +++ b/l1-contracts/test/unit_tests/governance_test.spec.ts @@ -1,13 +1,11 @@ import { expect } from "chai"; -import * as ethers from "ethers"; +import type * as ethers from "ethers"; import * as hardhat from "hardhat"; + import type { AdminFacetTest } from "../../typechain"; import { AdminFacetTestFactory, GovernanceFactory } from "../../typechain"; -import { getCallRevertReason } from "./utils"; -function randomAddress() { - return ethers.utils.hexlify(ethers.utils.randomBytes(20)); -} +import { getCallRevertReason, randomAddress } from "./utils"; describe("Admin facet tests", function () { let adminFacetTest: AdminFacetTest; @@ -20,12 +18,12 @@ describe("Admin facet tests", function () { const governanceContract = await contractFactory.deploy(); const governance = GovernanceFactory.connect(governanceContract.address, governanceContract.signer); - await adminFacetTest.setPendingGovernor(governance.address); + await adminFacetTest.setPendingAdmin(governance.address); randomSigner = (await hardhat.ethers.getSigners())[1]; }); - it("governor successfully set validator", async () => { + it("StateTransitionManager successfully set validator", async () => { const validatorAddress = randomAddress(); await adminFacetTest.setValidator(validatorAddress, true); @@ -38,10 +36,10 @@ describe("Admin facet tests", function () { const revertReason = await getCallRevertReason( adminFacetTest.connect(randomSigner).setValidator(validatorAddress, true) ); - expect(revertReason).equal("1k"); + expect(revertReason).equal("StateTransition Chain: not state transition manager"); }); - it("governor successfully set porter availability", async () => { + it("StateTransitionManager successfully set porter availability", async () => { await adminFacetTest.setPorterAvailability(true); const porterAvailability = await adminFacetTest.getPorterAvailability(); @@ -50,10 +48,10 @@ describe("Admin facet tests", function () { it("random account fails to set porter availability", async () => { const revertReason = await getCallRevertReason(adminFacetTest.connect(randomSigner).setPorterAvailability(false)); - expect(revertReason).equal("1g"); + expect(revertReason).equal("StateTransition Chain: not state transition manager"); }); - it("governor successfully set priority transaction max gas limit", async () => { + it("StateTransitionManager successfully set priority transaction max gas limit", async () => { const gasLimit = "12345678"; await adminFacetTest.setPriorityTxMaxGasLimit(gasLimit); @@ -66,42 +64,42 @@ describe("Admin facet tests", function () { const revertReason = await getCallRevertReason( adminFacetTest.connect(randomSigner).setPriorityTxMaxGasLimit(gasLimit) ); - expect(revertReason).equal("1g"); + expect(revertReason).equal("StateTransition Chain: not state transition manager"); }); - describe("change governor", function () { - let newGovernor: ethers.Signer; + describe("change admin", function () { + let newAdmin: ethers.Signer; before(async () => { - newGovernor = (await hardhat.ethers.getSigners())[2]; + newAdmin = (await hardhat.ethers.getSigners())[2]; }); - it("set pending governor", async () => { - const proposedGovernor = await randomSigner.getAddress(); - await adminFacetTest.setPendingGovernor(proposedGovernor); + it("set pending admin", async () => { + const proposedAdmin = await randomSigner.getAddress(); + await adminFacetTest.setPendingAdmin(proposedAdmin); - const pendingGovernor = await adminFacetTest.getPendingGovernor(); - expect(pendingGovernor).equal(proposedGovernor); + const pendingAdmin = await adminFacetTest.getPendingAdmin(); + expect(pendingAdmin).equal(proposedAdmin); }); - it("reset pending governor", async () => { - const proposedGovernor = await newGovernor.getAddress(); - await adminFacetTest.setPendingGovernor(proposedGovernor); + it("reset pending admin", async () => { + const proposedAdmin = await newAdmin.getAddress(); + await adminFacetTest.setPendingAdmin(proposedAdmin); - const pendingGovernor = await adminFacetTest.getPendingGovernor(); - expect(pendingGovernor).equal(proposedGovernor); + const pendingAdmin = await adminFacetTest.getPendingAdmin(); + expect(pendingAdmin).equal(proposedAdmin); }); - it("failed to accept governor from not proposed account", async () => { - const revertReason = await getCallRevertReason(adminFacetTest.connect(randomSigner).acceptGovernor()); + it("failed to accept admin from not proposed account", async () => { + const revertReason = await getCallRevertReason(adminFacetTest.connect(randomSigner).acceptAdmin()); expect(revertReason).equal("n4"); }); - it("accept governor from proposed account", async () => { - await adminFacetTest.connect(newGovernor).acceptGovernor(); + it("accept admin from proposed account", async () => { + await adminFacetTest.connect(newAdmin).acceptAdmin(); - const governor = await adminFacetTest.getGovernor(); - expect(governor).equal(await newGovernor.getAddress()); + const admin = await adminFacetTest.getAdmin(); + expect(admin).equal(await newAdmin.getAddress()); }); }); }); diff --git a/l1-contracts/test/unit_tests/hyperchain_migration_test.spec.ts b/l1-contracts/test/unit_tests/hyperchain_migration_test.spec.ts new file mode 100644 index 000000000000..14fe90695c00 --- /dev/null +++ b/l1-contracts/test/unit_tests/hyperchain_migration_test.spec.ts @@ -0,0 +1,94 @@ +import * as ethers from "ethers"; +import { Wallet } from "ethers"; +import * as hardhat from "hardhat"; + +import { initialTestnetDeploymentProcess } from "../../src.ts/deploy-test-process"; +import { ethTestConfig } from "../../src.ts/utils"; +import type { Deployer } from "../../src.ts/deploy"; + +import { upgradeToHyperchains } from "../../src.ts/hyperchain-upgrade"; +import type { FacetCut } from "../../src.ts/diamondCut"; +import { Action, facetCut } from "../../src.ts/diamondCut"; + +import type { ExecutorFacet, GettersFacet } from "../../typechain"; +import { DummyAdminFacetFactory, ExecutorFacetFactory, GettersFacetFactory } from "../../typechain"; +import type { CommitBatchInfo, StoredBatchInfo } from "./utils"; +import { + buildCommitBatchInfoWithUpgrade, + genesisStoredBatchInfo, + EMPTY_STRING_KECCAK, + makeExecutedEqualCommitted, + getBatchStoredInfo, +} from "./utils"; + +// note this test presumes that it is ok to start out with the new contracts, and upgrade them to themselves +describe("Hyperchain migration test", function () { + let owner: ethers.Signer; + let deployer: Deployer; + let gasPrice; + + let proxyExecutor: ExecutorFacet; + let proxyGetters: GettersFacet; + + let batch1InfoChainIdUpgrade: CommitBatchInfo; + let storedBatch1InfoChainIdUpgrade: StoredBatchInfo; + + let extraFacet: FacetCut; + + before(async () => { + [owner] = await hardhat.ethers.getSigners(); + + const deployWallet = Wallet.fromMnemonic(ethTestConfig.test_mnemonic3, "m/44'/60'/0'/0/1").connect(owner.provider); + const ownerAddress = await deployWallet.getAddress(); + + gasPrice = await owner.provider.getGasPrice(); + + const tx = { + from: await owner.getAddress(), + to: deployWallet.address, + value: ethers.utils.parseEther("1000"), + nonce: owner.getTransactionCount(), + gasLimit: 100000, + gasPrice: gasPrice, + }; + + await owner.sendTransaction(tx); + + const dummyAdminFacetFactory = await hardhat.ethers.getContractFactory("DummyAdminFacet"); + const dummyAdminfFacetContract = await dummyAdminFacetFactory.deploy(); + extraFacet = facetCut(dummyAdminfFacetContract.address, dummyAdminfFacetContract.interface, Action.Add, true); + + deployer = await initialTestnetDeploymentProcess(deployWallet, ownerAddress, gasPrice, [extraFacet]); + + proxyExecutor = ExecutorFacetFactory.connect(deployer.addresses.StateTransition.DiamondProxy, deployWallet); + proxyGetters = GettersFacetFactory.connect(deployer.addresses.StateTransition.DiamondProxy, deployWallet); + const dummyAdminFacet = DummyAdminFacetFactory.connect( + deployer.addresses.StateTransition.DiamondProxy, + deployWallet + ); + + await (await dummyAdminFacet.dummySetValidator(await deployWallet.getAddress())).wait(); + // do initial setChainIdUpgrade + const upgradeTxHash = await proxyGetters.getL2SystemContractsUpgradeTxHash(); + batch1InfoChainIdUpgrade = await buildCommitBatchInfoWithUpgrade( + genesisStoredBatchInfo(), + { + batchNumber: 1, + priorityOperationsHash: EMPTY_STRING_KECCAK, + numberOfLayer1Txs: "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + upgradeTxHash + ); + // console.log("committing batch1InfoChainIdUpgrade"); + const commitReceipt = await ( + await proxyExecutor.commitBatches(genesisStoredBatchInfo(), [batch1InfoChainIdUpgrade]) + ).wait(); + const commitment = commitReceipt.events[0].args.commitment; + storedBatch1InfoChainIdUpgrade = getBatchStoredInfo(batch1InfoChainIdUpgrade, commitment); + await makeExecutedEqualCommitted(proxyExecutor, genesisStoredBatchInfo(), [storedBatch1InfoChainIdUpgrade], []); + }); + + it("Start upgrade", async () => { + await upgradeToHyperchains(deployer, gasPrice); + }); +}); diff --git a/l1-contracts/test/unit_tests/initial_deployment_test.spec.ts b/l1-contracts/test/unit_tests/initial_deployment_test.spec.ts new file mode 100644 index 000000000000..14efb2b0c7d6 --- /dev/null +++ b/l1-contracts/test/unit_tests/initial_deployment_test.spec.ts @@ -0,0 +1,68 @@ +import { expect } from "chai"; +import * as ethers from "ethers"; +import { Wallet } from "ethers"; +import * as hardhat from "hardhat"; + +import type { Bridgehub, StateTransitionManager } from "../../typechain"; +import { BridgehubFactory, StateTransitionManagerFactory } from "../../typechain"; + +import { initialTestnetDeploymentProcess } from "../../src.ts/deploy-test-process"; +import { ethTestConfig } from "../../src.ts/utils"; + +import type { Deployer } from "../../src.ts/deploy"; + +describe("Initial deployment", function () { + let bridgehub: Bridgehub; + let stateTransition: StateTransitionManager; + let owner: ethers.Signer; + let deployer: Deployer; + // const MAX_CODE_LEN_WORDS = (1 << 16) - 1; + // const MAX_CODE_LEN_BYTES = MAX_CODE_LEN_WORDS * 32; + // let forwarder: Forwarder; + let chainId = process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID || 270; + + before(async () => { + [owner] = await hardhat.ethers.getSigners(); + + const deployWallet = Wallet.fromMnemonic(ethTestConfig.test_mnemonic3, "m/44'/60'/0'/0/1").connect(owner.provider); + const ownerAddress = await deployWallet.getAddress(); + + const gasPrice = await owner.provider.getGasPrice(); + + const tx = { + from: await owner.getAddress(), + to: deployWallet.address, + value: ethers.utils.parseEther("1000"), + nonce: owner.getTransactionCount(), + gasLimit: 100000, + gasPrice: gasPrice, + }; + + await owner.sendTransaction(tx); + + deployer = await initialTestnetDeploymentProcess(deployWallet, ownerAddress, gasPrice, []); + + chainId = deployer.chainId; + + // await deploySharedBridgeOnL2ThroughL1(deployer, chainId.toString(), gasPrice); + + bridgehub = BridgehubFactory.connect(deployer.addresses.Bridgehub.BridgehubProxy, deployWallet); + stateTransition = StateTransitionManagerFactory.connect( + deployer.addresses.StateTransition.StateTransitionProxy, + deployWallet + ); + }); + + it("Check addresses", async () => { + const stateTransitionManagerAddress1 = deployer.addresses.StateTransition.StateTransitionProxy; + const stateTransitionManagerAddress2 = await bridgehub.stateTransitionManager(chainId); + expect(stateTransitionManagerAddress1.toLowerCase()).equal(stateTransitionManagerAddress2.toLowerCase()); + + const stateTransitionAddress1 = deployer.addresses.StateTransition.DiamondProxy; + const stateTransitionAddress2 = await stateTransition.stateTransition(chainId); + expect(stateTransitionAddress1.toLowerCase()).equal(stateTransitionAddress2.toLowerCase()); + + const stateTransitionAddress3 = await bridgehub.getStateTransition(chainId); + expect(stateTransitionAddress1.toLowerCase()).equal(stateTransitionAddress3.toLowerCase()); + }); +}); diff --git a/l1-contracts/test/unit_tests/l1_erc20_bridge_test.spec.ts b/l1-contracts/test/unit_tests/l1_erc20_bridge_test.spec.ts deleted file mode 100644 index 6f05f06354b7..000000000000 --- a/l1-contracts/test/unit_tests/l1_erc20_bridge_test.spec.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { expect } from "chai"; -import { ethers } from "ethers"; -import * as hardhat from "hardhat"; -import { REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT } from "zksync-web3/build/src/utils"; -import type { IZkSync } from "zksync-web3/build/typechain"; -import { IZkSyncFactory } from "zksync-web3/build/typechain"; -import { Action, diamondCut, facetCut } from "../../src.ts/diamondCut"; -import type { TestnetERC20Token } from "../../typechain"; -import { - DiamondInitFactory, - GettersFacetFactory, - MailboxFacetFactory, - TestnetERC20TokenFactory, -} from "../../typechain"; -import type { IL1Bridge } from "../../typechain/IL1Bridge"; -import { IL1BridgeFactory } from "../../typechain/IL1BridgeFactory"; -import { defaultFeeParams, getCallRevertReason } from "./utils"; - -describe("L1ERC20Bridge tests", function () { - let owner: ethers.Signer; - let randomSigner: ethers.Signer; - let l1ERC20Bridge: IL1Bridge; - let erc20TestToken: TestnetERC20Token; - let testnetERC20TokenContract: ethers.Contract; - let l1Erc20BridgeContract: ethers.Contract; - let zksyncContract: IZkSync; - - before(async () => { - [owner, randomSigner] = await hardhat.ethers.getSigners(); - - const gettersFactory = await hardhat.ethers.getContractFactory("GettersFacet"); - const gettersContract = await gettersFactory.deploy(); - const gettersFacet = GettersFacetFactory.connect(gettersContract.address, gettersContract.signer); - - const mailboxFactory = await hardhat.ethers.getContractFactory("MailboxFacet"); - const mailboxContract = await mailboxFactory.deploy(); - const mailboxFacet = MailboxFacetFactory.connect(mailboxContract.address, mailboxContract.signer); - - const diamondInitFactory = await hardhat.ethers.getContractFactory("DiamondInit"); - const diamondInitContract = await diamondInitFactory.deploy(); - const diamondInit = DiamondInitFactory.connect(diamondInitContract.address, diamondInitContract.signer); - - const dummyHash = new Uint8Array(32); - dummyHash.set([1, 0, 0, 1]); - const dummyAddress = ethers.utils.hexlify(ethers.utils.randomBytes(20)); - const diamondInitData = diamondInit.interface.encodeFunctionData("initialize", [ - { - verifier: dummyAddress, - governor: await owner.getAddress(), - admin: await owner.getAddress(), - genesisBatchHash: ethers.constants.HashZero, - genesisIndexRepeatedStorageChanges: 0, - genesisBatchCommitment: ethers.constants.HashZero, - verifierParams: { - recursionCircuitsSetVksHash: ethers.constants.HashZero, - recursionLeafLevelVkHash: ethers.constants.HashZero, - recursionNodeLevelVkHash: ethers.constants.HashZero, - }, - zkPorterIsAvailable: false, - l2BootloaderBytecodeHash: dummyHash, - l2DefaultAccountBytecodeHash: dummyHash, - priorityTxMaxGasLimit: 10000000, - initialProtocolVersion: 0, - feeParams: defaultFeeParams(), - blobVersionedHashRetriever: ethers.constants.AddressZero, - }, - ]); - - const facetCuts = [ - facetCut(gettersFacet.address, gettersFacet.interface, Action.Add, false), - facetCut(mailboxFacet.address, mailboxFacet.interface, Action.Add, true), - ]; - - const diamondCutData = diamondCut(facetCuts, diamondInit.address, diamondInitData); - - const diamondProxyFactory = await hardhat.ethers.getContractFactory("DiamondProxy"); - const chainId = hardhat.network.config.chainId; - const diamondProxyContract = await diamondProxyFactory.deploy(chainId, diamondCutData); - - const l1Erc20BridgeFactory = await hardhat.ethers.getContractFactory("L1ERC20Bridge"); - l1Erc20BridgeContract = await l1Erc20BridgeFactory.deploy(diamondProxyContract.address); - l1ERC20Bridge = IL1BridgeFactory.connect(l1Erc20BridgeContract.address, l1Erc20BridgeContract.signer); - - const testnetERC20TokenFactory = await hardhat.ethers.getContractFactory("TestnetERC20Token"); - testnetERC20TokenContract = await testnetERC20TokenFactory.deploy("TestToken", "TT", 18); - erc20TestToken = TestnetERC20TokenFactory.connect( - testnetERC20TokenContract.address, - testnetERC20TokenContract.signer - ); - - await erc20TestToken.mint(await randomSigner.getAddress(), ethers.utils.parseUnits("10000", 18)); - await erc20TestToken - .connect(randomSigner) - .approve(l1Erc20BridgeContract.address, ethers.utils.parseUnits("10000", 18)); - - // Exposing the methods of IZkSync to the diamond proxy - zksyncContract = IZkSyncFactory.connect(diamondProxyContract.address, diamondProxyContract.provider); - }); - - it("Should not allow depositing zero amount", async () => { - const revertReason = await getCallRevertReason( - l1ERC20Bridge - .connect(randomSigner) - .deposit( - await randomSigner.getAddress(), - testnetERC20TokenContract.address, - 0, - 0, - 0, - ethers.constants.AddressZero - ) - ); - expect(revertReason).equal("2T"); - }); - - it("Should deposit successfully", async () => { - const depositorAddress = await randomSigner.getAddress(); - await depositERC20( - l1ERC20Bridge.connect(randomSigner), - zksyncContract, - depositorAddress, - testnetERC20TokenContract.address, - ethers.utils.parseUnits("800", 18), - 10000000 - ); - }); - - it("Should revert on finalizing a withdrawal with wrong message length", async () => { - const revertReason = await getCallRevertReason( - l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(0, 0, 0, "0x", []) - ); - expect(revertReason).equal("kk"); - }); - - it("Should revert on finalizing a withdrawal with wrong function signature", async () => { - const revertReason = await getCallRevertReason( - l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(0, 0, 0, ethers.utils.randomBytes(76), []) - ); - expect(revertReason).equal("nt"); - }); - - it("Should revert on finalizing a withdrawal with wrong batch number", async () => { - const functionSignature = "0x11a2ccc1"; - const l1Receiver = await randomSigner.getAddress(); - const l2ToL1message = ethers.utils.hexConcat([ - functionSignature, - l1Receiver, - testnetERC20TokenContract.address, - ethers.constants.HashZero, - ]); - const revertReason = await getCallRevertReason( - l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(10, 0, 0, l2ToL1message, []) - ); - expect(revertReason).equal("xx"); - }); - - it("Should revert on finalizing a withdrawal with wrong length of proof", async () => { - const functionSignature = "0x11a2ccc1"; - const l1Receiver = await randomSigner.getAddress(); - const l2ToL1message = ethers.utils.hexConcat([ - functionSignature, - l1Receiver, - testnetERC20TokenContract.address, - ethers.constants.HashZero, - ]); - const revertReason = await getCallRevertReason( - l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(0, 0, 0, l2ToL1message, []) - ); - expect(revertReason).equal("xc"); - }); - - it("Should revert on finalizing a withdrawal with wrong proof", async () => { - const functionSignature = "0x11a2ccc1"; - const l1Receiver = await randomSigner.getAddress(); - const l2ToL1message = ethers.utils.hexConcat([ - functionSignature, - l1Receiver, - testnetERC20TokenContract.address, - ethers.constants.HashZero, - ]); - const revertReason = await getCallRevertReason( - l1ERC20Bridge - .connect(randomSigner) - .finalizeWithdrawal(0, 0, 0, l2ToL1message, Array(9).fill(ethers.constants.HashZero)) - ); - expect(revertReason).equal("nq"); - }); -}); - -async function depositERC20( - bridge: IL1Bridge, - zksyncContract: IZkSync, - l2Receiver: string, - l1Token: string, - amount: ethers.BigNumber, - l2GasLimit: number, - l2RefundRecipient = ethers.constants.AddressZero -) { - const gasPrice = await bridge.provider.getGasPrice(); - const gasPerPubdata = REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT; - const neededValue = await zksyncContract.l2TransactionBaseCost(gasPrice, l2GasLimit, gasPerPubdata); - - await bridge.deposit( - l2Receiver, - l1Token, - amount, - l2GasLimit, - REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, - l2RefundRecipient, - { - value: neededValue, - } - ); -} diff --git a/l1-contracts/test/unit_tests/l1_shared_bridge_test.spec.ts b/l1-contracts/test/unit_tests/l1_shared_bridge_test.spec.ts new file mode 100644 index 000000000000..b53b12175d7e --- /dev/null +++ b/l1-contracts/test/unit_tests/l1_shared_bridge_test.spec.ts @@ -0,0 +1,241 @@ +import { expect } from "chai"; +import { ethers, Wallet } from "ethers"; +import { Interface } from "ethers/lib/utils"; +import * as hardhat from "hardhat"; +import type { L1SharedBridge, Bridgehub, WETH9 } from "../../typechain"; +import { L1SharedBridgeFactory, BridgehubFactory, WETH9Factory, TestnetERC20TokenFactory } from "../../typechain"; + +import { getTokens } from "../../src.ts/deploy-token"; +import { ADDRESS_ONE, ethTestConfig } from "../../src.ts/utils"; +import type { Deployer } from "../../src.ts/deploy"; +import { initialTestnetDeploymentProcess } from "../../src.ts/deploy-test-process"; + +import { getCallRevertReason, REQUIRED_L2_GAS_PRICE_PER_PUBDATA } from "./utils"; + +describe("Shared Bridge tests", () => { + let owner: ethers.Signer; + let randomSigner: ethers.Signer; + let deployWallet: Wallet; + let deployer: Deployer; + let bridgehub: Bridgehub; + let l1SharedBridge: L1SharedBridge; + let l1SharedBridgeInterface: Interface; + let l1Weth: WETH9; + let erc20TestToken: ethers.Contract; + const functionSignature = "0x6c0960f9"; + const ERC20functionSignature = "0x11a2ccc1"; + + let chainId = process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID || 270; + + before(async () => { + [owner, randomSigner] = await hardhat.ethers.getSigners(); + + deployWallet = Wallet.fromMnemonic(ethTestConfig.test_mnemonic4, "m/44'/60'/0'/0/1").connect(owner.provider); + const ownerAddress = await deployWallet.getAddress(); + + const gasPrice = await owner.provider.getGasPrice(); + + const tx = { + from: owner.getAddress(), + to: deployWallet.address, + value: ethers.utils.parseEther("1000"), + nonce: owner.getTransactionCount(), + gasLimit: 100000, + gasPrice: gasPrice, + }; + + await owner.sendTransaction(tx); + + // note we can use initialTestnetDeploymentProcess so we don't go into deployment details here + deployer = await initialTestnetDeploymentProcess(deployWallet, ownerAddress, gasPrice, []); + + chainId = deployer.chainId; + // prepare the bridge + + l1SharedBridge = L1SharedBridgeFactory.connect(deployer.addresses.Bridges.SharedBridgeProxy, deployWallet); + bridgehub = BridgehubFactory.connect(deployer.addresses.Bridgehub.BridgehubProxy, deployWallet); + l1SharedBridgeInterface = new Interface(hardhat.artifacts.readArtifactSync("L1SharedBridge").abi); + + const tokens = getTokens(); + const l1WethTokenAddress = tokens.find((token: { symbol: string }) => token.symbol == "WETH")!.address; + l1Weth = WETH9Factory.connect(l1WethTokenAddress, owner); + + const tokenAddress = tokens.find((token: { symbol: string }) => token.symbol == "DAI")!.address; + erc20TestToken = TestnetERC20TokenFactory.connect(tokenAddress, owner); + + await erc20TestToken.mint(await randomSigner.getAddress(), ethers.utils.parseUnits("10000", 18)); + await erc20TestToken.connect(randomSigner).approve(l1SharedBridge.address, ethers.utils.parseUnits("10000", 18)); + }); + + it("Check should initialize through governance", async () => { + const upgradeCall = l1SharedBridgeInterface.encodeFunctionData("initializeChainGovernance(uint256,address)", [ + chainId, + ADDRESS_ONE, + ]); + const txHash = await deployer.executeUpgrade(l1SharedBridge.address, 0, upgradeCall); + + expect(txHash).not.equal(ethers.constants.HashZero); + }); + + it("Should not allow depositing zero erc20 amount", async () => { + const mintValue = ethers.utils.parseEther("0.01"); + const revertReason = await getCallRevertReason( + bridgehub.connect(randomSigner).requestL2TransactionTwoBridges( + { + chainId, + mintValue, + l2Value: 0, + l2GasLimit: 0, + l2GasPerPubdataByteLimit: 0, + refundRecipient: ethers.constants.AddressZero, + secondBridgeAddress: l1SharedBridge.address, + secondBridgeValue: 0, + secondBridgeCalldata: new ethers.utils.AbiCoder().encode( + ["address", "uint256", "address"], + [erc20TestToken.address, 0, await randomSigner.getAddress()] + ), + }, + { value: mintValue } + ) + ); + expect(revertReason).equal("6T"); + }); + + it("Should deposit successfully", async () => { + const amount = ethers.utils.parseEther("1"); + const mintValue = ethers.utils.parseEther("2"); + await l1Weth.connect(randomSigner).deposit({ value: amount }); + await (await l1Weth.connect(randomSigner).approve(l1SharedBridge.address, amount)).wait(); + bridgehub.connect(randomSigner).requestL2TransactionTwoBridges( + { + chainId, + mintValue, + l2Value: amount, + l2GasLimit: 1000000, + l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + refundRecipient: ethers.constants.AddressZero, + secondBridgeAddress: l1SharedBridge.address, + secondBridgeValue: 0, + secondBridgeCalldata: new ethers.utils.AbiCoder().encode( + ["address", "uint256", "address"], + [l1Weth.address, amount, await randomSigner.getAddress()] + ), + }, + { value: mintValue } + ); + }); + + it("Should revert on finalizing a withdrawal with short message length", async () => { + const revertReason = await getCallRevertReason( + l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, "0x", [ethers.constants.HashZero]) + ); + expect(revertReason).equal("ShB wrong msg len"); + }); + + it("Should revert on finalizing a withdrawal with wrong message length", async () => { + const revertReason = await getCallRevertReason( + l1SharedBridge + .connect(randomSigner) + .finalizeWithdrawal( + chainId, + 0, + 0, + 0, + ethers.utils.hexConcat([ERC20functionSignature, l1SharedBridge.address, ethers.utils.randomBytes(72 + 4)]), + [ethers.constants.HashZero] + ) + ); + expect(revertReason).equal("ShB wrong msg len 2"); + }); + + it("Should revert on finalizing a withdrawal with wrong function selector", async () => { + const revertReason = await getCallRevertReason( + l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, ethers.utils.randomBytes(96), []) + ); + expect(revertReason).equal("ShB Incorrect message function selector"); + }); + + it("Should deposit erc20 token successfully", async () => { + const amount = ethers.utils.parseEther("0.001"); + const mintValue = ethers.utils.parseEther("0.002"); + await l1Weth.connect(randomSigner).deposit({ value: amount }); + await (await l1Weth.connect(randomSigner).approve(l1SharedBridge.address, amount)).wait(); + bridgehub.connect(randomSigner).requestL2TransactionTwoBridges( + { + chainId, + mintValue, + l2Value: amount, + l2GasLimit: 1000000, + l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + refundRecipient: ethers.constants.AddressZero, + secondBridgeAddress: l1SharedBridge.address, + secondBridgeValue: 0, + secondBridgeCalldata: new ethers.utils.AbiCoder().encode( + ["address", "uint256", "address"], + [l1Weth.address, amount, await randomSigner.getAddress()] + ), + }, + { value: mintValue } + ); + }); + + it("Should revert on finalizing a withdrawal with wrong message length", async () => { + const revertReason = await getCallRevertReason( + l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, "0x", [ethers.constants.HashZero]) + ); + expect(revertReason).equal("ShB wrong msg len"); + }); + + it("Should revert on finalizing a withdrawal with wrong function signature", async () => { + const revertReason = await getCallRevertReason( + l1SharedBridge + .connect(randomSigner) + .finalizeWithdrawal(chainId, 0, 0, 0, ethers.utils.randomBytes(76), [ethers.constants.HashZero]) + ); + expect(revertReason).equal("ShB Incorrect message function selector"); + }); + + it("Should revert on finalizing a withdrawal with wrong batch number", async () => { + const l1Receiver = await randomSigner.getAddress(); + const l2ToL1message = ethers.utils.hexConcat([ + functionSignature, + l1Receiver, + erc20TestToken.address, + ethers.constants.HashZero, + ]); + const revertReason = await getCallRevertReason( + l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 10, 0, 0, l2ToL1message, []) + ); + expect(revertReason).equal("xx"); + }); + + it("Should revert on finalizing a withdrawal with wrong length of proof", async () => { + const l1Receiver = await randomSigner.getAddress(); + const l2ToL1message = ethers.utils.hexConcat([ + functionSignature, + l1Receiver, + erc20TestToken.address, + ethers.constants.HashZero, + ]); + const revertReason = await getCallRevertReason( + l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, l2ToL1message, []) + ); + expect(revertReason).equal("xc"); + }); + + it("Should revert on finalizing a withdrawal with wrong proof", async () => { + const l1Receiver = await randomSigner.getAddress(); + const l2ToL1message = ethers.utils.hexConcat([ + functionSignature, + l1Receiver, + erc20TestToken.address, + ethers.constants.HashZero, + ]); + const revertReason = await getCallRevertReason( + l1SharedBridge + .connect(randomSigner) + .finalizeWithdrawal(chainId, 0, 0, 0, l2ToL1message, Array(9).fill(ethers.constants.HashZero)) + ); + expect(revertReason).equal("ShB withd w proof"); + }); +}); diff --git a/l1-contracts/test/unit_tests/l1_weth_bridge_test.spec.ts b/l1-contracts/test/unit_tests/l1_weth_bridge_test.spec.ts deleted file mode 100644 index f458bba22bc4..000000000000 --- a/l1-contracts/test/unit_tests/l1_weth_bridge_test.spec.ts +++ /dev/null @@ -1,212 +0,0 @@ -import { expect } from "chai"; -import { ethers } from "ethers"; -import * as hardhat from "hardhat"; -import { hashL2Bytecode } from "../../scripts/utils"; -import { Action, diamondCut, facetCut } from "../../src.ts/diamondCut"; -import type { L1WethBridge, WETH9 } from "../../typechain"; -import { - DiamondInitFactory, - GettersFacetFactory, - L1WethBridgeFactory, - MailboxFacetFactory, - WETH9Factory, -} from "../../typechain"; -import type { IZkSync } from "../../typechain/IZkSync"; -import { defaultFeeParams, getCallRevertReason } from "./utils"; - -import { Interface } from "ethers/lib/utils"; -import type { Address } from "zksync-web3/build/src/types"; - -const DEPLOYER_SYSTEM_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000008006"; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const REQUIRED_L2_GAS_PRICE_PER_PUBDATA = require("../../../SystemConfig.json").REQUIRED_L2_GAS_PRICE_PER_PUBDATA; - -export async function create2DeployFromL1( - zkSync: IZkSync, - walletAddress: Address, - bytecode: ethers.BytesLike, - constructor: ethers.BytesLike, - create2Salt: ethers.BytesLike, - l2GasLimit: ethers.BigNumberish -) { - const deployerSystemContracts = new Interface(hardhat.artifacts.readArtifactSync("IContractDeployer").abi); - const bytecodeHash = hashL2Bytecode(bytecode); - const calldata = deployerSystemContracts.encodeFunctionData("create2", [create2Salt, bytecodeHash, constructor]); - const gasPrice = await zkSync.provider.getGasPrice(); - const expectedCost = await zkSync.l2TransactionBaseCost(gasPrice, l2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA); - - await zkSync.requestL2Transaction( - DEPLOYER_SYSTEM_CONTRACT_ADDRESS, - 0, - calldata, - l2GasLimit, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - [bytecode], - walletAddress, - { value: expectedCost, gasPrice } - ); -} - -describe("WETH Bridge tests", () => { - let owner: ethers.Signer; - let randomSigner: ethers.Signer; - let bridgeProxy: L1WethBridge; - let l1Weth: WETH9; - const functionSignature = "0x6c0960f9"; - - before(async () => { - [owner, randomSigner] = await hardhat.ethers.getSigners(); - - // prepare the diamond - - const gettersFactory = await hardhat.ethers.getContractFactory("GettersFacet"); - const gettersContract = await gettersFactory.deploy(); - const gettersFacet = GettersFacetFactory.connect(gettersContract.address, gettersContract.signer); - - const mailboxFactory = await hardhat.ethers.getContractFactory("MailboxFacet"); - const mailboxContract = await mailboxFactory.deploy(); - const mailboxFacet = MailboxFacetFactory.connect(mailboxContract.address, mailboxContract.signer); - - const diamondInitFactory = await hardhat.ethers.getContractFactory("DiamondInit"); - const diamondInitContract = await diamondInitFactory.deploy(); - const diamondInit = DiamondInitFactory.connect(diamondInitContract.address, diamondInitContract.signer); - - const dummyHash = new Uint8Array(32); - dummyHash.set([1, 0, 0, 1]); - const dummyAddress = ethers.utils.hexlify(ethers.utils.randomBytes(20)); - const diamondInitData = diamondInit.interface.encodeFunctionData("initialize", [ - { - verifier: dummyAddress, - governor: await owner.getAddress(), - admin: await owner.getAddress(), - genesisBatchHash: ethers.constants.HashZero, - genesisIndexRepeatedStorageChanges: 0, - genesisBatchCommitment: ethers.constants.HashZero, - verifierParams: { - recursionCircuitsSetVksHash: ethers.constants.HashZero, - recursionLeafLevelVkHash: ethers.constants.HashZero, - recursionNodeLevelVkHash: ethers.constants.HashZero, - }, - zkPorterIsAvailable: false, - l2BootloaderBytecodeHash: dummyHash, - l2DefaultAccountBytecodeHash: dummyHash, - priorityTxMaxGasLimit: 10000000, - initialProtocolVersion: 0, - feeParams: defaultFeeParams(), - blobVersionedHashRetriever: ethers.constants.AddressZero, - }, - ]); - - const facetCuts = [ - facetCut(gettersFacet.address, gettersFacet.interface, Action.Add, false), - facetCut(mailboxFacet.address, mailboxFacet.interface, Action.Add, true), - ]; - - const diamondCutData = diamondCut(facetCuts, diamondInit.address, diamondInitData); - - const diamondProxyFactory = await hardhat.ethers.getContractFactory("DiamondProxy"); - const chainId = hardhat.network.config.chainId; - const diamondProxyContract = await diamondProxyFactory.deploy(chainId, diamondCutData); - - l1Weth = WETH9Factory.connect((await (await hardhat.ethers.getContractFactory("WETH9")).deploy()).address, owner); - - // prepare the bridge - - const bridge = await ( - await hardhat.ethers.getContractFactory("L1WethBridge") - ).deploy(l1Weth.address, diamondProxyContract.address); - - // we don't test L2, so it is ok to give garbage factory deps and L2 address - const garbageBytecode = "0x1111111111111111111111111111111111111111111111111111111111111111"; - const garbageAddress = "0x71C7656EC7ab88b098defB751B7401B5f6d8976F"; - - const bridgeInitData = bridge.interface.encodeFunctionData("initialize", [ - [garbageBytecode, garbageBytecode], - garbageAddress, - await owner.getAddress(), - ethers.constants.WeiPerEther, - ethers.constants.WeiPerEther, - ]); - const _bridgeProxy = await ( - await hardhat.ethers.getContractFactory("ERC1967Proxy") - ).deploy(bridge.address, bridgeInitData, { value: ethers.constants.WeiPerEther.mul(2) }); - - bridgeProxy = L1WethBridgeFactory.connect(_bridgeProxy.address, _bridgeProxy.signer); - }); - - it("Should not allow depositing zero WETH", async () => { - const revertReason = await getCallRevertReason( - bridgeProxy - .connect(randomSigner) - .deposit( - await randomSigner.getAddress(), - await bridgeProxy.l1WethAddress(), - 0, - 0, - 0, - ethers.constants.AddressZero - ) - ); - - expect(revertReason).equal("Amount cannot be zero"); - }); - - it("Should deposit successfully", async () => { - await l1Weth.connect(randomSigner).deposit({ value: 100 }); - await (await l1Weth.connect(randomSigner).approve(bridgeProxy.address, 100)).wait(); - await bridgeProxy - .connect(randomSigner) - .deposit( - await randomSigner.getAddress(), - l1Weth.address, - 100, - 1000000, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - await randomSigner.getAddress(), - { value: ethers.constants.WeiPerEther } - ); - }); - - it("Should revert on finalizing a withdrawal with wrong message length", async () => { - const revertReason = await getCallRevertReason( - bridgeProxy.connect(randomSigner).finalizeWithdrawal(0, 0, 0, "0x", []) - ); - expect(revertReason).equal("Incorrect ETH message with additional data length"); - }); - - it("Should revert on finalizing a withdrawal with wrong function selector", async () => { - const revertReason = await getCallRevertReason( - bridgeProxy.connect(randomSigner).finalizeWithdrawal(0, 0, 0, ethers.utils.randomBytes(96), []) - ); - expect(revertReason).equal("Incorrect ETH message function selector"); - }); - - it("Should revert on finalizing a withdrawal with wrong receiver", async () => { - const revertReason = await getCallRevertReason( - bridgeProxy - .connect(randomSigner) - .finalizeWithdrawal(0, 0, 0, ethers.utils.hexConcat([functionSignature, ethers.utils.randomBytes(92)]), []) - ); - expect(revertReason).equal("Wrong L1 ETH withdraw receiver"); - }); - - it("Should revert on finalizing a withdrawal with wrong L2 sender", async () => { - const revertReason = await getCallRevertReason( - bridgeProxy - .connect(randomSigner) - .finalizeWithdrawal( - 0, - 0, - 0, - ethers.utils.hexConcat([ - functionSignature, - bridgeProxy.address, - ethers.utils.randomBytes(32), - ethers.utils.randomBytes(40), - ]), - [] - ) - ); - expect(revertReason).equal("The withdrawal was not initiated by L2 bridge"); - }); -}); diff --git a/l1-contracts/test/unit_tests/l2-upgrade.test.spec.ts b/l1-contracts/test/unit_tests/l2-upgrade.test.spec.ts index d06563f86311..e9fa6d566a1d 100644 --- a/l1-contracts/test/unit_tests/l2-upgrade.test.spec.ts +++ b/l1-contracts/test/unit_tests/l2-upgrade.test.spec.ts @@ -1,155 +1,170 @@ import { expect } from "chai"; +import type { BigNumberish } from "ethers"; +import { Wallet } from "ethers"; +import * as ethers from "ethers"; import * as hardhat from "hardhat"; -import { Action, facetCut, diamondCut } from "../../src.ts/diamondCut"; -import type { ExecutorFacet, GettersFacet, AdminFacet } from "../../typechain"; +import { hashBytecode } from "zksync-ethers/build/src/utils"; + +import type { AdminFacet, ExecutorFacet, GettersFacet, StateTransitionManager } from "../../typechain"; import { - DiamondInitFactory, - ExecutorFacetFactory, - GettersFacetFactory, AdminFacetFactory, - DefaultUpgradeFactory, + DummyAdminFacetFactory, CustomUpgradeTestFactory, + DefaultUpgradeFactory, + ExecutorFacetFactory, + GettersFacetFactory, + StateTransitionManagerFactory, } from "../../typechain"; -import type { StoredBatchInfo, CommitBatchInfo } from "./utils"; + +import { L2_BOOTLOADER_BYTECODE_HASH, L2_DEFAULT_ACCOUNT_BYTECODE_HASH } from "../../src.ts/deploy-process"; +import { initialTestnetDeploymentProcess } from "../../src.ts/deploy-test-process"; + +import type { ProposedUpgrade, VerifierParams } from "../../src.ts/utils"; +import { ethTestConfig } from "../../src.ts/utils"; +import { diamondCut, Action, facetCut } from "../../src.ts/diamondCut"; + +import type { CommitBatchInfo, StoredBatchInfo, CommitBatchInfoWithTimestamp } from "./utils"; import { - getCallRevertReason, EMPTY_STRING_KECCAK, - genesisStoredBatchInfo, - L2_SYSTEM_CONTEXT_ADDRESS, L2_BOOTLOADER_ADDRESS, - createSystemLogs, + L2_SYSTEM_CONTEXT_ADDRESS, SYSTEM_LOG_KEYS, constructL2Log, + createSystemLogs, + genesisStoredBatchInfo, + getCallRevertReason, packBatchTimestampAndBatchTimestamp, - defaultFeeParams, + buildL2CanonicalTransaction, + buildCommitBatchInfoWithUpgrade, + makeExecutedEqualCommitted, + getBatchStoredInfo, } from "./utils"; -import * as ethers from "ethers"; -import type { BigNumberish, BytesLike } from "ethers"; -import { REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, hashBytecode } from "zksync-web3/build/src/utils"; - -const SYSTEM_UPGRADE_TX_TYPE = 254; describe("L2 upgrade test", function () { let proxyExecutor: ExecutorFacet; let proxyAdmin: AdminFacet; let proxyGetters: GettersFacet; - let diamondProxyContract: ethers.Contract; + let stateTransitionManager: StateTransitionManager; + let owner: ethers.Signer; - let batch1Info: CommitBatchInfo; - let storedBatch1Info: StoredBatchInfo; + let batch1InfoChainIdUpgrade: CommitBatchInfo; + let storedBatch1InfoChainIdUpgrade: StoredBatchInfo; + + let batch2Info: CommitBatchInfo; + let storedBatch2Info: StoredBatchInfo; let verifier: string; - let verifierParams: VerifierParams; const noopUpgradeTransaction = buildL2CanonicalTransaction({ txType: 0 }); + let chainId = process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID || 270; + // let priorityOperationsHash: string; + let initialProtocolVersion = 0; before(async () => { [owner] = await hardhat.ethers.getSigners(); - const executorFactory = await hardhat.ethers.getContractFactory("ExecutorFacet"); - const executorContract = await executorFactory.deploy(); - const executorFacet = ExecutorFacetFactory.connect(executorContract.address, executorContract.signer); - - const gettersFactory = await hardhat.ethers.getContractFactory("GettersFacet"); - const gettersContract = await gettersFactory.deploy(); - const gettersFacet = GettersFacetFactory.connect(gettersContract.address, gettersContract.signer); - - const adminFacetFactory = await hardhat.ethers.getContractFactory("AdminFacet"); - const adminFacetContract = await adminFacetFactory.deploy(); - const adminFacet = AdminFacetFactory.connect(adminFacetContract.address, adminFacetContract.signer); - - // Note, that while this testsuit is focused on testing MailboxFaucet only, - // we still need to initialize its storage via DiamondProxy - const diamondInitFactory = await hardhat.ethers.getContractFactory("DiamondInit"); - const diamondInitContract = await diamondInitFactory.deploy(); - const diamondInit = DiamondInitFactory.connect(diamondInitContract.address, diamondInitContract.signer); - - const dummyHash = new Uint8Array(32); - dummyHash.set([1, 0, 0, 1]); - verifier = ethers.utils.hexlify(ethers.utils.randomBytes(20)); - verifierParams = { - recursionCircuitsSetVksHash: ethers.constants.HashZero, - recursionLeafLevelVkHash: ethers.constants.HashZero, - recursionNodeLevelVkHash: ethers.constants.HashZero, + const deployWallet = Wallet.fromMnemonic(ethTestConfig.test_mnemonic3, "m/44'/60'/0'/0/1").connect(owner.provider); + const ownerAddress = await deployWallet.getAddress(); + + const gasPrice = await owner.provider.getGasPrice(); + + const tx = { + from: owner.getAddress(), + to: deployWallet.address, + value: ethers.utils.parseEther("1000"), + nonce: owner.getTransactionCount(), + gasLimit: 100000, + gasPrice: gasPrice, }; - const diamondInitData = diamondInit.interface.encodeFunctionData("initialize", [ - { - verifier, - governor: await owner.getAddress(), - admin: await owner.getAddress(), - genesisBatchHash: ethers.constants.HashZero, - genesisIndexRepeatedStorageChanges: 0, - genesisBatchCommitment: ethers.constants.HashZero, - verifierParams, - zkPorterIsAvailable: false, - l2BootloaderBytecodeHash: dummyHash, - l2DefaultAccountBytecodeHash: dummyHash, - priorityTxMaxGasLimit: 10000000, - initialProtocolVersion: 0, - feeParams: defaultFeeParams(), - blobVersionedHashRetriever: ethers.constants.AddressZero, - }, - ]); - const facetCuts = [ - // Should be unfreezable. The function to unfreeze contract is located on the admin facet. - // That means if the admin will be freezable, the proxy can NEVER be unfrozen. - facetCut(adminFacet.address, adminFacet.interface, Action.Add, false), - // Should be unfreezable. There are getters, that users can expect to be available. - facetCut(gettersFacet.address, gettersFacet.interface, Action.Add, false), - facetCut(executorFacet.address, executorFacet.interface, Action.Add, true), - ]; + await owner.sendTransaction(tx); + + const dummyAdminFacetFactory = await hardhat.ethers.getContractFactory("DummyAdminFacet"); + const dummyAdminfFacetContract = await dummyAdminFacetFactory.deploy(); + const extraFacet = facetCut(dummyAdminfFacetContract.address, dummyAdminfFacetContract.interface, Action.Add, true); - const diamondCutData = diamondCut(facetCuts, diamondInit.address, diamondInitData); + const deployer = await initialTestnetDeploymentProcess(deployWallet, ownerAddress, gasPrice, [extraFacet]); + initialProtocolVersion = parseInt(process.env.CONTRACTS_LATEST_PROTOCOL_VERSION); - const diamondProxyFactory = await hardhat.ethers.getContractFactory("DiamondProxy"); - const chainId = hardhat.network.config.chainId; - diamondProxyContract = await diamondProxyFactory.deploy(chainId, diamondCutData); + chainId = deployer.chainId; + verifier = deployer.addresses.StateTransition.Verifier; - proxyExecutor = ExecutorFacetFactory.connect(diamondProxyContract.address, owner); - proxyGetters = GettersFacetFactory.connect(diamondProxyContract.address, owner); - proxyAdmin = AdminFacetFactory.connect(diamondProxyContract.address, owner); + proxyExecutor = ExecutorFacetFactory.connect(deployer.addresses.StateTransition.DiamondProxy, deployWallet); + proxyGetters = GettersFacetFactory.connect(deployer.addresses.StateTransition.DiamondProxy, deployWallet); + proxyAdmin = AdminFacetFactory.connect(deployer.addresses.StateTransition.DiamondProxy, deployWallet); + const dummyAdminFacet = DummyAdminFacetFactory.connect( + deployer.addresses.StateTransition.DiamondProxy, + deployWallet + ); - await (await proxyAdmin.setValidator(await owner.getAddress(), true)).wait(); + stateTransitionManager = StateTransitionManagerFactory.connect( + deployer.addresses.StateTransition.StateTransitionProxy, + deployWallet + ); + + await (await dummyAdminFacet.dummySetValidator(await deployWallet.getAddress())).wait(); + + // do initial setChainIdUpgrade + const upgradeTxHash = await proxyGetters.getL2SystemContractsUpgradeTxHash(); + batch1InfoChainIdUpgrade = await buildCommitBatchInfoWithUpgrade( + genesisStoredBatchInfo(), + { + batchNumber: 1, + priorityOperationsHash: EMPTY_STRING_KECCAK, + numberOfLayer1Txs: "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + upgradeTxHash + ); + + const commitReceipt = await ( + await proxyExecutor.commitBatches(genesisStoredBatchInfo(), [batch1InfoChainIdUpgrade]) + ).wait(); + const commitment = commitReceipt.events[0].args.commitment; + storedBatch1InfoChainIdUpgrade = getBatchStoredInfo(batch1InfoChainIdUpgrade, commitment); + await makeExecutedEqualCommitted(proxyExecutor, genesisStoredBatchInfo(), [storedBatch1InfoChainIdUpgrade], []); }); it("Upgrade should work even if not all batches are processed", async () => { - batch1Info = await buildCommitBatchInfo(genesisStoredBatchInfo(), { - batchNumber: 1, + batch2Info = await buildCommitBatchInfo(storedBatch1InfoChainIdUpgrade, { + batchNumber: 2, + priorityOperationsHash: EMPTY_STRING_KECCAK, + numberOfLayer1Txs: "0x0000000000000000000000000000000000000000000000000000000000000000", }); - const commitReceipt = await (await proxyExecutor.commitBatches(genesisStoredBatchInfo(), [batch1Info])).wait(); + const commitReceipt = await ( + await proxyExecutor.commitBatches(storedBatch1InfoChainIdUpgrade, [batch2Info]) + ).wait(); const commitment = commitReceipt.events[0].args.commitment; - expect(await proxyGetters.getProtocolVersion()).to.equal(0); + expect(await proxyGetters.getProtocolVersion()).to.equal(initialProtocolVersion); expect(await proxyGetters.getL2SystemContractsUpgradeTxHash()).to.equal(ethers.constants.HashZero); await ( - await executeUpgrade(proxyGetters, proxyAdmin, { - newProtocolVersion: 1, + await executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { + newProtocolVersion: 1 + initialProtocolVersion, l2ProtocolUpgradeTx: noopUpgradeTransaction, }) ).wait(); - expect(await proxyGetters.getProtocolVersion()).to.equal(1); + expect(await proxyGetters.getProtocolVersion()).to.equal(1 + initialProtocolVersion); - storedBatch1Info = getBatchStoredInfo(batch1Info, commitment); + storedBatch2Info = getBatchStoredInfo(batch2Info, commitment); - await makeExecutedEqualCommitted(proxyExecutor, genesisStoredBatchInfo(), [storedBatch1Info], []); + await makeExecutedEqualCommitted(proxyExecutor, storedBatch1InfoChainIdUpgrade, [storedBatch2Info], []); }); it("Timestamp should behave correctly", async () => { // Upgrade was scheduled for now should work fine const timeNow = (await hardhat.ethers.provider.getBlock("latest")).timestamp; - await executeUpgrade(proxyGetters, proxyAdmin, { + await executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { upgradeTimestamp: ethers.BigNumber.from(timeNow), l2ProtocolUpgradeTx: noopUpgradeTransaction, }); // Upgrade that was scheduled for the future should not work now const revertReason = await getCallRevertReason( - executeUpgrade(proxyGetters, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { upgradeTimestamp: ethers.BigNumber.from(timeNow).mul(2), l2ProtocolUpgradeTx: noopUpgradeTransaction, }) @@ -162,8 +177,9 @@ describe("L2 upgrade test", function () { txType: 255, }); const revertReason = await getCallRevertReason( - executeUpgrade(proxyGetters, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, + newProtocolVersion: 3 + initialProtocolVersion, }) ); @@ -177,9 +193,9 @@ describe("L2 upgrade test", function () { }); const revertReason = await getCallRevertReason( - executeUpgrade(proxyGetters, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, - newProtocolVersion: 3, + newProtocolVersion: 3 + 1 + initialProtocolVersion, }) ); @@ -193,7 +209,7 @@ describe("L2 upgrade test", function () { }); const revertReason = await getCallRevertReason( - executeUpgrade(proxyGetters, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, newProtocolVersion: 0, }) @@ -209,7 +225,7 @@ describe("L2 upgrade test", function () { }); const revertReason = await getCallRevertReason( - executeUpgrade(proxyGetters, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, newProtocolVersion: 100000, }) @@ -225,9 +241,9 @@ describe("L2 upgrade test", function () { }); const revertReason = await getCallRevertReason( - executeUpgrade(proxyGetters, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, - newProtocolVersion: 3, + newProtocolVersion: 3 + 1 + initialProtocolVersion, }) ); @@ -241,9 +257,9 @@ describe("L2 upgrade test", function () { }); const revertReason = await getCallRevertReason( - executeUpgrade(proxyGetters, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, - newProtocolVersion: 3, + newProtocolVersion: 3 + 1 + initialProtocolVersion, }) ); @@ -258,9 +274,9 @@ describe("L2 upgrade test", function () { }); const revertReason = await getCallRevertReason( - executeUpgrade(proxyGetters, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, - newProtocolVersion: 3, + newProtocolVersion: 3 + 1 + initialProtocolVersion, }) ); @@ -272,14 +288,14 @@ describe("L2 upgrade test", function () { const wrongFactoryDepHash = ethers.utils.hexlify(hashBytecode(ethers.utils.randomBytes(32))); const wrongTx = buildL2CanonicalTransaction({ factoryDeps: [wrongFactoryDepHash], - nonce: 3, + nonce: 3 + 1 + initialProtocolVersion, }); const revertReason = await getCallRevertReason( - executeUpgrade(proxyGetters, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, factoryDeps: [myFactoryDep], - newProtocolVersion: 3, + newProtocolVersion: 3 + 1 + initialProtocolVersion, }) ); @@ -290,14 +306,14 @@ describe("L2 upgrade test", function () { const myFactoryDep = ethers.utils.hexlify(ethers.utils.randomBytes(32)); const wrongTx = buildL2CanonicalTransaction({ factoryDeps: [], - nonce: 3, + nonce: 3 + 1 + initialProtocolVersion, }); const revertReason = await getCallRevertReason( - executeUpgrade(proxyGetters, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, factoryDeps: [myFactoryDep], - newProtocolVersion: 3, + newProtocolVersion: 3 + 1 + initialProtocolVersion, }) ); @@ -310,14 +326,14 @@ describe("L2 upgrade test", function () { const wrongTx = buildL2CanonicalTransaction({ factoryDeps: Array(33).fill(randomDepHash), - nonce: 3, + nonce: 3 + 1 + initialProtocolVersion, }); const revertReason = await getCallRevertReason( - executeUpgrade(proxyGetters, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, factoryDeps: Array(33).fill(myFactoryDep), - newProtocolVersion: 3, + newProtocolVersion: 3 + 1 + initialProtocolVersion, }) ); @@ -339,7 +355,7 @@ describe("L2 upgrade test", function () { const myFactoryDepHash = hashBytecode(myFactoryDep); const upgradeTx = buildL2CanonicalTransaction({ factoryDeps: [myFactoryDepHash], - nonce: 4, + nonce: 4 + 1 + initialProtocolVersion, }); const upgrade = { @@ -350,10 +366,12 @@ describe("L2 upgrade test", function () { executeUpgradeTx: true, l2ProtocolUpgradeTx: upgradeTx, factoryDeps: [myFactoryDep], - newProtocolVersion: 4, + newProtocolVersion: 4 + 1 + initialProtocolVersion, }; - const upgradeReceipt = await (await executeUpgrade(proxyGetters, proxyAdmin, upgrade)).wait(); + const upgradeReceipt = await ( + await executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, upgrade) + ).wait(); const defaultUpgradeFactory = await hardhat.ethers.getContractFactory("DefaultUpgrade"); const upgradeEvents = upgradeReceipt.logs.map((log) => { @@ -366,7 +384,7 @@ describe("L2 upgrade test", function () { args: parsedArgs, }; } catch (_) { - // @ts-ignore + // lint no-empty } }); l2UpgradeTxHash = upgradeEvents.find((event) => event.name == "UpgradeComplete").args.l2UpgradeTxHash; @@ -375,7 +393,7 @@ describe("L2 upgrade test", function () { expect(await proxyGetters.getL2BootloaderBytecodeHash()).to.equal(bootloaderHash); expect(await proxyGetters.getL2DefaultAccountBytecodeHash()).to.equal(defaultAccountHash); expect((await proxyGetters.getVerifier()).toLowerCase()).to.equal(newVerifier.toLowerCase()); - expect(await proxyGetters.getProtocolVersion()).to.equal(4); + expect(await proxyGetters.getProtocolVersion()).to.equal(4 + 1 + initialProtocolVersion); const newVerifierParams = await proxyGetters.getVerifierParams(); expect(newVerifierParams.recursionNodeLevelVkHash).to.equal(newerVerifierParams.recursionNodeLevelVkHash); @@ -383,8 +401,8 @@ describe("L2 upgrade test", function () { expect(newVerifierParams.recursionCircuitsSetVksHash).to.equal(newerVerifierParams.recursionCircuitsSetVksHash); expect(upgradeEvents[0].name).to.eq("NewProtocolVersion"); - expect(upgradeEvents[0].args.previousProtocolVersion.toString()).to.eq("2"); - expect(upgradeEvents[0].args.newProtocolVersion.toString()).to.eq("4"); + expect(upgradeEvents[0].args.previousProtocolVersion.toString()).to.eq((2 + initialProtocolVersion).toString()); + expect(upgradeEvents[0].args.newProtocolVersion.toString()).to.eq((4 + 1 + initialProtocolVersion).toString()); expect(upgradeEvents[1].name).to.eq("NewVerifier"); expect(upgradeEvents[1].args.oldVerifier.toLowerCase()).to.eq(verifier.toLowerCase()); @@ -399,15 +417,11 @@ describe("L2 upgrade test", function () { expect(upgradeEvents[2].args.newVerifierParams[2]).to.eq(newerVerifierParams.recursionCircuitsSetVksHash); expect(upgradeEvents[3].name).to.eq("NewL2BootloaderBytecodeHash"); - expect(upgradeEvents[3].args.previousBytecodeHash).to.eq( - "0x0100000100000000000000000000000000000000000000000000000000000000" - ); + expect(upgradeEvents[3].args.previousBytecodeHash).to.eq(L2_BOOTLOADER_BYTECODE_HASH); expect(upgradeEvents[3].args.newBytecodeHash).to.eq(bootloaderHash); expect(upgradeEvents[4].name).to.eq("NewL2DefaultAccountBytecodeHash"); - expect(upgradeEvents[4].args.previousBytecodeHash).to.eq( - "0x0100000100000000000000000000000000000000000000000000000000000000" - ); + expect(upgradeEvents[4].args.previousBytecodeHash).to.eq(L2_DEFAULT_ACCOUNT_BYTECODE_HASH); expect(upgradeEvents[4].args.newBytecodeHash).to.eq(defaultAccountHash); }); @@ -425,7 +439,7 @@ describe("L2 upgrade test", function () { const myFactoryDepHash = hashBytecode(myFactoryDep); const upgradeTx = buildL2CanonicalTransaction({ factoryDeps: [myFactoryDepHash], - nonce: 4, + nonce: 5 + 1 + initialProtocolVersion, }); const upgrade = { @@ -436,10 +450,12 @@ describe("L2 upgrade test", function () { executeUpgradeTx: true, l2ProtocolUpgradeTx: upgradeTx, factoryDeps: [myFactoryDep], - newProtocolVersion: 5, + newProtocolVersion: 5 + 1 + initialProtocolVersion, }; - const revertReason = await getCallRevertReason(executeUpgrade(proxyGetters, proxyAdmin, upgrade)); - + const revertReason = await getCallRevertReason( + executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, upgrade) + ); + await rollBackToVersion((4 + 1 + initialProtocolVersion).toString(), stateTransitionManager, upgrade); expect(revertReason).to.equal("Previous upgrade has not been finalized"); }); @@ -448,11 +464,11 @@ describe("L2 upgrade test", function () { throw new Error("Can not perform this test without l2UpgradeTxHash"); } - const batch2InfoNoUpgradeTx = await buildCommitBatchInfo(storedBatch1Info, { - batchNumber: 2, + const batch3InfoNoUpgradeTx = await buildCommitBatchInfo(storedBatch2Info, { + batchNumber: 3, }); const revertReason = await getCallRevertReason( - proxyExecutor.commitBatches(storedBatch1Info, [batch2InfoNoUpgradeTx]) + proxyExecutor.commitBatches(storedBatch2Info, [batch3InfoNoUpgradeTx]) ); expect(revertReason).to.equal("b8"); }); @@ -483,18 +499,18 @@ describe("L2 upgrade test", function () { true, L2_SYSTEM_CONTEXT_ADDRESS, SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, - ethers.utils.hexlify(storedBatch1Info.batchHash) + ethers.utils.hexlify(storedBatch2Info.batchHash) ); - const batch2InfoNoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( - storedBatch1Info, + const batch3InfoNoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( + storedBatch2Info, { - batchNumber: 2, + batchNumber: 3, }, systemLogs ); const revertReason = await getCallRevertReason( - proxyExecutor.commitBatches(storedBatch1Info, [batch2InfoNoUpgradeTx]) + proxyExecutor.commitBatches(storedBatch2Info, [batch3InfoNoUpgradeTx]) ); expect(revertReason).to.equal("kp"); }); @@ -514,20 +530,20 @@ describe("L2 upgrade test", function () { true, L2_SYSTEM_CONTEXT_ADDRESS, SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, - ethers.utils.hexlify(storedBatch1Info.batchHash) + ethers.utils.hexlify(storedBatch2Info.batchHash) ); - const batch2InfoTwoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( - storedBatch1Info, + const batch3InfoTwoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( + storedBatch2Info, { - batchNumber: 2, + batchNumber: 3, timestamp, }, systemLogs ); const revertReason = await getCallRevertReason( - proxyExecutor.commitBatches(storedBatch1Info, [batch2InfoTwoUpgradeTx]) + proxyExecutor.commitBatches(storedBatch2Info, [batch3InfoTwoUpgradeTx]) ); expect(revertReason).to.equal("ut"); }); @@ -547,25 +563,25 @@ describe("L2 upgrade test", function () { true, L2_SYSTEM_CONTEXT_ADDRESS, SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, - ethers.utils.hexlify(storedBatch1Info.batchHash) + ethers.utils.hexlify(storedBatch2Info.batchHash) ); - const batch2InfoTwoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( - storedBatch1Info, + const batch3InfoTwoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( + storedBatch2Info, { - batchNumber: 2, + batchNumber: 3, timestamp, }, systemLogs ); - await (await proxyExecutor.commitBatches(storedBatch1Info, [batch2InfoTwoUpgradeTx])).wait(); + await (await proxyExecutor.commitBatches(storedBatch2Info, [batch3InfoTwoUpgradeTx])).wait(); - expect(await proxyGetters.getL2SystemContractsUpgradeBatchNumber()).to.equal(2); + expect(await proxyGetters.getL2SystemContractsUpgradeBatchNumber()).to.equal(3); }); it("Should commit successfully when batch was reverted and reupgraded", async () => { - await (await proxyExecutor.revertBatches(1)).wait(); + await (await proxyExecutor.revertBatches(2)).wait(); const timestamp = (await hardhat.ethers.provider.getBlock("latest")).timestamp; const systemLogs = createSystemLogs(); systemLogs.push( @@ -580,33 +596,33 @@ describe("L2 upgrade test", function () { true, L2_SYSTEM_CONTEXT_ADDRESS, SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, - ethers.utils.hexlify(storedBatch1Info.batchHash) + ethers.utils.hexlify(storedBatch2Info.batchHash) ); - const batch2InfoTwoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( - storedBatch1Info, + const batch3InfoTwoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( + storedBatch2Info, { - batchNumber: 2, + batchNumber: 3, timestamp, }, systemLogs ); - const commitReceipt = await (await proxyExecutor.commitBatches(storedBatch1Info, [batch2InfoTwoUpgradeTx])).wait(); + const commitReceipt = await (await proxyExecutor.commitBatches(storedBatch2Info, [batch3InfoTwoUpgradeTx])).wait(); - expect(await proxyGetters.getL2SystemContractsUpgradeBatchNumber()).to.equal(2); + expect(await proxyGetters.getL2SystemContractsUpgradeBatchNumber()).to.equal(3); const commitment = commitReceipt.events[0].args.commitment; - const newBatchStoredInfo = getBatchStoredInfo(batch2InfoTwoUpgradeTx, commitment); - await makeExecutedEqualCommitted(proxyExecutor, storedBatch1Info, [newBatchStoredInfo], []); + const newBatchStoredInfo = getBatchStoredInfo(batch3InfoTwoUpgradeTx, commitment); + await makeExecutedEqualCommitted(proxyExecutor, storedBatch2Info, [newBatchStoredInfo], []); - storedBatch1Info = newBatchStoredInfo; + storedBatch2Info = newBatchStoredInfo; }); it("Should successfully commit a sequential upgrade", async () => { expect(await proxyGetters.getL2SystemContractsUpgradeBatchNumber()).to.equal(0); await ( - await executeUpgrade(proxyGetters, proxyAdmin, { - newProtocolVersion: 5, + await executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { + newProtocolVersion: 5 + 1 + initialProtocolVersion, l2ProtocolUpgradeTx: noopUpgradeTransaction, }) ).wait(); @@ -617,35 +633,35 @@ describe("L2 upgrade test", function () { true, L2_SYSTEM_CONTEXT_ADDRESS, SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, - ethers.utils.hexlify(storedBatch1Info.batchHash) + ethers.utils.hexlify(storedBatch2Info.batchHash) ); - const batch3InfoTwoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( - storedBatch1Info, + const batch4InfoTwoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( + storedBatch2Info, { - batchNumber: 3, + batchNumber: 4, timestamp, }, systemLogs ); - const commitReceipt = await (await proxyExecutor.commitBatches(storedBatch1Info, [batch3InfoTwoUpgradeTx])).wait(); + const commitReceipt = await (await proxyExecutor.commitBatches(storedBatch2Info, [batch4InfoTwoUpgradeTx])).wait(); const commitment = commitReceipt.events[0].args.commitment; - const newBatchStoredInfo = getBatchStoredInfo(batch3InfoTwoUpgradeTx, commitment); + const newBatchStoredInfo = getBatchStoredInfo(batch4InfoTwoUpgradeTx, commitment); expect(await proxyGetters.getL2SystemContractsUpgradeBatchNumber()).to.equal(0); - await makeExecutedEqualCommitted(proxyExecutor, storedBatch1Info, [newBatchStoredInfo], []); + await makeExecutedEqualCommitted(proxyExecutor, storedBatch2Info, [newBatchStoredInfo], []); - storedBatch1Info = newBatchStoredInfo; + storedBatch2Info = newBatchStoredInfo; expect(await proxyGetters.getL2SystemContractsUpgradeBatchNumber()).to.equal(0); }); it("Should successfully commit custom upgrade", async () => { const upgradeReceipt = await ( - await executeCustomUpgrade(proxyGetters, proxyAdmin, { - newProtocolVersion: 6, + await executeCustomUpgrade(chainId, proxyGetters, proxyAdmin, stateTransitionManager, { + newProtocolVersion: 6 + 1 + initialProtocolVersion, l2ProtocolUpgradeTx: noopUpgradeTransaction, }) ).wait(); @@ -671,40 +687,36 @@ describe("L2 upgrade test", function () { true, L2_SYSTEM_CONTEXT_ADDRESS, SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, - ethers.utils.hexlify(storedBatch1Info.batchHash) + ethers.utils.hexlify(storedBatch2Info.batchHash) ); - const batch3InfoTwoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( - storedBatch1Info, + const batch5InfoTwoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( + storedBatch2Info, { - batchNumber: 4, + batchNumber: 5, timestamp, }, systemLogs ); - const commitReceipt = await (await proxyExecutor.commitBatches(storedBatch1Info, [batch3InfoTwoUpgradeTx])).wait(); + const commitReceipt = await (await proxyExecutor.commitBatches(storedBatch2Info, [batch5InfoTwoUpgradeTx])).wait(); const commitment = commitReceipt.events[0].args.commitment; - const newBatchStoredInfo = getBatchStoredInfo(batch3InfoTwoUpgradeTx, commitment); + const newBatchStoredInfo = getBatchStoredInfo(batch5InfoTwoUpgradeTx, commitment); - await makeExecutedEqualCommitted(proxyExecutor, storedBatch1Info, [newBatchStoredInfo], []); + await makeExecutedEqualCommitted(proxyExecutor, storedBatch2Info, [newBatchStoredInfo], []); - storedBatch1Info = newBatchStoredInfo; + storedBatch2Info = newBatchStoredInfo; expect(upgradeEvents[1].name).to.equal("Test"); }); }); -type CommitBatchInfoWithTimestamp = Partial & { - batchNumber: BigNumberish; -}; - async function buildCommitBatchInfo( prevInfo: StoredBatchInfo, info: CommitBatchInfoWithTimestamp ): Promise { const timestamp = info.timestamp || (await hardhat.ethers.provider.getBlock("latest")).timestamp; - const systemLogs = createSystemLogs(); + const systemLogs = createSystemLogs(info.priorityOperationsHash, info.numberOfLayer1Txs, prevInfo.batchHash); systemLogs[SYSTEM_LOG_KEYS.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY] = constructL2Log( true, L2_SYSTEM_CONTEXT_ADDRESS, @@ -753,76 +765,6 @@ async function buildCommitBatchInfoWithCustomLogs( }; } -function getBatchStoredInfo(commitInfo: CommitBatchInfo, commitment: string): StoredBatchInfo { - return { - batchNumber: commitInfo.batchNumber, - batchHash: commitInfo.newStateRoot, - indexRepeatedStorageChanges: commitInfo.indexRepeatedStorageChanges, - numberOfLayer1Txs: commitInfo.numberOfLayer1Txs, - priorityOperationsHash: commitInfo.priorityOperationsHash, - l2LogsTreeRoot: ethers.constants.HashZero, - timestamp: commitInfo.timestamp, - commitment: commitment, - }; -} - -interface L2CanonicalTransaction { - txType: BigNumberish; - from: BigNumberish; - to: BigNumberish; - gasLimit: BigNumberish; - gasPerPubdataByteLimit: BigNumberish; - maxFeePerGas: BigNumberish; - maxPriorityFeePerGas: BigNumberish; - paymaster: BigNumberish; - nonce: BigNumberish; - value: BigNumberish; - // In the future, we might want to add some - // new fields to the struct. The `txData` struct - // is to be passed to account and any changes to its structure - // would mean a breaking change to these accounts. In order to prevent this, - // we should keep some fields as "reserved". - // It is also recommended that their length is fixed, since - // it would allow easier proof integration (in case we will need - // some special circuit for preprocessing transactions). - reserved: [BigNumberish, BigNumberish, BigNumberish, BigNumberish]; - data: BytesLike; - signature: BytesLike; - factoryDeps: BigNumberish[]; - paymasterInput: BytesLike; - // Reserved dynamic type for the future use-case. Using it should be avoided, - // But it is still here, just in case we want to enable some additional functionality. - reservedDynamic: BytesLike; -} - -function buildL2CanonicalTransaction(tx: Partial): L2CanonicalTransaction { - return { - txType: SYSTEM_UPGRADE_TX_TYPE, - from: ethers.constants.AddressZero, - to: ethers.constants.AddressZero, - gasLimit: 5000000, - gasPerPubdataByteLimit: REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, - maxFeePerGas: 0, - maxPriorityFeePerGas: 0, - paymaster: 0, - nonce: 0, - value: 0, - reserved: [0, 0, 0, 0], - data: "0x", - signature: "0x", - factoryDeps: [], - paymasterInput: "0x", - reservedDynamic: "0x", - ...tx, - }; -} - -interface VerifierParams { - recursionNodeLevelVkHash: BytesLike; - recursionLeafLevelVkHash: BytesLike; - recursionCircuitsSetVksHash: BytesLike; -} - function buildVerifierParams(params: Partial): VerifierParams { return { recursionNodeLevelVkHash: ethers.constants.HashZero, @@ -832,29 +774,12 @@ function buildVerifierParams(params: Partial): VerifierParams { }; } -interface ProposedUpgrade { - // The tx for the upgrade call to the l2 system upgrade contract - l2ProtocolUpgradeTx: L2CanonicalTransaction; - factoryDeps: BytesLike[]; - executeUpgradeTx: boolean; - bootloaderHash: BytesLike; - defaultAccountHash: BytesLike; - verifier: string; - verifierParams: VerifierParams; - l1ContractsUpgradeCalldata: BytesLike; - postUpgradeCalldata: BytesLike; - upgradeTimestamp: ethers.BigNumber; - newProtocolVersion: BigNumberish; - newAllowList: string; -} - type PartialProposedUpgrade = Partial; function buildProposeUpgrade(proposedUpgrade: PartialProposedUpgrade): ProposedUpgrade { const newProtocolVersion = proposedUpgrade.newProtocolVersion || 0; return { l2ProtocolUpgradeTx: buildL2CanonicalTransaction({ nonce: newProtocolVersion }), - executeUpgradeTx: false, bootloaderHash: ethers.constants.HashZero, defaultAccountHash: ethers.constants.HashZero, verifier: ethers.constants.AddressZero, @@ -864,13 +789,14 @@ function buildProposeUpgrade(proposedUpgrade: PartialProposedUpgrade): ProposedU upgradeTimestamp: ethers.constants.Zero, factoryDeps: [], newProtocolVersion, - newAllowList: ethers.constants.AddressZero, ...proposedUpgrade, }; } async function executeUpgrade( + chainId: BigNumberish, proxyGetters: GettersFacet, + stateTransitionManager: StateTransitionManager, proxyAdmin: AdminFacet, partialUpgrade: Partial, contractFactory?: ethers.ethers.ContractFactory @@ -892,13 +818,52 @@ async function executeUpgrade( const diamondCutData = diamondCut([], diamondUpgradeInit.address, upgradeCalldata); + const oldProtocolVersion = await proxyGetters.getProtocolVersion(); // This promise will be handled in the tests - return proxyAdmin.executeUpgrade(diamondCutData); + ( + await stateTransitionManager.setNewVersionUpgrade( + diamondCutData, + oldProtocolVersion, + partialUpgrade.newProtocolVersion + ) + ).wait(); + return proxyAdmin.upgradeChainFromVersion(oldProtocolVersion, diamondCutData); +} + +// we rollback the protocolVersion ( we don't clear the upgradeHash mapping, but thats ok) +async function rollBackToVersion( + protocolVersion: string, + stateTransition: StateTransitionManager, + partialUpgrade: Partial +) { + partialUpgrade.newProtocolVersion = protocolVersion; + + const upgrade = buildProposeUpgrade(partialUpgrade); + + const defaultUpgradeFactory = await hardhat.ethers.getContractFactory("DefaultUpgrade"); + + const defaultUpgrade = await defaultUpgradeFactory.deploy(); + const diamondUpgradeInit = DefaultUpgradeFactory.connect(defaultUpgrade.address, defaultUpgrade.signer); + + const upgradeCalldata = diamondUpgradeInit.interface.encodeFunctionData("upgrade", [upgrade]); + + const diamondCutData = diamondCut([], diamondUpgradeInit.address, upgradeCalldata); + + // This promise will be handled in the tests + ( + await stateTransition.setNewVersionUpgrade( + diamondCutData, + (parseInt(protocolVersion) - 1).toString(), + protocolVersion + ) + ).wait(); } async function executeCustomUpgrade( + chainId: BigNumberish, proxyGetters: GettersFacet, proxyAdmin: AdminFacet, + stateTransition: StateTransitionManager, partialUpgrade: Partial, contractFactory?: ethers.ethers.ContractFactory ) { @@ -918,25 +883,11 @@ async function executeCustomUpgrade( const upgradeCalldata = diamondUpgradeInit.interface.encodeFunctionData("upgrade", [upgrade]); const diamondCutData = diamondCut([], diamondUpgradeInit.address, upgradeCalldata); + const oldProtocolVersion = await proxyGetters.getProtocolVersion(); // This promise will be handled in the tests - return proxyAdmin.executeUpgrade(diamondCutData); -} - -async function makeExecutedEqualCommitted( - proxyExecutor: ExecutorFacet, - prevBatchInfo: StoredBatchInfo, - batchesToProve: StoredBatchInfo[], - batchesToExecute: StoredBatchInfo[] -) { - batchesToExecute = [...batchesToProve, ...batchesToExecute]; - - await ( - await proxyExecutor.proveBatches(prevBatchInfo, batchesToProve, { - recursiveAggregationInput: [], - serializedProof: [], - }) + ( + await stateTransition.setNewVersionUpgrade(diamondCutData, oldProtocolVersion, partialUpgrade.newProtocolVersion) ).wait(); - - await (await proxyExecutor.executeBatches(batchesToExecute)).wait(); + return proxyAdmin.upgradeChainFromVersion(oldProtocolVersion, diamondCutData); } diff --git a/l1-contracts/test/unit_tests/legacy_era_test.spec.ts b/l1-contracts/test/unit_tests/legacy_era_test.spec.ts new file mode 100644 index 000000000000..46134d848dd6 --- /dev/null +++ b/l1-contracts/test/unit_tests/legacy_era_test.spec.ts @@ -0,0 +1,321 @@ +import { expect } from "chai"; +import { ethers, Wallet } from "ethers"; +import * as hardhat from "hardhat"; +import { Interface } from "ethers/lib/utils"; + +import type { Bridgehub, L1SharedBridge, GettersFacet, MockExecutorFacet } from "../../typechain"; +import { + L1SharedBridgeFactory, + BridgehubFactory, + TestnetERC20TokenFactory, + MailboxFacetFactory, + GettersFacetFactory, + MockExecutorFacetFactory, +} from "../../typechain"; +import type { IL1ERC20Bridge } from "../../typechain/IL1ERC20Bridge"; +import { IL1ERC20BridgeFactory } from "../../typechain/IL1ERC20BridgeFactory"; +import type { IMailbox } from "../../typechain/IMailbox"; + +import { ADDRESS_ONE, ethTestConfig } from "../../src.ts/utils"; +import { Action, facetCut } from "../../src.ts/diamondCut"; +import { getTokens } from "../../src.ts/deploy-token"; +import type { Deployer } from "../../src.ts/deploy"; +import { initialEraTestnetDeploymentProcess } from "../../src.ts/deploy-test-process"; + +import { + depositERC20, + L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, + L2_TO_L1_MESSENGER, + getCallRevertReason, + requestExecuteDirect, +} from "./utils"; + +describe("Legacy Era tests", function () { + let owner: ethers.Signer; + let randomSigner: ethers.Signer; + let deployWallet: Wallet; + let deployer: Deployer; + let l1ERC20BridgeAddress: string; + let l1ERC20Bridge: IL1ERC20Bridge; + let sharedBridgeProxy: L1SharedBridge; + let erc20TestToken: ethers.Contract; + let bridgehub: Bridgehub; + let chainId = "9"; // Hardhat config ERA_CHAIN_ID + const functionSignature = "0x11a2ccc1"; + + let mailbox: IMailbox; + let getter: GettersFacet; + let proxyAsMockExecutor: MockExecutorFacet; + const MAX_CODE_LEN_WORDS = (1 << 16) - 1; + const MAX_CODE_LEN_BYTES = MAX_CODE_LEN_WORDS * 32; + + before(async () => { + [owner, randomSigner] = await hardhat.ethers.getSigners(); + + const gasPrice = await owner.provider.getGasPrice(); + // WARNING!!! WARNING!!!WARNING!!!WARNING!!!WARNING!!!WARNING!!!WARNING!!!WARNING!!!WARNING!!! + // create2Address and Era diamond proxy address can be the same one as fixed in hardhat config + deployWallet = Wallet.fromMnemonic(ethTestConfig.test_mnemonic3, "m/44'/60'/0'/0/1").connect(owner.provider); + const ownerAddress = await deployWallet.getAddress(); + // process.env.ETH_CLIENT_CHAIN_ID = (await deployWallet.getChainId()).toString(); + + const tx = { + from: owner.getAddress(), + to: deployWallet.address, + value: ethers.utils.parseEther("1000"), + nonce: owner.getTransactionCount(), + gasLimit: 100000, + gasPrice: gasPrice, + }; + + await owner.sendTransaction(tx); + + const mockExecutorFactory = await hardhat.ethers.getContractFactory("MockExecutorFacet"); + const mockExecutorContract = await mockExecutorFactory.deploy(); + const extraFacet = facetCut(mockExecutorContract.address, mockExecutorContract.interface, Action.Add, true); + + deployer = await initialEraTestnetDeploymentProcess(deployWallet, ownerAddress, gasPrice, [extraFacet]); + chainId = deployer.chainId.toString(); + + bridgehub = BridgehubFactory.connect(deployer.addresses.Bridgehub.BridgehubProxy, deployWallet); + + l1ERC20BridgeAddress = deployer.addresses.Bridges.ERC20BridgeProxy; + + l1ERC20Bridge = IL1ERC20BridgeFactory.connect(l1ERC20BridgeAddress, deployWallet); + sharedBridgeProxy = L1SharedBridgeFactory.connect(deployer.addresses.Bridges.SharedBridgeProxy, deployWallet); + + const tokens = getTokens(); + const tokenAddress = tokens.find((token: { symbol: string }) => token.symbol == "DAI")!.address; + erc20TestToken = TestnetERC20TokenFactory.connect(tokenAddress, owner); + + await erc20TestToken.mint(await randomSigner.getAddress(), ethers.utils.parseUnits("10000", 18)); + await erc20TestToken.connect(randomSigner).approve(l1ERC20BridgeAddress, ethers.utils.parseUnits("10000", 18)); + + // The L1SharedBridge is compiled with the ERA_DIAMOND_PROXY address in hardhat config, so we update the shared bridge + // because all the varaibles are already set in the proxy, this is upgrade works. + const sharedBridgeTestFactory = await hardhat.ethers.getContractFactory("L1SharedBridgeTest"); + const l1WethToken = tokens.find((token: { symbol: string }) => token.symbol == "WETH")!.address; + const sharedBridgeTest = await sharedBridgeTestFactory.deploy( + deployer.addresses.StateTransition.DiamondProxy, + l1WethToken, + deployer.addresses.Bridgehub.BridgehubProxy, + deployer.addresses.Bridges.ERC20BridgeProxy + ); + + const proxyAdminInterface = new Interface(hardhat.artifacts.readArtifactSync("ProxyAdmin").abi); + const calldata = proxyAdminInterface.encodeFunctionData("upgrade(address,address)", [ + deployer.addresses.Bridges.SharedBridgeProxy, + sharedBridgeTest.address, + ]); + + await deployer.executeUpgrade(deployer.addresses.TransparentProxyAdmin, 0, calldata); + if (deployer.verbose) { + console.log("L1SharedBridge upgrade sent for testing"); + } + + mailbox = MailboxFacetFactory.connect(deployer.addresses.StateTransition.DiamondProxy, deployWallet); + getter = GettersFacetFactory.connect(deployer.addresses.StateTransition.DiamondProxy, deployWallet); + + proxyAsMockExecutor = MockExecutorFacetFactory.connect( + deployer.addresses.StateTransition.DiamondProxy, + mockExecutorContract.signer + ); + }); + + it("Check should initialize through governance", async () => { + const l1SharedBridgeInterface = new Interface(hardhat.artifacts.readArtifactSync("L1SharedBridge").abi); + const upgradeCall = l1SharedBridgeInterface.encodeFunctionData("initializeChainGovernance(uint256,address)", [ + chainId, + ADDRESS_ONE, + ]); + + const txHash = await deployer.executeUpgrade(sharedBridgeProxy.address, 0, upgradeCall); + + expect(txHash).not.equal(ethers.constants.HashZero); + }); + + it("Should not allow depositing zero amount", async () => { + const revertReason = await getCallRevertReason( + l1ERC20Bridge.connect(randomSigner)[ + // solhint-disable-next-line no-unexpected-multiline + "deposit(address,address,uint256,uint256,uint256,address)" + ](await randomSigner.getAddress(), erc20TestToken.address, 0, 0, 0, ethers.constants.AddressZero) + ); + expect(revertReason).equal("0T"); + }); + + it("Should deposit successfully", async () => { + const depositorAddress = await randomSigner.getAddress(); + await depositERC20( + l1ERC20Bridge.connect(randomSigner), + bridgehub, + chainId, + depositorAddress, + erc20TestToken.address, + ethers.utils.parseUnits("800", 18), + 10000000 + ); + }); + + it("Should revert on finalizing a withdrawal with wrong message length", async () => { + const revertReason = await getCallRevertReason( + l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(0, 0, 0, "0x", [ethers.constants.HashZero]) + ); + expect(revertReason).equal("ShB wrong msg len"); + }); + + it("Should revert on finalizing a withdrawal with wrong function signature", async () => { + const revertReason = await getCallRevertReason( + l1ERC20Bridge + .connect(randomSigner) + .finalizeWithdrawal(0, 0, 0, ethers.utils.randomBytes(76), [ethers.constants.HashZero]) + ); + expect(revertReason).equal("ShB Incorrect message function selector"); + }); + + it("Should revert on finalizing a withdrawal with wrong batch number", async () => { + const l1Receiver = await randomSigner.getAddress(); + const l2ToL1message = ethers.utils.hexConcat([ + functionSignature, + l1Receiver, + erc20TestToken.address, + ethers.constants.HashZero, + ]); + const revertReason = await getCallRevertReason( + l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(10, 0, 0, l2ToL1message, []) + ); + expect(revertReason).equal("xx"); + }); + + it("Should revert on finalizing a withdrawal with wrong length of proof", async () => { + const l1Receiver = await randomSigner.getAddress(); + const l2ToL1message = ethers.utils.hexConcat([ + functionSignature, + l1Receiver, + erc20TestToken.address, + ethers.constants.HashZero, + ]); + const revertReason = await getCallRevertReason( + l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(0, 0, 0, l2ToL1message, []) + ); + expect(revertReason).equal("xc"); + }); + + it("Should revert on finalizing a withdrawal with wrong proof", async () => { + const l1Receiver = await randomSigner.getAddress(); + const l2ToL1message = ethers.utils.hexConcat([ + functionSignature, + l1Receiver, + erc20TestToken.address, + ethers.constants.HashZero, + ]); + const revertReason = await getCallRevertReason( + l1ERC20Bridge + .connect(randomSigner) + .finalizeWithdrawal(0, 0, 0, l2ToL1message, Array(9).fill(ethers.constants.HashZero)) + ); + expect(revertReason).equal("ShB withd w proof"); + }); + + /////////// Mailbox. Note we have these two together because we need to fix ERA Diamond proxy Address + + /// we have this here as calling through the bridgehub does not work + it("Should not accept bytecode that is too long", async () => { + const revertReason = await getCallRevertReason( + requestExecuteDirect( + mailbox, + ethers.constants.AddressZero, + ethers.BigNumber.from(0), + "0x", + ethers.BigNumber.from(100000), + [ + // "+64" to keep the length in words odd and bytecode chunkable + new Uint8Array(MAX_CODE_LEN_BYTES + 64), + ], + ethers.constants.AddressZero + ) + ); + + expect(revertReason).equal("pp"); + }); + + describe("finalizeEthWithdrawal", function () { + const BLOCK_NUMBER = 0; + const MESSAGE_INDEX = 0; + const TX_NUMBER_IN_BLOCK = 0; + const L1_RECEIVER = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; + const AMOUNT = 1; + + const MESSAGE = + "0x6c0960f9d8dA6BF26964aF9D7eEd9e03E53415D37aA960450000000000000000000000000000000000000000000000000000000000000001"; + const MESSAGE_HASH = ethers.utils.keccak256(MESSAGE); + const key = ethers.utils.hexZeroPad(L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, 32); + const HASHED_LOG = ethers.utils.solidityKeccak256( + ["uint8", "bool", "uint16", "address", "bytes32", "bytes32"], + [0, true, TX_NUMBER_IN_BLOCK, L2_TO_L1_MESSENGER, key, MESSAGE_HASH] + ); + + const MERKLE_PROOF = [ + "0x72abee45b59e344af8a6e520241c4744aff26ed411f4c4b00f8af09adada43ba", + "0xc3d03eebfd83049991ea3d3e358b6712e7aa2e2e63dc2d4b438987cec28ac8d0", + "0xe3697c7f33c31a9b0f0aeb8542287d0d21e8c4cf82163d0c44c7a98aa11aa111", + "0x199cc5812543ddceeddd0fc82807646a4899444240db2c0d2f20c3cceb5f51fa", + "0xe4733f281f18ba3ea8775dd62d2fcd84011c8c938f16ea5790fd29a03bf8db89", + "0x1798a1fd9c8fbb818c98cff190daa7cc10b6e5ac9716b4a2649f7c2ebcef2272", + "0x66d7c5983afe44cf15ea8cf565b34c6c31ff0cb4dd744524f7842b942d08770d", + "0xb04e5ee349086985f74b73971ce9dfe76bbed95c84906c5dffd96504e1e5396c", + "0xac506ecb5465659b3a927143f6d724f91d8d9c4bdb2463aee111d9aa869874db", + ]; + + let L2_LOGS_TREE_ROOT = HASHED_LOG; + for (let i = 0; i < MERKLE_PROOF.length; i++) { + L2_LOGS_TREE_ROOT = ethers.utils.keccak256(L2_LOGS_TREE_ROOT + MERKLE_PROOF[i].slice(2)); + } + + before(async () => { + await proxyAsMockExecutor.saveL2LogsRootHash(BLOCK_NUMBER, L2_LOGS_TREE_ROOT); + }); + + it("Reverts when proof is invalid", async () => { + const invalidProof = [...MERKLE_PROOF]; + invalidProof[0] = "0x72abee45b59e344af8a6e520241c4744aff26ed411f4c4b00f8af09adada43bb"; + + const revertReason = await getCallRevertReason( + mailbox.finalizeEthWithdrawal(BLOCK_NUMBER, MESSAGE_INDEX, TX_NUMBER_IN_BLOCK, MESSAGE, invalidProof) + ); + expect(revertReason).equal("ShB withd w proof"); + }); + + it("Successful deposit", async () => { + const priorityQueueLengthBefore = await getter.getPriorityQueueSize(); + const amount = ethers.utils.parseEther("1"); + await requestExecuteDirect( + mailbox, + ethers.constants.AddressZero, + ethers.BigNumber.from(2000000), + "0x", + ethers.BigNumber.from(1000000), + [], + ethers.constants.AddressZero, + amount + ); + const priorityQueueLengthAfter = await getter.getPriorityQueueSize(); + expect(priorityQueueLengthAfter.sub(priorityQueueLengthBefore)).equal(1); + }); + + it("Successful withdrawal", async () => { + const balanceBefore = await hardhat.ethers.provider.getBalance(L1_RECEIVER); + + await mailbox.finalizeEthWithdrawal(BLOCK_NUMBER, MESSAGE_INDEX, TX_NUMBER_IN_BLOCK, MESSAGE, MERKLE_PROOF); + const balanceAfter = await hardhat.ethers.provider.getBalance(L1_RECEIVER); + expect(balanceAfter.sub(balanceBefore)).equal(AMOUNT); + }); + + it("Reverts when withdrawal is already finalized", async () => { + const revertReason = await getCallRevertReason( + mailbox.finalizeEthWithdrawal(BLOCK_NUMBER, MESSAGE_INDEX, TX_NUMBER_IN_BLOCK, MESSAGE, MERKLE_PROOF) + ); + expect(revertReason).equal("Withdrawal is already finalized"); + }); + }); +}); diff --git a/l1-contracts/test/unit_tests/mailbox_test.spec.ts b/l1-contracts/test/unit_tests/mailbox_test.spec.ts index 6feb0eb6bd8d..a73b6b661695 100644 --- a/l1-contracts/test/unit_tests/mailbox_test.spec.ts +++ b/l1-contracts/test/unit_tests/mailbox_test.spec.ts @@ -1,91 +1,74 @@ import { expect } from "chai"; +import * as ethers from "ethers"; +import { Wallet } from "ethers"; import * as hardhat from "hardhat"; -import { Action, facetCut, diamondCut } from "../../src.ts/diamondCut"; -import type { MailboxFacet, MockExecutorFacet, Forwarder, MailboxFacetTest } from "../../typechain"; + +import type { Bridgehub, Forwarder, MailboxFacetTest, MockExecutorFacet } from "../../typechain"; import { - MailboxFacetTestFactory, + BridgehubFactory, + ForwarderFactory, MailboxFacetFactory, + MailboxFacetTestFactory, MockExecutorFacetFactory, - DiamondInitFactory, - ForwarderFactory, } from "../../typechain"; +import type { IMailbox } from "../../typechain/IMailbox"; + +import { PubdataPricingMode, ethTestConfig } from "../../src.ts/utils"; +import { initialTestnetDeploymentProcess } from "../../src.ts/deploy-test-process"; +import { Action, facetCut } from "../../src.ts/diamondCut"; + import { DEFAULT_REVERT_REASON, - getCallRevertReason, + L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, + L2_TO_L1_MESSENGER, REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - requestExecute, defaultFeeParams, - PubdataPricingMode, + getCallRevertReason, + requestExecute, } from "./utils"; -import * as ethers from "ethers"; describe("Mailbox tests", function () { - let mailbox: MailboxFacet; + let mailbox: IMailbox; let proxyAsMockExecutor: MockExecutorFacet; - let diamondProxyContract: ethers.Contract; + let bridgehub: Bridgehub; let owner: ethers.Signer; - const MAX_CODE_LEN_WORDS = (1 << 16) - 1; - const MAX_CODE_LEN_BYTES = MAX_CODE_LEN_WORDS * 32; let forwarder: Forwarder; + let chainId = process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID || 270; before(async () => { [owner] = await hardhat.ethers.getSigners(); - const mailboxFactory = await hardhat.ethers.getContractFactory("MailboxFacet"); - const mailboxContract = await mailboxFactory.deploy(); - const mailboxFacet = MailboxFacetFactory.connect(mailboxContract.address, mailboxContract.signer); + const deployWallet = Wallet.fromMnemonic(ethTestConfig.test_mnemonic3, "m/44'/60'/0'/0/1").connect(owner.provider); + const ownerAddress = await deployWallet.getAddress(); + + const gasPrice = await owner.provider.getGasPrice(); + + const tx = { + from: await owner.getAddress(), + to: deployWallet.address, + value: ethers.utils.parseEther("1000"), + nonce: owner.getTransactionCount(), + gasLimit: 100000, + gasPrice: gasPrice, + }; + + await owner.sendTransaction(tx); const mockExecutorFactory = await hardhat.ethers.getContractFactory("MockExecutorFacet"); const mockExecutorContract = await mockExecutorFactory.deploy(); - const mockExecutorFacet = MockExecutorFacetFactory.connect( - mockExecutorContract.address, - mockExecutorContract.signer - ); + const extraFacet = facetCut(mockExecutorContract.address, mockExecutorContract.interface, Action.Add, true); - // Note, that while this testsuit is focused on testing MailboxFaucet only, - // we still need to initialize its storage via DiamondProxy - const diamondInitFactory = await hardhat.ethers.getContractFactory("DiamondInit"); - const diamondInitContract = await diamondInitFactory.deploy(); - const diamondInit = DiamondInitFactory.connect(diamondInitContract.address, diamondInitContract.signer); - - const dummyHash = new Uint8Array(32); - dummyHash.set([1, 0, 0, 1]); - const dummyAddress = ethers.utils.hexlify(ethers.utils.randomBytes(20)); - const diamondInitData = diamondInit.interface.encodeFunctionData("initialize", [ - { - verifier: dummyAddress, - governor: dummyAddress, - admin: dummyAddress, - genesisBatchHash: ethers.constants.HashZero, - genesisIndexRepeatedStorageChanges: 0, - genesisBatchCommitment: ethers.constants.HashZero, - verifierParams: { - recursionCircuitsSetVksHash: ethers.constants.HashZero, - recursionLeafLevelVkHash: ethers.constants.HashZero, - recursionNodeLevelVkHash: ethers.constants.HashZero, - }, - zkPorterIsAvailable: false, - l2BootloaderBytecodeHash: dummyHash, - l2DefaultAccountBytecodeHash: dummyHash, - priorityTxMaxGasLimit: 10000000, - initialProtocolVersion: 0, - feeParams: defaultFeeParams(), - blobVersionedHashRetriever: ethers.constants.AddressZero, - }, - ]); - - const facetCuts = [ - facetCut(mailboxFacet.address, mailboxFacet.interface, Action.Add, false), - facetCut(mockExecutorFacet.address, mockExecutorFacet.interface, Action.Add, false), - ]; - const diamondCutData = diamondCut(facetCuts, diamondInit.address, diamondInitData); + const deployer = await initialTestnetDeploymentProcess(deployWallet, ownerAddress, gasPrice, [extraFacet]); + + chainId = deployer.chainId; - const diamondProxyFactory = await hardhat.ethers.getContractFactory("DiamondProxy"); - const chainId = hardhat.network.config.chainId; - diamondProxyContract = await diamondProxyFactory.deploy(chainId, diamondCutData); + bridgehub = BridgehubFactory.connect(deployer.addresses.Bridgehub.BridgehubProxy, deployWallet); + mailbox = MailboxFacetFactory.connect(deployer.addresses.StateTransition.DiamondProxy, deployWallet); - mailbox = MailboxFacetFactory.connect(diamondProxyContract.address, mailboxContract.signer); - proxyAsMockExecutor = MockExecutorFacetFactory.connect(diamondProxyContract.address, mockExecutorContract.signer); + proxyAsMockExecutor = MockExecutorFacetFactory.connect( + deployer.addresses.StateTransition.DiamondProxy, + mockExecutorContract.signer + ); const forwarderFactory = await hardhat.ethers.getContractFactory("Forwarder"); const forwarderContract = await forwarderFactory.deploy(); @@ -95,7 +78,8 @@ describe("Mailbox tests", function () { it("Should accept correctly formatted bytecode", async () => { const revertReason = await getCallRevertReason( requestExecute( - mailbox, + chainId, + bridgehub, ethers.constants.AddressZero, ethers.BigNumber.from(0), "0x", @@ -104,14 +88,14 @@ describe("Mailbox tests", function () { ethers.constants.AddressZero ) ); - expect(revertReason).equal(DEFAULT_REVERT_REASON); }); it("Should not accept bytecode is not chunkable", async () => { const revertReason = await getCallRevertReason( requestExecute( - mailbox, + chainId, + bridgehub, ethers.constants.AddressZero, ethers.BigNumber.from(0), "0x", @@ -127,7 +111,8 @@ describe("Mailbox tests", function () { it("Should not accept bytecode of even length in words", async () => { const revertReason = await getCallRevertReason( requestExecute( - mailbox, + chainId, + bridgehub, ethers.constants.AddressZero, ethers.BigNumber.from(0), "0x", @@ -140,36 +125,20 @@ describe("Mailbox tests", function () { expect(revertReason).equal("ps"); }); - it("Should not accept bytecode that is too long", async () => { - const revertReason = await getCallRevertReason( - requestExecute( - mailbox, - ethers.constants.AddressZero, - ethers.BigNumber.from(0), - "0x", - ethers.BigNumber.from(100000), - [ - // "+64" to keep the length in words odd and bytecode chunkable - new Uint8Array(MAX_CODE_LEN_BYTES + 64), - ], - ethers.constants.AddressZero - ) - ); - - expect(revertReason).equal("pp"); - }); - describe("finalizeEthWithdrawal", function () { - const BLOCK_NUMBER = 1; + const BLOCK_NUMBER = 0; const MESSAGE_INDEX = 0; const TX_NUMBER_IN_BLOCK = 0; - const L1_RECEIVER = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; - const AMOUNT = 1; + const MESSAGE = "0x6c0960f9d8dA6BF26964aF9D7eEd9e03E53415D37aA960450000000000000000000000000000000000000000000000000000000000000001"; - // MESSAGE_HASH = 0xf55ef1c502bb79468b8ffe79955af4557a068ec4894e2207010866b182445c52 - // HASHED_LOG = 0x110c937a27f7372384781fe744c2e971daa9556b1810f2edea90fb8b507f84b1 - const L2_LOGS_TREE_ROOT = "0xfa6b5a02c911a05e9dfe9e03f6dedb9cd30795bbac2aaf5bdd2632d2671a7e3d"; + const MESSAGE_HASH = ethers.utils.keccak256(MESSAGE); + const key = ethers.utils.hexZeroPad(L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, 32); + const HASHED_LOG = ethers.utils.solidityKeccak256( + ["uint8", "bool", "uint16", "address", "bytes32", "bytes32"], + [0, true, TX_NUMBER_IN_BLOCK, L2_TO_L1_MESSENGER, key, MESSAGE_HASH] + ); + const MERKLE_PROOF = [ "0x72abee45b59e344af8a6e520241c4744aff26ed411f4c4b00f8af09adada43ba", "0xc3d03eebfd83049991ea3d3e358b6712e7aa2e2e63dc2d4b438987cec28ac8d0", @@ -182,6 +151,11 @@ describe("Mailbox tests", function () { "0xac506ecb5465659b3a927143f6d724f91d8d9c4bdb2463aee111d9aa869874db", ]; + let L2_LOGS_TREE_ROOT = HASHED_LOG; + for (let i = 0; i < MERKLE_PROOF.length; i++) { + L2_LOGS_TREE_ROOT = ethers.utils.keccak256(L2_LOGS_TREE_ROOT + MERKLE_PROOF[i].slice(2)); + } + before(async () => { await proxyAsMockExecutor.saveL2LogsRootHash(BLOCK_NUMBER, L2_LOGS_TREE_ROOT); }); @@ -193,23 +167,21 @@ describe("Mailbox tests", function () { const revertReason = await getCallRevertReason( mailbox.finalizeEthWithdrawal(BLOCK_NUMBER, MESSAGE_INDEX, TX_NUMBER_IN_BLOCK, MESSAGE, invalidProof) ); - expect(revertReason).equal("pi"); + expect(revertReason).equal("finalizeEthWithdrawal only available for Era on mailbox"); }); it("Successful withdrawal", async () => { - const balanceBefore = await hardhat.ethers.provider.getBalance(L1_RECEIVER); - - await mailbox.finalizeEthWithdrawal(BLOCK_NUMBER, MESSAGE_INDEX, TX_NUMBER_IN_BLOCK, MESSAGE, MERKLE_PROOF); - - const balanceAfter = await hardhat.ethers.provider.getBalance(L1_RECEIVER); - expect(balanceAfter.sub(balanceBefore)).equal(AMOUNT); + const revertReason = await getCallRevertReason( + mailbox.finalizeEthWithdrawal(BLOCK_NUMBER, MESSAGE_INDEX, TX_NUMBER_IN_BLOCK, MESSAGE, MERKLE_PROOF) + ); + expect(revertReason).equal("finalizeEthWithdrawal only available for Era on mailbox"); }); it("Reverts when withdrawal is already finalized", async () => { const revertReason = await getCallRevertReason( mailbox.finalizeEthWithdrawal(BLOCK_NUMBER, MESSAGE_INDEX, TX_NUMBER_IN_BLOCK, MESSAGE, MERKLE_PROOF) ); - expect(revertReason).equal("jj"); + expect(revertReason).equal("finalizeEthWithdrawal only available for Era on mailbox"); }); }); @@ -288,7 +260,8 @@ describe("Mailbox tests", function () { callDirectly = async (refundRecipient) => { return { transaction: await requestExecute( - mailbox.connect(owner), + chainId, + bridgehub.connect(owner), ethers.constants.AddressZero, ethers.BigNumber.from(0), "0x", @@ -300,29 +273,35 @@ describe("Mailbox tests", function () { }; }; - const encodeRequest = (refundRecipient) => - mailbox.interface.encodeFunctionData("requestL2Transaction", [ - ethers.constants.AddressZero, - 0, - "0x", - l2GasLimit, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - [new Uint8Array(32)], - refundRecipient, - ]); - const overrides: ethers.PayableOverrides = {}; - overrides.gasPrice = await mailbox.provider.getGasPrice(); - overrides.value = await mailbox.l2TransactionBaseCost( + overrides.gasPrice = await bridgehub.provider.getGasPrice(); + overrides.value = await bridgehub.l2TransactionBaseCost( + chainId, overrides.gasPrice, l2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA ); + const mintValue = await overrides.value; overrides.gasLimit = 10000000; + const encodeRequest = (refundRecipient) => + bridgehub.interface.encodeFunctionData("requestL2TransactionDirect", [ + { + chainId, + l2Contract: ethers.constants.AddressZero, + mintValue: mintValue, + l2Value: 0, + l2Calldata: "0x", + l2GasLimit, + l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + factoryDeps: [new Uint8Array(32)], + refundRecipient, + }, + ]); + callViaForwarder = async (refundRecipient) => { return { - transaction: await forwarder.forward(mailbox.address, encodeRequest(refundRecipient), overrides), + transaction: await forwarder.forward(bridgehub.address, await encodeRequest(refundRecipient), overrides), expectedSender: aliasAddress(forwarder.address), }; }; @@ -330,7 +309,7 @@ describe("Mailbox tests", function () { callViaConstructorForwarder = async (refundRecipient) => { const constructorForwarder = await ( await hardhat.ethers.getContractFactory("ConstructorForwarder") - ).deploy(mailbox.address, encodeRequest(refundRecipient), overrides); + ).deploy(bridgehub.address, encodeRequest(refundRecipient), overrides); return { transaction: constructorForwarder.deployTransaction, @@ -342,16 +321,16 @@ describe("Mailbox tests", function () { it("Should only alias externally-owned addresses", async () => { const indirections = [callDirectly, callViaForwarder, callViaConstructorForwarder]; const refundRecipients = [ - [mailbox.address, false], - [await mailbox.signer.getAddress(), true], + [bridgehub.address, false], + [await bridgehub.signer.getAddress(), true], ]; for (const sendTransaction of indirections) { for (const [refundRecipient, externallyOwned] of refundRecipients) { const result = await sendTransaction(refundRecipient); - const [event] = (await result.transaction.wait()).logs; - const parsedEvent = mailbox.interface.parseLog(event); + const [, event2] = (await result.transaction.wait()).logs; + const parsedEvent = mailbox.interface.parseLog(event2); expect(parsedEvent.name).to.equal("NewPriorityRequest"); const canonicalTransaction = parsedEvent.args.transaction; diff --git a/l1-contracts/test/unit_tests/new_hyperchain_test.spec.ts b/l1-contracts/test/unit_tests/new_hyperchain_test.spec.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/l1-contracts/test/unit_tests/proxy_test.spec.ts b/l1-contracts/test/unit_tests/proxy_test.spec.ts index c8533ac4de94..a5b830200ef2 100644 --- a/l1-contracts/test/unit_tests/proxy_test.spec.ts +++ b/l1-contracts/test/unit_tests/proxy_test.spec.ts @@ -1,22 +1,24 @@ import { expect } from "chai"; import * as ethers from "ethers"; import * as hardhat from "hardhat"; -import { Action, facetCut, diamondCut, getAllSelectors } from "../../src.ts/diamondCut"; +import { Action, diamondCut, facetCut, getAllSelectors } from "../../src.ts/diamondCut"; import type { + AdminFacet, + DiamondInit, DiamondProxy, DiamondProxyTest, - AdminFacet, + ExecutorFacet, GettersFacet, MailboxFacet, - DiamondInit, } from "../../typechain"; import { + AdminFacetFactory, + DiamondInitFactory, DiamondProxyFactory, DiamondProxyTestFactory, - AdminFacetFactory, + ExecutorFacetFactory, GettersFacetFactory, MailboxFacetFactory, - DiamondInitFactory, TestnetERC20TokenFactory, } from "../../typechain"; import { defaultFeeParams, getCallRevertReason } from "./utils"; @@ -27,12 +29,15 @@ describe("Diamond proxy tests", function () { let adminFacet: AdminFacet; let gettersFacet: GettersFacet; let mailboxFacet: MailboxFacet; + let executorFacet: ExecutorFacet; let diamondProxyTest: DiamondProxyTest; let governor: ethers.Signer; + let owner: ethers.Signer; let governorAddress: string; + const chainId = process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID || 270; before(async () => { - [governor] = await hardhat.ethers.getSigners(); + [owner, governor] = await hardhat.ethers.getSigners(); governorAddress = await governor.getAddress(); const diamondInitFactory = await hardhat.ethers.getContractFactory("DiamondInit"); @@ -51,8 +56,13 @@ describe("Diamond proxy tests", function () { const mailboxFacetContract = await mailboxFacetFactory.deploy(); mailboxFacet = MailboxFacetFactory.connect(mailboxFacetContract.address, mailboxFacetContract.signer); + const executorFactory = await hardhat.ethers.getContractFactory("ExecutorFacet"); + const executorContract = await executorFactory.deploy(); + executorFacet = ExecutorFacetFactory.connect(executorContract.address, executorContract.signer); + const diamondProxyTestFactory = await hardhat.ethers.getContractFactory("DiamondProxyTest"); const diamondProxyTestContract = await diamondProxyTestFactory.deploy(); + diamondProxyTest = DiamondProxyTestFactory.connect( diamondProxyTestContract.address, diamondProxyTestContract.signer @@ -61,6 +71,7 @@ describe("Diamond proxy tests", function () { const facetCuts = [ facetCut(adminFacet.address, adminFacet.interface, Action.Add, false), facetCut(gettersFacet.address, gettersFacet.interface, Action.Add, false), + facetCut(executorFacet.address, executorFacet.interface, Action.Add, true), facetCut(mailboxFacet.address, mailboxFacet.interface, Action.Add, true), ]; @@ -69,20 +80,23 @@ describe("Diamond proxy tests", function () { recursionLeafLevelVkHash: ethers.constants.HashZero, recursionCircuitsSetVksHash: ethers.constants.HashZero, }; + const diamondInitCalldata = diamondInit.interface.encodeFunctionData("initialize", [ { - verifier: "0x03752D8252d67f99888E741E3fB642803B29B155", - governor: governorAddress, + chainId, + bridgehub: "0x0000000000000000000000000000000000000000", + stateTransitionManager: await owner.getAddress(), + protocolVersion: 0, admin: governorAddress, - genesisBatchHash: "0x02c775f0a90abf7a0e8043f2fdc38f0580ca9f9996a895d05a501bfeaa3b2e21", - genesisIndexRepeatedStorageChanges: 0, - genesisBatchCommitment: ethers.constants.HashZero, + validatorTimelock: governorAddress, + baseToken: "0x0000000000000000000000000000000000000001", + baseTokenBridge: "0x0000000000000000000000000000000000000001", + storedBatchZero: "0x02c775f0a90abf7a0e8043f2fdc38f0580ca9f9996a895d05a501bfeaa3b2e21", + verifier: "0x0000000000000000000000000000000000000001", verifierParams: dummyVerifierParams, - zkPorterIsAvailable: false, l2BootloaderBytecodeHash: "0x0100000000000000000000000000000000000000000000000000000000000000", l2DefaultAccountBytecodeHash: "0x0100000000000000000000000000000000000000000000000000000000000000", - priorityTxMaxGasLimit: 500000, - initialProtocolVersion: 0, + priorityTxMaxGasLimit: 500000, // priority tx max L2 gas limit feeParams: defaultFeeParams(), blobVersionedHashRetriever: ethers.constants.AddressZero, }, @@ -91,8 +105,8 @@ describe("Diamond proxy tests", function () { const diamondCutData = diamondCut(facetCuts, diamondInit.address, diamondInitCalldata); const proxyFactory = await hardhat.ethers.getContractFactory("DiamondProxy"); - const chainId = hardhat.network.config.chainId; - const proxyContract = await proxyFactory.deploy(chainId, diamondCutData); + const ethChainId = hardhat.network.config.chainId; + const proxyContract = await proxyFactory.deploy(ethChainId, diamondCutData); proxy = DiamondProxyFactory.connect(proxyContract.address, proxyContract.signer); }); @@ -157,13 +171,17 @@ describe("Diamond proxy tests", function () { await proxy.fallback({ data: adminFacetExecuteCalldata }); }); - it("should revert on calling a freezable faucet when diamondStorage is frozen", async () => { - const mailboxFacetSelector0 = getAllSelectors(mailboxFacet.interface)[0]; - const revertReason = await getCallRevertReason(proxy.fallback({ data: mailboxFacetSelector0 })); + it("should revert on calling a freezable facet when diamondStorage is frozen", async () => { + const executorFacetSelector3 = getAllSelectors(executorFacet.interface)[3]; + const revertReason = await getCallRevertReason( + proxy.fallback({ + data: executorFacetSelector3 + "0000000000000000000000000000000000000000000000000000000000000000", + }) + ); expect(revertReason).equal("q1"); }); - it("should be able to call an unfreezable faucet when diamondStorage is frozen", async () => { + it("should be able to call an unfreezable facet when diamondStorage is frozen", async () => { const gettersFacetSelector1 = getAllSelectors(gettersFacet.interface)[1]; await proxy.fallback({ data: gettersFacetSelector1 }); }); diff --git a/l1-contracts/test/unit_tests/utils.ts b/l1-contracts/test/unit_tests/utils.ts index f73f1871de60..f302fe5ceb2f 100644 --- a/l1-contracts/test/unit_tests/utils.ts +++ b/l1-contracts/test/unit_tests/utils.ts @@ -1,7 +1,19 @@ +import * as hardhat from "hardhat"; import type { BigNumberish, BytesLike } from "ethers"; import { BigNumber, ethers } from "ethers"; -import type { Address } from "zksync-web3/build/src/types"; +import type { Address } from "zksync-ethers/build/src/types"; +import { REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT } from "zksync-ethers/build/src/utils"; +import type { IBridgehub } from "../../typechain/IBridgehub"; +import type { IL1ERC20Bridge } from "../../typechain/IL1ERC20Bridge"; +import type { IMailbox } from "../../typechain/IMailbox"; + +import type { ExecutorFacet } from "../../typechain"; + +import type { FeeParams, L2CanonicalTransaction } from "../../src.ts/utils"; +import { ADDRESS_ONE, PubdataPricingMode } from "../../src.ts/utils"; + +export const CONTRACTS_LATEST_PROTOCOL_VERSION = (21).toString(); // eslint-disable-next-line @typescript-eslint/no-var-requires export const IERC20_INTERFACE = require("@openzeppelin/contracts/build/contracts/IERC20"); export const DEFAULT_REVERT_REASON = "VM did not revert"; @@ -12,8 +24,17 @@ export const L2_SYSTEM_CONTEXT_ADDRESS = "0x000000000000000000000000000000000000 export const L2_BOOTLOADER_ADDRESS = "0x0000000000000000000000000000000000008001"; export const L2_KNOWN_CODE_STORAGE_ADDRESS = "0x0000000000000000000000000000000000008004"; export const L2_TO_L1_MESSENGER = "0x0000000000000000000000000000000000008008"; +export const L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR = "0x000000000000000000000000000000000000800a"; export const L2_BYTECODE_COMPRESSOR_ADDRESS = "0x000000000000000000000000000000000000800e"; +export const DEPLOYER_SYSTEM_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000008006"; export const PUBDATA_CHUNK_PUBLISHER_ADDRESS = "0x0000000000000000000000000000000000008011"; +const PUBDATA_HASH = "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563"; + +export const SYSTEM_UPGRADE_TX_TYPE = 254; + +export function randomAddress() { + return ethers.utils.hexlify(ethers.utils.randomBytes(20)); +} export enum SYSTEM_LOG_KEYS { L2_TO_L1_LOGS_TREE_ROOT_KEY, @@ -48,16 +69,42 @@ export async function getCallRevertReason(promise) { await promise; } catch (e) { try { - revertReason = e.reason.match(/reverted with reason string '(.*)'/)?.[1] || e.reason; - } catch (_) { - throw e; + await promise; + } catch (e) { + // KL todo. The error messages are messed up. So we need all these cases. + try { + revertReason = e.reason.match(/reverted with reason string '([^']*)'/)?.[1] || e.reason; + if ( + revertReason === "cannot estimate gas; transaction may fail or may require manual gas limit" || + revertReason === DEFAULT_REVERT_REASON + ) { + revertReason = e.error.toString().match(/revert with reason "([^']*)"/)[1] || "PLACEHOLDER_STRING"; + } + } catch (_) { + try { + if ( + revertReason === "cannot estimate gas; transaction may fail or may require manual gas limit" || + revertReason === DEFAULT_REVERT_REASON + ) { + if (e.error) { + revertReason = + e.error.toString().match(/reverted with reason string '([^']*)'/)[1] || "PLACEHOLDER_STRING"; + } else { + revertReason = e.toString().match(/reverted with reason string '([^']*)'/)[1] || "PLACEHOLDER_STRING"; + } + } + } catch (_) { + throw e; + } + } } } return revertReason; } export async function requestExecute( - mailbox: ethers.Contract, + chainId: ethers.BigNumberish, + bridgehub: IBridgehub, to: Address, l2Value: ethers.BigNumber, calldata: ethers.BytesLike, @@ -67,17 +114,59 @@ export async function requestExecute( overrides?: ethers.PayableOverrides ) { overrides ??= {}; - overrides.gasPrice ??= mailbox.provider.getGasPrice(); - + overrides.gasPrice ??= bridgehub.provider.getGasPrice(); + // overrides.gasLimit ??= 30000000; if (!overrides.value) { - const baseCost = await mailbox.l2TransactionBaseCost( - overrides.gasPrice, + const baseCost = await bridgehub.l2TransactionBaseCost( + chainId, + await overrides.gasPrice, l2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA ); overrides.value = baseCost.add(l2Value); } + return await bridgehub.requestL2TransactionDirect( + { + chainId, + l2Contract: to, + mintValue: await overrides.value, + l2Value, + l2Calldata: calldata, + l2GasLimit, + l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + factoryDeps, + refundRecipient, + }, + overrides + ); +} + +// due to gas reasons we call tha chains's contract directly, instead of the bridgehub. +export async function requestExecuteDirect( + mailbox: IMailbox, + to: Address, + l2Value: ethers.BigNumber, + calldata: ethers.BytesLike, + l2GasLimit: ethers.BigNumber, + factoryDeps: BytesLike[], + refundRecipient: string, + value?: ethers.BigNumber +) { + const gasPrice = await mailbox.provider.getGasPrice(); + + // we call bridgehubChain direcetly to avoid running out of gas. + const baseCost = await mailbox.l2TransactionBaseCost( + gasPrice, + ethers.BigNumber.from(100000), + REQUIRED_L2_GAS_PRICE_PER_PUBDATA + ); + + const overrides = { + gasPrice, + value: baseCost.add(value || ethers.BigNumber.from(0)), + }; + return await mailbox.requestL2Transaction( to, l2Value, @@ -100,15 +189,52 @@ export function constructL2Log(isService: boolean, sender: string, key: number | ]); } -export function createSystemLogs() { +export function createSystemLogs( + chainedPriorityTxHashKey?: BytesLike, + numberOfLayer1Txs?: BigNumberish, + previousBatchHash?: BytesLike +) { return [ constructL2Log(true, L2_TO_L1_MESSENGER, SYSTEM_LOG_KEYS.L2_TO_L1_LOGS_TREE_ROOT_KEY, ethers.constants.HashZero), + constructL2Log(true, L2_TO_L1_MESSENGER, SYSTEM_LOG_KEYS.TOTAL_L2_TO_L1_PUBDATA_KEY, PUBDATA_HASH), + constructL2Log(true, L2_TO_L1_MESSENGER, SYSTEM_LOG_KEYS.STATE_DIFF_HASH_KEY, ethers.constants.HashZero), constructL2Log( true, - L2_TO_L1_MESSENGER, - SYSTEM_LOG_KEYS.TOTAL_L2_TO_L1_PUBDATA_KEY, - "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563" + L2_SYSTEM_CONTEXT_ADDRESS, + SYSTEM_LOG_KEYS.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY, + ethers.constants.HashZero ), + constructL2Log( + true, + L2_SYSTEM_CONTEXT_ADDRESS, + SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, + previousBatchHash ? ethers.utils.hexlify(previousBatchHash) : ethers.constants.HashZero + ), + constructL2Log( + true, + L2_BOOTLOADER_ADDRESS, + SYSTEM_LOG_KEYS.CHAINED_PRIORITY_TXN_HASH_KEY, + chainedPriorityTxHashKey ? chainedPriorityTxHashKey.toString() : EMPTY_STRING_KECCAK + ), + constructL2Log( + true, + L2_BOOTLOADER_ADDRESS, + SYSTEM_LOG_KEYS.NUMBER_OF_LAYER_1_TXS_KEY, + numberOfLayer1Txs ? numberOfLayer1Txs.toString() : ethers.constants.HashZero + ), + constructL2Log(true, PUBDATA_CHUNK_PUBLISHER_ADDRESS, SYSTEM_LOG_KEYS.BLOB_ONE_HASH_KEY, ethers.constants.HashZero), + constructL2Log(true, PUBDATA_CHUNK_PUBLISHER_ADDRESS, SYSTEM_LOG_KEYS.BLOB_TWO_HASH_KEY, ethers.constants.HashZero), + ]; +} + +export function createSystemLogsWithUpgrade( + chainedPriorityTxHashKey?: BytesLike, + numberOfLayer1Txs?: BigNumberish, + upgradeTxHash?: string +) { + return [ + constructL2Log(true, L2_TO_L1_MESSENGER, SYSTEM_LOG_KEYS.L2_TO_L1_LOGS_TREE_ROOT_KEY, ethers.constants.HashZero), + constructL2Log(true, L2_TO_L1_MESSENGER, SYSTEM_LOG_KEYS.TOTAL_L2_TO_L1_PUBDATA_KEY, PUBDATA_HASH), constructL2Log(true, L2_TO_L1_MESSENGER, SYSTEM_LOG_KEYS.STATE_DIFF_HASH_KEY, ethers.constants.HashZero), constructL2Log( true, @@ -117,10 +243,26 @@ export function createSystemLogs() { ethers.constants.HashZero ), constructL2Log(true, L2_SYSTEM_CONTEXT_ADDRESS, SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, ethers.constants.HashZero), - constructL2Log(true, L2_BOOTLOADER_ADDRESS, SYSTEM_LOG_KEYS.CHAINED_PRIORITY_TXN_HASH_KEY, EMPTY_STRING_KECCAK), - constructL2Log(true, L2_BOOTLOADER_ADDRESS, SYSTEM_LOG_KEYS.NUMBER_OF_LAYER_1_TXS_KEY, ethers.constants.HashZero), + constructL2Log( + true, + L2_BOOTLOADER_ADDRESS, + SYSTEM_LOG_KEYS.CHAINED_PRIORITY_TXN_HASH_KEY, + chainedPriorityTxHashKey ? chainedPriorityTxHashKey.toString() : EMPTY_STRING_KECCAK + ), + constructL2Log( + true, + L2_BOOTLOADER_ADDRESS, + SYSTEM_LOG_KEYS.NUMBER_OF_LAYER_1_TXS_KEY, + numberOfLayer1Txs ? numberOfLayer1Txs.toString() : ethers.constants.HashZero + ), constructL2Log(true, PUBDATA_CHUNK_PUBLISHER_ADDRESS, SYSTEM_LOG_KEYS.BLOB_ONE_HASH_KEY, ethers.constants.HashZero), constructL2Log(true, PUBDATA_CHUNK_PUBLISHER_ADDRESS, SYSTEM_LOG_KEYS.BLOB_TWO_HASH_KEY, ethers.constants.HashZero), + constructL2Log( + true, + L2_BOOTLOADER_ADDRESS, + SYSTEM_LOG_KEYS.EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY, + upgradeTxHash + ), ]; } @@ -182,16 +324,115 @@ export interface CommitBatchInfo { pubdataCommitments: BytesLike; } -export enum PubdataPricingMode { - Rollup, - Validium, +export async function depositERC20( + bridge: IL1ERC20Bridge, + bridgehubContract: IBridgehub, + chainId: string, + l2Receiver: string, + l1Token: string, + amount: ethers.BigNumber, + l2GasLimit: number, + l2RefundRecipient = ethers.constants.AddressZero +) { + const gasPrice = await bridge.provider.getGasPrice(); + const gasPerPubdata = REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT; + const neededValue = await bridgehubContract.l2TransactionBaseCost(chainId, gasPrice, l2GasLimit, gasPerPubdata); + const ethIsBaseToken = (await bridgehubContract.baseToken(chainId)) == ADDRESS_ONE; + + await bridge["deposit(address,address,uint256,uint256,uint256,address)"]( + l2Receiver, + l1Token, + amount, + l2GasLimit, + REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, + l2RefundRecipient, + { + value: ethIsBaseToken ? neededValue : 0, + } + ); } -export interface FeeParams { - pubdataPricingMode: PubdataPricingMode; - batchOverheadL1Gas: number; - maxPubdataPerBatch: number; - maxL2GasPerBatch: number; - priorityTxMaxPubdata: number; - minimalL2GasPrice: BigNumberish; +export function buildL2CanonicalTransaction(tx: Partial): L2CanonicalTransaction { + return { + txType: SYSTEM_UPGRADE_TX_TYPE, + from: ethers.constants.AddressZero, + to: ethers.constants.AddressZero, + gasLimit: 5000000, + gasPerPubdataByteLimit: REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, + maxFeePerGas: 0, + maxPriorityFeePerGas: 0, + paymaster: 0, + nonce: 0, + value: 0, + reserved: [0, 0, 0, 0], + data: "0x", + signature: "0x", + factoryDeps: [], + paymasterInput: "0x", + reservedDynamic: "0x", + ...tx, + }; +} + +export type CommitBatchInfoWithTimestamp = Partial & { + batchNumber: BigNumberish; +}; + +export async function buildCommitBatchInfoWithUpgrade( + prevInfo: StoredBatchInfo, + info: CommitBatchInfoWithTimestamp, + upgradeTxHash: string +): Promise { + const timestamp = info.timestamp || (await hardhat.ethers.provider.getBlock("latest")).timestamp; + const systemLogs = createSystemLogsWithUpgrade(info.priorityOperationsHash, info.numberOfLayer1Txs, upgradeTxHash); + systemLogs[SYSTEM_LOG_KEYS.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY] = constructL2Log( + true, + L2_SYSTEM_CONTEXT_ADDRESS, + SYSTEM_LOG_KEYS.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY, + packBatchTimestampAndBatchTimestamp(timestamp, timestamp) + ); + + return { + timestamp, + indexRepeatedStorageChanges: 0, + newStateRoot: ethers.utils.randomBytes(32), + numberOfLayer1Txs: 0, + priorityOperationsHash: EMPTY_STRING_KECCAK, + systemLogs: ethers.utils.hexConcat(systemLogs), + pubdataCommitments: `0x${"0".repeat(130)}`, + bootloaderHeapInitialContentsHash: ethers.utils.randomBytes(32), + eventsQueueStateHash: ethers.utils.randomBytes(32), + ...info, + }; +} + +export async function makeExecutedEqualCommitted( + proxyExecutor: ExecutorFacet, + prevBatchInfo: StoredBatchInfo, + batchesToProve: StoredBatchInfo[], + batchesToExecute: StoredBatchInfo[] +) { + batchesToExecute = [...batchesToProve, ...batchesToExecute]; + + await ( + await proxyExecutor.proveBatches(prevBatchInfo, batchesToProve, { + recursiveAggregationInput: [], + serializedProof: [], + }) + ).wait(); + + await (await proxyExecutor.executeBatches(batchesToExecute)).wait(); +} + +export function getBatchStoredInfo(commitInfo: CommitBatchInfo, commitment: string): StoredBatchInfo { + return { + batchNumber: commitInfo.batchNumber, + batchHash: commitInfo.newStateRoot, + indexRepeatedStorageChanges: commitInfo.indexRepeatedStorageChanges, + numberOfLayer1Txs: commitInfo.numberOfLayer1Txs, + priorityOperationsHash: commitInfo.priorityOperationsHash, + l2LogsTreeRoot: ethers.constants.HashZero, + timestamp: commitInfo.timestamp, + commitment: commitment, + }; } diff --git a/l1-contracts/test/unit_tests/validator_timelock_test.spec.ts b/l1-contracts/test/unit_tests/validator_timelock_test.spec.ts index 0077dfdc4b31..97b326633867 100644 --- a/l1-contracts/test/unit_tests/validator_timelock_test.spec.ts +++ b/l1-contracts/test/unit_tests/validator_timelock_test.spec.ts @@ -1,8 +1,8 @@ import { expect } from "chai"; import { ethers } from "ethers"; import * as hardhat from "hardhat"; -import type { DummyExecutor, ValidatorTimelock } from "../../typechain"; -import { DummyExecutorFactory, ValidatorTimelockFactory } from "../../typechain"; +import type { DummyExecutor, ValidatorTimelock, DummyStateTransitionManager } from "../../typechain"; +import { DummyExecutorFactory, ValidatorTimelockFactory, DummyStateTransitionManagerFactory } from "../../typechain"; import { getCallRevertReason } from "./utils"; describe("ValidatorTimelock tests", function () { @@ -11,6 +11,8 @@ describe("ValidatorTimelock tests", function () { let randomSigner: ethers.Signer; let validatorTimelock: ValidatorTimelock; let dummyExecutor: DummyExecutor; + let dummyStateTransitionManager: DummyStateTransitionManager; + const chainId: number = 270; const MOCK_PROOF_INPUT = { recursiveAggregationInput: [], @@ -53,17 +55,34 @@ describe("ValidatorTimelock tests", function () { const dummyExecutorContract = await dummyExecutorFactory.deploy(); dummyExecutor = DummyExecutorFactory.connect(dummyExecutorContract.address, dummyExecutorContract.signer); - const validatorTimelockFactory = await hardhat.ethers.getContractFactory("ValidatorTimelock"); - const validatorTimelockContract = await validatorTimelockFactory.deploy( - await owner.getAddress(), - dummyExecutor.address, - 0, - [ethers.constants.AddressZero] + const dummyStateTransitionManagerFactory = await hardhat.ethers.getContractFactory("DummyStateTransitionManager"); + const dummyStateTransitionManagerContract = await dummyStateTransitionManagerFactory.deploy(); + dummyStateTransitionManager = DummyStateTransitionManagerFactory.connect( + dummyStateTransitionManagerContract.address, + dummyStateTransitionManagerContract.signer ); + + const setSTtx = await dummyStateTransitionManager.setStateTransition(chainId, dummyExecutor.address); + await setSTtx.wait(); + + const validatorTimelockFactory = await hardhat.ethers.getContractFactory("ValidatorTimelock"); + const validatorTimelockContract = await validatorTimelockFactory.deploy(await owner.getAddress(), 0); validatorTimelock = ValidatorTimelockFactory.connect( validatorTimelockContract.address, validatorTimelockContract.signer ); + const setSTMtx = await validatorTimelock.setStateTransitionManager(dummyStateTransitionManager.address); + await setSTMtx.wait(); + }); + + it("Should check deployment", async () => { + expect(await validatorTimelock.owner()).equal(await owner.getAddress()); + expect(await validatorTimelock.executionDelay()).equal(0); + expect(await validatorTimelock.validators(chainId, ethers.constants.AddressZero)).equal(false); + expect(await validatorTimelock.stateTransitionManager()).equal(dummyStateTransitionManager.address); + expect(await dummyStateTransitionManager.stateTransition(chainId)).equal(dummyExecutor.address); + expect(await dummyStateTransitionManager.getChainAdmin(chainId)).equal(await owner.getAddress()); + expect(await dummyExecutor.getAdmin()).equal(await owner.getAddress()); }); it("Should revert if non-validator commits batches", async () => { @@ -71,7 +90,7 @@ describe("ValidatorTimelock tests", function () { validatorTimelock.connect(randomSigner).commitBatches(getMockStoredBatchInfo(0), [getMockCommitBatchInfo(1)]) ); - expect(revertReason).equal("8h"); + expect(revertReason).equal("ValidatorTimelock: only validator"); }); it("Should revert if non-validator proves batches", async () => { @@ -81,13 +100,13 @@ describe("ValidatorTimelock tests", function () { .proveBatches(getMockStoredBatchInfo(0), [getMockStoredBatchInfo(1)], MOCK_PROOF_INPUT) ); - expect(revertReason).equal("8h"); + expect(revertReason).equal("ValidatorTimelock: only validator"); }); it("Should revert if non-validator revert batches", async () => { const revertReason = await getCallRevertReason(validatorTimelock.connect(randomSigner).revertBatches(1)); - expect(revertReason).equal("8h"); + expect(revertReason).equal("ValidatorTimelock: only validator"); }); it("Should revert if non-validator executes batches", async () => { @@ -95,15 +114,15 @@ describe("ValidatorTimelock tests", function () { validatorTimelock.connect(randomSigner).executeBatches([getMockStoredBatchInfo(1)]) ); - expect(revertReason).equal("8h"); + expect(revertReason).equal("ValidatorTimelock: only validator"); }); - it("Should revert if non-owner sets validator", async () => { + it("Should revert if not chain governor sets validator", async () => { const revertReason = await getCallRevertReason( - validatorTimelock.connect(randomSigner).addValidator(await randomSigner.getAddress()) + validatorTimelock.connect(randomSigner).addValidator(chainId, await randomSigner.getAddress()) ); - expect(revertReason).equal("Ownable: caller is not the owner"); + expect(revertReason).equal("ValidatorTimelock: only chain admin"); }); it("Should revert if non-owner sets execution delay", async () => { @@ -114,9 +133,9 @@ describe("ValidatorTimelock tests", function () { it("Should successfully set the validator", async () => { const validatorAddress = await validator.getAddress(); - await validatorTimelock.connect(owner).addValidator(validatorAddress); + await validatorTimelock.connect(owner).addValidator(chainId, validatorAddress); - expect(await validatorTimelock.validators(validatorAddress)).equal(true); + expect(await validatorTimelock.validators(chainId, validatorAddress)).equal(true); }); it("Should successfully set the execution delay", async () => { @@ -126,7 +145,9 @@ describe("ValidatorTimelock tests", function () { }); it("Should successfully commit batches", async () => { - await validatorTimelock.connect(validator).commitBatches(getMockStoredBatchInfo(0), [getMockCommitBatchInfo(1)]); + await validatorTimelock + .connect(validator) + .commitBatchesSharedBridge(chainId, getMockStoredBatchInfo(0), [getMockCommitBatchInfo(1)]); expect(await dummyExecutor.getTotalBatchesCommitted()).equal(1); }); @@ -134,49 +155,53 @@ describe("ValidatorTimelock tests", function () { it("Should successfully prove batches", async () => { await validatorTimelock .connect(validator) - .proveBatches(getMockStoredBatchInfo(0), [getMockStoredBatchInfo(1, 1)], MOCK_PROOF_INPUT); + .proveBatchesSharedBridge(chainId, getMockStoredBatchInfo(0), [getMockStoredBatchInfo(1, 1)], MOCK_PROOF_INPUT); expect(await dummyExecutor.getTotalBatchesVerified()).equal(1); }); it("Should revert on executing earlier than the delay", async () => { const revertReason = await getCallRevertReason( - validatorTimelock.connect(validator).executeBatches([getMockStoredBatchInfo(1)]) + validatorTimelock.connect(validator).executeBatchesSharedBridge(chainId, [getMockStoredBatchInfo(1)]) ); expect(revertReason).equal("5c"); }); it("Should successfully revert batches", async () => { - await validatorTimelock.connect(validator).revertBatches(0); + await validatorTimelock.connect(validator).revertBatchesSharedBridge(chainId, 0); expect(await dummyExecutor.getTotalBatchesVerified()).equal(0); expect(await dummyExecutor.getTotalBatchesCommitted()).equal(0); }); it("Should successfully overwrite the committing timestamp on the reverted batches timestamp", async () => { - const revertedBatchesTimestamp = Number(await validatorTimelock.getCommittedBatchTimestamp(1)); + const revertedBatchesTimestamp = Number(await validatorTimelock.getCommittedBatchTimestamp(chainId, 1)); - await validatorTimelock.connect(validator).commitBatches(getMockStoredBatchInfo(0), [getMockCommitBatchInfo(1)]); + await validatorTimelock + .connect(validator) + .commitBatchesSharedBridge(chainId, getMockStoredBatchInfo(0), [getMockCommitBatchInfo(1)]); await validatorTimelock .connect(validator) - .proveBatches(getMockStoredBatchInfo(0), [getMockStoredBatchInfo(1)], MOCK_PROOF_INPUT); + .proveBatchesSharedBridge(chainId, getMockStoredBatchInfo(0), [getMockStoredBatchInfo(1)], MOCK_PROOF_INPUT); - const newBatchesTimestamp = Number(await validatorTimelock.getCommittedBatchTimestamp(1)); + const newBatchesTimestamp = Number(await validatorTimelock.getCommittedBatchTimestamp(chainId, 1)); expect(newBatchesTimestamp).greaterThanOrEqual(revertedBatchesTimestamp); }); it("Should successfully execute batches after the delay", async () => { await hardhat.network.provider.send("hardhat_mine", ["0x2", "0xc"]); //mine 2 batches with intervals of 12 seconds - await validatorTimelock.connect(validator).executeBatches([getMockStoredBatchInfo(1)]); + await validatorTimelock.connect(validator).executeBatchesSharedBridge(chainId, [getMockStoredBatchInfo(1)]); expect(await dummyExecutor.getTotalBatchesExecuted()).equal(1); }); it("Should revert if validator tries to commit batches with invalid last committed batchNumber", async () => { const revertReason = await getCallRevertReason( - validatorTimelock.connect(validator).commitBatches(getMockStoredBatchInfo(0), [getMockCommitBatchInfo(2)]) + validatorTimelock + .connect(validator) + .commitBatchesSharedBridge(chainId, getMockStoredBatchInfo(0), [getMockCommitBatchInfo(2)]) ); // Error should be forwarded from the DummyExecutor @@ -188,7 +213,7 @@ describe("ValidatorTimelock tests", function () { const revertReason = await getCallRevertReason( validatorTimelock .connect(validator) - .proveBatches(getMockStoredBatchInfo(0), [getMockStoredBatchInfo(2, 1)], MOCK_PROOF_INPUT) + .proveBatchesSharedBridge(chainId, getMockStoredBatchInfo(0), [getMockStoredBatchInfo(2, 1)], MOCK_PROOF_INPUT) ); expect(revertReason).equal("DummyExecutor: Invalid previous batch number"); @@ -197,10 +222,10 @@ describe("ValidatorTimelock tests", function () { it("Should revert if validator tries to execute more batches than were proven", async () => { await hardhat.network.provider.send("hardhat_mine", ["0x2", "0xc"]); //mine 2 batches with intervals of 12 seconds const revertReason = await getCallRevertReason( - validatorTimelock.connect(validator).executeBatches([getMockStoredBatchInfo(2)]) + validatorTimelock.connect(validator).executeBatchesSharedBridge(chainId, [getMockStoredBatchInfo(2)]) ); - expect(revertReason).equal("DummyExecutor: Can't execute batches more than committed and proven currently"); + expect(revertReason).equal("DummyExecutor 2: Can"); }); // These tests primarily needed to make gas statistics be more accurate. @@ -208,7 +233,7 @@ describe("ValidatorTimelock tests", function () { it("Should commit multiple batches in one transaction", async () => { await validatorTimelock .connect(validator) - .commitBatches(getMockStoredBatchInfo(1), [ + .commitBatchesSharedBridge(chainId, getMockStoredBatchInfo(1), [ getMockCommitBatchInfo(2), getMockCommitBatchInfo(3), getMockCommitBatchInfo(4), @@ -225,7 +250,12 @@ describe("ValidatorTimelock tests", function () { for (let i = 1; i < 8; i++) { await validatorTimelock .connect(validator) - .proveBatches(getMockStoredBatchInfo(i), [getMockStoredBatchInfo(i + 1)], MOCK_PROOF_INPUT); + .proveBatchesSharedBridge( + chainId, + getMockStoredBatchInfo(i), + [getMockStoredBatchInfo(i + 1)], + MOCK_PROOF_INPUT + ); expect(await dummyExecutor.getTotalBatchesVerified()).equal(i + 1); } @@ -235,7 +265,7 @@ describe("ValidatorTimelock tests", function () { await hardhat.network.provider.send("hardhat_mine", ["0x2", "0xc"]); //mine 2 batches with intervals of 12 seconds await validatorTimelock .connect(validator) - .executeBatches([ + .executeBatchesSharedBridge(chainId, [ getMockStoredBatchInfo(2), getMockStoredBatchInfo(3), getMockStoredBatchInfo(4), diff --git a/l1-contracts/test/unit_tests/zksync-upgrade.fork.ts b/l1-contracts/test/unit_tests/zksync-upgrade.fork.ts index 9098dd45192c..400ba3d9503b 100644 --- a/l1-contracts/test/unit_tests/zksync-upgrade.fork.ts +++ b/l1-contracts/test/unit_tests/zksync-upgrade.fork.ts @@ -12,8 +12,8 @@ import { IGovernanceFactory } from "../../typechain/IGovernanceFactory"; import type { IGovernance } from "../../typechain/IGovernance"; import { IMailboxFactory } from "../../typechain/IMailboxFactory"; import type { IMailbox } from "../../typechain/IMailbox"; -import { IZkSyncFactory } from "../../typechain/IZkSyncFactory"; -import type { IZkSync } from "../../typechain/IZkSync"; +import { IBridgehubFactory } from "../../typechain/IBridgehubFactory"; +import type { IBridgehub } from "../../typechain/IBridgehub"; import { ethers } from "ethers"; // TODO: change to the mainet config @@ -21,7 +21,7 @@ const DIAMOND_PROXY_ADDRESS = "0x1908e2BF4a88F91E4eF0DC72f02b8Ea36BEa2319"; describe("Diamond proxy upgrade fork test", function () { let governor: ethers.Signer; - let diamondProxy: IZkSync; + let diamondProxy: IBridgehub; let newDiamondCutFacet: IDiamondCut; let newExecutorFacet: IExecutor; @@ -33,8 +33,8 @@ describe("Diamond proxy upgrade fork test", function () { before(async () => { const signers = await hardhat.ethers.getSigners(); - diamondProxy = IZkSyncFactory.connect(DIAMOND_PROXY_ADDRESS, signers[0]); - const governorAddress = await diamondProxy.getGovernor(); + diamondProxy = IBridgehubFactory.connect(DIAMOND_PROXY_ADDRESS, signers[0]); + const governorAddress = await diamondProxy.getAdmin(); await hardhat.network.provider.request({ method: "hardhat_impersonateAccount", params: [governorAddress] }); governor = await hardhat.ethers.provider.getSigner(governorAddress); @@ -57,7 +57,7 @@ describe("Diamond proxy upgrade fork test", function () { const governanceFacet = await governanceFacetFactory.deploy(); newGovernanceFacet = IGovernanceFactory.connect(governanceFacet.address, governanceFacet.signer); - const mailboxFacetFactory = await hardhat.ethers.getContractFactory("MailboxFacet"); + const mailboxFacetFactory = await hardhat.ethers.getContractFactory("Mailbox"); const mailboxFacet = await mailboxFacetFactory.deploy(); newMailboxFacet = IMailboxFactory.connect(mailboxFacet.address, mailboxFacet.signer); @@ -149,7 +149,7 @@ describe("Diamond proxy upgrade fork test", function () { }); it("check getters functions", async () => { - const governorAddr = await diamondProxy.getGovernor(); + const governorAddr = await diamondProxy.getAdmin(); expect(governorAddr).to.be.eq(await governor.getAddress()); const isFrozen = await diamondProxy.isDiamondStorageFrozen(); diff --git a/l1-contracts/upgrade-system/facets.ts b/l1-contracts/upgrade-system/facets.ts index d6b554d78fea..4c67d9266def 100644 --- a/l1-contracts/upgrade-system/facets.ts +++ b/l1-contracts/upgrade-system/facets.ts @@ -1,15 +1,15 @@ +// hardhat import should be the first import in the file +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import * as hardhat from "hardhat"; import { Command } from "commander"; import type { BigNumber } from "ethers"; import { ethers } from "ethers"; import * as fs from "fs"; -import * as path from "path"; import { web3Url } from "zk/build/utils"; import { deployViaCreate2 } from "../src.ts/deploy-utils"; import { getFacetCutsForUpgrade } from "../src.ts/diamondCut"; import { insertGasPrice } from "./utils"; - -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); +import { ethTestConfig } from "../src.ts/utils"; async function deployFacetCut( wallet: ethers.Wallet, diff --git a/l1-contracts/upgrade-system/index.ts b/l1-contracts/upgrade-system/index.ts index fe05b8a23739..a774730c8511 100644 --- a/l1-contracts/upgrade-system/index.ts +++ b/l1-contracts/upgrade-system/index.ts @@ -1,3 +1,6 @@ +// hardhat import should be the first import in the file +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import * as hardhat from "hardhat"; import { program } from "commander"; import { command as facetCuts } from "./facets"; import { command as verifier } from "./verifier"; diff --git a/l1-contracts/upgrade-system/verifier.ts b/l1-contracts/upgrade-system/verifier.ts index 21dd8f763703..9b210bfb68a4 100644 --- a/l1-contracts/upgrade-system/verifier.ts +++ b/l1-contracts/upgrade-system/verifier.ts @@ -1,3 +1,6 @@ +// hardhat import should be the first import in the file +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import * as hardhat from "hardhat"; import { Command } from "commander"; import type { BigNumber } from "ethers"; import { ethers } from "ethers"; diff --git a/l2-contracts/.env b/l2-contracts/.env index 6bd439110fc2..44cbb2ad85cd 100644 --- a/l2-contracts/.env +++ b/l2-contracts/.env @@ -1,3 +1,3 @@ -CHAIN_ETH_NETWORK=localhost -CONTRACTS_PRIORITY_TX_MAX_ERGS_LIMIT=50000 +CHAIN_ETH_NETWORK=hardhat +CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT=50000 ETH_CLIENT_WEB3_URL=http://127.0.0.1:8545 diff --git a/l2-contracts/contracts/Config.sol b/l2-contracts/contracts/Config.sol new file mode 100644 index 000000000000..79b6cf676c7f --- /dev/null +++ b/l2-contracts/contracts/Config.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +/// @dev Era's chainID +uint256 constant ERA_CHAIN_ID = $(ERA_CHAIN_ID); + +address constant ERA_WETH_ADDRESS = $(ERA_WETH_ADDRESS); diff --git a/l2-contracts/contracts/Dependencies.sol b/l2-contracts/contracts/Dependencies.sol index f894b58863f9..51f59e4fc234 100644 --- a/l2-contracts/contracts/Dependencies.sol +++ b/l2-contracts/contracts/Dependencies.sol @@ -3,3 +3,4 @@ pragma solidity 0.8.20; import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; diff --git a/l2-contracts/contracts/L2ContractHelper.sol b/l2-contracts/contracts/L2ContractHelper.sol index 13b1c5313cec..8699cf761982 100644 --- a/l2-contracts/contracts/L2ContractHelper.sol +++ b/l2-contracts/contracts/L2ContractHelper.sol @@ -58,7 +58,7 @@ interface IContractDeployer { * @custom:security-contact security@matterlabs.dev * @notice Interface for the contract that is used to simulate ETH on L2. */ -interface IEthToken { +interface IBaseToken { /// @notice Allows the withdrawal of ETH to a given L1 receiver along with an additional message. /// @param _l1Receiver The address on L1 to receive the withdrawn ETH. /// @param _additionalData Additional message or data to be sent alongside the withdrawal. @@ -73,7 +73,7 @@ address constant DEPLOYER_SYSTEM_CONTRACT = address(SYSTEM_CONTRACTS_OFFSET + 0x IL2Messenger constant L2_MESSENGER = IL2Messenger(address(SYSTEM_CONTRACTS_OFFSET + 0x08)); -IEthToken constant L2_ETH_ADDRESS = IEthToken(address(SYSTEM_CONTRACTS_OFFSET + 0x0a)); +IBaseToken constant L2_BASE_TOKEN_ADDRESS = IBaseToken(address(SYSTEM_CONTRACTS_OFFSET + 0x0a)); /** * @author Matter Labs diff --git a/l2-contracts/contracts/bridge/L2ERC20Bridge.sol b/l2-contracts/contracts/bridge/L2SharedBridge.sol similarity index 81% rename from l2-contracts/contracts/bridge/L2ERC20Bridge.sol rename to l2-contracts/contracts/bridge/L2SharedBridge.sol index 74764b439c49..040bd8565c43 100644 --- a/l2-contracts/contracts/bridge/L2ERC20Bridge.sol +++ b/l2-contracts/contracts/bridge/L2SharedBridge.sol @@ -6,20 +6,21 @@ import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.s import {BeaconProxy} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; -import {IL1Bridge} from "./interfaces/IL1Bridge.sol"; -import {IL2Bridge} from "./interfaces/IL2Bridge.sol"; +import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; +import {IL2SharedBridge} from "./interfaces/IL2SharedBridge.sol"; import {IL2StandardToken} from "./interfaces/IL2StandardToken.sol"; import {L2StandardERC20} from "./L2StandardERC20.sol"; import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; -import {L2ContractHelper, DEPLOYER_SYSTEM_CONTRACT, IContractDeployer} from "../L2ContractHelper.sol"; +import {L2ContractHelper, DEPLOYER_SYSTEM_CONTRACT, L2_BASE_TOKEN_ADDRESS, IContractDeployer} from "../L2ContractHelper.sol"; import {SystemContractsCaller} from "../SystemContractsCaller.sol"; +import {ERA_CHAIN_ID, ERA_WETH_ADDRESS} from "../Config.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev /// @notice The "default" bridge implementation for the ERC20 tokens. Note, that it does not /// support any custom token logic, i.e. rebase tokens' functionality is not supported. -contract L2ERC20Bridge is IL2Bridge, Initializable { +contract L2SharedBridge is IL2SharedBridge, Initializable { /// @dev The address of the L1 bridge counterpart. address public override l1Bridge; @@ -42,18 +43,26 @@ contract L2ERC20Bridge is IL2Bridge, Initializable { /// @notice Initializes the bridge contract for later use. Expected to be used in the proxy. /// @param _l1Bridge The address of the L1 Bridge contract. /// @param _l2TokenProxyBytecodeHash The bytecode hash of the proxy for tokens deployed by the bridge. - /// @param _governor The address of the governor contract. - function initialize(address _l1Bridge, bytes32 _l2TokenProxyBytecodeHash, address _governor) external initializer { + /// @param _aliasedOwner The address of the governor contract. + function initialize( + address _l1Bridge, + bytes32 _l2TokenProxyBytecodeHash, + address _aliasedOwner + ) external reinitializer(2) { require(_l1Bridge != address(0), "bf"); require(_l2TokenProxyBytecodeHash != bytes32(0), "df"); - require(_governor != address(0), "sf"); + require(_aliasedOwner != address(0), "sf"); + require(_l2TokenProxyBytecodeHash != bytes32(0), "df"); l1Bridge = _l1Bridge; - l2TokenProxyBytecodeHash = _l2TokenProxyBytecodeHash; - address l2StandardToken = address(new L2StandardERC20{salt: bytes32(0)}()); - l2TokenBeacon = new UpgradeableBeacon{salt: bytes32(0)}(l2StandardToken); - l2TokenBeacon.transferOwnership(_governor); + + if (block.chainid != ERA_CHAIN_ID) { + address l2StandardToken = address(new L2StandardERC20{salt: bytes32(0)}()); + l2TokenBeacon = new UpgradeableBeacon{salt: bytes32(0)}(l2StandardToken); + l2TokenBeacon.transferOwnership(_aliasedOwner); + } + // else: l2StandardToken and l2TokenBeacon are already deployed on ERA, and stored in the proxy } /// @notice Finalize the deposit and mint funds @@ -71,7 +80,6 @@ contract L2ERC20Bridge is IL2Bridge, Initializable { ) external payable override { // Only the L1 bridge counterpart can initiate and finalize the deposit. require(AddressAliasHelper.undoL1ToL2Alias(msg.sender) == l1Bridge, "mq"); - // The passed value should be 0 for ERC20 bridge. require(msg.value == 0, "Value should be 0 for ERC20 bridge"); address expectedL2Token = l2TokenAddress(_l1Token); @@ -85,7 +93,6 @@ contract L2ERC20Bridge is IL2Bridge, Initializable { } IL2StandardToken(expectedL2Token).bridgeMint(_l2Receiver, _amount); - emit FinalizeDeposit(_l1Sender, _l2Receiver, expectedL2Token, _amount); } @@ -105,6 +112,8 @@ contract L2ERC20Bridge is IL2Bridge, Initializable { /// @param _l2Token The L2 token address which is withdrawn /// @param _amount The total amount of tokens to be withdrawn function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external override { + require(_amount > 0, "Amount cannot be zero"); + IL2StandardToken(_l2Token).bridgeBurn(msg.sender, _amount); address l1Token = l1TokenAddress[_l2Token]; @@ -122,14 +131,15 @@ contract L2ERC20Bridge is IL2Bridge, Initializable { address _l1Token, uint256 _amount ) internal pure returns (bytes memory) { - return abi.encodePacked(IL1Bridge.finalizeWithdrawal.selector, _to, _l1Token, _amount); + // note we use the IL1ERC20Bridge.finalizeWithdrawal function selector to specify the selector for L1<>L2 messages, + // and we use this interface so that when the switch happened the old messages could be processed + return abi.encodePacked(IL1ERC20Bridge.finalizeWithdrawal.selector, _to, _l1Token, _amount); } /// @return Address of an L2 token counterpart function l2TokenAddress(address _l1Token) public view override returns (address) { bytes32 constructorInputHash = keccak256(abi.encode(address(l2TokenBeacon), "")); bytes32 salt = _getCreate2Salt(_l1Token); - return L2ContractHelper.computeCreate2Address(address(this), salt, l2TokenProxyBytecodeHash, constructorInputHash); } diff --git a/l2-contracts/contracts/bridge/L2WethBridge.sol b/l2-contracts/contracts/bridge/L2WethBridge.sol deleted file mode 100644 index 5049d073b08f..000000000000 --- a/l2-contracts/contracts/bridge/L2WethBridge.sol +++ /dev/null @@ -1,115 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; - -import {IL2Bridge} from "./interfaces/IL2Bridge.sol"; -import {IL2Weth} from "./interfaces/IL2Weth.sol"; -import {IL2StandardToken} from "./interfaces/IL2StandardToken.sol"; - -import {L2_ETH_ADDRESS} from "../L2ContractHelper.sol"; -import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; - -/// @author Matter Labs -/// @custom:security-contact security@matterlabs.dev -/// @dev This contract works in conjunction with the L1WethBridge to streamline the process of bridging WETH tokens between L1 and L2. -/// @dev This contract accepts Ether from the L1 Bridge during deposits, converts it to WETH, and sends it to the user. -/// @dev For withdrawals, it processes the user's WETH tokens by unwrapping them and transferring the equivalent Ether to the L1 Bridge. -/// @dev This custom bridge differs from the standard ERC20 bridge by handling the conversion between Ether and WETH directly, -/// eliminating the need for users to perform additional wrapping and unwrapping transactions on both L1 and L2 networks. -contract L2WethBridge is IL2Bridge, Initializable { - /// @dev Event emitted when ETH is received by the contract. - event EthReceived(uint256 amount); - - /// @dev The address of the L1 bridge counterpart. - address public override l1Bridge; - - /// @dev WETH token address on L1. - address public l1WethAddress; - - /// @dev WETH token address on L2. - address public l2WethAddress; - - /// @dev Contract is expected to be used as proxy implementation. - /// @dev Disable the initialization to prevent Parity hack. - constructor() { - _disableInitializers(); - } - - /// @notice Initializes the contract with parameters needed for its functionality. - /// @param _l1Bridge The address of the L1 Bridge contract. - /// @param _l1WethAddress The address of the L1 WETH token. - /// @param _l2WethAddress The address of the L2 WETH token. - /// @dev The function can only be called once during contract deployment due to the 'initializer' modifier. - function initialize(address _l1Bridge, address _l1WethAddress, address _l2WethAddress) external initializer { - require(_l1Bridge != address(0), "L1 WETH bridge address cannot be zero"); - require(_l1WethAddress != address(0), "L1 WETH token address cannot be zero"); - require(_l2WethAddress != address(0), "L2 WETH token address cannot be zero"); - - l1Bridge = _l1Bridge; - l1WethAddress = _l1WethAddress; - l2WethAddress = _l2WethAddress; - } - - /// @notice Initiate the withdrawal of WETH from L2 to L1 by sending a message to L1 and calling withdraw on L2EthToken contract - /// @param _l1Receiver The account address that would receive the WETH on L1 - /// @param _l2Token Address of the L2 WETH token - /// @param _amount Total amount of WETH to withdraw - function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external override { - require(_l2Token == l2WethAddress, "Only WETH can be withdrawn"); - require(_amount > 0, "Amount cannot be zero"); - - // Burn WETH on L2, receive ETH. - IL2StandardToken(l2WethAddress).bridgeBurn(msg.sender, _amount); - - // WETH withdrawal message. - bytes memory wethMessage = abi.encodePacked(_l1Receiver); - - // Withdraw ETH to L1 bridge. - L2_ETH_ADDRESS.withdrawWithMessage{value: _amount}(l1Bridge, wethMessage); - - emit WithdrawalInitiated(msg.sender, _l1Receiver, l2WethAddress, _amount); - } - - /// @notice Finalize the deposit of WETH from L1 to L2 by calling deposit on L2Weth contract - /// @param _l1Sender The account address that initiated the deposit on L1 - /// @param _l2Receiver The account address that would receive the WETH on L2 - /// @param _l1Token Address of the L1 WETH token - /// @param _amount Total amount of WETH to deposit - function finalizeDeposit( - address _l1Sender, - address _l2Receiver, - address _l1Token, - uint256 _amount, - bytes calldata // _data - ) external payable override { - require( - AddressAliasHelper.undoL1ToL2Alias(msg.sender) == l1Bridge, - "Only L1 WETH bridge can call this function" - ); - - require(_l1Token == l1WethAddress, "Only WETH can be deposited"); - require(msg.value == _amount, "Amount mismatch"); - - // Deposit WETH to L2 receiver. - IL2Weth(l2WethAddress).depositTo{value: msg.value}(_l2Receiver); - - emit FinalizeDeposit(_l1Sender, _l2Receiver, l2WethAddress, _amount); - } - - /// @return l1Token Address of an L1 token counterpart. - function l1TokenAddress(address _l2Token) public view override returns (address l1Token) { - l1Token = _l2Token == l2WethAddress ? l1WethAddress : address(0); - } - - /// @return l2Token Address of an L2 token counterpart. - function l2TokenAddress(address _l1Token) public view override returns (address l2Token) { - l2Token = _l1Token == l1WethAddress ? l2WethAddress : address(0); - } - - receive() external payable { - require(msg.sender == l2WethAddress, "pd"); - emit EthReceived(msg.value); - } -} diff --git a/l2-contracts/contracts/bridge/L2Weth.sol b/l2-contracts/contracts/bridge/L2WrappedBaseToken.sol similarity index 90% rename from l2-contracts/contracts/bridge/L2Weth.sol rename to l2-contracts/contracts/bridge/L2WrappedBaseToken.sol index 5d3efcca94e6..3b9caa5937f5 100644 --- a/l2-contracts/contracts/bridge/L2Weth.sol +++ b/l2-contracts/contracts/bridge/L2WrappedBaseToken.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.20; import {ERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol"; -import {IL2Weth} from "./interfaces/IL2Weth.sol"; +import {IL2WrappedBaseToken} from "./interfaces/IL2WrappedBaseToken.sol"; import {IL2StandardToken} from "./interfaces/IL2StandardToken.sol"; /// @author Matter Labs @@ -20,11 +20,11 @@ import {IL2StandardToken} from "./interfaces/IL2StandardToken.sol"; /// /// Note: This is an upgradeable contract. In the future, we will remove upgradeability to make it trustless. /// But for now, when the Rollup has instant upgradability, we leave the possibility of upgrading to improve the contract if needed. -contract L2Weth is ERC20PermitUpgradeable, IL2Weth, IL2StandardToken { +contract L2WrappedBaseToken is ERC20PermitUpgradeable, IL2WrappedBaseToken, IL2StandardToken { /// @dev Address of the L2 WETH Bridge. address public override l2Bridge; - /// @dev Address of the L1 WETH token. It can be deposited to mint this L2 token. + /// @dev Address of the L1 base token. It can be deposited to mint this L2 token. address public override l1Address; /// @dev Contract is expected to be used as proxy implementation. @@ -34,11 +34,24 @@ contract L2Weth is ERC20PermitUpgradeable, IL2Weth, IL2StandardToken { } /// @notice Initializes a contract token for later use. Expected to be used in the proxy. + /// @notice This function is used to integrate the previously deployed WETH token with the bridge. /// @dev Sets up `name`/`symbol`/`decimals` getters. /// @param name_ The name of the token. /// @param symbol_ The symbol of the token. + /// @param _l2Bridge Address of the L2 bridge + /// @param _l1Address Address of the L1 token that can be deposited to mint this L2 WETH. /// Note: The decimals are hardcoded to 18, the same as on Ether. - function initialize(string memory name_, string memory symbol_) external initializer { + function initializeV2( + string memory name_, + string memory symbol_, + address _l2Bridge, + address _l1Address + ) external reinitializer(2) { + require(_l2Bridge != address(0), "L2 bridge address cannot be zero"); + require(_l1Address != address(0), "L1 WETH token address cannot be zero"); + l2Bridge = _l2Bridge; + l1Address = _l1Address; + // Set decoded values for name and symbol. __ERC20_init_unchained(name_, symbol_); @@ -48,16 +61,6 @@ contract L2Weth is ERC20PermitUpgradeable, IL2Weth, IL2StandardToken { emit Initialize(name_, symbol_, 18); } - /// @notice This function is used to integrate the previously deployed WETH token with the bridge. - /// @param _l2Bridge Address of the L2 bridge - /// @param _l1Address Address of the L1 token that can be deposited to mint this L2 WETH. - function initializeV2(address _l2Bridge, address _l1Address) external reinitializer(2) { - require(_l2Bridge != address(0), "L2 bridge address cannot be zero"); - require(_l1Address != address(0), "L1 WETH token address cannot be zero"); - l2Bridge = _l2Bridge; - l1Address = _l1Address; - } - modifier onlyBridge() { require(msg.sender == l2Bridge, "permission denied"); // Only L2 bridge can call this method _; @@ -66,10 +69,7 @@ contract L2Weth is ERC20PermitUpgradeable, IL2Weth, IL2StandardToken { /// @notice Function for minting tokens on L2, implemented only to be compatible with IL2StandardToken interface. /// Always reverts instead of minting anything! /// Note: Use `deposit`/`depositTo` methods instead. - function bridgeMint( - address, // _account - uint256 // _amount - ) external view override { + function bridgeMint(address _to, uint256 _amount) external override onlyBridge { revert("bridgeMint is not implemented! Use deposit/depositTo methods instead."); } diff --git a/l2-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol b/l2-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol new file mode 100644 index 000000000000..4076696138c9 --- /dev/null +++ b/l2-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +/// @author Matter Labs +// note we use the IL1ERC20Bridge only to send L1<>L2 messages, +// and we use this interface so that when the switch happened the old messages could be processed +interface IL1ERC20Bridge { + function finalizeWithdrawal( + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes calldata _message, + bytes32[] calldata _merkleProof + ) external; +} diff --git a/l2-contracts/contracts/bridge/interfaces/IL1Bridge.sol b/l2-contracts/contracts/bridge/interfaces/IL1SharedBridge.sol similarity index 88% rename from l2-contracts/contracts/bridge/interfaces/IL1Bridge.sol rename to l2-contracts/contracts/bridge/interfaces/IL1SharedBridge.sol index 7f5a1a115aff..8ec1ff75702d 100644 --- a/l2-contracts/contracts/bridge/interfaces/IL1Bridge.sol +++ b/l2-contracts/contracts/bridge/interfaces/IL1SharedBridge.sol @@ -5,8 +5,9 @@ pragma solidity 0.8.20; /// @title L1 Bridge contract interface /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -interface IL1Bridge { +interface IL1SharedBridge { function finalizeWithdrawal( + uint256 _chainId, uint256 _l2BatchNumber, uint256 _l2MessageIndex, uint16 _l2TxNumberInBatch, diff --git a/l2-contracts/contracts/bridge/interfaces/IL2Bridge.sol b/l2-contracts/contracts/bridge/interfaces/IL2SharedBridge.sol similarity index 86% rename from l2-contracts/contracts/bridge/interfaces/IL2Bridge.sol rename to l2-contracts/contracts/bridge/interfaces/IL2SharedBridge.sol index 4eb43132f36b..b95d091b717d 100644 --- a/l2-contracts/contracts/bridge/interfaces/IL2Bridge.sol +++ b/l2-contracts/contracts/bridge/interfaces/IL2SharedBridge.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.20; /// @author Matter Labs -interface IL2Bridge { +interface IL2SharedBridge { event FinalizeDeposit( address indexed l1Sender, address indexed l2Receiver, @@ -18,6 +18,9 @@ interface IL2Bridge { uint256 amount ); + /// @dev Event emitted when baseToken is received by the contract. + event BaseTokenReceived(uint256 amount); + function finalizeDeposit( address _l1Sender, address _l2Receiver, diff --git a/l2-contracts/contracts/bridge/interfaces/IL2Weth.sol b/l2-contracts/contracts/bridge/interfaces/IL2WrappedBaseToken.sol similarity index 91% rename from l2-contracts/contracts/bridge/interfaces/IL2Weth.sol rename to l2-contracts/contracts/bridge/interfaces/IL2WrappedBaseToken.sol index f9eff400dd19..693aa139a19f 100644 --- a/l2-contracts/contracts/bridge/interfaces/IL2Weth.sol +++ b/l2-contracts/contracts/bridge/interfaces/IL2WrappedBaseToken.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.20; -interface IL2Weth { +interface IL2WrappedBaseToken { event Initialize(string name, string symbol, uint8 decimals); function deposit() external payable; diff --git a/l2-contracts/hardhat.config.ts b/l2-contracts/hardhat.config.ts index 908567be8cb9..8e41da64a6d2 100644 --- a/l2-contracts/hardhat.config.ts +++ b/l2-contracts/hardhat.config.ts @@ -4,6 +4,8 @@ import "@nomicfoundation/hardhat-chai-matchers"; import "@nomiclabs/hardhat-ethers"; import "@nomiclabs/hardhat-solpp"; import "hardhat-typechain"; +import { task } from "hardhat/config"; +import { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } from "hardhat/builtin-tasks/task-names"; // If no network is specified, use the default config if (!process.env.CHAIN_ETH_NETWORK) { @@ -11,6 +13,32 @@ if (!process.env.CHAIN_ETH_NETWORK) { require("dotenv").config(); } +const prodConfig = { + ERA_CHAIN_ID: 324, +}; +const testnetConfig = { + ERA_CHAIN_ID: 300, + ERA_WETH_ADDRESS: "address(0)", +}; +const hardhatConfig = { + ERA_CHAIN_ID: 9, + ERA_WETH_ADDRESS: "address(0)", +}; +const localConfig = { + ERA_CHAIN_ID: 9, + ERA_WETH_ADDRESS: "address(0)", +}; + +const contractDefs = { + sepolia: testnetConfig, + rinkeby: testnetConfig, + ropsten: testnetConfig, + goerli: testnetConfig, + mainnet: prodConfig, + hardhat: hardhatConfig, + localhost: localConfig, +}; + export default { zksolc: { version: "1.3.18", @@ -22,6 +50,15 @@ export default { solidity: { version: "0.8.20", }, + solpp: { + defs: (() => { + const defs = contractDefs[process.env.CHAIN_ETH_NETWORK]; + + return { + ...defs, + }; + })(), + }, defaultNetwork: "localhost", networks: { localhost: { @@ -53,3 +90,7 @@ export default { }, }, }; + +task("solpp", "Preprocess Solidity source files").setAction(async (_, hre) => + hre.run(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS) +); diff --git a/l2-contracts/package.json b/l2-contracts/package.json index 46004d1eb96f..f2f49afa9aea 100644 --- a/l2-contracts/package.json +++ b/l2-contracts/package.json @@ -36,12 +36,16 @@ "clean": "hardhat clean", "test": "hardhat test", "verify": "hardhat run src/verify.ts", - "deploy-testnet-paymaster": "ts-node src/deployTestnetPaymaster.ts", - "deploy-force-deploy-upgrader": "ts-node src/deployForceDeployUpgrader.ts", + "deploy-testnet-paymaster": "ts-node src/deploy-testnet-paymaster.ts", + "deploy-testnet-paymaster-through-l1": "ts-node src/deploy-testnet-paymaster-through-l1.ts", + "deploy-force-deploy-upgrader": "ts-node src/deploy-force-deploy-upgrader.ts", + "deploy-force-deploy-upgrader-through-l1": "ts-node src/deploy-force-deploy-upgrader-through-l1.ts", + "deploy-shared-bridge-on-l2": "ts-node scripts/deploy-shared-bridge-on-l2.ts", + "deploy-shared-bridge-on-l2-through-l1": "ts-node src/deploy-shared-bridge-on-l2-through-l1.ts", "publish-bridge-preimages": "ts-node src/publish-bridge-preimages.ts", - "deploy-l2-weth": "ts-node src/deployL2Weth.ts", - "upgrade-bridge-contracts": "ts-node src/upgradeBridgeImpl.ts", - "update-l2-erc20-metadata": "ts-node src/updateL2ERC20Metadata.ts" + "deploy-l2-weth": "ts-node src/deploy-l2-weth.ts", + "upgrade-bridge-contracts": "ts-node src/upgrade-bridge-impl.ts", + "update-l2-erc20-metadata": "ts-node src/update-l2-erc20-metadata.ts" }, "dependencies": { "dotenv": "^16.0.3" diff --git a/l2-contracts/src/deployForceDeployUpgrader.ts b/l2-contracts/src/deploy-force-deploy-upgrader-through-l1.ts similarity index 69% rename from l2-contracts/src/deployForceDeployUpgrader.ts rename to l2-contracts/src/deploy-force-deploy-upgrader-through-l1.ts index d63bc50e403b..11a55dc07fa1 100644 --- a/l2-contracts/src/deployForceDeployUpgrader.ts +++ b/l2-contracts/src/deploy-force-deploy-upgrader-through-l1.ts @@ -3,16 +3,7 @@ import * as hre from "hardhat"; import { Command } from "commander"; import { ethers, Wallet } from "ethers"; -import { computeL2Create2Address, create2DeployFromL1, getNumberFromEnv } from "./utils"; -import { web3Provider } from "../../l1-contracts/scripts/utils"; -import * as fs from "fs"; -import * as path from "path"; - -const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); - -const priorityTxMaxGasLimit = getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT"); +import { computeL2Create2Address, create2DeployFromL1, priorityTxMaxGasLimit, ethTestConfig, provider } from "./utils"; // Script to deploy the force deploy upgrader contract and output its address. // Note, that this script expects that the L2 contracts have been compiled PRIOR @@ -22,10 +13,12 @@ async function main() { program .version("0.1.0") - .name("deploy-force-deploy-upgrader") + .name("deploy-force-deploy-upgrader-through-l1") + .option("--chain-id ") .description("Deploys the force deploy upgrader contract to L2"); program.option("--private-key ").action(async (cmd) => { + const chainId: string = cmd.chainId ? cmd.chainId : process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID; const deployWallet = cmd.privateKey ? new Wallet(cmd.privateKey, provider) : Wallet.fromMnemonic( @@ -44,7 +37,14 @@ async function main() { ); // TODO: request from API how many L2 gas needs for the transaction. - await create2DeployFromL1(deployWallet, forceDeployUpgraderBytecode, "0x", create2Salt, priorityTxMaxGasLimit); + await create2DeployFromL1( + chainId, + deployWallet, + forceDeployUpgraderBytecode, + "0x", + create2Salt, + priorityTxMaxGasLimit + ); console.log(`CONTRACTS_L2_DEFAULT_UPGRADE_ADDR=${forceDeployUpgraderAddress}`); }); diff --git a/l2-contracts/src/deploy-force-deploy-upgrader.ts b/l2-contracts/src/deploy-force-deploy-upgrader.ts new file mode 100644 index 000000000000..b97d48180afb --- /dev/null +++ b/l2-contracts/src/deploy-force-deploy-upgrader.ts @@ -0,0 +1,59 @@ +import { Command } from "commander"; +import { ethers, Wallet } from "ethers"; +import { computeL2Create2Address, create2DeployFromL1, provider, priorityTxMaxGasLimit, ethTestConfig } from "./utils"; + +import * as hre from "hardhat"; + +// Script to deploy the force deploy upgrader contract and output its address. +// Note, that this script expects that the L2 contracts have been compiled PRIOR +// to running this script. +async function main() { + const program = new Command(); + + program + .version("0.1.0") + .name("deploy-force-deploy-upgrader") + .option("--chain-id ") + .description("Deploys the force deploy upgrader contract to L2"); + + program.option("--private-key ").action(async (cmd) => { + const chainId: string = cmd.chainId ? cmd.chainId : process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID; + const deployWallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + console.log(`Using deployer wallet: ${deployWallet.address}`); + + const forceDeployUpgraderBytecode = hre.artifacts.readArtifactSync("ForceDeployUpgrader").bytecode; + const create2Salt = ethers.constants.HashZero; + const forceDeployUpgraderAddress = computeL2Create2Address( + deployWallet, + forceDeployUpgraderBytecode, + "0x", + create2Salt + ); + + // TODO: request from API how many L2 gas needs for the transaction. + await create2DeployFromL1( + chainId, + deployWallet, + forceDeployUpgraderBytecode, + "0x", + create2Salt, + priorityTxMaxGasLimit + ); + + console.log(`CONTRACTS_L2_DEFAULT_UPGRADE_ADDR=${forceDeployUpgraderAddress}`); + }); + + await program.parseAsync(process.argv); +} + +main() + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts b/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts new file mode 100644 index 000000000000..68d35aaea7a3 --- /dev/null +++ b/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts @@ -0,0 +1,226 @@ +import { Command } from "commander"; +import { ethers, Wallet } from "ethers"; +import { formatUnits, Interface, parseUnits } from "ethers/lib/utils"; +import { + computeL2Create2Address, + create2DeployFromL1, + ethTestConfig, + provider, + priorityTxMaxGasLimit, + hashL2Bytecode, + applyL1ToL2Alias, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA, +} from "./utils"; + +import { ADDRESS_ONE } from "../../l1-contracts/src.ts/utils"; +import { Deployer } from "../../l1-contracts/src.ts/deploy"; +import { GAS_MULTIPLIER } from "../../l1-contracts/scripts/utils"; +import * as hre from "hardhat"; + +export async function deploySharedBridgeOnL2ThroughL1(deployer: Deployer, chainId: string, gasPrice: ethers.BigNumber) { + const l1SharedBridge = deployer.defaultSharedBridge(deployer.deployWallet); + + /// #################################################################################################################### + + if (deployer.verbose) { + console.log("Providing necessary L2 bytecodes"); + } + const bridgehub = deployer.bridgehubContract(deployer.deployWallet); + + const requiredValueToPublishBytecodes = await bridgehub.l2TransactionBaseCost( + chainId, + gasPrice, + priorityTxMaxGasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA + ); + + const ethIsBaseToken = ADDRESS_ONE == deployer.addresses.BaseToken; + + if (!ethIsBaseToken) { + const erc20 = deployer.baseTokenContract(deployer.deployWallet); + + const approveTx = await erc20.approve( + deployer.addresses.Bridges.SharedBridgeProxy, + requiredValueToPublishBytecodes.add(requiredValueToPublishBytecodes) + ); + await approveTx.wait(1); + } + const nonce = await deployer.deployWallet.getTransactionCount(); + const L2_STANDARD_ERC20_PROXY_FACTORY_BYTECODE = hre.artifacts.readArtifactSync("UpgradeableBeacon").bytecode; + const L2_STANDARD_ERC20_IMPLEMENTATION_BYTECODE = hre.artifacts.readArtifactSync("L2StandardERC20").bytecode; + + const tx1 = await bridgehub.requestL2TransactionDirect( + { + chainId, + l2Contract: ethers.constants.AddressZero, + mintValue: requiredValueToPublishBytecodes, + l2Value: 0, + l2Calldata: "0x", + l2GasLimit: priorityTxMaxGasLimit, + l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + factoryDeps: [L2_STANDARD_ERC20_PROXY_FACTORY_BYTECODE, L2_STANDARD_ERC20_IMPLEMENTATION_BYTECODE], + refundRecipient: deployer.deployWallet.address, + }, + { gasPrice, nonce, value: ethIsBaseToken ? requiredValueToPublishBytecodes : 0 } + ); + await tx1.wait(); + if (deployer.verbose) { + console.log("Bytecodes published on L2"); + } + /// #################################################################################################################### + if (deployer.verbose) { + console.log("Deploying L2SharedBridge Implementation"); + } + const L2_SHARED_BRIDGE_IMPLEMENTATION_BYTECODE = hre.artifacts.readArtifactSync("L2SharedBridge").bytecode; + + if (!L2_SHARED_BRIDGE_IMPLEMENTATION_BYTECODE) { + throw new Error("L2_SHARED_BRIDGE_IMPLEMENTATION_BYTECODE not found"); + } + if (deployer.verbose) { + console.log("L2_SHARED_BRIDGE_IMPLEMENTATION_BYTECODE loaded"); + + console.log("Computing L2SharedBridge Implementation Address"); + } + const l2SharedBridgeImplAddress = computeL2Create2Address( + deployer.deployWallet, + L2_SHARED_BRIDGE_IMPLEMENTATION_BYTECODE, + "0x", + ethers.constants.HashZero + ); + if (deployer.verbose) { + console.log(`L2SharedBridge Implementation Address: ${l2SharedBridgeImplAddress}`); + + console.log("Deploying L2SharedBridge Implementation"); + } + + /// L2StandardTokenProxy bytecode. We need this bytecode to be accessible on the L2, it is enough to add to factoryDeps + const L2_STANDARD_TOKEN_PROXY_BYTECODE = hre.artifacts.readArtifactSync("BeaconProxy").bytecode; + + // TODO: request from API how many L2 gas needs for the transaction. + const tx2 = await create2DeployFromL1( + chainId, + deployer.deployWallet, + L2_SHARED_BRIDGE_IMPLEMENTATION_BYTECODE, + "0x", + ethers.constants.HashZero, + priorityTxMaxGasLimit, + gasPrice, + [L2_STANDARD_TOKEN_PROXY_BYTECODE] + ); + + await tx2.wait(); + if (deployer.verbose) { + console.log("Deployed L2SharedBridge Implementation"); + } + /// #################################################################################################################### + if (deployer.verbose) { + console.log("Deploying L2SharedBridge Proxy"); + } + /// prepare proxyInitializationParams + const l2GovernorAddress = applyL1ToL2Alias(deployer.addresses.Governance); + const BEACON_PROXY_BYTECODE = hre.artifacts.readArtifactSync("BeaconProxy").bytecode; + const l2SharedBridgeInterface = new Interface(hre.artifacts.readArtifactSync("L2SharedBridge").abi); + // console.log("kl todo l2GovernorAddress", l2GovernorAddress, deployer.addresses.Governance) + const proxyInitializationParams = l2SharedBridgeInterface.encodeFunctionData("initialize", [ + l1SharedBridge.address, + hashL2Bytecode(BEACON_PROXY_BYTECODE), + l2GovernorAddress, + ]); + + /// prepare constructor data + const l2SharedBridgeProxyConstructorData = ethers.utils.arrayify( + new ethers.utils.AbiCoder().encode( + ["address", "address", "bytes"], + [l2SharedBridgeImplAddress, l2GovernorAddress, proxyInitializationParams] + ) + ); + + /// loading TransparentUpgradeableProxy bytecode + const L2_SHARED_BRIDGE_PROXY_BYTECODE = hre.artifacts.readArtifactSync("TransparentUpgradeableProxy").bytecode; + + /// compute L2SharedBridgeProxy address + const l2SharedBridgeProxyAddress = computeL2Create2Address( + deployer.deployWallet, + L2_SHARED_BRIDGE_PROXY_BYTECODE, + l2SharedBridgeProxyConstructorData, + ethers.constants.HashZero + ); + + /// deploy L2SharedBridgeProxy + // TODO: request from API how many L2 gas needs for the transaction. + const tx3 = await create2DeployFromL1( + chainId, + deployer.deployWallet, + L2_SHARED_BRIDGE_PROXY_BYTECODE, + l2SharedBridgeProxyConstructorData, + ethers.constants.HashZero, + priorityTxMaxGasLimit, + gasPrice + ); + await tx3.wait(); + if (deployer.verbose) { + console.log(`CONTRACTS_L2_SHARED_BRIDGE_ADDR=${l2SharedBridgeProxyAddress}`); + } + /// #################################################################################################################### + + if (deployer.verbose) { + console.log("Initializing chain governance"); + } + await deployer.executeUpgrade( + l1SharedBridge.address, + 0, + l1SharedBridge.interface.encodeFunctionData("initializeChainGovernance", [chainId, l2SharedBridgeProxyAddress]) + ); + + if (deployer.verbose) { + console.log("L2 shared bridge address registered on L1 via governance"); + } +} + +async function main() { + const program = new Command(); + + program.version("0.1.0").name("deploy-shared-bridge-on-l2-through-l1"); + + program + .option("--private-key ") + .option("--chain-id ") + .option("--gas-price ") + .option("--nonce ") + .option("--erc20-bridge ") + .action(async (cmd) => { + const chainId: string = cmd.chainId ? cmd.chainId : process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID; + const deployWallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + console.log(`Using deployer wallet: ${deployWallet.address}`); + + const deployer = new Deployer({ + deployWallet, + ownerAddress: deployWallet.address, + verbose: true, + }); + + const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployer.deployWallet.getTransactionCount(); + console.log(`Using nonce: ${nonce}`); + + const gasPrice = cmd.gasPrice + ? parseUnits(cmd.gasPrice, "gwei") + : (await provider.getGasPrice()).mul(GAS_MULTIPLIER); + console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); + + await deploySharedBridgeOnL2ThroughL1(deployer, chainId, gasPrice); + }); + + await program.parseAsync(process.argv); +} + +main() + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l2-contracts/src/deploy-shared-bridge-on-l2.ts b/l2-contracts/src/deploy-shared-bridge-on-l2.ts new file mode 100644 index 000000000000..da765f33aae6 --- /dev/null +++ b/l2-contracts/src/deploy-shared-bridge-on-l2.ts @@ -0,0 +1,54 @@ +import { Command } from "commander"; +import { Wallet } from "ethers"; +import { formatUnits, parseUnits } from "ethers/lib/utils"; +import { Deployer } from "../../l1-contracts/src.ts/deploy"; +import { GAS_MULTIPLIER } from "../../l1-contracts/scripts/utils"; +import { ethTestConfig, provider } from "./utils"; + +async function main() { + const program = new Command(); + + program.version("0.1.0").name("initialize-erc20-bridge-chain"); + + program + .option("--private-key ") + .option("--chain-id ") + .option("--gas-price ") + .option("--nonce ") + .option("--erc20-bridge ") + .action(async (cmd) => { + // const chainId: string = cmd.chainId ? cmd.chainId : process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID; + const deployWallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/0" + ).connect(provider); + console.log(`Using deployer wallet: ${deployWallet.address}`); + + const gasPrice = cmd.gasPrice + ? parseUnits(cmd.gasPrice, "gwei") + : (await provider.getGasPrice()).mul(GAS_MULTIPLIER); + console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); + + const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); + console.log(`Using nonce: ${nonce}`); + + const deployer = new Deployer({ + deployWallet, + ownerAddress: deployWallet.address, + verbose: true, + }); + + deployer; + }); + + await program.parseAsync(process.argv); +} + +main() + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l2-contracts/src/deploy-testnet-paymaster-through-l1.ts b/l2-contracts/src/deploy-testnet-paymaster-through-l1.ts new file mode 100644 index 000000000000..6e13e1434e7f --- /dev/null +++ b/l2-contracts/src/deploy-testnet-paymaster-through-l1.ts @@ -0,0 +1,57 @@ +import { Command } from "commander"; +import { ethers, Wallet } from "ethers"; +import { computeL2Create2Address, create2DeployFromL1, priorityTxMaxGasLimit, ethTestConfig, provider } from "./utils"; +import * as hre from "hardhat"; + +// Script to deploy the testnet paymaster and output its address. +// Note, that this script expects that the L2 contracts have been compiled PRIOR +// to running this script. +async function main() { + const program = new Command(); + + program + .version("0.1.0") + .name("deploy-testnet-paymaster-through-l1") + .description("Deploys the testnet paymaster to L2"); + + program + .option("--private-key ") + .option("--chain-id ") + .action(async (cmd) => { + const chainId: string = cmd.chainId ? cmd.chainId : process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID; + const deployWallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + console.log(`Using deployer wallet: ${deployWallet.address}`); + + const testnetPaymasterBytecode = hre.artifacts.readArtifactSync("TestnetPaymaster").bytecode; + const create2Salt = ethers.constants.HashZero; + const paymasterAddress = computeL2Create2Address(deployWallet, testnetPaymasterBytecode, "0x", create2Salt); + + // TODO: request from API how many L2 gas needs for the transaction. + await ( + await create2DeployFromL1( + chainId, + deployWallet, + testnetPaymasterBytecode, + "0x", + create2Salt, + priorityTxMaxGasLimit + ) + ).wait(); + + console.log(`CONTRACTS_L2_TESTNET_PAYMASTER_ADDR=${paymasterAddress}`); + }); + + await program.parseAsync(process.argv); +} + +main() + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l2-contracts/src/deployL2Weth.ts b/l2-contracts/src/deployL2Weth.ts deleted file mode 100644 index 9209993dd433..000000000000 --- a/l2-contracts/src/deployL2Weth.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { Command } from "commander"; -import { ethers, Wallet } from "ethers"; -import { formatUnits, parseUnits } from "ethers/lib/utils"; -import { getTokens, web3Provider } from "../../l1-contracts/scripts/utils"; -import { Deployer } from "../../l1-contracts/src.ts/deploy"; - -import { applyL1ToL2Alias, computeL2Create2Address, create2DeployFromL1, getNumberFromEnv } from "./utils"; - -import * as fs from "fs"; -import * as path from "path"; - -const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); - -const contractArtifactsPath = path.join(process.env.ZKSYNC_HOME as string, "contracts/l2-contracts/artifacts-zk/"); -const l2BridgeArtifactsPath = path.join(contractArtifactsPath, "cache-zk/solpp-generated-contracts/bridge/"); - -const openzeppelinTransparentProxyArtifactsPath = path.join( - contractArtifactsPath, - "@openzeppelin/contracts/proxy/transparent/" -); - -function readBytecode(path: string, fileName: string) { - return JSON.parse(fs.readFileSync(`${path}/${fileName}.sol/${fileName}.json`, { encoding: "utf-8" })).bytecode; -} - -function readInterface(path: string, fileName: string) { - const abi = JSON.parse(fs.readFileSync(`${path}/${fileName}.sol/${fileName}.json`, { encoding: "utf-8" })).abi; - return new ethers.utils.Interface(abi); -} - -const L2_WETH_INTERFACE = readInterface(l2BridgeArtifactsPath, "L2Weth"); -const L2_WETH_IMPLEMENTATION_BYTECODE = readBytecode(l2BridgeArtifactsPath, "L2Weth"); -const L2_WETH_PROXY_BYTECODE = readBytecode(openzeppelinTransparentProxyArtifactsPath, "TransparentUpgradeableProxy"); -const tokens = getTokens(process.env.CHAIN_ETH_NETWORK || "localhost"); -const l1WethToken = tokens.find((token: { symbol: string }) => token.symbol == "WETH")!.address; - -async function main() { - const program = new Command(); - - program.version("0.1.0").name("deploy-l2-weth"); - - program - .option("--private-key ") - .option("--gas-price ") - .option("--nonce ") - .action(async (cmd) => { - if (!l1WethToken) { - // Makes no sense to deploy the Rollup WETH if there is no base Layer WETH provided - console.log("Base Layer WETH address not provided so WETH deployment will be skipped."); - return; - } - - const deployWallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/0" - ).connect(provider); - console.log(`Using deployer wallet: ${deployWallet.address}`); - - const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice(); - console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); - - const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); - console.log(`Using initial nonce: ${nonce}`); - - const deployer = new Deployer({ - deployWallet, - verbose: true, - }); - - const zkSync = deployer.zkSyncContract(deployWallet); - - const priorityTxMaxGasLimit = getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT"); - const l1GovernorAddress = await zkSync.getGovernor(); - // Check whether governor is a smart contract on L1 to apply alias if needed. - const l1GovernorCodeSize = ethers.utils.hexDataLength(await deployWallet.provider.getCode(l1GovernorAddress)); - const l2GovernorAddress = l1GovernorCodeSize == 0 ? l1GovernorAddress : applyL1ToL2Alias(l1GovernorAddress); - - const abiCoder = new ethers.utils.AbiCoder(); - - const l2WethImplAddr = computeL2Create2Address( - deployWallet, - L2_WETH_IMPLEMENTATION_BYTECODE, - "0x", - ethers.constants.HashZero - ); - - const proxyInitializationParams = L2_WETH_INTERFACE.encodeFunctionData("initialize", ["Wrapped Ether", "WETH"]); - const l2ERC20BridgeProxyConstructor = ethers.utils.arrayify( - abiCoder.encode(["address", "address", "bytes"], [l2WethImplAddr, l2GovernorAddress, proxyInitializationParams]) - ); - const l2WethProxyAddr = computeL2Create2Address( - deployWallet, - L2_WETH_PROXY_BYTECODE, - l2ERC20BridgeProxyConstructor, - ethers.constants.HashZero - ); - - const tx = await create2DeployFromL1( - deployWallet, - L2_WETH_IMPLEMENTATION_BYTECODE, - "0x", - ethers.constants.HashZero, - priorityTxMaxGasLimit - ); - console.log( - `WETH implementation transaction sent with hash ${tx.hash} and nonce ${tx.nonce}. Waiting for receipt...` - ); - - await tx.wait(); - - const tx2 = await create2DeployFromL1( - deployWallet, - L2_WETH_PROXY_BYTECODE, - l2ERC20BridgeProxyConstructor, - ethers.constants.HashZero, - priorityTxMaxGasLimit - ); - console.log(`WETH proxy transaction sent with hash ${tx2.hash} and nonce ${tx2.nonce}. Waiting for receipt...`); - - await tx2.wait(); - - console.log(`CONTRACTS_L2_WETH_TOKEN_IMPL_ADDR=${l2WethImplAddr}`); - console.log(`CONTRACTS_L2_WETH_TOKEN_PROXY_ADDR=${l2WethProxyAddr}`); - }); - - await program.parseAsync(process.argv); -} - -main() - .then(() => process.exit(0)) - .catch((err) => { - console.error("Error:", err); - process.exit(1); - }); diff --git a/l2-contracts/src/deployTestnetPaymaster.ts b/l2-contracts/src/deployTestnetPaymaster.ts deleted file mode 100644 index 5d32c2c17af5..000000000000 --- a/l2-contracts/src/deployTestnetPaymaster.ts +++ /dev/null @@ -1,54 +0,0 @@ -// hardhat import should be the first import in the file -import * as hre from "hardhat"; - -import { Command } from "commander"; -import { ethers, Wallet } from "ethers"; -import { computeL2Create2Address, create2DeployFromL1, getNumberFromEnv } from "./utils"; -import { web3Provider } from "../../l1-contracts/scripts/utils"; -import * as fs from "fs"; -import * as path from "path"; - -const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); - -const priorityTxMaxGasLimit = getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT"); - -// Script to deploy the testnet paymaster and output its address. -// Note, that this script expects that the L2 contracts have been compiled PRIOR -// to running this script. -async function main() { - const program = new Command(); - - program.version("0.1.0").name("deploy-testnet-paymaster").description("Deploys the testnet paymaster to L2"); - - program.option("--private-key ").action(async (cmd) => { - const deployWallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/1" - ).connect(provider); - console.log(`Using deployer wallet: ${deployWallet.address}`); - - const testnetPaymasterBytecode = hre.artifacts.readArtifactSync("TestnetPaymaster").bytecode; - const create2Salt = ethers.constants.HashZero; - const paymasterAddress = computeL2Create2Address(deployWallet, testnetPaymasterBytecode, "0x", create2Salt); - - // TODO: request from API how many L2 gas needs for the transaction. - await ( - await create2DeployFromL1(deployWallet, testnetPaymasterBytecode, "0x", create2Salt, priorityTxMaxGasLimit) - ).wait(); - - console.log(`CONTRACTS_L2_TESTNET_PAYMASTER_ADDR=${paymasterAddress}`); - }); - - await program.parseAsync(process.argv); -} - -main() - .then(() => process.exit(0)) - .catch((err) => { - console.error("Error:", err); - process.exit(1); - }); diff --git a/l2-contracts/src/publish-bridge-preimages.ts b/l2-contracts/src/publish-bridge-preimages.ts index cd21209e7e65..eaa98a8817a4 100644 --- a/l2-contracts/src/publish-bridge-preimages.ts +++ b/l2-contracts/src/publish-bridge-preimages.ts @@ -3,16 +3,8 @@ import * as hre from "hardhat"; import { Command } from "commander"; import { Wallet, ethers } from "ethers"; -import * as fs from "fs"; import { Deployer } from "../../l1-contracts/src.ts/deploy"; -import * as path from "path"; -import { getNumberFromEnv, web3Provider } from "../../l1-contracts/scripts/utils"; -import { REQUIRED_L2_GAS_PRICE_PER_PUBDATA } from "./utils"; - -const PRIORITY_TX_MAX_GAS_LIMIT = getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT"); -const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); +import { REQUIRED_L2_GAS_PRICE_PER_PUBDATA, provider, ethTestConfig, priorityTxMaxGasLimit } from "./utils"; function getContractBytecode(contractName: string) { return hre.artifacts.readArtifactSync(contractName).bytecode; @@ -25,9 +17,11 @@ async function main() { program .option("--private-key ") + .option("--chain-id ") .option("--nonce ") .option("--gas-price ") .action(async (cmd) => { + const chainId: string = cmd.chainId ? cmd.chainId : process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID; const wallet = cmd.privateKey ? new Wallet(cmd.privateKey, provider) : Wallet.fromMnemonic( @@ -43,19 +37,23 @@ async function main() { console.log(`Using gas price: ${gasPrice}`); const deployer = new Deployer({ deployWallet: wallet }); - const zkSync = deployer.zkSyncContract(wallet); + const bridgehub = deployer.bridgehubContract(wallet); - const publishL2ERC20BridgeTx = await zkSync.requestL2Transaction( - ethers.constants.AddressZero, - 0, - "0x", - PRIORITY_TX_MAX_GAS_LIMIT, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - [getContractBytecode("L2ERC20Bridge")], - wallet.address, + const publishL2SharedBridgeTx = await bridgehub.requestL2TransactionDirect( + { + chainId, + l2Contract: ethers.constants.AddressZero, + mintValue: 0, + l2Value: 0, + l2Calldata: "0x", + l2GasLimit: priorityTxMaxGasLimit, + l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + factoryDeps: [getContractBytecode("L2SharedBridge")], + refundRecipient: wallet.address, + }, { nonce, gasPrice } ); - await publishL2ERC20BridgeTx.wait(); + await publishL2SharedBridgeTx.wait(); }); await program.parseAsync(process.argv); diff --git a/l2-contracts/src/updateL2ERC20Metadata.ts b/l2-contracts/src/update-l2-erc20-metadata.ts similarity index 94% rename from l2-contracts/src/updateL2ERC20Metadata.ts rename to l2-contracts/src/update-l2-erc20-metadata.ts index 243439d24733..7a1957395b7f 100644 --- a/l2-contracts/src/updateL2ERC20Metadata.ts +++ b/l2-contracts/src/update-l2-erc20-metadata.ts @@ -2,19 +2,16 @@ import * as hre from "hardhat"; import "@nomiclabs/hardhat-ethers"; import { Command } from "commander"; import { Wallet, ethers, BigNumber } from "ethers"; -import * as fs from "fs"; -import * as path from "path"; import { Provider } from "zksync-web3"; -import { getNumberFromEnv, web3Provider } from "../../l1-contracts/scripts/utils"; +import { getNumberFromEnv } from "../../l1-contracts/src.ts/utils"; +import { web3Provider } from "../../l1-contracts/scripts/utils"; import { Deployer } from "../../l1-contracts/src.ts/deploy"; -import { getL1TxInfo } from "./utils"; +import { getL1TxInfo, ethTestConfig } from "./utils"; // From openzeppelin-upgradable v4.9.5 Initializable contract implementation. const INITIALIZED_STORAGE_SLOT = 0; const priorityTxMaxGasLimit = BigNumber.from(getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT")); const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); async function getReinitializeTokenCalldata( newName: string, diff --git a/l2-contracts/src/upgradeBridgeImpl.ts b/l2-contracts/src/upgrade-bridge-impl.ts similarity index 86% rename from l2-contracts/src/upgradeBridgeImpl.ts rename to l2-contracts/src/upgrade-bridge-impl.ts index 5784d8f6b07a..8d62cd09f33f 100644 --- a/l2-contracts/src/upgradeBridgeImpl.ts +++ b/l2-contracts/src/upgrade-bridge-impl.ts @@ -3,16 +3,17 @@ import * as hre from "hardhat"; import "@nomiclabs/hardhat-ethers"; import { Command } from "commander"; -import { Wallet, ethers, BigNumber } from "ethers"; +import { BigNumber, Wallet, ethers } from "ethers"; import * as fs from "fs"; import * as path from "path"; import { Provider } from "zksync-web3"; import { REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT } from "zksync-web3/build/src/utils"; -import { getAddressFromEnv, getNumberFromEnv, web3Provider } from "../../l1-contracts/scripts/utils"; +import { web3Provider } from "../../l1-contracts/scripts/utils"; +import { getAddressFromEnv, getNumberFromEnv } from "../../l1-contracts/src.ts/utils"; import { Deployer } from "../../l1-contracts/src.ts/deploy"; import { awaitPriorityOps, computeL2Create2Address, create2DeployFromL1, getL1TxInfo } from "./utils"; -const SupportedL2Contracts = ["L2ERC20Bridge", "L2StandardERC20", "L2WethBridge", "L2Weth"] as const; +const SupportedL2Contracts = ["L2SharedBridge", "L2StandardERC20", "L2WrappedBaseToken"] as const; // For L1 contracts we can not read bytecodes, but we can still produce the upgrade calldata const SupportedL1Contracts = ["L1ERC20Bridge"] as const; @@ -58,8 +59,8 @@ function validateUpgradeInfo(info: UpgradeInfo) { } const priorityTxMaxGasLimit = BigNumber.from(getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT")); -const l2Erc20BridgeProxyAddress = getAddressFromEnv("CONTRACTS_L2_ERC20_BRIDGE_ADDR"); -const l1Erc20BridgeProxyAddress = getAddressFromEnv("CONTRACTS_L1_ERC20_BRIDGE_PROXY_ADDR"); +const l2SharedBridgeProxyAddress = getAddressFromEnv("CONTRACTS_L2_SHARED_BRIDGE_ADDR"); +const l1Erc20BridgeProxyAddress = getAddressFromEnv("CONTRACTS_L1_SHARED_BRIDGE_PROXY_ADDR"); const EIP1967_IMPLEMENTATION_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"; const provider = web3Provider(); @@ -69,7 +70,7 @@ const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { async function getERC20BeaconAddress() { const provider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); const bridge = (await provider.getDefaultBridgeAddresses()).erc20L2; - const artifact = await hre.artifacts.readArtifact("L2ERC20Bridge"); + const artifact = await hre.artifacts.readArtifact("L2SharedBridge"); const contract = new ethers.Contract(bridge, artifact.abi, provider); return await contract.l2TokenBeacon(); @@ -142,17 +143,11 @@ async function getTxInfo( contract: SupportedContract, l2ProxyAddress?: string ) { - if (contract === "L2ERC20Bridge") { - return await getTransparentProxyUpgradeTxInfo( - deployer, - target, - l2Erc20BridgeProxyAddress, - refundRecipient, - gasPrice - ); - } else if (contract == "L2Weth") { + if (contract === "L2SharedBridge") { + return getTransparentProxyUpgradeTxInfo(deployer, target, l2SharedBridgeProxyAddress, refundRecipient, gasPrice); + } else if (contract == "L2WrappedBaseToken") { throw new Error( - "The latest L2Weth implementation requires L2WethBridge to be deployed in order to be correctly initialized, which is not the case on the majority of networks. Remove this error once the bridge is deployed." + "The latest L2WrappedBaseToken implementation requires L2SharedBridge to be deployed in order to be correctly initialized, which is not the case on the majority of networks. Remove this error once the bridge is deployed." ); } else if (contract == "L2StandardERC20") { if (!l2ProxyAddress) { @@ -161,7 +156,7 @@ async function getTxInfo( } console.log(`Using beacon address: ${l2ProxyAddress}`); - return await getTokenBeaconUpgradeTxInfo(deployer, target, refundRecipient, gasPrice, l2ProxyAddress); + return getTokenBeaconUpgradeTxInfo(deployer, target, refundRecipient, gasPrice, l2ProxyAddress); } else if (contract == "L1ERC20Bridge") { return await getL1BridgeUpgradeTxInfo(target); } else { @@ -183,6 +178,7 @@ async function main() { .option("--no-l2-double-check") .action(async (cmd) => { // We deploy the target contract through L1 to ensure security + const chainId: string = cmd.chainId ? cmd.chainId : process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID; const deployWallet = cmd.privateKey ? new Wallet(cmd.privateKey, provider) : Wallet.fromMnemonic( @@ -201,16 +197,16 @@ async function main() { console.log("Salt: ", salt); const bridgeImplBytecode = getContractBytecode(cmd.contract); - const l2ERC20BridgeImplAddr = computeL2Create2Address(deployWallet, bridgeImplBytecode, "0x", salt); - console.log("Bridge implemenation address: ", l2ERC20BridgeImplAddr); + const l2SharedBridgeImplAddr = computeL2Create2Address(deployWallet, bridgeImplBytecode, "0x", salt); + console.log("Bridge implemenation address: ", l2SharedBridgeImplAddr); if (cmd.l2DoubleCheck !== false) { // If the bytecode has already been deployed there is no need to deploy it again. const zksProvider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); - const deployedBytecode = await zksProvider.getCode(l2ERC20BridgeImplAddr); + const deployedBytecode = await zksProvider.getCode(l2SharedBridgeImplAddr); if (deployedBytecode === bridgeImplBytecode) { console.log("The bytecode has been already deployed!"); - console.log("Address:", l2ERC20BridgeImplAddr); + console.log("Address:", l2SharedBridgeImplAddr); return; } else if (ethers.utils.arrayify(deployedBytecode).length > 0) { console.log("CREATE2 DERIVATION: A different bytecode has been deployed on that address"); @@ -221,6 +217,7 @@ async function main() { } const tx = await create2DeployFromL1( + chainId, deployWallet, bridgeImplBytecode, "0x", @@ -241,10 +238,10 @@ async function main() { if (cmd.l2DoubleCheck !== false) { console.log("Waiting for the L2 transaction to be committed..."); const zksProvider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); - await awaitPriorityOps(zksProvider, receipt, deployer.zkSyncContract(deployWallet).interface); + await awaitPriorityOps(zksProvider, receipt, deployer.bridgehubContract(deployWallet).interface); // Double checking that the bridge implementation has been deployed - const deployedBytecode = await zksProvider.getCode(l2ERC20BridgeImplAddr); + const deployedBytecode = await zksProvider.getCode(l2SharedBridgeImplAddr); if (deployedBytecode != bridgeImplBytecode) { console.error("Bridge implementation has not been deployed"); process.exit(1); @@ -255,7 +252,7 @@ async function main() { console.log("\n"); console.log("Bridge implementation has been successfuly deployed!"); - console.log("Address:", l2ERC20BridgeImplAddr); + console.log("Address:", l2SharedBridgeImplAddr); }); program @@ -346,9 +343,10 @@ async function main() { const gasPrice = ethers.utils.parseUnits(cmd.gasPrice, "gwei"); const deployer = new Deployer({ deployWallet: Wallet.createRandom().connect(provider) }); - const zksync = deployer.zkSyncContract(ethers.Wallet.createRandom().connect(provider)); - + const zksync = deployer.bridgehubContract(ethers.Wallet.createRandom().connect(provider)); + const chainId = process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID; const neededValue = await zksync.l2TransactionBaseCost( + chainId, gasPrice, priorityTxMaxGasLimit, REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT diff --git a/l2-contracts/src/utils.ts b/l2-contracts/src/utils.ts index 465a7a11d92b..393667085146 100644 --- a/l2-contracts/src/utils.ts +++ b/l2-contracts/src/utils.ts @@ -1,14 +1,22 @@ import { artifacts } from "hardhat"; import { Interface } from "ethers/lib/utils"; +import { deployedAddressesFromEnv } from "../../l1-contracts/src.ts/deploy-utils"; import type { Deployer } from "../../l1-contracts/src.ts/deploy"; -import { deployedAddressesFromEnv } from "../../l1-contracts/scripts/utils"; -import { IZkSyncFactory } from "../../l1-contracts/typechain/IZkSyncFactory"; +import { ADDRESS_ONE, getNumberFromEnv } from "../../l1-contracts/src.ts/utils"; +import { IBridgehubFactory } from "../../l1-contracts/typechain/IBridgehubFactory"; +import { web3Provider } from "../../l1-contracts/scripts/utils"; import type { BigNumber, BytesLike, Wallet } from "ethers"; import { ethers } from "ethers"; import type { Provider } from "zksync-web3"; import { REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, sleep } from "zksync-web3/build/src/utils"; +import { IERC20Factory } from "zksync-web3/build/typechain"; + +import * as fs from "fs"; +import * as path from "path"; + +export const provider = web3Provider(); // eslint-disable-next-line @typescript-eslint/no-var-requires export const REQUIRED_L2_GAS_PRICE_PER_PUBDATA = require("../../SystemConfig.json").REQUIRED_L2_GAS_PRICE_PER_PUBDATA; @@ -18,6 +26,11 @@ const CREATE2_PREFIX = ethers.utils.solidityKeccak256(["string"], ["zksyncCreate const L1_TO_L2_ALIAS_OFFSET = "0x1111000000000000000000000000000000001111"; const ADDRESS_MODULO = ethers.BigNumber.from(2).pow(160); +export const priorityTxMaxGasLimit = getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT"); + +export const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +export const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); + export function applyL1ToL2Alias(address: string): string { return ethers.utils.hexlify(ethers.BigNumber.from(address).add(L1_TO_L2_ALIAS_OFFSET).mod(ADDRESS_MODULO)); } @@ -79,40 +92,53 @@ export function computeL2Create2Address( } export async function create2DeployFromL1( + chainId: ethers.BigNumberish, wallet: ethers.Wallet, bytecode: ethers.BytesLike, constructor: ethers.BytesLike, create2Salt: ethers.BytesLike, l2GasLimit: ethers.BigNumberish, - gasPrice?: ethers.BigNumberish + gasPrice?: ethers.BigNumberish, + extraFactoryDeps?: ethers.BytesLike[] ) { - const zkSyncAddress = deployedAddressesFromEnv().ZkSync.DiamondProxy; - const zkSync = IZkSyncFactory.connect(zkSyncAddress, wallet); + const bridgehubAddress = deployedAddressesFromEnv().Bridgehub.BridgehubProxy; + const bridgehub = IBridgehubFactory.connect(bridgehubAddress, wallet); const deployerSystemContracts = new Interface(artifacts.readArtifactSync("IContractDeployer").abi); const bytecodeHash = hashL2Bytecode(bytecode); const calldata = deployerSystemContracts.encodeFunctionData("create2", [create2Salt, bytecodeHash, constructor]); - gasPrice ??= await zkSync.provider.getGasPrice(); - const expectedCost = await zkSync.l2TransactionBaseCost(gasPrice, l2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA); - - return await zkSync.requestL2Transaction( - DEPLOYER_SYSTEM_CONTRACT_ADDRESS, - 0, - calldata, + gasPrice ??= await bridgehub.provider.getGasPrice(); + const expectedCost = await bridgehub.l2TransactionBaseCost( + chainId, + gasPrice, l2GasLimit, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - [bytecode], - wallet.address, - { value: expectedCost, gasPrice } + REQUIRED_L2_GAS_PRICE_PER_PUBDATA ); -} -export function getNumberFromEnv(envName: string): string { - const number = process.env[envName]; - if (!/^([1-9]\d*|0)$/.test(number)) { - throw new Error(`Incorrect number format number in ${envName} env: ${number}`); + const baseTokenAddress = await bridgehub.baseToken(chainId); + const baseTokenBridge = deployedAddressesFromEnv().Bridges.SharedBridgeProxy; + const baseToken = IERC20Factory.connect(baseTokenAddress, wallet); + const ethIsBaseToken = ADDRESS_ONE == baseTokenAddress; + + if (!ethIsBaseToken) { + const tx = await baseToken.approve(baseTokenBridge, expectedCost); + await tx.wait(); } - return number; + const factoryDeps = extraFactoryDeps ? [bytecode, ...extraFactoryDeps] : [bytecode]; + return await bridgehub.requestL2TransactionDirect( + { + chainId, + l2Contract: DEPLOYER_SYSTEM_CONTRACT_ADDRESS, + mintValue: expectedCost, + l2Value: 0, + l2Calldata: calldata, + l2GasLimit, + l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + factoryDeps: factoryDeps, + refundRecipient: wallet.address, + }, + { value: ethIsBaseToken ? expectedCost : 0, gasPrice } + ); } export async function awaitPriorityOps( @@ -138,6 +164,12 @@ export async function awaitPriorityOps( } } +export type TxInfo = { + data: string; + target: string; + value?: string; +}; + export async function getL1TxInfo( deployer: Deployer, to: string, @@ -146,8 +178,8 @@ export async function getL1TxInfo( gasPrice: BigNumber, priorityTxMaxGasLimit: BigNumber, provider: ethers.providers.JsonRpcProvider -) { - const zksync = deployer.zkSyncContract(ethers.Wallet.createRandom().connect(provider)); +): Promise { + const zksync = deployer.stateTransitionContract(ethers.Wallet.createRandom().connect(provider)); const l1Calldata = zksync.interface.encodeFunctionData("requestL2Transaction", [ to, 0, diff --git a/l2-contracts/test/erc20.test.ts b/l2-contracts/test/erc20.test.ts index a026265b19fe..cc43dddddb23 100644 --- a/l2-contracts/test/erc20.test.ts +++ b/l2-contracts/test/erc20.test.ts @@ -4,9 +4,9 @@ import { ethers } from "ethers"; import * as hre from "hardhat"; import { Provider, Wallet } from "zksync-web3"; import { hashBytecode } from "zksync-web3/build/src/utils"; -import { unapplyL1ToL2Alias } from "../src/utils"; -import { L2ERC20BridgeFactory, L2StandardERC20Factory } from "../typechain"; -import type { L2ERC20Bridge, L2StandardERC20 } from "../typechain"; +import { unapplyL1ToL2Alias } from "./test-utils"; +import { L2SharedBridgeFactory, L2StandardERC20Factory } from "../typechain"; +import type { L2SharedBridge, L2StandardERC20 } from "../typechain"; const richAccount = [ { @@ -35,7 +35,7 @@ describe("ERC20Bridge", function () { // We won't actually deploy an L1 token in these tests, but we need some address for it. const L1_TOKEN_ADDRESS = "0x1111000000000000000000000000000000001111"; - let erc20Bridge: L2ERC20Bridge; + let erc20Bridge: L2SharedBridge; let erc20Token: L2StandardERC20; before("Deploy token and bridge", async function () { @@ -50,7 +50,7 @@ describe("ERC20Bridge", function () { const beaconProxyBytecodeHash = hashBytecode((await deployer.loadArtifact("BeaconProxy")).bytecode); - const erc20BridgeImpl = await deployer.deploy(await deployer.loadArtifact("L2ERC20Bridge")); + const erc20BridgeImpl = await deployer.deploy(await deployer.loadArtifact("L2SharedBridge")); const bridgeInitializeData = erc20BridgeImpl.interface.encodeFunctionData("initialize", [ unapplyL1ToL2Alias(l1BridgeWallet.address), beaconProxyBytecodeHash, @@ -63,11 +63,11 @@ describe("ERC20Bridge", function () { bridgeInitializeData, ]); - erc20Bridge = L2ERC20BridgeFactory.connect(erc20BridgeProxy.address, deployerWallet); + erc20Bridge = L2SharedBridgeFactory.connect(erc20BridgeProxy.address, deployerWallet); }); it("Should finalize deposit ERC20 deposit", async function () { - const erc20BridgeWithL1Bridge = L2ERC20BridgeFactory.connect(erc20Bridge.address, l1BridgeWallet); + const erc20BridgeWithL1Bridge = L2SharedBridgeFactory.connect(erc20Bridge.address, l1BridgeWallet); const l1Depositor = ethers.Wallet.createRandom(); const l2Receiver = ethers.Wallet.createRandom(); diff --git a/l2-contracts/test/test-utils.ts b/l2-contracts/test/test-utils.ts new file mode 100644 index 000000000000..c62d76c11d3b --- /dev/null +++ b/l2-contracts/test/test-utils.ts @@ -0,0 +1,11 @@ +import { ethers } from "ethers"; + +const L1_TO_L2_ALIAS_OFFSET = "0x1111000000000000000000000000000000001111"; +const ADDRESS_MODULO = ethers.BigNumber.from(2).pow(160); + +export function unapplyL1ToL2Alias(address: string): string { + // We still add ADDRESS_MODULO to avoid negative numbers + return ethers.utils.hexlify( + ethers.BigNumber.from(address).sub(L1_TO_L2_ALIAS_OFFSET).add(ADDRESS_MODULO).mod(ADDRESS_MODULO) + ); +} diff --git a/l2-contracts/test/weth.test.ts b/l2-contracts/test/weth.test.ts index 1af07d2dc08f..c88c9a4457db 100644 --- a/l2-contracts/test/weth.test.ts +++ b/l2-contracts/test/weth.test.ts @@ -3,10 +3,10 @@ import { expect } from "chai"; import { ethers } from "ethers"; import * as hre from "hardhat"; import { Provider, Wallet } from "zksync-web3"; -import type { L2Weth } from "../typechain/L2Weth"; -import type { L2WethBridge } from "../typechain/L2WethBridge"; -import { L2WethBridgeFactory } from "../typechain/L2WethBridgeFactory"; -import { L2WethFactory } from "../typechain/L2WethFactory"; +import type { L2WrappedBaseToken } from "../typechain/L2WrappedBaseToken"; +import type { L2SharedBridge } from "../typechain/L2SharedBridge"; +import { L2SharedBridgeFactory } from "../typechain/L2SharedBridgeFactory"; +import { L2WrappedBaseTokenFactory } from "../typechain/L2WrappedBaseTokenFactory"; const richAccount = { address: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", @@ -18,13 +18,13 @@ const eth18 = ethers.utils.parseEther("18"); describe("WETH token & WETH bridge", function () { const provider = new Provider(hre.config.networks.localhost.url); const wallet = new Wallet(richAccount.privateKey, provider); - let wethToken: L2Weth; - let wethBridge: L2WethBridge; + let wethToken: L2WrappedBaseToken; + let wethBridge: L2SharedBridge; before("Deploy token and bridge", async function () { const deployer = new Deployer(hre, wallet); - const wethTokenImpl = await deployer.deploy(await deployer.loadArtifact("L2Weth")); - const wethBridgeImpl = await deployer.deploy(await deployer.loadArtifact("L2WethBridge")); + const wethTokenImpl = await deployer.deploy(await deployer.loadArtifact("L2WrappedBaseToken")); + const wethBridgeImpl = await deployer.deploy(await deployer.loadArtifact("L2SharedBridge")); const randomAddress = ethers.utils.hexlify(ethers.utils.randomBytes(20)); const wethTokenProxy = await deployer.deploy(await deployer.loadArtifact("TransparentUpgradeableProxy"), [ @@ -32,20 +32,19 @@ describe("WETH token & WETH bridge", function () { randomAddress, "0x", ]); - const wethBridgeProxy = (await deployer.deploy(await deployer.loadArtifact("TransparentUpgradeableProxy"), [ + const wethBridgeProxy = await deployer.deploy(await deployer.loadArtifact("TransparentUpgradeableProxy"), [ wethBridgeImpl.address, randomAddress, "0x", - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ])) as any; + ]); - wethToken = L2WethFactory.connect(wethTokenProxy.address, wallet); - wethBridge = L2WethBridgeFactory.connect(wethBridgeProxy.address, wallet); + wethToken = L2WrappedBaseTokenFactory.connect(wethTokenProxy.address, wallet); + wethBridge = L2SharedBridgeFactory.connect(wethBridgeProxy.address, wallet); - await wethToken.initialize("Wrapped Ether", "WETH"); - await wethToken.initializeV2(wethBridge.address, randomAddress); + // await wethToken.initialize(); + await wethToken.initializeV2("Wrapped Ether", "WETH", wethBridge.address, randomAddress); - await wethBridge.initialize(randomAddress, randomAddress, wethToken.address); + // await wethBridge.initialize(randomAddress, randomAddress, wethToken.address); }); it("Should deposit WETH by calling deposit()", async function () { @@ -79,12 +78,13 @@ describe("WETH token & WETH bridge", function () { expect(await wethToken.balanceOf(wallet.address)).to.equal(eth18); }); - it("Should withdraw WETH to L1 ETH", async function () { - await expect(wethBridge.withdraw(wallet.address, wethToken.address, eth18.div(2))) - .to.emit(wethBridge, "WithdrawalInitiated") - .and.to.emit(wethToken, "BridgeBurn"); - expect(await wethToken.balanceOf(wallet.address)).to.equal(eth18.div(2)); - }); + // bridging not supported + // it("Should withdraw WETH to L1 ETH", async function () { + // await expect(wethBridge.withdraw(wallet.address, wethToken.address, eth18.div(2))) + // .to.emit(wethBridge, "WithdrawalInitiated") + // .and.to.emit(wethToken, "BridgeBurn"); + // expect(await wethToken.balanceOf(wallet.address)).to.equal(eth18.div(2)); + // }); it("Should deposit WETH to another account", async function () { const anotherWallet = new Wallet(ethers.utils.randomBytes(32), provider); @@ -96,27 +96,26 @@ describe("WETH token & WETH bridge", function () { const anotherWallet = new Wallet(ethers.utils.randomBytes(32), provider); await wethToken.withdrawTo(anotherWallet.address, eth18.div(2)).then((tx) => tx.wait()); expect(await anotherWallet.getBalance()).to.equal(eth18.div(2)); - expect(await wethToken.balanceOf(wallet.address)).to.equal(0); + expect(await wethToken.balanceOf(wallet.address)).to.equal(eth18.div(2)); }); it("Should fail withdrawing with insufficient balance", async function () { await expect(wethToken.withdraw(1, { gasLimit: 100_000 })).to.be.reverted; }); - it("Should fail depositing directly to WETH bridge", async function () { - await expect( - wallet.sendTransaction({ - to: wethBridge.address, - value: eth18, - gasLimit: 100_000, - }) - ).to.be.reverted; - }); + // bridging not supported + // it("Should fail depositing directly to WETH bridge", async function () { + // await expect( + // wallet.sendTransaction({ + // to: wethBridge.address, + // value: eth18, + // gasLimit: 100_000, + // }) + // ).to.be.reverted; + // }); it("Should fail calling bridgeMint()", async function () { - await expect(wethToken.bridgeMint(wallet.address, eth18, { gasLimit: 100_000 })).to.be.revertedWith( - /Use deposit\/depositTo methods instead/ - ); + await expect(await wethToken.bridgeMint(wallet.address, eth18, { gasLimit: 1_000_000 })).to.be.reverted; }); it("Should fail calling bridgeBurn() directly", async function () { diff --git a/system-contracts/SystemContractsHashes.json b/system-contracts/SystemContractsHashes.json index 44f6d2b7058d..659906c273e4 100644 --- a/system-contracts/SystemContractsHashes.json +++ b/system-contracts/SystemContractsHashes.json @@ -3,42 +3,42 @@ "contractName": "AccountCodeStorage", "bytecodePath": "artifacts-zk/contracts-preprocessed/AccountCodeStorage.sol/AccountCodeStorage.json", "sourceCodePath": "contracts-preprocessed/AccountCodeStorage.sol", - "bytecodeHash": "0x0100007575c58b26e0fc5fc878a6b5de645cc15309971e4c08651726a631f562", + "bytecodeHash": "0x01000075ef4b930073bb50bba0a45aa2f7868abb17bc983a0a0c4cbe6068e786", "sourceCodeHash": "0xa4bb031f7c6e95044b3c69f15107141d8ee4f2fd637986955f3d5bde4444ff3f" }, { "contractName": "BootloaderUtilities", "bytecodePath": "artifacts-zk/contracts-preprocessed/BootloaderUtilities.sol/BootloaderUtilities.json", "sourceCodePath": "contracts-preprocessed/BootloaderUtilities.sol", - "bytecodeHash": "0x010007c96cf792a56dd43bfdc66362fd59b04ea31ce649fcf8f65a832d9c158d", + "bytecodeHash": "0x010007c90e61b6310fbae9adaa6f5d1d5eda3ce917159183f5890d372b7b8453", "sourceCodeHash": "0xf48319ea1cfc95e6e2203b8186b21e3f3168136b92125e935b9bca81da42ad2a" }, { "contractName": "ComplexUpgrader", "bytecodePath": "artifacts-zk/contracts-preprocessed/ComplexUpgrader.sol/ComplexUpgrader.json", "sourceCodePath": "contracts-preprocessed/ComplexUpgrader.sol", - "bytecodeHash": "0x010000558999c83f749f0cc37e7050abd20bda5c574aaefa4e3110152ef2003e", + "bytecodeHash": "0x010000551e2bdb5aa4cfd0495baf8b888585fa5aa698d994dc37f26be769f153", "sourceCodeHash": "0x0aa5d7ed159e783acde47856b13801b7f2268ba39b2fa50807fe3d705c506e96" }, { "contractName": "Compressor", "bytecodePath": "artifacts-zk/contracts-preprocessed/Compressor.sol/Compressor.json", "sourceCodePath": "contracts-preprocessed/Compressor.sol", - "bytecodeHash": "0x01000167dbad1abc770781cdf3e62af0f757b31f02b54c8fc93ab674cc1859fd", - "sourceCodeHash": "0x25ff4b50b5373f4fed1ae95f461a4547bb45bf5255ca94d8645b046aaab026a6" + "bytecodeHash": "0x0100016dc0bd6a2fb4d8c6724fb304816a2d93d5a08b5980f802d388d127b714", + "sourceCodeHash": "0xe0e22aa80843159daff6f09ed907c42d0d0d55225d04ba35b211389e05264f39" }, { "contractName": "ContractDeployer", "bytecodePath": "artifacts-zk/contracts-preprocessed/ContractDeployer.sol/ContractDeployer.json", "sourceCodePath": "contracts-preprocessed/ContractDeployer.sol", - "bytecodeHash": "0x01000555155db89a3e25aa18c55c9bf7e79e75463a2fdecd9a65c0d8164e8069", - "sourceCodeHash": "0x931064466ed44e5964d3c7a09f1a0f798418fd643be4e4628bc80be3d2806c1a" + "bytecodeHash": "0x010005558dd568acab2df5bb3a24d5f39bede5f065d7833d314836aa25bc555e", + "sourceCodeHash": "0x37deef4ef277cf0d1e0c56194650beceaeaa2fa3f0e5d0165ef60b7f2fb6a859" }, { "contractName": "DefaultAccount", "bytecodePath": "artifacts-zk/contracts-preprocessed/DefaultAccount.sol/DefaultAccount.json", "sourceCodePath": "contracts-preprocessed/DefaultAccount.sol", - "bytecodeHash": "0x0100055bb7fd3553675614261e983f26617c3559f606444fb205edd27d97de4a", + "bytecodeHash": "0x0100055b876f4d9667ef3c0e0788e74ae92978736ec711ef5736c117c17f061f", "sourceCodeHash": "0x7f7c2dc241f593353aea2eb4f42b3365d620b02a5c69d1359eca80c356b628f9" }, { @@ -52,57 +52,57 @@ "contractName": "ImmutableSimulator", "bytecodePath": "artifacts-zk/contracts-preprocessed/ImmutableSimulator.sol/ImmutableSimulator.json", "sourceCodePath": "contracts-preprocessed/ImmutableSimulator.sol", - "bytecodeHash": "0x0100003dc5023e5db317825f341e4a3a20b36f36452179a9069f838942abcfee", + "bytecodeHash": "0x0100003d122dbffc54eddfd3f6b73a497c78d9772c2e88e775f0235c84b9cfbf", "sourceCodeHash": "0x30df621c72cb35b8820b902b91057f72d0214a0e4a6b7ad4c0847e674e8b9df8" }, { "contractName": "KnownCodesStorage", "bytecodePath": "artifacts-zk/contracts-preprocessed/KnownCodesStorage.sol/KnownCodesStorage.json", "sourceCodePath": "contracts-preprocessed/KnownCodesStorage.sol", - "bytecodeHash": "0x0100007dcc2ff4aedbdaa2b670e423112a3dccacaaa9bfd2840119fbab1dea4c", + "bytecodeHash": "0x0100007d70e79621fdd389100a29962e7939388c69505ebfcba1e53c9cfc319b", "sourceCodeHash": "0x51d388adc58f67ef975a94a7978caa60ed8a0df9d3bd9ac723dfcfc540286c70" }, { "contractName": "L1Messenger", "bytecodePath": "artifacts-zk/contracts-preprocessed/L1Messenger.sol/L1Messenger.json", "sourceCodePath": "contracts-preprocessed/L1Messenger.sol", - "bytecodeHash": "0x010002af245006d172ee055b65aa753974aa4fe9dca175a9068cf234bebe1394", + "bytecodeHash": "0x010002af93bf9216a2bdbb908432e2897f83d24d9ca79e254bccda4285a8552e", "sourceCodeHash": "0x85a2d5884c92a28c298e626f8c9b0cdba868f2e6fddfe199dacb7900b889131a" }, { - "contractName": "L2EthToken", - "bytecodePath": "artifacts-zk/contracts-preprocessed/L2EthToken.sol/L2EthToken.json", - "sourceCodePath": "contracts-preprocessed/L2EthToken.sol", - "bytecodeHash": "0x01000101b7005d29f29f6773e4d6a41b3997cc93466fd8658242e094093438a0", - "sourceCodeHash": "0xcd01e3e781df35aa53fec1509008967e9e60dd833d4f54bdfc51ac7fbf97ae8a" + "contractName": "L2BaseToken", + "bytecodePath": "artifacts-zk/contracts-preprocessed/L2BaseToken.sol/L2BaseToken.json", + "sourceCodePath": "contracts-preprocessed/L2BaseToken.sol", + "bytecodeHash": "0x01000101fc008431ffe686b9b813f6cb1510cdba7b38eac58167b19d9b4992be", + "sourceCodeHash": "0x5cfb3a7f69db57a455682f339879a5d5ae679c8deff2ea6669b0a6d0fe538d6c" }, { "contractName": "MsgValueSimulator", "bytecodePath": "artifacts-zk/contracts-preprocessed/MsgValueSimulator.sol/MsgValueSimulator.json", "sourceCodePath": "contracts-preprocessed/MsgValueSimulator.sol", - "bytecodeHash": "0x01000063becae62e202c554601a1affa2cb2b425b02f94f877a76d692afcf98a", - "sourceCodeHash": "0xf6363e8d73fa8579ff74b6e6880e6ed62b6ff80ba984826dc2b0bf0f71fdef2d" + "bytecodeHash": "0x010000637886818379b5463d48e75978b42345c1ba8a6518ddaaeb8f6006d6f1", + "sourceCodeHash": "0xd052abaab1b0091c37d0763bba924ff5ed2be59f68cc29a850cb9b41de72bc06" }, { "contractName": "NonceHolder", "bytecodePath": "artifacts-zk/contracts-preprocessed/NonceHolder.sol/NonceHolder.json", "sourceCodePath": "contracts-preprocessed/NonceHolder.sol", - "bytecodeHash": "0x010000e5b53228a5929ad670fcaa9c9b1269efd3a1c37586a1055c2714834232", + "bytecodeHash": "0x010000e5cc91c0e7f87185703d644512ad30f211a03b9feee985332de3279247", "sourceCodeHash": "0x91847512344ac5026e9fd396189c23ad9e253f22cb6e2fe65805c20c915797d4" }, { "contractName": "PubdataChunkPublisher", "bytecodePath": "artifacts-zk/contracts-preprocessed/PubdataChunkPublisher.sol/PubdataChunkPublisher.json", "sourceCodePath": "contracts-preprocessed/PubdataChunkPublisher.sol", - "bytecodeHash": "0x010000479488bfd33eacde28702cd8190320e42baff0eea7b074441a2e179924", + "bytecodeHash": "0x01000047e9de25747988cd116a56fea7df9f1f3aee7d711bd861cfc3177c2534", "sourceCodeHash": "0x4ef08d1890c90e8c1aee092022c56bdef975b4ceb69da62a1a76830a35d703ed" }, { "contractName": "SystemContext", "bytecodePath": "artifacts-zk/contracts-preprocessed/SystemContext.sol/SystemContext.json", "sourceCodePath": "contracts-preprocessed/SystemContext.sol", - "bytecodeHash": "0x010001813dd8528e3171620b19b7e19b12b143bcc3384e89635fd4875122d6dd", - "sourceCodeHash": "0x45a7a3446e651c35337e47e25bb3d3ef110d45210deccc2a88e04ff1e779c546" + "bytecodeHash": "0x010001872c4830e7abbe40360018b36d1164b1e2ac1b45d48bbce839220c5797", + "sourceCodeHash": "0x821b7099c1ed4ad9209caa1a2de38e4550698ab330eff3907184230adba81d7f" }, { "contractName": "EventWriter", @@ -150,35 +150,35 @@ "contractName": "bootloader_test", "bytecodePath": "bootloader/build/artifacts/bootloader_test.yul.zbin", "sourceCodePath": "bootloader/build/bootloader_test.yul", - "bytecodeHash": "0x0100036767b0c9a595f81ae029daede8b6ae299959d373acfd08850e086122e4", - "sourceCodeHash": "0xdad2740cbf94f77d7c3f15915972b7638c571d04b53f4cf10a9a3ab87f1efff4" + "bytecodeHash": "0x010003673668742d6b751c86c58528677ff7fc48f3a5c79ec5eedd5f9ec36b96", + "sourceCodeHash": "0x7048b2060281d5b25bce519c18bbd376363e5524be607fbfe0e95ceca3f49c03" }, { "contractName": "fee_estimate", "bytecodePath": "bootloader/build/artifacts/fee_estimate.yul.zbin", "sourceCodePath": "bootloader/build/fee_estimate.yul", - "bytecodeHash": "0x010008298a55eb93a16fca18e7b7c2ae99da376afe3e6e8441c0271152775ccd", - "sourceCodeHash": "0xef5bfb4ac7ea3a529ad3e29da01a6a7387e862e32e4e3c21a7b213ed5a7b0efe" + "bytecodeHash": "0x01000829c14c6eeaade05a0718a29d8ae011e4f4b8eccda6dc0523bfcbeeb381", + "sourceCodeHash": "0x533ace59432e7e08328a4f645072f220df03c7fd9e4522b6dfe4763b115058a8" }, { "contractName": "gas_test", "bytecodePath": "bootloader/build/artifacts/gas_test.yul.zbin", "sourceCodePath": "bootloader/build/gas_test.yul", - "bytecodeHash": "0x010007f95ab47a6498999d3f07f5811eab81e185d92eb652da434b5cac3f3da7", - "sourceCodeHash": "0x689ad13bcf3d5076207a079f1087f3d1e5413010ae1b91b60d8c65c006da0d6c" + "bytecodeHash": "0x010007f9278bf2ee0859320276a1e5a1e94dcf890439f594a05ce67e71c5f81f", + "sourceCodeHash": "0x3acc488a2ef69311ed56fda903304011211dd7c20a76ea0b50395c353008812e" }, { "contractName": "playground_batch", "bytecodePath": "bootloader/build/artifacts/playground_batch.yul.zbin", "sourceCodePath": "bootloader/build/playground_batch.yul", - "bytecodeHash": "0x0100082fdc20a44607edb9e3e2153b75d6c79a053ef9776547c6fe583a620ae7", - "sourceCodeHash": "0xe64b56afe0445fe1f596232029ef2115794b124c19c2d220b269dba780fc920a" + "bytecodeHash": "0x0100082fd2dbceb5f3f2eda1e69856f201d5b1657578dd2268b97a7013df9e06", + "sourceCodeHash": "0xf37ba75814facd344edc8975538a20141534f3766986a417e04252299918525c" }, { "contractName": "proved_batch", "bytecodePath": "bootloader/build/artifacts/proved_batch.yul.zbin", "sourceCodePath": "bootloader/build/proved_batch.yul", - "bytecodeHash": "0x010008094f8cdb74e2ac929075bbbd74fa574549a79451dffe0c78ab8f1cebdf", - "sourceCodeHash": "0x14f7100c772c32d8dcc06251dd192cff83f70c9df841308aa81ef34c06629ed4" + "bytecodeHash": "0x0100080907d8209b790821d0e80c7490e4470bbdd91071ae6eebcd8ad78b2f16", + "sourceCodeHash": "0x09d1d448e7f998124bf27e40bf42c544b8fb14ff743d5963cd5b512d655140ce" } ] diff --git a/system-contracts/bootloader/bootloader.yul b/system-contracts/bootloader/bootloader.yul index 49eea2db14f3..ff9d68e82b39 100644 --- a/system-contracts/bootloader/bootloader.yul +++ b/system-contracts/bootloader/bootloader.yul @@ -1564,7 +1564,7 @@ object "Bootloader" { finalRefund := refundInGas } - /// @notice A function that transfers ETH directly through the L2EthToken system contract. + /// @notice A function that transfers ETH directly through the L2BaseToken system contract. /// Note, that unlike classical EVM transfers it does NOT call the recipient, but only changes the balance. function directETHTransfer(amount, recipient) { let ptr := 0 diff --git a/system-contracts/bootloader/test_infra/Cargo.lock b/system-contracts/bootloader/test_infra/Cargo.lock index 8dcf06d7bfa0..0ce0b866a9a7 100644 --- a/system-contracts/bootloader/test_infra/Cargo.lock +++ b/system-contracts/bootloader/test_infra/Cargo.lock @@ -28,56 +28,13 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "aes" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" -dependencies = [ - "aes-soft", - "aesni", - "cipher", -] - -[[package]] -name = "aes-ctr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7729c3cde54d67063be556aeac75a81330d802f0259500ca40cb52967f975763" -dependencies = [ - "aes-soft", - "aesni", - "cipher", - "ctr", -] - -[[package]] -name = "aes-soft" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" -dependencies = [ - "cipher", - "opaque-debug", -] - -[[package]] -name = "aesni" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" -dependencies = [ - "cipher", - "opaque-debug", -] - [[package]] name = "ahash" version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" dependencies = [ - "getrandom 0.2.11", + "getrandom", "once_cell", "version_check", ] @@ -89,7 +46,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ "cfg-if 1.0.0", - "getrandom 0.2.11", + "getrandom", "once_cell", "version_check", "zerocopy", @@ -229,15 +186,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "autocfg" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" -dependencies = [ - "autocfg 1.1.0", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -407,28 +355,16 @@ dependencies = [ "serde", ] -[[package]] -name = "bitvec" -version = "0.20.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7774144344a4faa177370406a7ff5f1da24303817368584c6206c8303eb07848" -dependencies = [ - "funty 1.1.0", - "radium 0.6.2", - "tap", - "wyz 0.2.0", -] - [[package]] name = "bitvec" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ - "funty 2.0.0", - "radium 0.7.0", + "funty", + "radium", "tap", - "wyz 0.5.1", + "wyz", ] [[package]] @@ -437,7 +373,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174" dependencies = [ - "crypto-mac 0.8.0", + "crypto-mac", "digest 0.9.0", "opaque-debug", ] @@ -520,16 +456,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block-modes" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a0e8073e8baa88212fb5823574c02ebccb395136ba9a164ab89379ec6072f0" -dependencies = [ - "block-padding", - "cipher", -] - [[package]] name = "block-padding" version = "0.2.1" @@ -550,8 +476,8 @@ dependencies = [ [[package]] name = "boojum" -version = "0.1.0" -source = "git+https://github.com/matter-labs/era-boojum.git?branch=main#93b5e0f0dbff0a9b606d9025e207c8405c141bd9" +version = "0.2.0" +source = "git+https://github.com/matter-labs/era-boojum.git?branch=main#30300f043c9afaeeb35d0f7bd3cc0acaf69ccde4" dependencies = [ "arrayvec 0.7.4", "bincode", @@ -562,7 +488,7 @@ dependencies = [ "crypto-bigint 0.5.5", "cs_derive 0.1.0 (git+https://github.com/matter-labs/era-boojum.git?branch=main)", "derivative", - "ethereum-types 0.14.1", + "ethereum-types", "firestorm", "itertools 0.10.5", "lazy_static", @@ -749,19 +675,10 @@ dependencies = [ "windows-targets 0.48.5", ] -[[package]] -name = "cipher" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" -dependencies = [ - "generic-array", -] - [[package]] name = "circuit_definitions" version = "0.1.0" -source = "git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.4.0#fb47657ae3b6ff6e4bb5199964d3d37212978200" +source = "git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.4.0#aff36380e6a740f7286c9c522e1416b8d38327ce" dependencies = [ "crossbeam 0.8.2", "derivative", @@ -775,7 +692,7 @@ dependencies = [ [[package]] name = "circuit_definitions" version = "0.1.0" -source = "git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.4.1#6db7c01717d157945f0f2939119dbd8a170de6bc" +source = "git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.4.1#e957ef50421393914dee7796b0091586e5153192" dependencies = [ "crossbeam 0.8.2", "derivative", @@ -820,31 +737,6 @@ dependencies = [ "vec_map", ] -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "codegen" -version = "0.1.0" -source = "git+https://github.com/matter-labs/solidity_plonk_verifier.git?branch=dev#82f96b7156551087f1c9bfe4f0ea68845b6debfc" -dependencies = [ - "ethereum-types 0.14.1", - "franklin-crypto 0.0.5 (git+https://github.com/matter-labs/franklin-crypto?branch=dev)", - "handlebars", - "hex", - "paste", - "rescue_poseidon 0.4.1 (git+https://github.com/matter-labs/rescue-poseidon)", - "serde", - "serde_derive", - "serde_json", -] - [[package]] name = "codegen" version = "0.2.0" @@ -1023,7 +915,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" dependencies = [ - "autocfg 1.1.0", + "autocfg", "cfg-if 0.1.10", "crossbeam-utils 0.7.2", "lazy_static", @@ -1038,7 +930,7 @@ version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ - "autocfg 1.1.0", + "autocfg", "cfg-if 1.0.0", "crossbeam-utils 0.8.16", "memoffset 0.9.0", @@ -1072,7 +964,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ - "autocfg 1.1.0", + "autocfg", "cfg-if 0.1.10", "lazy_static", ] @@ -1136,20 +1028,10 @@ dependencies = [ "subtle", ] -[[package]] -name = "crypto-mac" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a" -dependencies = [ - "generic-array", - "subtle", -] - [[package]] name = "cs_derive" version = "0.1.0" -source = "git+https://github.com/matter-labs/era-boojum.git?branch=main#93b5e0f0dbff0a9b606d9025e207c8405c141bd9" +source = "git+https://github.com/matter-labs/era-boojum.git?branch=main#30300f043c9afaeeb35d0f7bd3cc0acaf69ccde4" dependencies = [ "proc-macro-error", "proc-macro2 1.0.70", @@ -1169,15 +1051,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "ctr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f" -dependencies = [ - "cipher", -] - [[package]] name = "curl" version = "0.4.44" @@ -1575,7 +1448,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" dependencies = [ - "ethereum-types 0.14.1", + "ethereum-types", "hex", "once_cell", "regex", @@ -1586,19 +1459,6 @@ dependencies = [ "uint", ] -[[package]] -name = "ethbloom" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb684ac8fa8f6c5759f788862bb22ec6fe3cb392f6bfd08e3c64b603661e3f8" -dependencies = [ - "crunchy", - "fixed-hash 0.7.0", - "impl-rlp", - "impl-serde 0.3.2", - "tiny-keccak 2.0.2", -] - [[package]] name = "ethbloom" version = "0.13.0" @@ -1606,37 +1466,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" dependencies = [ "crunchy", - "fixed-hash 0.8.0", + "fixed-hash", "impl-rlp", - "impl-serde 0.4.0", + "impl-serde", "tiny-keccak 2.0.2", ] -[[package]] -name = "ethereum-types" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05136f7057fe789f06e6d41d07b34e6f70d8c86e5693b60f97aaa6553553bdaf" -dependencies = [ - "ethbloom 0.11.1", - "fixed-hash 0.7.0", - "impl-rlp", - "impl-serde 0.3.2", - "primitive-types 0.10.1", - "uint", -] - [[package]] name = "ethereum-types" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" dependencies = [ - "ethbloom 0.13.0", - "fixed-hash 0.8.0", + "ethbloom", + "fixed-hash", "impl-rlp", - "impl-serde 0.4.0", - "primitive-types 0.12.2", + "impl-serde", + "primitive-types", "uint", ] @@ -1730,18 +1576,6 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c5f6c2c942da57e2aaaa84b8a521489486f14e75e7fa91dab70aba913975f98" -[[package]] -name = "fixed-hash" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" -dependencies = [ - "byteorder", - "rand 0.8.5", - "rustc-hex", - "static_assertions", -] - [[package]] name = "fixed-hash" version = "0.8.0" @@ -1869,12 +1703,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -[[package]] -name = "funty" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" - [[package]] name = "funty" version = "2.0.0" @@ -1999,17 +1827,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.2.11" @@ -2018,7 +1835,7 @@ checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -2074,20 +1891,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "handlebars" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa67bab9ff362228eb3d00bd024a4965d8231bbb7921167f0cfa66c6626b225" -dependencies = [ - "log", - "pest", - "pest_derive", - "serde", - "serde_json", - "thiserror", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -2185,17 +1988,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" dependencies = [ - "hmac 0.12.1", -] - -[[package]] -name = "hmac" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" -dependencies = [ - "crypto-mac 0.10.1", - "digest 0.9.0", + "hmac", ] [[package]] @@ -2367,22 +2160,13 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "impl-codec" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "161ebdfec3c8e3b52bf61c4f3550a1eea4f9579d10dc1b936f3171ebdcd6c443" -dependencies = [ - "parity-scale-codec 2.3.1", -] - [[package]] name = "impl-codec" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" dependencies = [ - "parity-scale-codec 3.6.9", + "parity-scale-codec", ] [[package]] @@ -2394,15 +2178,6 @@ dependencies = [ "rlp", ] -[[package]] -name = "impl-serde" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" -dependencies = [ - "serde", -] - [[package]] name = "impl-serde" version = "0.4.0" @@ -2429,7 +2204,7 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ - "autocfg 1.1.0", + "autocfg", "hashbrown 0.12.3", ] @@ -2676,7 +2451,7 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ - "autocfg 1.1.0", + "autocfg", "scopeguard", ] @@ -2761,7 +2536,7 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" dependencies = [ - "autocfg 1.1.0", + "autocfg", ] [[package]] @@ -2770,7 +2545,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ - "autocfg 1.1.0", + "autocfg", ] [[package]] @@ -2861,7 +2636,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.48.0", ] @@ -2874,7 +2649,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "multivm" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?branch=zk-bootloader-memory-increase#2520a6f1dc90a26016f3d4641b8a6d7b11731d6a" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=64e814e780856325085244dea62fc0388a91baa6#64e814e780856325085244dea62fc0388a91baa6" dependencies = [ "anyhow", "hex", @@ -2887,8 +2662,9 @@ dependencies = [ "zk_evm 1.3.3 (git+https://github.com/matter-labs/era-zk_evm.git?tag=v1.3.3-rc2)", "zk_evm 1.4.0", "zk_evm 1.4.1", - "zkevm_test_harness 1.4.0 (git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.4.0)", - "zkevm_test_harness 1.4.0 (git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.4.1)", + "zkevm_test_harness 1.3.3", + "zkevm_test_harness 1.4.0", + "zkevm_test_harness 1.4.1", "zksync_contracts", "zksync_state", "zksync_system_constants", @@ -2951,20 +2727,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "num" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f" -dependencies = [ - "num-bigint 0.3.3", - "num-complex 0.3.1", - "num-integer", - "num-iter", - "num-rational 0.3.2", - "num-traits", -] - [[package]] name = "num" version = "0.4.1" @@ -2972,10 +2734,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" dependencies = [ "num-bigint 0.4.4", - "num-complex 0.4.4", + "num-complex", "num-integer", "num-iter", - "num-rational 0.4.1", + "num-rational", "num-traits", ] @@ -2985,7 +2747,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" dependencies = [ - "autocfg 1.1.0", + "autocfg", "num-integer", "num-traits", ] @@ -2996,7 +2758,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ - "autocfg 1.1.0", + "autocfg", "num-integer", "num-traits", "serde", @@ -3019,15 +2781,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "num-complex" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" -dependencies = [ - "num-traits", -] - [[package]] name = "num-complex" version = "0.4.4" @@ -3066,7 +2819,7 @@ version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ - "autocfg 1.1.0", + "autocfg", "num-traits", ] @@ -3076,7 +2829,7 @@ version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" dependencies = [ - "autocfg 1.1.0", + "autocfg", "num-integer", "num-traits", ] @@ -3091,25 +2844,13 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-rational" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" -dependencies = [ - "autocfg 1.1.0", - "num-bigint 0.3.3", - "num-integer", - "num-traits", -] - [[package]] name = "num-rational" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ - "autocfg 1.1.0", + "autocfg", "num-bigint 0.4.4", "num-integer", "num-traits", @@ -3122,7 +2863,7 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ - "autocfg 1.1.0", + "autocfg", "libm", ] @@ -3295,44 +3036,6 @@ dependencies = [ "serde", ] -[[package]] -name = "parity-crypto" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b92ea9ddac0d6e1db7c49991e7d397d34a9fd814b4c93cda53788e8eef94e35" -dependencies = [ - "aes", - "aes-ctr", - "block-modes", - "digest 0.9.0", - "ethereum-types 0.12.1", - "hmac 0.10.1", - "lazy_static", - "pbkdf2 0.7.5", - "ripemd160", - "rustc-hex", - "scrypt", - "secp256k1 0.20.3", - "sha2 0.9.9", - "subtle", - "tiny-keccak 2.0.2", - "zeroize", -] - -[[package]] -name = "parity-scale-codec" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373b1a4c1338d9cd3d1fa53b3a11bdab5ab6bd80a20f7f7becd76953ae2be909" -dependencies = [ - "arrayvec 0.7.4", - "bitvec 0.20.4", - "byte-slice-cast", - "impl-trait-for-tuples", - "parity-scale-codec-derive 2.3.1", - "serde", -] - [[package]] name = "parity-scale-codec" version = "3.6.9" @@ -3340,25 +3043,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "881331e34fa842a2fb61cc2db9643a8fedc615e47cfcc52597d1af0db9a7e8fe" dependencies = [ "arrayvec 0.7.4", - "bitvec 1.0.1", + "bitvec", "byte-slice-cast", "impl-trait-for-tuples", - "parity-scale-codec-derive 3.6.9", + "parity-scale-codec-derive", "serde", ] -[[package]] -name = "parity-scale-codec-derive" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1557010476e0595c9b568d16dcfb81b93cdeb157612726f5170d31aa707bed27" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2 1.0.70", - "quote 1.0.33", - "syn 1.0.109", -] - [[package]] name = "parity-scale-codec-derive" version = "3.6.9" @@ -3394,44 +3085,12 @@ dependencies = [ "windows-targets 0.48.5", ] -[[package]] -name = "password-hash" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54986aa4bfc9b98c6a5f40184223658d187159d7b3c6af33f2b2aa25ae1db0fa" -dependencies = [ - "base64ct", - "rand_core 0.6.4", -] - [[package]] name = "paste" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" -[[package]] -name = "pbkdf2" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b8c0d71734018084da0c0354193a5edfb81b20d2d57a92c5b154aefc554a4a" -dependencies = [ - "crypto-mac 0.10.1", -] - -[[package]] -name = "pbkdf2" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf916dd32dd26297907890d99dc2740e33f6bd9073965af4ccff2967962f5508" -dependencies = [ - "base64ct", - "crypto-mac 0.10.1", - "hmac 0.10.1", - "password-hash", - "sha2 0.9.9", -] - [[package]] name = "peeking_take_while" version = "0.1.2" @@ -3453,51 +3112,6 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pest" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" -dependencies = [ - "memchr", - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2 1.0.70", - "quote 1.0.33", - "syn 2.0.40", -] - -[[package]] -name = "pest_meta" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" -dependencies = [ - "once_cell", - "pest", - "sha2 0.10.8", -] - [[package]] name = "petgraph" version = "0.6.4" @@ -3611,29 +3225,16 @@ dependencies = [ "syn 2.0.40", ] -[[package]] -name = "primitive-types" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05e4722c697a58a99d5d06a08c30821d7c082a4632198de1eaa5a6c22ef42373" -dependencies = [ - "fixed-hash 0.7.0", - "impl-codec 0.5.1", - "impl-rlp", - "impl-serde 0.3.2", - "uint", -] - [[package]] name = "primitive-types" version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ - "fixed-hash 0.8.0", - "impl-codec 0.6.0", + "fixed-hash", + "impl-codec", "impl-rlp", - "impl-serde 0.4.0", + "impl-serde", "uint", ] @@ -3883,12 +3484,6 @@ dependencies = [ "proc-macro2 1.0.70", ] -[[package]] -name = "radium" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" - [[package]] name = "radium" version = "0.7.0" @@ -3908,36 +3503,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -dependencies = [ - "autocfg 0.1.8", - "libc", - "rand_chacha 0.1.1", - "rand_core 0.4.2", - "rand_hc 0.1.0", - "rand_isaac", - "rand_jitter", - "rand_os", - "rand_pcg", - "rand_xorshift", - "winapi", -] - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc 0.2.0", -] - [[package]] name = "rand" version = "0.8.5" @@ -3945,30 +3510,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", + "rand_chacha", "rand_core 0.6.4", ] -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -dependencies = [ - "autocfg 0.1.8", - "rand_core 0.3.1", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - [[package]] name = "rand_chacha" version = "0.3.1" @@ -3994,93 +3539,13 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.11", -] - -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -dependencies = [ - "libc", - "rand_core 0.4.2", - "winapi", -] - -[[package]] -name = "rand_os" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -dependencies = [ - "cloudabi", - "fuchsia-cprng", - "libc", - "rand_core 0.4.2", - "rdrand", - "winapi", -] - -[[package]] -name = "rand_pcg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -dependencies = [ - "autocfg 0.1.8", - "rand_core 0.4.2", -] - -[[package]] -name = "rand_xorshift" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" -dependencies = [ - "rand_core 0.3.1", + "getrandom", ] [[package]] @@ -4220,7 +3685,7 @@ dependencies = [ [[package]] name = "rescue_poseidon" version = "0.4.1" -source = "git+https://github.com/matter-labs/rescue-poseidon.git?branch=poseidon2#2e5e8afb152adc326fcf776a71ad3735fa7f3186" +source = "git+https://github.com/matter-labs/rescue-poseidon.git?branch=poseidon2#126937ef0e7a281f1ff9f512ac41a746a691a342" dependencies = [ "addchain", "arrayvec 0.7.4", @@ -4228,6 +3693,7 @@ dependencies = [ "byteorder", "derivative", "franklin-crypto 0.0.5 (git+https://github.com/matter-labs/franklin-crypto?branch=snark_wrapper)", + "lazy_static", "log", "num-bigint 0.3.3", "num-integer", @@ -4237,6 +3703,7 @@ dependencies = [ "serde", "sha3 0.9.1", "smallvec", + "typemap_rev", ] [[package]] @@ -4266,7 +3733,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ "crypto-bigint 0.4.9", - "hmac 0.12.1", + "hmac", "zeroize", ] @@ -4276,7 +3743,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac 0.12.1", + "hmac", "subtle", ] @@ -4287,31 +3754,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" dependencies = [ "cc", - "getrandom 0.2.11", + "getrandom", "libc", "spin 0.9.8", "untrusted", "windows-sys 0.48.0", ] -[[package]] -name = "ripemd160" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251" -dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "rkyv" version = "0.7.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527a97cdfef66f65998b5f3b637c26f5a5ec09cc52a3f9932313ac645f4190f5" dependencies = [ - "bitvec 1.0.1", + "bitvec", "bytecheck", "bytes", "hashbrown 0.12.3", @@ -4473,15 +3929,6 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" -[[package]] -name = "salsa20" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "399f290ffc409596022fce5ea5d4138184be4784f2b28c62c59f0d8389059a15" -dependencies = [ - "cipher", -] - [[package]] name = "same-file" version = "1.0.6" @@ -4506,22 +3953,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "scrypt" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da492dab03f925d977776a0b7233d7b934d6dc2b94faead48928e2e9bacedb9" -dependencies = [ - "base64 0.13.1", - "hmac 0.10.1", - "pbkdf2 0.6.0", - "rand 0.7.3", - "rand_core 0.5.1", - "salsa20", - "sha2 0.9.9", - "subtle", -] - [[package]] name = "sct" version = "0.7.1" @@ -4566,32 +3997,13 @@ dependencies = [ "zeroize", ] -[[package]] -name = "secp256k1" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a" -dependencies = [ - "rand 0.6.5", - "secp256k1-sys 0.4.2", -] - [[package]] name = "secp256k1" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" dependencies = [ - "secp256k1-sys 0.8.1", -] - -[[package]] -name = "secp256k1-sys" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" -dependencies = [ - "cc", + "secp256k1-sys", ] [[package]] @@ -4972,7 +4384,7 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ - "autocfg 1.1.0", + "autocfg", ] [[package]] @@ -4987,7 +4399,7 @@ dependencies = [ [[package]] name = "snark_wrapper" version = "0.1.0" -source = "git+https://github.com/matter-labs/snark-wrapper.git?branch=main#42661a9ff9d00853441589679c101f71e3785f55" +source = "git+https://github.com/matter-labs/snark-wrapper.git?branch=main#76959cadabeec344b9fa1458728400d60340e496" dependencies = [ "derivative", "rand 0.4.6", @@ -5188,7 +4600,7 @@ dependencies = [ "generic-array", "hex", "hkdf", - "hmac 0.12.1", + "hmac", "itoa", "log", "md-5", @@ -5230,7 +4642,7 @@ dependencies = [ "futures-util", "hex", "hkdf", - "hmac 0.12.1", + "hmac", "home", "ipnetwork", "itoa", @@ -5835,16 +5247,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] -name = "typenum" -version = "1.17.0" +name = "typemap_rev" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "74b08b0c1257381af16a5c3605254d529d3e7e109f3c62befc5d168968192998" [[package]] -name = "ucd-trie" -version = "0.1.6" +name = "typenum" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "uint" @@ -6033,7 +5445,7 @@ dependencies = [ [[package]] name = "vlog" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?branch=zk-bootloader-memory-increase#2520a6f1dc90a26016f3d4641b8a6d7b11731d6a" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=64e814e780856325085244dea62fc0388a91baa6#64e814e780856325085244dea62fc0388a91baa6" dependencies = [ "chrono", "sentry", @@ -6061,12 +5473,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -6160,7 +5566,7 @@ dependencies = [ "bytes", "derive_more", "ethabi", - "ethereum-types 0.14.1", + "ethereum-types", "futures", "futures-timer", "headers", @@ -6173,7 +5579,7 @@ dependencies = [ "pin-project", "reqwest", "rlp", - "secp256k1 0.27.0", + "secp256k1", "serde", "serde_json", "tiny-keccak 2.0.2", @@ -6395,12 +5801,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "wyz" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" - [[package]] name = "wyz" version = "0.5.1" @@ -6458,7 +5858,7 @@ dependencies = [ "blake2 0.10.6 (git+https://github.com/RustCrypto/hashes.git?rev=1f727ce37ff40fa0cce84eb8543a45bdd3ca4a4e)", "k256 0.11.6", "lazy_static", - "num 0.4.1", + "num", "serde", "serde_json", "sha2 0.10.6", @@ -6474,7 +5874,7 @@ source = "git+https://github.com/matter-labs/era-zk_evm.git?tag=v1.3.3-rc2#fbee2 dependencies = [ "anyhow", "lazy_static", - "num 0.4.1", + "num", "serde", "serde_json", "static_assertions", @@ -6489,7 +5889,7 @@ source = "git+https://github.com/matter-labs/era-zk_evm.git?branch=v1.3.3#fbee20 dependencies = [ "anyhow", "lazy_static", - "num 0.4.1", + "num", "serde", "serde_json", "static_assertions", @@ -6504,7 +5904,7 @@ source = "git+https://github.com/matter-labs/era-zk_evm.git?branch=v1.4.0#dd76fc dependencies = [ "anyhow", "lazy_static", - "num 0.4.1", + "num", "serde", "serde_json", "static_assertions", @@ -6519,7 +5919,7 @@ source = "git+https://github.com/matter-labs/era-zk_evm.git?branch=v1.4.1#6250db dependencies = [ "anyhow", "lazy_static", - "num 0.4.1", + "num", "serde", "serde_json", "static_assertions", @@ -6542,7 +5942,7 @@ dependencies = [ [[package]] name = "zk_evm_abstractions" version = "1.4.1" -source = "git+https://github.com/matter-labs/era-zk_evm_abstractions.git?branch=v1.4.1#e3102e53fd2193bde9ecb5eba91efd7b8fb11ba9" +source = "git+https://github.com/matter-labs/era-zk_evm_abstractions.git?branch=v1.4.1#0aac08c3b097ee8147e748475117ac46bddcdcef" dependencies = [ "anyhow", "num_enum", @@ -6572,8 +5972,8 @@ dependencies = [ [[package]] name = "zkevm-assembly" -version = "1.3.2" -source = "git+https://github.com/matter-labs/era-zkEVM-assembly.git?branch=v1.4.1#50282016d01bd2fd147021dd558209778db2268b" +version = "1.4.1" +source = "git+https://github.com/matter-labs/era-zkEVM-assembly.git?branch=v1.4.1#e59d5da67f18f8829c3cfaebb967265d73c168ed" dependencies = [ "env_logger 0.9.3", "hex", @@ -6613,7 +6013,7 @@ dependencies = [ [[package]] name = "zkevm_circuits" version = "1.4.1" -source = "git+https://github.com/matter-labs/era-zkevm_circuits.git?branch=v1.4.1#5076a9a8cd775c8f7a84507a02af1e2350e3679d" +source = "git+https://github.com/matter-labs/era-zkevm_circuits.git?branch=v1.4.1#3a973afb3cf2b50b7138c1af61cc6ac3d7d0189f" dependencies = [ "arrayvec 0.7.4", "bincode", @@ -6637,7 +6037,7 @@ version = "1.3.1" source = "git+https://github.com/matter-labs/era-zkevm_opcode_defs.git?branch=v1.3.1#00d4ad2292bd55374a0fa10fe11686d7a109d8a0" dependencies = [ "bitflags 1.3.2", - "ethereum-types 0.14.1", + "ethereum-types", "lazy_static", "sha2 0.10.8", ] @@ -6649,7 +6049,7 @@ source = "git+https://github.com/matter-labs/era-zkevm_opcode_defs.git?branch=v1 dependencies = [ "bitflags 2.4.1", "blake2 0.10.6 (git+https://github.com/RustCrypto/hashes.git?rev=1f727ce37ff40fa0cce84eb8543a45bdd3ca4a4e)", - "ethereum-types 0.14.1", + "ethereum-types", "k256 0.11.6", "lazy_static", "sha2 0.10.6", @@ -6663,7 +6063,7 @@ source = "git+https://github.com/matter-labs/era-zkevm_opcode_defs.git?branch=v1 dependencies = [ "bitflags 2.4.1", "blake2 0.10.6 (registry+https://github.com/rust-lang/crates.io-index)", - "ethereum-types 0.14.1", + "ethereum-types", "k256 0.13.3", "lazy_static", "sha2 0.10.8", @@ -6677,7 +6077,7 @@ source = "git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v dependencies = [ "bincode", "circuit_testing", - "codegen 0.2.0", + "codegen", "crossbeam 0.8.2", "derivative", "env_logger 0.10.1", @@ -6694,17 +6094,17 @@ dependencies = [ "test-log", "tracing", "zk_evm 1.3.3 (git+https://github.com/matter-labs/era-zk_evm.git?branch=v1.3.3)", - "zkevm-assembly 1.3.2 (git+https://github.com/matter-labs/era-zkEVM-assembly.git?branch=v1.3.2)", + "zkevm-assembly 1.3.2", ] [[package]] name = "zkevm_test_harness" version = "1.4.0" -source = "git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.4.0#fb47657ae3b6ff6e4bb5199964d3d37212978200" +source = "git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.4.0#aff36380e6a740f7286c9c522e1416b8d38327ce" dependencies = [ "bincode", "circuit_definitions 0.1.0 (git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.4.0)", - "codegen 0.2.0", + "codegen", "crossbeam 0.8.2", "derivative", "env_logger 0.10.1", @@ -6717,17 +6117,17 @@ dependencies = [ "structopt", "test-log", "tracing", - "zkevm-assembly 1.3.2 (git+https://github.com/matter-labs/era-zkEVM-assembly.git?branch=v1.3.2)", + "zkevm-assembly 1.3.2", ] [[package]] name = "zkevm_test_harness" -version = "1.4.0" -source = "git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.4.1#6db7c01717d157945f0f2939119dbd8a170de6bc" +version = "1.4.1" +source = "git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.4.1#e957ef50421393914dee7796b0091586e5153192" dependencies = [ "bincode", "circuit_definitions 0.1.0 (git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.4.1)", - "codegen 0.2.0", + "codegen", "crossbeam 0.8.2", "curl", "derivative", @@ -6746,13 +6146,13 @@ dependencies = [ "test-log", "tracing", "walkdir", - "zkevm-assembly 1.3.2 (git+https://github.com/matter-labs/era-zkEVM-assembly.git?branch=v1.4.1)", + "zkevm-assembly 1.4.1", ] [[package]] name = "zksync_basic_types" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?branch=zk-bootloader-memory-increase#2520a6f1dc90a26016f3d4641b8a6d7b11731d6a" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=64e814e780856325085244dea62fc0388a91baa6#64e814e780856325085244dea62fc0388a91baa6" dependencies = [ "serde", "serde_json", @@ -6762,7 +6162,7 @@ dependencies = [ [[package]] name = "zksync_concurrency" version = "0.1.0" -source = "git+https://github.com/matter-labs/era-consensus.git?rev=5727a3e0b22470bb90092388f9125bcb366df613#5727a3e0b22470bb90092388f9125bcb366df613" +source = "git+https://github.com/matter-labs/era-consensus.git?rev=5b3d383d7a65b0fbe2a771fecf4313f5083be9ae#5b3d383d7a65b0fbe2a771fecf4313f5083be9ae" dependencies = [ "anyhow", "once_cell", @@ -6780,9 +6180,10 @@ dependencies = [ [[package]] name = "zksync_config" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?branch=zk-bootloader-memory-increase#2520a6f1dc90a26016f3d4641b8a6d7b11731d6a" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=64e814e780856325085244dea62fc0388a91baa6#64e814e780856325085244dea62fc0388a91baa6" dependencies = [ "anyhow", + "rand 0.8.5", "serde", "zksync_basic_types", ] @@ -6790,7 +6191,7 @@ dependencies = [ [[package]] name = "zksync_consensus_crypto" version = "0.1.0" -source = "git+https://github.com/matter-labs/era-consensus.git?rev=5727a3e0b22470bb90092388f9125bcb366df613#5727a3e0b22470bb90092388f9125bcb366df613" +source = "git+https://github.com/matter-labs/era-consensus.git?rev=5b3d383d7a65b0fbe2a771fecf4313f5083be9ae#5b3d383d7a65b0fbe2a771fecf4313f5083be9ae" dependencies = [ "anyhow", "blst", @@ -6808,7 +6209,7 @@ dependencies = [ [[package]] name = "zksync_consensus_roles" version = "0.1.0" -source = "git+https://github.com/matter-labs/era-consensus.git?rev=5727a3e0b22470bb90092388f9125bcb366df613#5727a3e0b22470bb90092388f9125bcb366df613" +source = "git+https://github.com/matter-labs/era-consensus.git?rev=5b3d383d7a65b0fbe2a771fecf4313f5083be9ae#5b3d383d7a65b0fbe2a771fecf4313f5083be9ae" dependencies = [ "anyhow", "bit-vec", @@ -6828,7 +6229,7 @@ dependencies = [ [[package]] name = "zksync_consensus_storage" version = "0.1.0" -source = "git+https://github.com/matter-labs/era-consensus.git?rev=5727a3e0b22470bb90092388f9125bcb366df613#5727a3e0b22470bb90092388f9125bcb366df613" +source = "git+https://github.com/matter-labs/era-consensus.git?rev=5b3d383d7a65b0fbe2a771fecf4313f5083be9ae#5b3d383d7a65b0fbe2a771fecf4313f5083be9ae" dependencies = [ "anyhow", "async-trait", @@ -6846,7 +6247,7 @@ dependencies = [ [[package]] name = "zksync_consensus_utils" version = "0.1.0" -source = "git+https://github.com/matter-labs/era-consensus.git?rev=5727a3e0b22470bb90092388f9125bcb366df613#5727a3e0b22470bb90092388f9125bcb366df613" +source = "git+https://github.com/matter-labs/era-consensus.git?rev=5b3d383d7a65b0fbe2a771fecf4313f5083be9ae#5b3d383d7a65b0fbe2a771fecf4313f5083be9ae" dependencies = [ "thiserror", "zksync_concurrency", @@ -6855,7 +6256,7 @@ dependencies = [ [[package]] name = "zksync_contracts" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?branch=zk-bootloader-memory-increase#2520a6f1dc90a26016f3d4641b8a6d7b11731d6a" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=64e814e780856325085244dea62fc0388a91baa6#64e814e780856325085244dea62fc0388a91baa6" dependencies = [ "envy", "ethabi", @@ -6869,9 +6270,8 @@ dependencies = [ [[package]] name = "zksync_crypto" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?branch=zk-bootloader-memory-increase#2520a6f1dc90a26016f3d4641b8a6d7b11731d6a" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=64e814e780856325085244dea62fc0388a91baa6#64e814e780856325085244dea62fc0388a91baa6" dependencies = [ - "base64 0.13.1", "blake2 0.10.6 (registry+https://github.com/rust-lang/crates.io-index)", "hex", "once_cell", @@ -6884,14 +6284,14 @@ dependencies = [ [[package]] name = "zksync_dal" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?branch=zk-bootloader-memory-increase#2520a6f1dc90a26016f3d4641b8a6d7b11731d6a" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=64e814e780856325085244dea62fc0388a91baa6#64e814e780856325085244dea62fc0388a91baa6" dependencies = [ "anyhow", "bigdecimal", "bincode", + "chrono", "hex", "itertools 0.10.5", - "num 0.4.1", "once_cell", "prost", "rand 0.8.5", @@ -6918,7 +6318,7 @@ dependencies = [ [[package]] name = "zksync_health_check" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?branch=zk-bootloader-memory-increase#2520a6f1dc90a26016f3d4641b8a6d7b11731d6a" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=64e814e780856325085244dea62fc0388a91baa6#64e814e780856325085244dea62fc0388a91baa6" dependencies = [ "async-trait", "futures", @@ -6926,12 +6326,13 @@ dependencies = [ "serde_json", "tokio", "tracing", + "vise", ] [[package]] name = "zksync_mini_merkle_tree" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?branch=zk-bootloader-memory-increase#2520a6f1dc90a26016f3d4641b8a6d7b11731d6a" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=64e814e780856325085244dea62fc0388a91baa6#64e814e780856325085244dea62fc0388a91baa6" dependencies = [ "once_cell", "zksync_basic_types", @@ -6941,7 +6342,7 @@ dependencies = [ [[package]] name = "zksync_protobuf" version = "0.1.0" -source = "git+https://github.com/matter-labs/era-consensus.git?rev=5727a3e0b22470bb90092388f9125bcb366df613#5727a3e0b22470bb90092388f9125bcb366df613" +source = "git+https://github.com/matter-labs/era-consensus.git?rev=5b3d383d7a65b0fbe2a771fecf4313f5083be9ae#5b3d383d7a65b0fbe2a771fecf4313f5083be9ae" dependencies = [ "anyhow", "bit-vec", @@ -6959,7 +6360,7 @@ dependencies = [ [[package]] name = "zksync_protobuf_build" version = "0.1.0" -source = "git+https://github.com/matter-labs/era-consensus.git?rev=5727a3e0b22470bb90092388f9125bcb366df613#5727a3e0b22470bb90092388f9125bcb366df613" +source = "git+https://github.com/matter-labs/era-consensus.git?rev=5b3d383d7a65b0fbe2a771fecf4313f5083be9ae#5b3d383d7a65b0fbe2a771fecf4313f5083be9ae" dependencies = [ "anyhow", "heck 0.4.1", @@ -6975,7 +6376,7 @@ dependencies = [ [[package]] name = "zksync_state" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?branch=zk-bootloader-memory-increase#2520a6f1dc90a26016f3d4641b8a6d7b11731d6a" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=64e814e780856325085244dea62fc0388a91baa6#64e814e780856325085244dea62fc0388a91baa6" dependencies = [ "anyhow", "itertools 0.10.5", @@ -6992,7 +6393,7 @@ dependencies = [ [[package]] name = "zksync_storage" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?branch=zk-bootloader-memory-increase#2520a6f1dc90a26016f3d4641b8a6d7b11731d6a" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=64e814e780856325085244dea62fc0388a91baa6#64e814e780856325085244dea62fc0388a91baa6" dependencies = [ "num_cpus", "once_cell", @@ -7004,50 +6405,35 @@ dependencies = [ [[package]] name = "zksync_system_constants" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?branch=zk-bootloader-memory-increase#2520a6f1dc90a26016f3d4641b8a6d7b11731d6a" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=64e814e780856325085244dea62fc0388a91baa6#64e814e780856325085244dea62fc0388a91baa6" dependencies = [ - "anyhow", - "bigdecimal", - "hex", - "num 0.3.1", "once_cell", - "serde", - "serde_json", - "url", "zksync_basic_types", - "zksync_contracts", "zksync_utils", ] [[package]] name = "zksync_types" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?branch=zk-bootloader-memory-increase#2520a6f1dc90a26016f3d4641b8a6d7b11731d6a" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=64e814e780856325085244dea62fc0388a91baa6#64e814e780856325085244dea62fc0388a91baa6" dependencies = [ "anyhow", "blake2 0.10.6 (registry+https://github.com/rust-lang/crates.io-index)", "chrono", - "codegen 0.1.0", - "ethereum-types 0.12.1", "hex", - "num 0.4.1", + "num", "num_enum", "once_cell", - "parity-crypto", "prost", "rlp", + "secp256k1", "serde", "serde_json", "serde_with", "strum", "thiserror", - "zk_evm 1.3.3 (git+https://github.com/matter-labs/era-zk_evm.git?tag=v1.3.3-rc2)", - "zk_evm 1.4.0", - "zk_evm 1.4.1", - "zkevm_test_harness 1.3.3", "zksync_basic_types", "zksync_config", - "zksync_consensus_roles", "zksync_contracts", "zksync_mini_merkle_tree", "zksync_protobuf", @@ -7059,7 +6445,7 @@ dependencies = [ [[package]] name = "zksync_utils" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?branch=zk-bootloader-memory-increase#2520a6f1dc90a26016f3d4641b8a6d7b11731d6a" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=64e814e780856325085244dea62fc0388a91baa6#64e814e780856325085244dea62fc0388a91baa6" dependencies = [ "anyhow", "bigdecimal", @@ -7067,7 +6453,7 @@ dependencies = [ "hex", "itertools 0.10.5", "metrics", - "num 0.4.1", + "num", "reqwest", "serde", "thiserror", diff --git a/system-contracts/bootloader/test_infra/Cargo.toml b/system-contracts/bootloader/test_infra/Cargo.toml index aca9ace75e59..785971a61042 100644 --- a/system-contracts/bootloader/test_infra/Cargo.toml +++ b/system-contracts/bootloader/test_infra/Cargo.toml @@ -7,12 +7,12 @@ edition = "2021" [dependencies] -multivm = { git = "https://github.com/matter-labs/zksync-era.git", branch = "zk-bootloader-memory-increase" } -zksync_types = { git = "https://github.com/matter-labs/zksync-era.git", branch = "zk-bootloader-memory-increase" } -zksync_contracts = { git = "https://github.com/matter-labs/zksync-era.git", branch = "zk-bootloader-memory-increase" } -zksync_utils = { git = "https://github.com/matter-labs/zksync-era.git", branch = "zk-bootloader-memory-increase" } -zksync_state = { git = "https://github.com/matter-labs/zksync-era.git", branch = "zk-bootloader-memory-increase" } -vlog = { git = "https://github.com/matter-labs/zksync-era.git", branch = "zk-bootloader-memory-increase" } +multivm = { git = "https://github.com/matter-labs/zksync-era.git", rev = "64e814e780856325085244dea62fc0388a91baa6" } +zksync_types = { git = "https://github.com/matter-labs/zksync-era.git", rev = "64e814e780856325085244dea62fc0388a91baa6" } +zksync_contracts = { git = "https://github.com/matter-labs/zksync-era.git", rev = "64e814e780856325085244dea62fc0388a91baa6" } +zksync_utils = { git = "https://github.com/matter-labs/zksync-era.git", rev = "64e814e780856325085244dea62fc0388a91baa6" } +zksync_state = { git = "https://github.com/matter-labs/zksync-era.git", rev = "64e814e780856325085244dea62fc0388a91baa6" } +vlog = { git = "https://github.com/matter-labs/zksync-era.git", rev = "64e814e780856325085244dea62fc0388a91baa6" } colored = "2.0" hex = "0.4" diff --git a/system-contracts/bootloader/test_infra/src/hook.rs b/system-contracts/bootloader/test_infra/src/hook.rs index e346ecf055fb..405578cefe2f 100644 --- a/system-contracts/bootloader/test_infra/src/hook.rs +++ b/system-contracts/bootloader/test_infra/src/hook.rs @@ -3,7 +3,7 @@ use multivm::vm_latest::{ HistoryMode, SimpleMemory, }; -use multivm::zk_evm_1_4_1::{ +use multivm::zk_evm_latest::{ aux_structures::MemoryPage, tracing::{BeforeExecutionData, VmLocalStateData}, zkevm_opcode_defs::{FatPointer, Opcode, UMAOpcode}, diff --git a/system-contracts/bootloader/test_infra/src/test_count_tracer.rs b/system-contracts/bootloader/test_infra/src/test_count_tracer.rs index bb3f9ef87052..fa3c96673118 100644 --- a/system-contracts/bootloader/test_infra/src/test_count_tracer.rs +++ b/system-contracts/bootloader/test_infra/src/test_count_tracer.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use multivm::interface::dyn_tracers::vm_1_4_1::DynTracer; use multivm::vm_latest::{HistoryMode, SimpleMemory, VmTracer}; -use multivm::zk_evm_1_4_1::tracing::{BeforeExecutionData, VmLocalStateData}; +use multivm::zk_evm_latest::tracing::{BeforeExecutionData, VmLocalStateData}; use once_cell::sync::OnceCell; use zksync_state::{StoragePtr, WriteStorage}; diff --git a/system-contracts/bootloader/test_infra/src/tracer.rs b/system-contracts/bootloader/test_infra/src/tracer.rs index e0e0022dbb05..49a162b549cd 100644 --- a/system-contracts/bootloader/test_infra/src/tracer.rs +++ b/system-contracts/bootloader/test_infra/src/tracer.rs @@ -8,7 +8,7 @@ use multivm::interface::{ tracer::{TracerExecutionStatus, TracerExecutionStopReason}, }; use multivm::vm_latest::{BootloaderState, HistoryMode, SimpleMemory, VmTracer, ZkSyncVmState}; -use multivm::zk_evm_1_4_1::tracing::{BeforeExecutionData, VmLocalStateData}; +use multivm::zk_evm_latest::tracing::{BeforeExecutionData, VmLocalStateData}; use zksync_state::{StoragePtr, WriteStorage}; diff --git a/system-contracts/contracts/Compressor.sol b/system-contracts/contracts/Compressor.sol index 24aac725a6c2..9830f92cbd77 100644 --- a/system-contracts/contracts/Compressor.sol +++ b/system-contracts/contracts/Compressor.sol @@ -28,6 +28,7 @@ contract Compressor is ICompressor, ISystemContract { /// - 2 bytes: the length of the dictionary /// - N bytes: the dictionary /// - M bytes: the encoded data + /// @return bytecodeHash The hash of the original bytecode. /// @dev The dictionary is a sequence of 8-byte chunks, each of them has the associated index. /// @dev The encoded data is a sequence of 2-byte chunks, each of them is an index of the dictionary. /// @dev The compression algorithm works as follows: @@ -39,6 +40,7 @@ contract Compressor is ICompressor, ISystemContract { /// * The 2-byte index of the chunk in the dictionary is added to the encoded data. /// @dev Currently, the method may be called only from the bootloader because the server is not ready to publish bytecodes /// in internal transactions. However, in the future, we will allow everyone to publish compressed bytecodes. + /// @dev Read more about the compression: https://github.com/matter-labs/zksync-era/blob/main/docs/guides/advanced/compression.md function publishCompressedBytecode( bytes calldata _bytecode, bytes calldata _rawCompressedData @@ -46,13 +48,16 @@ contract Compressor is ICompressor, ISystemContract { unchecked { (bytes calldata dictionary, bytes calldata encodedData) = _decodeRawBytecode(_rawCompressedData); - require(dictionary.length % 8 == 0, "Dictionary length should be a multiple of 8"); - require(dictionary.length <= 2 ** 16 * 8, "Dictionary is too big"); require( encodedData.length * 4 == _bytecode.length, "Encoded data length should be 4 times shorter than the original bytecode" ); + require( + dictionary.length / 8 <= encodedData.length / 2, + "Dictionary should have at most the same number of entries as the encoded data" + ); + for (uint256 encodedDataPointer = 0; encodedDataPointer < encodedData.length; encodedDataPointer += 2) { uint256 indexOfEncodedChunk = uint256(encodedData.readUint16(encodedDataPointer)) * 8; require(indexOfEncodedChunk < dictionary.length, "Encoded chunk index is out of bounds"); diff --git a/system-contracts/contracts/Constants.sol b/system-contracts/contracts/Constants.sol index cf0242c65752..e5a7f4fb4bfc 100644 --- a/system-contracts/contracts/Constants.sol +++ b/system-contracts/contracts/Constants.sol @@ -7,7 +7,7 @@ import {INonceHolder} from "./interfaces/INonceHolder.sol"; import {IContractDeployer} from "./interfaces/IContractDeployer.sol"; import {IKnownCodesStorage} from "./interfaces/IKnownCodesStorage.sol"; import {IImmutableSimulator} from "./interfaces/IImmutableSimulator.sol"; -import {IEthToken} from "./interfaces/IEthToken.sol"; +import {IBaseToken} from "./interfaces/IBaseToken.sol"; import {IL1Messenger} from "./interfaces/IL1Messenger.sol"; import {ISystemContext} from "./interfaces/ISystemContext.sol"; import {ICompressor} from "./interfaces/ICompressor.sol"; @@ -50,7 +50,7 @@ address constant FORCE_DEPLOYER = address(SYSTEM_CONTRACTS_OFFSET + 0x07); IL1Messenger constant L1_MESSENGER_CONTRACT = IL1Messenger(address(SYSTEM_CONTRACTS_OFFSET + 0x08)); address constant MSG_VALUE_SYSTEM_CONTRACT = address(SYSTEM_CONTRACTS_OFFSET + 0x09); -IEthToken constant ETH_TOKEN_SYSTEM_CONTRACT = IEthToken(address(SYSTEM_CONTRACTS_OFFSET + 0x0a)); +IBaseToken constant BASE_TOKEN_SYSTEM_CONTRACT = IBaseToken(address(SYSTEM_CONTRACTS_OFFSET + 0x0a)); // Hardcoded because even for tests we should keep the address. (Instead `SYSTEM_CONTRACTS_OFFSET + 0x10`) // Precompile call depends on it. diff --git a/system-contracts/contracts/ContractDeployer.sol b/system-contracts/contracts/ContractDeployer.sol index 90519b1fe534..f61b45aed582 100644 --- a/system-contracts/contracts/ContractDeployer.sol +++ b/system-contracts/contracts/ContractDeployer.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.20; import {ImmutableData} from "./interfaces/IImmutableSimulator.sol"; import {IContractDeployer} from "./interfaces/IContractDeployer.sol"; -import {CREATE2_PREFIX, CREATE_PREFIX, NONCE_HOLDER_SYSTEM_CONTRACT, ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT, FORCE_DEPLOYER, MAX_SYSTEM_CONTRACT_ADDRESS, KNOWN_CODE_STORAGE_CONTRACT, ETH_TOKEN_SYSTEM_CONTRACT, IMMUTABLE_SIMULATOR_SYSTEM_CONTRACT, COMPLEX_UPGRADER_CONTRACT, KECCAK256_SYSTEM_CONTRACT} from "./Constants.sol"; +import {CREATE2_PREFIX, CREATE_PREFIX, NONCE_HOLDER_SYSTEM_CONTRACT, ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT, FORCE_DEPLOYER, MAX_SYSTEM_CONTRACT_ADDRESS, KNOWN_CODE_STORAGE_CONTRACT, BASE_TOKEN_SYSTEM_CONTRACT, IMMUTABLE_SIMULATOR_SYSTEM_CONTRACT, COMPLEX_UPGRADER_CONTRACT, KECCAK256_SYSTEM_CONTRACT} from "./Constants.sol"; import {Utils} from "./libraries/Utils.sol"; import {EfficientCall} from "./libraries/EfficientCall.sol"; @@ -348,7 +348,7 @@ contract ContractDeployer is IContractDeployer, ISystemContract { if (_callConstructor) { // 1. Transfer the balance to the new address on the constructor call. if (value > 0) { - ETH_TOKEN_SYSTEM_CONTRACT.transferFromTo(address(this), _newAddress, value); + BASE_TOKEN_SYSTEM_CONTRACT.transferFromTo(address(this), _newAddress, value); } // 2. Set the constructed code hash on the account _storeConstructingByteCodeHashOnAddress(_newAddress, _bytecodeHash); diff --git a/system-contracts/contracts/L2EthToken.sol b/system-contracts/contracts/L2BaseToken.sol similarity index 98% rename from system-contracts/contracts/L2EthToken.sol rename to system-contracts/contracts/L2BaseToken.sol index 71df9333c734..573548a4414d 100644 --- a/system-contracts/contracts/L2EthToken.sol +++ b/system-contracts/contracts/L2BaseToken.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.20; -import {IEthToken} from "./interfaces/IEthToken.sol"; +import {IBaseToken} from "./interfaces/IBaseToken.sol"; import {ISystemContract} from "./interfaces/ISystemContract.sol"; import {MSG_VALUE_SYSTEM_CONTRACT, DEPLOYER_SYSTEM_CONTRACT, BOOTLOADER_FORMAL_ADDRESS, L1_MESSENGER_CONTRACT} from "./Constants.sol"; import {IMailbox} from "./interfaces/IMailbox.sol"; @@ -15,7 +15,7 @@ import {IMailbox} from "./interfaces/IMailbox.sol"; * Instead, this contract is used by the bootloader and `MsgValueSimulator`/`ContractDeployer` system contracts * to perform the balance changes while simulating the `msg.value` Ethereum behavior. */ -contract L2EthToken is IEthToken, ISystemContract { +contract L2BaseToken is IBaseToken, ISystemContract { /// @notice The balances of the users. mapping(address account => uint256 balance) internal balance; diff --git a/system-contracts/contracts/MsgValueSimulator.sol b/system-contracts/contracts/MsgValueSimulator.sol index a5be6043baa7..09b6d0152012 100644 --- a/system-contracts/contracts/MsgValueSimulator.sol +++ b/system-contracts/contracts/MsgValueSimulator.sol @@ -6,7 +6,7 @@ import {Utils} from "./libraries/Utils.sol"; import {EfficientCall} from "./libraries/EfficientCall.sol"; import {ISystemContract} from "./interfaces/ISystemContract.sol"; import {SystemContractHelper} from "./libraries/SystemContractHelper.sol"; -import {MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT, ETH_TOKEN_SYSTEM_CONTRACT} from "./Constants.sol"; +import {MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT, BASE_TOKEN_SYSTEM_CONTRACT} from "./Constants.sol"; /** * @author Matter Labs @@ -43,8 +43,8 @@ contract MsgValueSimulator is ISystemContract { require(to != address(this), "MsgValueSimulator calls itself"); if (value != 0) { - (bool success, ) = address(ETH_TOKEN_SYSTEM_CONTRACT).call( - abi.encodeCall(ETH_TOKEN_SYSTEM_CONTRACT.transferFromTo, (msg.sender, to, value)) + (bool success, ) = address(BASE_TOKEN_SYSTEM_CONTRACT).call( + abi.encodeCall(BASE_TOKEN_SYSTEM_CONTRACT.transferFromTo, (msg.sender, to, value)) ); // If the transfer of ETH fails, we do the most Ethereum-like behaviour in such situation: revert(0,0) diff --git a/system-contracts/contracts/SystemContext.sol b/system-contracts/contracts/SystemContext.sol index fe079d41d083..6877a3b29e6e 100644 --- a/system-contracts/contracts/SystemContext.sol +++ b/system-contracts/contracts/SystemContext.sol @@ -79,6 +79,12 @@ contract SystemContext is ISystemContext, ISystemContextDeprecated, ISystemContr /// @notice The information about the virtual blocks upgrade, which tracks when the migration to the L2 blocks has started and finished. VirtualBlockUpgradeInfo internal virtualBlockUpgradeInfo; + /// @notice Set the chainId origin. + /// @param _newChainId The chainId + function setChainId(uint256 _newChainId) external onlyCallFromForceDeployer { + chainId = _newChainId; + } + /// @notice Number of current transaction in block. uint16 public txNumberInBlock; diff --git a/system-contracts/contracts/interfaces/IEthToken.sol b/system-contracts/contracts/interfaces/IBaseToken.sol similarity index 97% rename from system-contracts/contracts/interfaces/IEthToken.sol rename to system-contracts/contracts/interfaces/IBaseToken.sol index ec9b399f54f4..d15f2f12343a 100644 --- a/system-contracts/contracts/interfaces/IEthToken.sol +++ b/system-contracts/contracts/interfaces/IBaseToken.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.20; -interface IEthToken { +interface IBaseToken { function balanceOf(uint256) external view returns (uint256); function transferFromTo(address _from, address _to, uint256 _amount) external; diff --git a/system-contracts/contracts/interfaces/ISystemContract.sol b/system-contracts/contracts/interfaces/ISystemContract.sol index 1a2bf514dda2..01ff9d95f92c 100644 --- a/system-contracts/contracts/interfaces/ISystemContract.sol +++ b/system-contracts/contracts/interfaces/ISystemContract.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.20; import {SystemContractHelper} from "../libraries/SystemContractHelper.sol"; -import {BOOTLOADER_FORMAL_ADDRESS} from "../Constants.sol"; +import {BOOTLOADER_FORMAL_ADDRESS, FORCE_DEPLOYER} from "../Constants.sol"; /** * @author Matter Labs @@ -48,4 +48,11 @@ abstract contract ISystemContract { require(msg.sender == BOOTLOADER_FORMAL_ADDRESS, "Callable only by the bootloader"); _; } + + /// @notice Modifier that makes sure that the method + /// can only be called from the L1 force deployer. + modifier onlyCallFromForceDeployer() { + require(msg.sender == FORCE_DEPLOYER); + _; + } } diff --git a/system-contracts/contracts/libraries/TransactionHelper.sol b/system-contracts/contracts/libraries/TransactionHelper.sol index 27a4f8594ec0..507991dabaa6 100644 --- a/system-contracts/contracts/libraries/TransactionHelper.sol +++ b/system-contracts/contracts/libraries/TransactionHelper.sol @@ -7,7 +7,7 @@ import "../openzeppelin/token/ERC20/utils/SafeERC20.sol"; import "../interfaces/IPaymasterFlow.sol"; import "../interfaces/IContractDeployer.sol"; -import {ETH_TOKEN_SYSTEM_CONTRACT, BOOTLOADER_FORMAL_ADDRESS} from "../Constants.sol"; +import {BASE_TOKEN_SYSTEM_CONTRACT, BOOTLOADER_FORMAL_ADDRESS} from "../Constants.sol"; import "./RLPEncoder.sol"; import "./EfficientCall.sol"; @@ -90,9 +90,9 @@ library TransactionHelper { /// @param _addr The address of the token /// @return `true` or `false` based on whether the token is Ether. /// @dev This method assumes that address is Ether either if the address is 0 (for convenience) - /// or if the address is the address of the L2EthToken system contract. + /// or if the address is the address of the L2BaseToken system contract. function isEthToken(uint256 _addr) internal pure returns (bool) { - return _addr == uint256(uint160(address(ETH_TOKEN_SYSTEM_CONTRACT))) || _addr == 0; + return _addr == uint256(uint160(address(BASE_TOKEN_SYSTEM_CONTRACT))) || _addr == 0; } /// @notice Calculate the suggested signed hash of the transaction, diff --git a/system-contracts/package.json b/system-contracts/package.json index 6867cdbec28d..8a3cbdd7eb1d 100644 --- a/system-contracts/package.json +++ b/system-contracts/package.json @@ -11,12 +11,12 @@ "fast-glob": "^3.3.2", "hardhat": "^2.18.3", "preprocess": "^3.2.0", - "zksync-web3": "^0.14.3" + "zksync-ethers": "https://github.com/kelemeno/zksync-ethers#ethers-v5-feat/bridgehub" }, "devDependencies": { "@matterlabs/hardhat-zksync-chai-matchers": "^0.1.4", "@matterlabs/hardhat-zksync-node": "^0.0.1-beta.7", - "@matterlabs/hardhat-zksync-solc": "^0.4.2", + "@matterlabs/hardhat-zksync-solc": "^1.0.6", "@nomicfoundation/hardhat-chai-matchers": "^1.0.3", "@nomiclabs/hardhat-ethers": "^2.0.0", "@typechain/ethers-v5": "^2.0.0", @@ -33,7 +33,7 @@ "ts-node": "^10.1.0", "typechain": "^4.0.0", "typescript": "^4.6.4", - "zksync-ethers": "^5.0.0" + "zksync-ethers": "https://github.com/kelemeno/zksync-ethers#ethers-v5-feat/bridgehub" }, "mocha": { "timeout": 240000, diff --git a/system-contracts/scripts/constants.ts b/system-contracts/scripts/constants.ts index 437eb90a6ef2..9bf86953d1eb 100644 --- a/system-contracts/scripts/constants.ts +++ b/system-contracts/scripts/constants.ts @@ -106,9 +106,9 @@ export const SYSTEM_CONTRACTS: ISystemContracts = { codeName: "MsgValueSimulator", lang: Language.Solidity, }, - l2EthToken: { + L2BaseToken: { address: "0x000000000000000000000000000000000000800a", - codeName: "L2EthToken", + codeName: "L2BaseToken", lang: Language.Solidity, }, systemContext: { diff --git a/system-contracts/scripts/deploy-preimages.ts b/system-contracts/scripts/deploy-preimages.ts index 035b98e88f9a..7806af7086d8 100644 --- a/system-contracts/scripts/deploy-preimages.ts +++ b/system-contracts/scripts/deploy-preimages.ts @@ -8,8 +8,7 @@ import { ethers } from "ethers"; import { formatUnits, parseUnits } from "ethers/lib/utils"; import * as fs from "fs"; import * as path from "path"; -import type { types } from "zksync-web3"; -import { Provider, Wallet } from "zksync-web3"; +import { Provider, Wallet } from "zksync-ethers"; import { hashBytecode } from "zksync-web3/build/src/utils"; import { Language, SYSTEM_CONTRACTS } from "./constants"; import type { Dependency, DeployedDependency } from "./utils"; diff --git a/system-contracts/scripts/preprocess-bootloader.ts b/system-contracts/scripts/preprocess-bootloader.ts index ffb44000441b..c0dafe011083 100644 --- a/system-contracts/scripts/preprocess-bootloader.ts +++ b/system-contracts/scripts/preprocess-bootloader.ts @@ -4,7 +4,7 @@ import * as hre from "hardhat"; import { ethers } from "ethers"; import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs"; import { render, renderFile } from "template-file"; -import { utils } from "zksync-web3"; +import { utils } from "zksync-ethers"; import { getRevertSelector, getTransactionUtils } from "./constants"; /* eslint-disable @typescript-eslint/no-var-requires */ @@ -69,13 +69,13 @@ const params = { // Error REVERT_ERROR_SELECTOR: padZeroRight(getRevertSelector(), PADDED_SELECTOR_LENGTH), RIGHT_PADDED_VALIDATE_NONCE_USAGE_SELECTOR: getPaddedSelector("INonceHolder", "validateNonceUsage"), - RIGHT_PADDED_MINT_ETHER_SELECTOR: getPaddedSelector("L2EthToken", "mint"), + RIGHT_PADDED_MINT_ETHER_SELECTOR: getPaddedSelector("L2BaseToken", "mint"), GET_TX_HASHES_SELECTOR: getSelector("BootloaderUtilities", "getTransactionHashes"), CREATE_SELECTOR: getSelector("ContractDeployer", "create"), CREATE2_SELECTOR: getSelector("ContractDeployer", "create2"), CREATE_ACCOUNT_SELECTOR: getSelector("ContractDeployer", "createAccount"), CREATE2_ACCOUNT_SELECTOR: getSelector("ContractDeployer", "create2Account"), - PADDED_TRANSFER_FROM_TO_SELECTOR: getPaddedSelector("L2EthToken", "transferFromTo"), + PADDED_TRANSFER_FROM_TO_SELECTOR: getPaddedSelector("L2BaseToken", "transferFromTo"), SUCCESSFUL_ACCOUNT_VALIDATION_MAGIC_VALUE: getPaddedSelector("IAccount", "validateTransaction"), SUCCESSFUL_PAYMASTER_VALIDATION_MAGIC_VALUE: getPaddedSelector("IPaymaster", "validateAndPayForPaymasterTransaction"), PUBLISH_COMPRESSED_BYTECODE_SELECTOR: getSelector("Compressor", "publishCompressedBytecode"), diff --git a/system-contracts/test/AccountCodeStorage.spec.ts b/system-contracts/test/AccountCodeStorage.spec.ts index c7d46ef966cf..994cfacc8ecb 100644 --- a/system-contracts/test/AccountCodeStorage.spec.ts +++ b/system-contracts/test/AccountCodeStorage.spec.ts @@ -1,6 +1,6 @@ import { expect } from "chai"; import { ethers, network } from "hardhat"; -import type { Wallet } from "zksync-web3"; +import type { Wallet } from "zksync-ethers"; import type { AccountCodeStorage } from "../typechain"; import { AccountCodeStorageFactory } from "../typechain"; import { diff --git a/system-contracts/test/BootloaderUtilities.spec.ts b/system-contracts/test/BootloaderUtilities.spec.ts index 0178fc9cf5b1..6beaa2d4f336 100644 --- a/system-contracts/test/BootloaderUtilities.spec.ts +++ b/system-contracts/test/BootloaderUtilities.spec.ts @@ -1,7 +1,7 @@ import { expect } from "chai"; import { ethers } from "hardhat"; -import type { Wallet } from "zksync-web3"; -import * as zksync from "zksync-web3"; +import type { Wallet } from "zksync-ethers"; +import * as zksync from "zksync-ethers"; import { serialize } from "zksync-web3/build/src/utils"; import type { BootloaderUtilities } from "../typechain"; import { BootloaderUtilitiesFactory } from "../typechain"; diff --git a/system-contracts/test/Compressor.spec.ts b/system-contracts/test/Compressor.spec.ts index 7913412e5b35..094eddd9959a 100644 --- a/system-contracts/test/Compressor.spec.ts +++ b/system-contracts/test/Compressor.spec.ts @@ -1,8 +1,8 @@ import { expect } from "chai"; import { BigNumber } from "ethers"; import { ethers, network } from "hardhat"; -import type { Wallet } from "zksync-web3"; -import * as zksync from "zksync-web3"; +import type { Wallet } from "zksync-ethers"; +import * as zksync from "zksync-ethers"; import type { Compressor } from "../typechain"; import { CompressorFactory } from "../typechain"; import { @@ -45,54 +45,107 @@ describe("Compressor tests", function () { }); describe("publishCompressedBytecode", function () { - it("non-bootloader failed to call", async () => { + it("should revert when it's a non-bootloader call", async () => { await expect(compressor.publishCompressedBytecode("0x", "0x0000")).to.be.revertedWith( "Callable only by the bootloader" ); }); - it("invalid encoded length", async () => { - const BYTECODE = "0xdeadbeefdeadbeef"; - const COMPRESSED_BYTECODE = "0x0001deadbeefdeadbeef00000000"; + it("should revert when the dictionary length is incorrect", async () => { + const BYTECODE = "0x" + "deadbeefdeadbeef" + "deadbeefdeadbeef" + "deadbeefdeadbeef" + "deadbeefdeadbeef"; + // Dictionary has only 1 entry, but the dictionary length is 2 + const COMPRESSED_BYTECODE = "0x0002" + "deadbeefdeadbeef" + "0000" + "0000" + "0000" + "0000"; await expect( compressor.connect(bootloaderAccount).publishCompressedBytecode(BYTECODE, COMPRESSED_BYTECODE) ).to.be.revertedWith("Encoded data length should be 4 times shorter than the original bytecode"); }); - it("chunk index is out of bounds", async () => { - const BYTECODE = "0xdeadbeefdeadbeef"; - const COMPRESSED_BYTECODE = "0x0001deadbeefdeadbeef0001"; + it("should revert when there is no encoded data", async () => { + const BYTECODE = "0x" + "deadbeefdeadbeef" + "deadbeefdeadbeef" + "deadbeefdeadbeef" + "deadbeefdeadbeef"; + // Dictionary has 2 entries, but there is no encoded data + const COMPRESSED_BYTECODE = "0x0002" + "deadbeefdeadbeef" + "deadbeefdeadbeef"; + await expect( + compressor.connect(bootloaderAccount).publishCompressedBytecode(BYTECODE, COMPRESSED_BYTECODE) + ).to.be.revertedWith("Encoded data length should be 4 times shorter than the original bytecode"); + }); + + it("should revert when the encoded data length is invalid", async () => { + // Bytecode length is 32 bytes (4 chunks) + const BYTECODE = "0x" + "deadbeefdeadbeef" + "deadbeefdeadbeef" + "deadbeefdeadbeef" + "deadbeefdeadbeef"; + // Compressed bytecode is 14 bytes + // Dictionary length is 2 bytes + // Dictionary is 8 bytes (1 entry) + // Encoded data is 4 bytes + const COMPRESSED_BYTECODE = "0x0001" + "deadbeefdeadbeef" + "00000000"; + // The length of the encodedData should be 32 / 4 = 8 bytes + await expect( + compressor.connect(bootloaderAccount).publishCompressedBytecode(BYTECODE, COMPRESSED_BYTECODE) + ).to.be.revertedWith("Encoded data length should be 4 times shorter than the original bytecode"); + }); + + it("should revert when the dictionary has too many entries", async () => { + const BYTECODE = "0x" + "deadbeefdeadbeef" + "deadbeefdeadbeef" + "deadbeefdeadbeef" + "deadbeefdeadbeef"; + // Dictionary has 5 entries + // Encoded data has 4 entries + const COMPRESSED_BYTECODE = + "0x0005" + + "deadbeefdeadbeef" + + "deadbeefdeadbeef" + + "deadbeefdeadbeef" + + "deadbeefdeadbeef" + + "deadbeefdeadbeef" + + "0000" + + "0000" + + "0000" + + "0000"; + // The dictionary should have at most encode data length entries + await expect( + compressor.connect(bootloaderAccount).publishCompressedBytecode(BYTECODE, COMPRESSED_BYTECODE) + ).to.be.revertedWith("Dictionary should have at most the same number of entries as the encoded data"); + }); + + it("should revert when the encoded data has chunks where index is out of bounds", async () => { + const BYTECODE = "0x" + "deadbeefdeadbeef" + "deadbeefdeadbeef" + "deadbeefdeadbeef" + "deadbeefdeadbeef"; + // Dictionary has 1 entry + // Encoded data has 4 entries, three 0000 and one 0001 + const COMPRESSED_BYTECODE = "0x0001" + "deadbeefdeadbeef" + "0000" + "0000" + "0000" + "0001"; + // The dictionary has only 1 entry, so at the last entry of the encoded data the chunk index is out of bounds await expect( compressor.connect(bootloaderAccount).publishCompressedBytecode(BYTECODE, COMPRESSED_BYTECODE) ).to.be.revertedWith("Encoded chunk index is out of bounds"); }); - it("chunk does not match the original bytecode", async () => { - const BYTECODE = "0xdeadbeefdeadbeef1111111111111111"; - const COMPRESSED_BYTECODE = "0x0002deadbeefdeadbeef111111111111111100000000"; + it("should revert when the encoded data has chunks that does not match the original bytecode", async () => { + const BYTECODE = "0x" + "deadbeefdeadbeef" + "deadbeefdeadbeef" + "deadbeefdeadbeef" + "1111111111111111"; + // Encoded data has 4 entries, but the first one points to the wrong chunk of the dictionary + const COMPRESSED_BYTECODE = + "0x0002" + "deadbeefdeadbeef" + "1111111111111111" + "0001" + "0000" + "0000" + "0001"; await expect( compressor.connect(bootloaderAccount).publishCompressedBytecode(BYTECODE, COMPRESSED_BYTECODE) ).to.be.revertedWith("Encoded chunk does not match the original bytecode"); }); - it("invalid bytecode length in bytes", async () => { - const BYTECODE = "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; - const COMPRESSED_BYTECODE = "0x0001deadbeefdeadbeef000000000000"; + it("should revert when the bytecode length in bytes is invalid", async () => { + // Bytecode length is 24 bytes (3 chunks), which is invalid because it's not a multiple of 32 + const BYTECODE = "0x" + "deadbeefdeadbeef" + "deadbeefdeadbeef" + "deadbeefdeadbeef"; + const COMPRESSED_BYTECODE = "0x0001" + "deadbeefdeadbeef" + "0000" + "0000" + "0000"; await expect( compressor.connect(bootloaderAccount).publishCompressedBytecode(BYTECODE, COMPRESSED_BYTECODE) ).to.be.revertedWith("po"); }); - // Test case with too big bytecode is unrealistic because API cannot accept so much data. - it("invalid bytecode length in words", async () => { + it("should revert when the bytecode length in words is odd", async () => { + // Bytecode length is 2 words (64 bytes), which is invalid because it's odd const BYTECODE = "0x" + "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef".repeat(2); - const COMPRESSED_BYTECODE = "0x0001deadbeefdeadbeef" + "0000".repeat(4 * 2); + const COMPRESSED_BYTECODE = "0x0001" + "deadbeefdeadbeef" + "0000".repeat(4 * 2); await expect( compressor.connect(bootloaderAccount).publishCompressedBytecode(BYTECODE, COMPRESSED_BYTECODE) ).to.be.revertedWith("pr"); }); - it("successfully published", async () => { + // Test case with too big bytecode is unrealistic because API cannot accept so much data. + + it("should successfully publish the bytecode", async () => { const BYTECODE = "0x000200000000000200010000000103550000006001100270000000150010019d0000000101200190000000080000c13d0000000001000019004e00160000040f0000000101000039004e00160000040f0000001504000041000000150510009c000000000104801900000040011002100000000001310019000000150320009c0000000002048019000000600220021000000000012100190000004f0001042e000000000100001900000050000104300000008002000039000000400020043f0000000002000416000000000110004c000000240000613d000000000120004c0000004d0000c13d000000200100003900000100001004430000012000000443000001000100003900000040020000390000001d03000041004e000a0000040f000000000120004c0000004d0000c13d0000000001000031000000030110008c0000004d0000a13d0000000101000367000000000101043b0000001601100197000000170110009c0000004d0000c13d0000000101000039000000000101041a0000000202000039000000000202041a000000400300043d00000040043000390000001805200197000000000600041a0000000000540435000000180110019700000020043000390000000000140435000000a0012002700000001901100197000000600430003900000000001404350000001a012001980000001b010000410000000001006019000000b8022002700000001c02200197000000000121019f0000008002300039000000000012043500000018016001970000000000130435000000400100043d0000000002130049000000a0022000390000000003000019004e000a0000040f004e00140000040f0000004e000004320000004f0001042e000000500001043000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff000000000000000000000000000000000000000000000000000000008903573000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000ffffff0000000000008000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80000000000000000000000000000000000000000000000000000000000000007fffff00000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; const COMPRESSED_BYTECODE = @@ -108,6 +161,24 @@ describe("Compressor tests", function () { await encodeCalldata("KnownCodesStorage", "markBytecodeAsPublished", [zksync.utils.hashBytecode(BYTECODE)]) ); }); + + // documentation example from https://github.com/matter-labs/zksync-era/blob/main/docs/guides/advanced/compression.md + it("documentation example", async () => { + const BYTECODE = + "0x000000000000000A000000000000000D000000000000000A000000000000000C000000000000000B000000000000000A000000000000000D000000000000000A000000000000000D000000000000000A000000000000000B000000000000000B"; + const COMPRESSED_BYTECODE = + "0x0004000000000000000A000000000000000D000000000000000B000000000000000C000000010000000300020000000100000001000000020002"; + await setResult("L1Messenger", "sendToL1", [COMPRESSED_BYTECODE], { + failure: false, + returnData: ethers.constants.HashZero, + }); + await expect(compressor.connect(bootloaderAccount).publishCompressedBytecode(BYTECODE, COMPRESSED_BYTECODE)) + .to.emit(getMock("KnownCodesStorage"), "Called") + .withArgs( + 0, + await encodeCalldata("KnownCodesStorage", "markBytecodeAsPublished", [zksync.utils.hashBytecode(BYTECODE)]) + ); + }); }); describe("verifyCompressedStateDiffs", function () { diff --git a/system-contracts/test/ContractDeployer.spec.ts b/system-contracts/test/ContractDeployer.spec.ts index b509b7d3b3f4..6f8984eaed4c 100644 --- a/system-contracts/test/ContractDeployer.spec.ts +++ b/system-contracts/test/ContractDeployer.spec.ts @@ -1,8 +1,8 @@ import type { ZkSyncArtifact } from "@matterlabs/hardhat-zksync-deploy/dist/types"; import { expect } from "chai"; import { ethers, network } from "hardhat"; -import type { Wallet } from "zksync-web3"; -import { utils } from "zksync-web3"; +import type { Wallet } from "zksync-ethers"; +import { utils } from "zksync-ethers"; import type { ContractDeployer } from "../typechain"; import { ContractDeployerFactory, DeployableFactory } from "../typechain"; import { diff --git a/system-contracts/test/DefaultAccount.spec.ts b/system-contracts/test/DefaultAccount.spec.ts index 4aa8cd6a2d41..1d26edef75bc 100644 --- a/system-contracts/test/DefaultAccount.spec.ts +++ b/system-contracts/test/DefaultAccount.spec.ts @@ -1,7 +1,7 @@ import { expect } from "chai"; import { ethers, network } from "hardhat"; -import type { Wallet } from "zksync-web3"; -import * as zksync from "zksync-web3"; +import type { Wallet } from "zksync-ethers"; +import * as zksync from "zksync-ethers"; import { serialize } from "zksync-web3/build/src/utils"; import type { DefaultAccount, DelegateCaller, MockContract } from "../typechain"; import { DefaultAccountFactory } from "../typechain"; diff --git a/system-contracts/test/EmptyContract.spec.ts b/system-contracts/test/EmptyContract.spec.ts index 73966fe7e979..c383ed66242e 100644 --- a/system-contracts/test/EmptyContract.spec.ts +++ b/system-contracts/test/EmptyContract.spec.ts @@ -1,6 +1,6 @@ import { expect } from "chai"; import { ethers } from "hardhat"; -import type { Wallet } from "zksync-web3"; +import type { Wallet } from "zksync-ethers"; import type { EmptyContract } from "../typechain"; import { deployContract, getWallets, provider } from "./shared/utils"; diff --git a/system-contracts/test/EventWriter.spec.ts b/system-contracts/test/EventWriter.spec.ts index 908036963f06..072f8e35bcce 100644 --- a/system-contracts/test/EventWriter.spec.ts +++ b/system-contracts/test/EventWriter.spec.ts @@ -1,7 +1,7 @@ import { expect } from "chai"; import { ethers } from "hardhat"; -import type { Wallet } from "zksync-web3"; -import { Contract } from "zksync-web3"; +import type { Wallet } from "zksync-ethers"; +import { Contract } from "zksync-ethers"; import type { TransactionResponse } from "zksync-web3/build/src/types"; import { ONE_BYTES32_HEX, REAL_EVENT_WRITER_CONTRACT_ADDRESS } from "./shared/constants"; import { EXTRA_ABI_CALLER_ADDRESS, encodeExtraAbiCallerCalldata } from "./shared/extraAbiCaller"; diff --git a/system-contracts/test/KnownCodesStorage.spec.ts b/system-contracts/test/KnownCodesStorage.spec.ts index df1cb46beb6d..7b8af139170f 100644 --- a/system-contracts/test/KnownCodesStorage.spec.ts +++ b/system-contracts/test/KnownCodesStorage.spec.ts @@ -1,6 +1,6 @@ import { expect } from "chai"; import { ethers, network } from "hardhat"; -import type { Wallet } from "zksync-web3"; +import type { Wallet } from "zksync-ethers"; import type { KnownCodesStorage } from "../typechain"; import { KnownCodesStorageFactory } from "../typechain"; import { diff --git a/system-contracts/test/L2EthToken.spec.ts b/system-contracts/test/L2BaseToken.spec.ts similarity index 60% rename from system-contracts/test/L2EthToken.spec.ts rename to system-contracts/test/L2BaseToken.spec.ts index d7d8923f9a4f..87005b5f0791 100644 --- a/system-contracts/test/L2EthToken.spec.ts +++ b/system-contracts/test/L2BaseToken.spec.ts @@ -1,25 +1,25 @@ import { expect } from "chai"; import { ethers, network } from "hardhat"; import type { Wallet } from "zksync-web3"; -import type { L2EthToken } from "../typechain"; -import { L2EthTokenFactory } from "../typechain"; +import type { L2BaseToken } from "../typechain"; +import { L2BaseTokenFactory } from "../typechain"; import { deployContractOnAddress, getWallets, loadArtifact, provider } from "./shared/utils"; import type { BigNumber } from "ethers"; -import { TEST_BOOTLOADER_FORMAL_ADDRESS, TEST_ETH_TOKEN_SYSTEM_CONTRACT_ADDRESS } from "./shared/constants"; +import { TEST_BOOTLOADER_FORMAL_ADDRESS, TEST_BASE_TOKEN_SYSTEM_CONTRACT_ADDRESS } from "./shared/constants"; import { prepareEnvironment, setResult } from "./shared/mocks"; import { randomBytes } from "crypto"; -describe("L2EthToken tests", () => { +describe("L2BaseToken tests", () => { const richWallet = getWallets()[0]; let wallets: Array; - let l2EthToken: L2EthToken; + let L2BaseToken: L2BaseToken; let bootloaderAccount: ethers.Signer; let mailboxIface: ethers.utils.Interface; before(async () => { await prepareEnvironment(); - await deployContractOnAddress(TEST_ETH_TOKEN_SYSTEM_CONTRACT_ADDRESS, "L2EthToken"); - l2EthToken = L2EthTokenFactory.connect(TEST_ETH_TOKEN_SYSTEM_CONTRACT_ADDRESS, richWallet); + await deployContractOnAddress(TEST_BASE_TOKEN_SYSTEM_CONTRACT_ADDRESS, "L2BaseToken"); + L2BaseToken = L2BaseTokenFactory.connect(TEST_BASE_TOKEN_SYSTEM_CONTRACT_ADDRESS, richWallet); bootloaderAccount = await ethers.getImpersonatedSigner(TEST_BOOTLOADER_FORMAL_ADDRESS); mailboxIface = new ethers.utils.Interface((await loadArtifact("IMailbox")).abi); }); @@ -37,23 +37,23 @@ describe("L2EthToken tests", () => { describe("mint", () => { it("called by bootlader", async () => { - const initialSupply: BigNumber = await l2EthToken.totalSupply(); - const initialBalanceOfWallet: BigNumber = await l2EthToken.balanceOf(wallets[0].address); + const initialSupply: BigNumber = await L2BaseToken.totalSupply(); + const initialBalanceOfWallet: BigNumber = await L2BaseToken.balanceOf(wallets[0].address); const amountToMint: BigNumber = ethers.utils.parseEther("10.0"); - await expect(l2EthToken.connect(bootloaderAccount).mint(wallets[0].address, amountToMint)) - .to.emit(l2EthToken, "Mint") + await expect(L2BaseToken.connect(bootloaderAccount).mint(wallets[0].address, amountToMint)) + .to.emit(L2BaseToken, "Mint") .withArgs(wallets[0].address, amountToMint); - const finalSupply: BigNumber = await l2EthToken.totalSupply(); - const balanceOfWallet: BigNumber = await l2EthToken.balanceOf(wallets[0].address); + const finalSupply: BigNumber = await L2BaseToken.totalSupply(); + const balanceOfWallet: BigNumber = await L2BaseToken.balanceOf(wallets[0].address); expect(finalSupply).to.equal(initialSupply.add(amountToMint)); expect(balanceOfWallet).to.equal(initialBalanceOfWallet.add(amountToMint)); }); it("not called by bootloader", async () => { const amountToMint: BigNumber = ethers.utils.parseEther("10.0"); - await expect(l2EthToken.connect(wallets[0]).mint(wallets[0].address, amountToMint)).to.be.rejectedWith( + await expect(L2BaseToken.connect(wallets[0]).mint(wallets[0].address, amountToMint)).to.be.rejectedWith( "Callable only by the bootloader" ); }); @@ -62,49 +62,51 @@ describe("L2EthToken tests", () => { describe("transfer", () => { it("transfer successfully", async () => { await ( - await l2EthToken.connect(bootloaderAccount).mint(wallets[0].address, ethers.utils.parseEther("100.0")) + await L2BaseToken.connect(bootloaderAccount).mint(wallets[0].address, ethers.utils.parseEther("100.0")) ).wait(); - const senderBalanceBeforeTransfer: BigNumber = await l2EthToken.balanceOf(wallets[0].address); - const recipientBalanceBeforeTransfer: BigNumber = await l2EthToken.balanceOf(wallets[1].address); + const senderBalanceBeforeTransfer: BigNumber = await L2BaseToken.balanceOf(wallets[0].address); + const recipientBalanceBeforeTransfer: BigNumber = await L2BaseToken.balanceOf(wallets[1].address); const amountToTransfer = ethers.utils.parseEther("10.0"); await expect( - l2EthToken.connect(bootloaderAccount).transferFromTo(wallets[0].address, wallets[1].address, amountToTransfer) + L2BaseToken.connect(bootloaderAccount).transferFromTo(wallets[0].address, wallets[1].address, amountToTransfer) ) - .to.emit(l2EthToken, "Transfer") + .to.emit(L2BaseToken, "Transfer") .withArgs(wallets[0].address, wallets[1].address, amountToTransfer); - const senderBalanceAfterTransfer: BigNumber = await l2EthToken.balanceOf(wallets[0].address); - const recipientBalanceAfterTransfer: BigNumber = await l2EthToken.balanceOf(wallets[1].address); + const senderBalanceAfterTransfer: BigNumber = await L2BaseToken.balanceOf(wallets[0].address); + const recipientBalanceAfterTransfer: BigNumber = await L2BaseToken.balanceOf(wallets[1].address); expect(senderBalanceAfterTransfer).to.be.eq(senderBalanceBeforeTransfer.sub(amountToTransfer)); expect(recipientBalanceAfterTransfer).to.be.eq(recipientBalanceBeforeTransfer.add(amountToTransfer)); }); it("no tranfser due to insufficient balance", async () => { await ( - await l2EthToken.connect(bootloaderAccount).mint(wallets[0].address, ethers.utils.parseEther("5.0")) + await L2BaseToken.connect(bootloaderAccount).mint(wallets[0].address, ethers.utils.parseEther("5.0")) ).wait(); const amountToTransfer: BigNumber = ethers.utils.parseEther("6.0"); await expect( - l2EthToken.connect(bootloaderAccount).transferFromTo(wallets[0].address, wallets[1].address, amountToTransfer) + L2BaseToken.connect(bootloaderAccount).transferFromTo(wallets[0].address, wallets[1].address, amountToTransfer) ).to.be.rejectedWith("Transfer amount exceeds balance"); }); it("no transfer - require special access", async () => { const maliciousWallet: Wallet = ethers.Wallet.createRandom().connect(provider); await ( - await l2EthToken.connect(bootloaderAccount).mint(maliciousWallet.address, ethers.utils.parseEther("20.0")) + await L2BaseToken.connect(bootloaderAccount).mint(maliciousWallet.address, ethers.utils.parseEther("20.0")) ).wait(); const amountToTransfer: BigNumber = ethers.utils.parseEther("20.0"); await expect( - l2EthToken - .connect(maliciousWallet) - .transferFromTo(maliciousWallet.address, wallets[1].address, amountToTransfer) + L2BaseToken.connect(maliciousWallet).transferFromTo( + maliciousWallet.address, + wallets[1].address, + amountToTransfer + ) ).to.be.rejectedWith("Only system contracts with special access can call this method"); }); }); @@ -113,20 +115,20 @@ describe("L2EthToken tests", () => { it("walletFrom address", async () => { const amountToMint: BigNumber = ethers.utils.parseEther("10.0"); - await l2EthToken.connect(bootloaderAccount).mint(wallets[0].address, amountToMint); - const balance = await l2EthToken.balanceOf(wallets[0].address); + await L2BaseToken.connect(bootloaderAccount).mint(wallets[0].address, amountToMint); + const balance = await L2BaseToken.balanceOf(wallets[0].address); expect(balance).to.equal(amountToMint); }); it("address larger than 20 bytes", async () => { const amountToMint: BigNumber = ethers.utils.parseEther("123.0"); - const res = await l2EthToken.connect(bootloaderAccount).mint(wallets[0].address, amountToMint); + const res = await L2BaseToken.connect(bootloaderAccount).mint(wallets[0].address, amountToMint); await res.wait(); const largerAddress = ethers.BigNumber.from( "0x" + randomBytes(12).toString("hex") + wallets[0].address.slice(2) ).toHexString(); - const balance = await l2EthToken.balanceOf(largerAddress); + const balance = await L2BaseToken.balanceOf(largerAddress); expect(balance).to.equal(amountToMint); }); @@ -134,11 +136,11 @@ describe("L2EthToken tests", () => { describe("totalSupply", () => { it("correct total supply", async () => { - const totalSupplyBefore = await l2EthToken.totalSupply(); + const totalSupplyBefore = await L2BaseToken.totalSupply(); const amountToMint: BigNumber = ethers.utils.parseEther("10.0"); - await l2EthToken.connect(bootloaderAccount).mint(wallets[0].address, amountToMint); - const totalSupply = await l2EthToken.totalSupply(); + await L2BaseToken.connect(bootloaderAccount).mint(wallets[0].address, amountToMint); + const totalSupply = await L2BaseToken.totalSupply(); expect(totalSupply).to.equal(totalSupplyBefore.add(amountToMint)); }); @@ -146,21 +148,21 @@ describe("L2EthToken tests", () => { describe("name", () => { it("correct name", async () => { - const name = await l2EthToken.name(); + const name = await L2BaseToken.name(); expect(name).to.equal("Ether"); }); }); describe("symbol", () => { it("correct symbol", async () => { - const symbol = await l2EthToken.symbol(); + const symbol = await L2BaseToken.symbol(); expect(symbol).to.equal("ETH"); }); }); describe("decimals", () => { it("correct decimals", async () => { - const decimals = await l2EthToken.decimals(); + const decimals = await L2BaseToken.decimals(); expect(decimals).to.equal(18); }); }); @@ -180,17 +182,17 @@ describe("L2EthToken tests", () => { // To prevent underflow since initial values are 0's and we are substracting from them const amountToMint: BigNumber = ethers.utils.parseEther("100.0"); - await (await l2EthToken.connect(bootloaderAccount).mint(l2EthToken.address, amountToMint)).wait(); + await (await L2BaseToken.connect(bootloaderAccount).mint(L2BaseToken.address, amountToMint)).wait(); - const balanceBeforeWithdrawal: BigNumber = await l2EthToken.balanceOf(l2EthToken.address); - const totalSupplyBefore = await l2EthToken.totalSupply(); + const balanceBeforeWithdrawal: BigNumber = await L2BaseToken.balanceOf(L2BaseToken.address); + const totalSupplyBefore = await L2BaseToken.totalSupply(); - await expect(l2EthToken.connect(richWallet).withdraw(wallets[1].address, { value: amountToWithdraw })) - .to.emit(l2EthToken, "Withdrawal") + await expect(L2BaseToken.connect(richWallet).withdraw(wallets[1].address, { value: amountToWithdraw })) + .to.emit(L2BaseToken, "Withdrawal") .withArgs(richWallet.address, wallets[1].address, amountToWithdraw); - const balanceAfterWithdrawal: BigNumber = await l2EthToken.balanceOf(l2EthToken.address); - const totalSupplyAfter = await l2EthToken.totalSupply(); + const balanceAfterWithdrawal: BigNumber = await L2BaseToken.balanceOf(L2BaseToken.address); + const totalSupplyAfter = await L2BaseToken.totalSupply(); expect(balanceAfterWithdrawal).to.equal(balanceBeforeWithdrawal.sub(amountToWithdraw)); expect(totalSupplyAfter).to.equal(totalSupplyBefore.sub(amountToWithdraw)); @@ -217,19 +219,19 @@ describe("L2EthToken tests", () => { // Consitency reasons - won't crash if test order reverse const amountToMint: BigNumber = ethers.utils.parseEther("100.0"); - await (await l2EthToken.connect(bootloaderAccount).mint(l2EthToken.address, amountToMint)).wait(); + await (await L2BaseToken.connect(bootloaderAccount).mint(L2BaseToken.address, amountToMint)).wait(); - const totalSupplyBefore = await l2EthToken.totalSupply(); - const balanceBeforeWithdrawal: BigNumber = await l2EthToken.balanceOf(l2EthToken.address); + const totalSupplyBefore = await L2BaseToken.totalSupply(); + const balanceBeforeWithdrawal: BigNumber = await L2BaseToken.balanceOf(L2BaseToken.address); await expect( - l2EthToken.connect(richWallet).withdrawWithMessage(wallets[1].address, additionalData, { + L2BaseToken.connect(richWallet).withdrawWithMessage(wallets[1].address, additionalData, { value: amountToWithdraw, }) ) - .to.emit(l2EthToken, "WithdrawalWithMessage") + .to.emit(L2BaseToken, "WithdrawalWithMessage") .withArgs(richWallet.address, wallets[1].address, amountToWithdraw, additionalData); - const totalSupplyAfter = await l2EthToken.totalSupply(); - const balanceAfterWithdrawal: BigNumber = await l2EthToken.balanceOf(l2EthToken.address); + const totalSupplyAfter = await L2BaseToken.totalSupply(); + const balanceAfterWithdrawal: BigNumber = await L2BaseToken.balanceOf(L2BaseToken.address); expect(balanceAfterWithdrawal).to.equal(balanceBeforeWithdrawal.sub(amountToWithdraw)); expect(totalSupplyAfter).to.equal(totalSupplyBefore.sub(amountToWithdraw)); }); diff --git a/system-contracts/test/precompiles/EcAdd.spec.ts b/system-contracts/test/precompiles/EcAdd.spec.ts index 499ad6843f5e..9f00d8a0bd09 100644 --- a/system-contracts/test/precompiles/EcAdd.spec.ts +++ b/system-contracts/test/precompiles/EcAdd.spec.ts @@ -1,5 +1,5 @@ import { expect } from "chai"; -import type { Contract } from "zksync-web3"; +import type { Contract } from "zksync-ethers"; import { callFallback, deployContractYul } from "../shared/utils"; describe("EcAdd tests", function () { diff --git a/system-contracts/test/precompiles/EcMul.spec.ts b/system-contracts/test/precompiles/EcMul.spec.ts index bf83518ed7f1..28c2473da978 100644 --- a/system-contracts/test/precompiles/EcMul.spec.ts +++ b/system-contracts/test/precompiles/EcMul.spec.ts @@ -1,5 +1,5 @@ import { expect } from "chai"; -import type { Contract } from "zksync-web3"; +import type { Contract } from "zksync-ethers"; import { callFallback, deployContractYul } from "../shared/utils"; describe("EcMul tests", function () { diff --git a/system-contracts/test/shared/constants.ts b/system-contracts/test/shared/constants.ts index c2ac9f6a57a7..996c8b5f782c 100644 --- a/system-contracts/test/shared/constants.ts +++ b/system-contracts/test/shared/constants.ts @@ -9,7 +9,7 @@ export const TEST_DEPLOYER_SYSTEM_CONTRACT_ADDRESS = "0x000000000000000000000000 export const TEST_FORCE_DEPLOYER_ADDRESS = "0x0000000000000000000000000000000000009007"; export const TEST_L1_MESSENGER_SYSTEM_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000009008"; export const TEST_MSG_VALUE_SYSTEM_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000009009"; -export const TEST_ETH_TOKEN_SYSTEM_CONTRACT_ADDRESS = "0x000000000000000000000000000000000000900a"; +export const TEST_BASE_TOKEN_SYSTEM_CONTRACT_ADDRESS = "0x000000000000000000000000000000000000900a"; export const TEST_SYSTEM_CONTEXT_CONTRACT_ADDRESS = "0x000000000000000000000000000000000000900b"; export const TEST_BOOTLOADER_UTILITIES_ADDRESS = "0x000000000000000000000000000000000000900c"; export const TEST_COMPRESSOR_CONTRACT_ADDRESS = "0x000000000000000000000000000000000000900e"; diff --git a/system-contracts/test/shared/mocks.ts b/system-contracts/test/shared/mocks.ts index e950b13b861a..2b8c356540ee 100644 --- a/system-contracts/test/shared/mocks.ts +++ b/system-contracts/test/shared/mocks.ts @@ -4,7 +4,7 @@ import { MockContractFactory } from "../../typechain"; import { TEST_ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT_ADDRESS, TEST_BOOTLOADER_FORMAL_ADDRESS, - TEST_ETH_TOKEN_SYSTEM_CONTRACT_ADDRESS, + TEST_BASE_TOKEN_SYSTEM_CONTRACT_ADDRESS, TEST_IMMUTABLE_SIMULATOR_SYSTEM_CONTRACT_ADDRESS, TEST_KNOWN_CODE_STORAGE_CONTRACT_ADDRESS, TEST_L1_MESSENGER_SYSTEM_CONTRACT_ADDRESS, @@ -30,7 +30,7 @@ const TEST_SYSTEM_CONTRACTS_MOCKS = { L1Messenger: TEST_L1_MESSENGER_SYSTEM_CONTRACT_ADDRESS, KnownCodesStorage: TEST_KNOWN_CODE_STORAGE_CONTRACT_ADDRESS, AccountCodeStorage: TEST_ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT_ADDRESS, - L2EthToken: TEST_ETH_TOKEN_SYSTEM_CONTRACT_ADDRESS, + L2BaseToken: TEST_BASE_TOKEN_SYSTEM_CONTRACT_ADDRESS, ImmutableSimulator: TEST_IMMUTABLE_SIMULATOR_SYSTEM_CONTRACT_ADDRESS, MsgValueSimulator: TEST_MSG_VALUE_SYSTEM_CONTRACT_ADDRESS, Bootloader: TEST_BOOTLOADER_FORMAL_ADDRESS, diff --git a/system-contracts/test/shared/transactions.ts b/system-contracts/test/shared/transactions.ts index 73c4fcc216d5..b588c643ff6e 100644 --- a/system-contracts/test/shared/transactions.ts +++ b/system-contracts/test/shared/transactions.ts @@ -1,5 +1,5 @@ import type { BigNumberish, BytesLike, Transaction } from "ethers"; -import * as zksync from "zksync-web3"; +import * as zksync from "zksync-ethers"; // Interface encoding the transaction struct used for AA protocol export interface TransactionData { diff --git a/system-contracts/test/shared/utils.ts b/system-contracts/test/shared/utils.ts index 9f6d96ee08b0..6db4105b7c87 100644 --- a/system-contracts/test/shared/utils.ts +++ b/system-contracts/test/shared/utils.ts @@ -4,9 +4,9 @@ import { BigNumber } from "ethers"; import type { BytesLike } from "ethers"; import * as hre from "hardhat"; import { ethers } from "hardhat"; -import type { Contract } from "zksync-web3"; -import * as zksync from "zksync-web3"; -import { Provider, utils, Wallet } from "zksync-web3"; +import type { Contract } from "zksync-ethers"; +import * as zksync from "zksync-ethers"; +import { Provider, utils, Wallet } from "zksync-ethers"; import { Language } from "../../scripts/constants"; import { readYulBytecode, readZasmBytecode } from "../../scripts/utils"; import { AccountCodeStorageFactory, ContractDeployerFactory } from "../../typechain"; diff --git a/tools/README.md b/tools/README.md index 51f16b00a1bb..081ab8d70f89 100644 --- a/tools/README.md +++ b/tools/README.md @@ -5,5 +5,5 @@ To generate the verifier from the scheduler key in 'data' directory, just run: ```shell -cargo run --bin zksync_verifier_contract_generator --release -- --input_path data/scheduler_key.json --output_path ../l1-contracts/contracts/zksync/Verifier.sol +cargo run --bin zksync_verifier_contract_generator --release -- --input_path data/scheduler_key.json --output_path ../l1-contracts/contracts/state-transition/Verifier.sol ``` diff --git a/tools/data/verifier_contract_template.txt b/tools/data/verifier_contract_template.txt index 64236b1b183a..e05f8ffd3bb0 100644 --- a/tools/data/verifier_contract_template.txt +++ b/tools/data/verifier_contract_template.txt @@ -2,13 +2,13 @@ pragma solidity 0.8.20; -import "./interfaces/IVerifier.sol"; +import "./chain-interfaces/IVerifier.sol"; /* solhint-disable max-line-length */ /// @author Matter Labs /// @notice Modified version of the Permutations over Lagrange-bases for Oecumenical Noninteractive arguments of /// Knowledge (PLONK) verifier. -/// Modifications have been made to optimize the proof system for zkSync Era circuits. +/// Modifications have been made to optimize the proof system for zkSync hyperchain circuits. /// @dev It uses a custom memory layout inside the inline assembly block. Each reserved memory cell is declared in the /// constants below. /// @dev For a better understanding of the verifier algorithm please refer to the following papers: diff --git a/yarn.lock b/yarn.lock index 4d3b4650242a..1974c19dbdbb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -754,7 +754,7 @@ fs-extra "^11.1.1" semver "^7.5.1" -"@matterlabs/hardhat-zksync-solc@0.4.2", "@matterlabs/hardhat-zksync-solc@^0.4.2": +"@matterlabs/hardhat-zksync-solc@0.4.2": version "0.4.2" resolved "https://registry.yarnpkg.com/@matterlabs/hardhat-zksync-solc/-/hardhat-zksync-solc-0.4.2.tgz#64121082e88c5ab22eb4e9594d120e504f6af499" integrity sha512-6NFWPSZiOAoo7wNuhMg4ztj7mMEH+tLrx09WuCbcURrHPijj/KxYNsJD6Uw5lapKr7G8H7SQISGid1/MTXVmXQ== @@ -787,6 +787,23 @@ proper-lockfile "^4.1.2" semver "^7.5.1" +"@matterlabs/hardhat-zksync-solc@^1.0.6": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@matterlabs/hardhat-zksync-solc/-/hardhat-zksync-solc-1.1.0.tgz#14e05b4365392fe7a473a636e132883687dcf69a" + integrity sha512-DbknnOFsRNVqYMkxIrVofUk0cXA6uRIqSqyuVvhktg53KTHWubYlj6zEqCC+Q+wtONXijXQoN1BTIQ5FqDmAIQ== + dependencies: + "@nomiclabs/hardhat-docker" "^2.0.0" + chai "^4.3.6" + chalk "4.1.2" + debug "^4.3.4" + dockerode "^4.0.0" + fs-extra "^11.1.1" + proper-lockfile "^4.1.2" + semver "^7.5.1" + sinon "^16.0.0" + sinon-chai "^3.7.0" + undici "^5.14.0" + "@matterlabs/hardhat-zksync-verify@^0.2.0": version "0.2.1" resolved "https://registry.yarnpkg.com/@matterlabs/hardhat-zksync-verify/-/hardhat-zksync-verify-0.2.1.tgz#f52cbe3670b7a6b01c8c4d0bbf3e6e13583e5d99" @@ -1305,6 +1322,48 @@ "@sentry/types" "5.30.0" tslib "^1.9.3" +"@sinonjs/commons@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3" + integrity sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg== + dependencies: + type-detect "4.0.8" + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.3.0": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@sinonjs/fake-timers@^11.2.2": + version "11.2.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz#50063cc3574f4a27bd8453180a04171c85cc9699" + integrity sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@sinonjs/samsam@^8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-8.0.0.tgz#0d488c91efb3fa1442e26abea81759dfc8b5ac60" + integrity sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew== + dependencies: + "@sinonjs/commons" "^2.0.0" + lodash.get "^4.4.2" + type-detect "^4.0.8" + +"@sinonjs/text-encoding@^0.7.2": + version "0.7.2" + resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz#5981a8db18b56ba38ef0efb7d995b12aa7b51918" + integrity sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ== + "@solidity-parser/parser@^0.14.0": version "0.14.5" resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.14.5.tgz#87bc3cc7b068e08195c219c91cd8ddff5ef1a804" @@ -2421,6 +2480,19 @@ chai@^4.3.10: pathval "^1.1.1" type-detect "^4.0.8" +chai@^4.3.6: + version "4.4.1" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.4.1.tgz#3603fa6eba35425b0f2ac91a009fe924106e50d1" + integrity sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.3" + deep-eql "^4.1.3" + get-func-name "^2.0.2" + loupe "^2.3.6" + pathval "^1.1.1" + type-detect "^4.0.8" + chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -2898,6 +2970,11 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +diff@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" + integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw== + difflib@^0.2.4: version "0.2.4" resolved "https://registry.yarnpkg.com/difflib/-/difflib-0.2.4.tgz#b5e30361a6db023176d562892db85940a718f47e" @@ -3946,7 +4023,26 @@ get-func-name@^2.0.1, get-func-name@^2.0.2: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2: +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" + integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + +get-intrinsic@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" + integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-proto "^1.0.1" + has-symbols "^1.0.3" + +get-intrinsic@^1.2.1, get-intrinsic@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b" integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA== @@ -4932,6 +5028,11 @@ jsprim@^1.2.2: json-schema "0.4.0" verror "1.10.0" +just-extend@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-6.2.0.tgz#b816abfb3d67ee860482e7401564672558163947" + integrity sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw== + jwa@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" @@ -5160,6 +5261,11 @@ lodash.clonedeep@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== + lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" @@ -5618,6 +5724,17 @@ neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +nise@^5.1.4: + version "5.1.7" + resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.7.tgz#03ca96539efb306612eb60a8c5d6beeb208e27e5" + integrity sha512-wWtNUhkT7k58uvWTB/Gy26eA/EJKtPZFVAhEilN5UYVmmGRYOURbejRUyKm0Uu9XVEW7K5nBOZfR8VMB4QR2RQ== + dependencies: + "@sinonjs/commons" "^3.0.0" + "@sinonjs/fake-timers" "^11.2.2" + "@sinonjs/text-encoding" "^0.7.2" + just-extend "^6.2.0" + path-to-regexp "^6.2.1" + node-addon-api@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" @@ -5935,6 +6052,11 @@ path-parse@^1.0.6, path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-to-regexp@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5" + integrity sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw== + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -6623,6 +6745,23 @@ signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +sinon-chai@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-3.7.0.tgz#cfb7dec1c50990ed18c153f1840721cf13139783" + integrity sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g== + +sinon@^16.0.0: + version "16.1.3" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-16.1.3.tgz#b760ddafe785356e2847502657b4a0da5501fba8" + integrity sha512-mjnWWeyxcAf9nC0bXcPmiDut+oE8HYridTNzBbF98AYVLmWwGRp2ISEpyhYflG1ifILT+eNn3BmKUJPxjXUPlA== + dependencies: + "@sinonjs/commons" "^3.0.0" + "@sinonjs/fake-timers" "^10.3.0" + "@sinonjs/samsam" "^8.0.0" + diff "^5.1.0" + nise "^5.1.4" + supports-color "^7.2.0" + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -6678,6 +6817,13 @@ solc@0.8.17: semver "^5.5.0" tmp "0.0.33" +solhint-plugin-prettier@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/solhint-plugin-prettier/-/solhint-plugin-prettier-0.0.5.tgz#e3b22800ba435cd640a9eca805a7f8bc3e3e6a6b" + integrity sha512-7jmWcnVshIrO2FFinIvDQmhQpfpS2rRRn3RejiYgnjIE68xO2bvrYvjqVNfrio4xH9ghOqn83tKuTzLjEbmGIA== + dependencies: + prettier-linter-helpers "^1.0.0" + solhint@^3.6.2: version "3.6.2" resolved "https://registry.yarnpkg.com/solhint/-/solhint-3.6.2.tgz#2b2acbec8fdc37b2c68206a71ba89c7f519943fe" @@ -6947,7 +7093,7 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^7.1.0: +supports-color@^7.1.0, supports-color@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -7287,7 +7433,7 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-detect@^4.0.0, type-detect@^4.0.8: +type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== @@ -7731,18 +7877,12 @@ yocto-queue@^1.0.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== -zksync-ethers@^5.0.0: +"zksync-ethers@https://github.com/kelemeno/zksync-ethers#ethers-v5-feat/bridgehub": version "5.0.0" - resolved "https://registry.yarnpkg.com/zksync-ethers/-/zksync-ethers-5.0.0.tgz#2d0bb9a98ad1198957038f7669a50b10ef96bc3c" - integrity sha512-zPowwK/2wG3YqIlKNtRfzpMnHMnWSa5wFsfDrxKFgLjbA3VvuHl4mFCFUxMapsroGyG619+zt71HKM1jyNpSmQ== + resolved "https://github.com/kelemeno/zksync-ethers#1c0767b5b16f3800fdd037de9e5151dd72735eb0" dependencies: ethers "~5.7.0" -zksync-web3@^0.14.3: - version "0.14.4" - resolved "https://registry.yarnpkg.com/zksync-web3/-/zksync-web3-0.14.4.tgz#0b70a7e1a9d45cc57c0971736079185746d46b1f" - integrity sha512-kYehMD/S6Uhe1g434UnaMN+sBr9nQm23Ywn0EUP5BfQCsbjcr3ORuS68PosZw8xUTu3pac7G6YMSnNHk+fwzvg== - zksync-web3@^0.15.4: version "0.15.5" resolved "https://registry.yarnpkg.com/zksync-web3/-/zksync-web3-0.15.5.tgz#aabe379464963ab573e15948660a709f409b5316"