diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 2f2871cea..9b136eac8 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -5,7 +5,7 @@ We truly appreciate efforts to discover and disclose security issues responsibly ## Vulnerabilities If you'd like to report a security issue in the repositories of matter-labs organization, please proceed to our -[Bug Bounty Program on Immunefi](https://era.zksync.io/docs/reference/troubleshooting/audit-bug-bounty.html#bug-bounty-program). +[Bug Bounty Program on Immunefi](https://immunefi.com/bug-bounty/zksyncera/information/). ## Other Security Issues diff --git a/.github/workflows/build-release.yaml b/.github/workflows/build-release.yaml index b4292e5e3..6877ac60a 100644 --- a/.github/workflows/build-release.yaml +++ b/.github/workflows/build-release.yaml @@ -27,7 +27,7 @@ jobs: - name: Install foundry-zksync run: | mkdir ./foundry-zksync - curl -LO https://github.com/matter-labs/foundry-zksync/releases/download/nightly/foundry_nightly_linux_amd64.tar.gz + curl -LO https://github.com/matter-labs/foundry-zksync/releases/download/nightly-27360d4c8d12beddbb730dae07ad33a206b38f4b/foundry_nightly_linux_amd64.tar.gz tar zxf foundry_nightly_linux_amd64.tar.gz -C ./foundry-zksync chmod +x ./foundry-zksync/forge ./foundry-zksync/cast echo "$PWD/foundry-zksync" >> $GITHUB_PATH @@ -47,21 +47,18 @@ jobs: - name: Build l1 contracts working-directory: l1-contracts run: | - forge build + yarn build:foundry - name: Build l2 contracts working-directory: l2-contracts run: | - forge build --zksync --zk-enable-eravm-extensions + yarn build:foundry - name: Build system-contracts working-directory: system-contracts run: | yarn install - yarn preprocess:system-contracts - forge build --zksync --zk-enable-eravm-extensions - yarn preprocess:bootloader - forge build --zksync --zk-enable-eravm-extensions + yarn build:foundry - name: Prepare artifacts run: | diff --git a/.github/workflows/codespell.yaml b/.github/workflows/codespell.yaml index 39c2a69d5..8a4b4cbf8 100644 --- a/.github/workflows/codespell.yaml +++ b/.github/workflows/codespell.yaml @@ -12,25 +12,26 @@ name: Codespell on: pull_request jobs: - codespell: - runs-on: ubuntu-latest - - steps: - - name: Checkout the repository - uses: actions/checkout@v4 - - - name: pip cache - uses: actions/cache@v4 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: ${{ runner.os }}-pip- - - - name: Install prerequisites - run: sudo pip install -r ./.codespell/requirements.txt - - - name: Spell check - run: codespell --config=./.codespell/.codespellrc + # TODO: fix codespell CI + # codespell: + # runs-on: ubuntu-latest + + # steps: + # - name: Checkout the repository + # uses: actions/checkout@v4 + + # - name: pip cache + # uses: actions/cache@v4 + # with: + # path: ~/.cache/pip + # key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + # restore-keys: ${{ runner.os }}-pip- + + # - name: Install prerequisites + # run: sudo pip install -r ./.codespell/requirements.txt + + # - name: Spell check + # run: codespell --config=./.codespell/.codespellrc typos: runs-on: ubuntu-latest diff --git a/.github/workflows/dead-links.yaml b/.github/workflows/dead-links.yaml new file mode 100644 index 000000000..0bf2e6f88 --- /dev/null +++ b/.github/workflows/dead-links.yaml @@ -0,0 +1,22 @@ +name: Check Dead Links in Markdown Files + +on: pull_request + +jobs: + check-dead-links: + name: Check Dead Links in Markdown Files + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Rust and Lychee + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + ~/.cargo/bin/cargo install lychee + + - name: Find and check markdown files + run: | + GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} + find . -type f -name "*.md" ! -path "*/node_modules/*" ! -path "*/openzeppelin*" ! -path "*/murky/*" -exec lychee --github-token $GITHUB_TOKEN {} + diff --git a/.github/workflows/l1-contracts-ci.yaml b/.github/workflows/l1-contracts-ci.yaml index 5a27c65f8..9b8b10369 100644 --- a/.github/workflows/l1-contracts-ci.yaml +++ b/.github/workflows/l1-contracts-ci.yaml @@ -21,7 +21,7 @@ jobs: - name: Install foundry-zksync run: | mkdir ./foundry-zksync - curl -LO https://github.com/matter-labs/foundry-zksync/releases/download/nightly/foundry_nightly_linux_amd64.tar.gz + curl -LO https://github.com/matter-labs/foundry-zksync/releases/download/nightly-27360d4c8d12beddbb730dae07ad33a206b38f4b/foundry_nightly_linux_amd64.tar.gz tar zxf foundry_nightly_linux_amd64.tar.gz -C ./foundry-zksync chmod +x ./foundry-zksync/forge ./foundry-zksync/cast echo "$PWD/foundry-zksync" >> $GITHUB_PATH @@ -32,25 +32,42 @@ jobs: node-version: 18.18.0 cache: yarn + - name: Install dependencies + run: yarn + + - name: Build da contracts + working-directory: da-contracts + run: | + yarn build:foundry + - name: Build l1 contracts working-directory: l1-contracts run: | - forge build + yarn build:foundry - name: Build l2 contracts working-directory: l2-contracts run: | - forge build --zksync --zk-enable-eravm-extensions + yarn build:foundry + + - name: Build system contracts + working-directory: system-contracts + run: | + yarn install + yarn build:foundry - name: Create cache uses: actions/cache/save@v3 with: key: artifacts-l1-${{ github.sha }} path: | + da-contracts/out l1-contracts/cache-forge l1-contracts/out + l1-contracts/zkout l2-contracts/cache-forge l2-contracts/zkout + system-contracts/zkout lint: runs-on: ubuntu-latest @@ -71,6 +88,9 @@ jobs: - name: Lint run: yarn lint:check + - name: Lint errors + run: yarn l1 errors-lint --check + test-foundry: needs: [build, lint] runs-on: ubuntu-latest @@ -90,10 +110,11 @@ jobs: - name: Install foundry-zksync run: | mkdir ./foundry-zksync - curl -LO https://github.com/matter-labs/foundry-zksync/releases/download/nightly/foundry_nightly_linux_amd64.tar.gz + curl -LO https://github.com/matter-labs/foundry-zksync/releases/download/nightly-27360d4c8d12beddbb730dae07ad33a206b38f4b/foundry_nightly_linux_amd64.tar.gz tar zxf foundry_nightly_linux_amd64.tar.gz -C ./foundry-zksync chmod +x ./foundry-zksync/forge ./foundry-zksync/cast echo "$PWD/foundry-zksync" >> $GITHUB_PATH + - name: Install dependencies run: yarn @@ -103,20 +124,27 @@ jobs: fail-on-cache-miss: true key: artifacts-l1-${{ github.sha }} path: | - l1-contracts/artifacts - l1-contracts/cache - l1-contracts/typechain + da-contracts/out + l1-contracts/cache-forge + l1-contracts/out + l1-contracts/zkout + l2-contracts/cache-forge + l2-contracts/zkout + system-contracts/zkout - name: Run tests - run: yarn l1 test:foundry + working-directory: ./l1-contracts + run: FOUNDRY_PROFILE=default yarn test:foundry - test-hardhat: + test-foundry-zksync: needs: [build, lint] runs-on: ubuntu-latest steps: - name: Checkout the repository uses: actions/checkout@v4 + with: + submodules: recursive - name: Use Node.js uses: actions/setup-node@v3 @@ -124,26 +152,39 @@ jobs: node-version: 18.18.0 cache: yarn + - name: Install foundry-zksync + run: | + mkdir ./foundry-zksync + curl -LO https://github.com/matter-labs/foundry-zksync/releases/download/nightly-27360d4c8d12beddbb730dae07ad33a206b38f4b/foundry_nightly_linux_amd64.tar.gz + tar zxf foundry_nightly_linux_amd64.tar.gz -C ./foundry-zksync + chmod +x ./foundry-zksync/forge ./foundry-zksync/cast + echo "$PWD/foundry-zksync" >> $GITHUB_PATH + - name: Install dependencies run: yarn + - name: Build system contract artifacts + run: yarn sc build:foundry + - name: Restore artifacts cache uses: actions/cache/restore@v3 with: fail-on-cache-miss: true key: artifacts-l1-${{ github.sha }} path: | - l1-contracts/artifacts - l1-contracts/cache - l1-contracts/typechain - l2-contracts/artifacts-zk - l2-contracts/cache-zk - l2-contracts/typechain + da-contracts/out + l1-contracts/cache-forge + l1-contracts/out + # TODO: cached `zkout` and the one for tests produce different hashes and so it causes the tests to fail + l2-contracts/cache-forge + l2-contracts/zkout + system-contracts/zkout - name: Run tests - run: yarn l1 test + working-directory: ./l1-contracts + run: FOUNDRY_PROFILE=default yarn test:zkfoundry - check-verifier-generator: + check-verifier-generator-l1: runs-on: ubuntu-latest steps: @@ -186,7 +227,7 @@ jobs: - name: Install foundry-zksync run: | mkdir ./foundry-zksync - curl -LO https://github.com/matter-labs/foundry-zksync/releases/download/nightly/foundry_nightly_linux_amd64.tar.gz + curl -LO https://github.com/matter-labs/foundry-zksync/releases/download/nightly-27360d4c8d12beddbb730dae07ad33a206b38f4b/foundry_nightly_linux_amd64.tar.gz tar zxf foundry_nightly_linux_amd64.tar.gz -C ./foundry-zksync chmod +x ./foundry-zksync/forge ./foundry-zksync/cast echo "$PWD/foundry-zksync" >> $GITHUB_PATH @@ -200,41 +241,104 @@ jobs: fail-on-cache-miss: true key: artifacts-l1-${{ github.sha }} path: | - l1-contracts/artifacts - l1-contracts/cache - l1-contracts/typechain + da-contracts/out + l1-contracts/cache-forge + l1-contracts/out + l1-contracts/zkout + l2-contracts/cache-forge + l2-contracts/zkout + system-contracts/zkout - name: Run coverage run: FOUNDRY_PROFILE=default yarn test:foundry && FOUNDRY_PROFILE=default yarn coverage:foundry --report summary --report lcov - # To ignore coverage for certain directories modify the paths in this step as needed. The - # below default ignores coverage results for the test and script directories. Alternatively, - # to include coverage in all directories, comment out this step. Note that because this - # filtering applies to the lcov file, the summary table generated in the previous step will - # still include all files and directories. - # The `--rc lcov_branch_coverage=1` part keeps branch info in the filtered report, since lcov - # defaults to removing branch info. - - name: Filter directories - run: | - sudo apt update && sudo apt install -y lcov - lcov --remove lcov.info 'test/*' 'contracts/dev-contracts/*' '../lib/forge-std/*' '../lib/murky/*' 'lib/*' '../lib/*' 'lib/' --output-file lcov.info --rc lcov_branch_coverage=1 - - # This step posts a detailed coverage report as a comment and deletes previous comments on - # each push. The below step is used to fail coverage if the specified coverage threshold is - # not met. The below step can post a comment (when it's `github-token` is specified) but it's - # not as useful, and this action cannot fail CI based on a minimum coverage threshold, which - # is why we use both in this way. - - name: Post coverage report - if: github.event_name == 'pull_request' # This action fails when ran outside of a pull request. - uses: romeovs/lcov-reporter-action@v0.3.1 - with: - delete-old-comments: true - lcov-file: ./l1-contracts/lcov.info - github-token: ${{ secrets.GITHUB_TOKEN }} # Adds a coverage summary comment to the PR. - - - name: Verify minimum coverage - uses: zgosalvez/github-actions-report-lcov@v2 - with: - coverage-files: ./l1-contracts/lcov.info - working-directory: l1-contracts - minimum-coverage: 85 # Set coverage threshold. + # TODO: for some reason filtering directories stopped working. + # # To ignore coverage for certain directories modify the paths in this step as needed. The + # # below default ignores coverage results for the test and script directories. Alternatively, + # # to include coverage in all directories, comment out this step. Note that because this + # # filtering applies to the lcov file, the summary table generated in the previous step will + # # still include all files and directories. + # # The `--rc lcov_branch_coverage=1` part keeps branch info in the filtered report, since lcov + # # defaults to removing branch info. + # - name: Filter directories + # run: | + # sudo apt update && sudo apt install -y lcov + # lcov --remove lcov.info 'test/*' 'contracts/dev-contracts/*' '../lib/forge-std/*' '../lib/murky/*' 'lib/*' '../lib/*' 'lib/' 'deploy-scripts/*' --output-file lcov.info --rc lcov_branch_coverage=1 + + # # This step posts a detailed coverage report as a comment and deletes previous comments on + # # each push. The below step is used to fail coverage if the specified coverage threshold is + # # not met. The below step can post a comment (when it's `github-token` is specified) but it's + # # not as useful, and this action cannot fail CI based on a minimum coverage threshold, which + # # is why we use both in this way. + # - name: Post coverage report + # if: github.event_name == 'pull_request' # This action fails when ran outside of a pull request. + # uses: romeovs/lcov-reporter-action@v0.3.1 + # with: + # delete-old-comments: true + # lcov-file: ./l1-contracts/lcov.info + # github-token: ${{ secrets.GITHUB_TOKEN }} # Adds a coverage summary comment to the PR. + + # - name: Verify minimum coverage + # uses: zgosalvez/github-actions-report-lcov@v2 + # with: + # coverage-files: ./l1-contracts/lcov.info + # working-directory: l1-contracts + # minimum-coverage: 85 # Set coverage threshold. + + # FIXME: restore gas report CI + # gas-report: + # needs: [build, lint] + # runs-on: ubuntu-latest + + # steps: + # - name: Checkout the repository + # uses: actions/checkout@v4 + # with: + # submodules: recursive + + # - name: Use Foundry + # uses: foundry-rs/foundry-toolchain@v1 + + # - name: Use Node.js + # uses: actions/setup-node@v3 + # with: + # node-version: 18.18.0 + # cache: yarn + + # - name: Install dependencies + # run: yarn + + # - name: Restore artifacts cache + # uses: actions/cache/restore@v3 + # with: + # fail-on-cache-miss: true + # key: artifacts-l1-${{ github.sha }} + # path: | + # da-contracts/out + # l1-contracts/cache-forge + # l1-contracts/out + # l1-contracts/zkout + # l2-contracts/cache-forge + # l2-contracts/zkout + # system-contracts/zkout + + # # Add any step generating a gas report to a temporary file named gasreport.ansi. For example: + # - name: Run tests + # run: yarn l1 test:foundry --gas-report | tee gasreport.ansi # <- this file name should be unique in your repository! + + # - name: Compare gas reports + # uses: Rubilmax/foundry-gas-diff@v3.18 + # with: + # summaryQuantile: 0.0 # only display the 10% most significant gas diffs in the summary (defaults to 20%) + # sortCriteria: avg,max # sort diff rows by criteria + # sortOrders: desc,asc # and directions + # ignore: test-foundry/**/*,l1-contracts/contracts/dev-contracts/**/*,l1-contracts/lib/**/*,l1-contracts/contracts/common/Dependencies.sol + # id: gas_diff + + # - name: Add gas diff to sticky comment + # if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target' + # uses: marocchino/sticky-pull-request-comment@v2 + # with: + # # delete the comment in case changes no longer impact gas costs + # delete: ${{ !steps.gas_diff.outputs.markdown }} + # message: ${{ steps.gas_diff.outputs.markdown }} diff --git a/.github/workflows/l1-contracts-foundry-ci.yaml b/.github/workflows/l1-contracts-foundry-ci.yaml new file mode 100644 index 000000000..81ec33e0a --- /dev/null +++ b/.github/workflows/l1-contracts-foundry-ci.yaml @@ -0,0 +1,127 @@ +name: L1 contracts foundry CI + +env: + ANVIL_PRIVATE_KEY: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + ANVIL_RPC_URL: "http://127.0.0.1:8545" + +on: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + with: + submodules: true + + - name: Install foundry-zksync + run: | + mkdir ./foundry-zksync + curl -LO https://github.com/matter-labs/foundry-zksync/releases/download/nightly-27360d4c8d12beddbb730dae07ad33a206b38f4b/foundry_nightly_linux_amd64.tar.gz + tar zxf foundry_nightly_linux_amd64.tar.gz -C ./foundry-zksync + chmod +x ./foundry-zksync/forge ./foundry-zksync/cast + echo "$PWD/foundry-zksync" >> $GITHUB_PATH + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 18.18.0 + cache: yarn + + - name: Install dependencies + run: yarn + + - name: Build artifacts + run: yarn l1 build:foundry + + - name: Build system-contract artifacts + run: yarn sc build:foundry + + - name: Build l2 artifacts + run: yarn l2 build:foundry + + - name: Build da-contracts artifacts + run: yarn da build:foundry + + - name: Create cache + uses: actions/cache/save@v3 + with: + key: artifacts-l1-contracts-foudry-${{ github.sha }} + path: | + da-contracts/out + l1-contracts/cache-forge + l1-contracts/out + l1-contracts/zkout + l2-contracts/cache-forge + l2-contracts/zkout + system-contracts/zkout + + scripts: + runs-on: ubuntu-latest + needs: build + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + with: + submodules: true + + - name: Restore artifacts cache + uses: actions/cache/restore@v3 + with: + fail-on-cache-miss: true + key: artifacts-l1-contracts-foudry-${{ github.sha }} + path: | + da-contracts/out + l1-contracts/cache-forge + l1-contracts/out + l1-contracts/zkout + l2-contracts/cache-forge + l2-contracts/zkout + system-contracts/zkout + + - name: Use Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Copy configs from template + working-directory: ./l1-contracts + run: cp -r deploy-script-config-template/. script-config + + - name: Run anvil + run: | + anvil --silent & + + ANVIL_READY=0 + for i in {1..10}; do + if curl -s -o /dev/null $ANVIL_RPC_URL -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_chainId","id":1}'; then + echo "Anvil is ready" + ANVIL_READY=1 + break + else + echo "Waiting for Anvil to become ready..." + sleep 1 + fi + done + + if [ $ANVIL_READY -ne 1 ]; then + echo "Anvil failed to become ready after 10 attempts." + exit 1 + fi + + - name: Run DeployL1 script + working-directory: ./l1-contracts + run: forge script ./deploy-scripts/DeployL1.s.sol --ffi --rpc-url $ANVIL_RPC_URL --broadcast --private-key $ANVIL_PRIVATE_KEY + + - name: Run DeployErc20 script + working-directory: ./l1-contracts + run: forge script ./deploy-scripts/DeployErc20.s.sol --ffi --rpc-url $ANVIL_RPC_URL --broadcast --private-key $ANVIL_PRIVATE_KEY +# TODO restore scripts verification +# - name: Run RegisterZKChain script +# working-directory: ./l1-contracts +# run: | +# cat ./script-out/output-deploy-l1.toml >> ./script-config/register-zk-chain.toml +# forge script ./deploy-scripts/RegisterZKChain.s.sol --ffi --rpc-url $ANVIL_RPC_URL --broadcast --private-key $ANVIL_PRIVATE_KEY +# - name: Run InitializeL2WethToken script +# working-directory: ./l1-contracts-foundry +# run: forge script ./deploy-scripts/InitializeL2WethToken.s.sol --ffi --rpc-url $ANVIL_RPC_URL --broadcast --private-key $ANVIL_PRIVATE_KEY diff --git a/.github/workflows/l2-contracts-ci.yaml b/.github/workflows/l2-contracts-ci.yaml index e19ac376b..706a98944 100644 --- a/.github/workflows/l2-contracts-ci.yaml +++ b/.github/workflows/l2-contracts-ci.yaml @@ -16,7 +16,7 @@ jobs: - name: Install foundry-zksync run: | mkdir ./foundry-zksync - curl -LO https://github.com/matter-labs/foundry-zksync/releases/download/nightly/foundry_nightly_linux_amd64.tar.gz + curl -LO https://github.com/matter-labs/foundry-zksync/releases/download/nightly-27360d4c8d12beddbb730dae07ad33a206b38f4b/foundry_nightly_linux_amd64.tar.gz tar zxf foundry_nightly_linux_amd64.tar.gz -C ./foundry-zksync chmod +x ./foundry-zksync/forge ./foundry-zksync/cast echo "$PWD/foundry-zksync" >> $GITHUB_PATH @@ -33,12 +33,15 @@ jobs: - name: Build l1 contracts working-directory: l1-contracts run: | - forge build + yarn build:foundry - name: Build l2 contracts working-directory: l2-contracts run: | - forge build --zksync --zk-enable-eravm-extensions + yarn build:foundry + + - name: Build system contract artifacts + run: yarn sc build:foundry - name: Create cache uses: actions/cache/save@v3 @@ -49,6 +52,8 @@ jobs: l1-contracts/out l2-contracts/cache-forge l2-contracts/zkout + system-contracts/cache-forge + system-contracts/zkout lint: runs-on: ubuntu-latest @@ -69,6 +74,23 @@ jobs: - name: Lint run: yarn lint:check + check-verifier-generator-l2: + needs: [build] + runs-on: ubuntu-latest + + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Generate Verifier.sol + working-directory: tools + run: cargo run --bin zksync_verifier_contract_generator --release -- --input_path data/scheduler_key.json --l2_mode + + - name: Compare + run: diff tools/data/Verifier.sol l2-contracts/contracts/verifier/Verifier.sol + test: needs: [build, lint] runs-on: ubuntu-latest @@ -94,15 +116,20 @@ jobs: fail-on-cache-miss: true key: artifacts-l2-${{ github.sha }} path: | - l1-contracts/artifacts - l1-contracts/cache - l1-contracts/typechain - l2-contracts/artifacts-zk - l2-contracts/cache-zk - l2-contracts/typechain + da-contracts/out + l1-contracts/cache-forge + l1-contracts/out + l1-contracts/zkout + l2-contracts/cache-forge + l2-contracts/zkout - - name: Run Era test node - uses: dutterbutter/era-test-node-action@v0.1.3 + - name: Install foundry-zksync + run: | + mkdir ./foundry-zksync + curl -LO https://github.com/matter-labs/foundry-zksync/releases/download/nightly-27360d4c8d12beddbb730dae07ad33a206b38f4b/foundry_nightly_linux_amd64.tar.gz + tar zxf foundry_nightly_linux_amd64.tar.gz -C ./foundry-zksync + chmod +x ./foundry-zksync/forge ./foundry-zksync/cast + echo "$PWD/foundry-zksync" >> $GITHUB_PATH - name: Run tests - run: yarn l2 test + run: yarn l2 test:foundry diff --git a/.github/workflows/slither.yaml b/.github/workflows/slither.yaml index 50c9194dc..43518de07 100644 --- a/.github/workflows/slither.yaml +++ b/.github/workflows/slither.yaml @@ -44,6 +44,7 @@ jobs: rm -rf ./l1-contracts/contracts/state-transition/utils/ rm -rf ./l1-contracts/contracts/state-transition/Verifier.sol rm -rf ./l1-contracts/contracts/state-transition/TestnetVerifier.sol + rm -rf ./l1-contracts/contracts/state-transition/chain-deps/GatewayCTMDeployer.sol rm -rf ./l1-contracts/contracts/dev-contracts/test/VerifierTest.sol rm -rf ./l1-contracts/contracts/dev-contracts/test/VerifierRecursiveTest.sol diff --git a/.github/workflows/system-contracts-ci.yaml b/.github/workflows/system-contracts-ci.yaml index d1338983e..7a8ad783c 100644 --- a/.github/workflows/system-contracts-ci.yaml +++ b/.github/workflows/system-contracts-ci.yaml @@ -16,7 +16,7 @@ jobs: - name: Install foundry-zksync run: | mkdir ./foundry-zksync - curl -LO https://github.com/matter-labs/foundry-zksync/releases/download/nightly/foundry_nightly_linux_amd64.tar.gz + curl -LO https://github.com/matter-labs/foundry-zksync/releases/download/nightly-27360d4c8d12beddbb730dae07ad33a206b38f4b/foundry_nightly_linux_amd64.tar.gz tar zxf foundry_nightly_linux_amd64.tar.gz -C ./foundry-zksync chmod +x ./foundry-zksync/forge ./foundry-zksync/cast echo "$PWD/foundry-zksync" >> $GITHUB_PATH @@ -31,10 +31,7 @@ jobs: working-directory: system-contracts run: | yarn install - yarn preprocess:system-contracts - forge build --zksync --zk-enable-eravm-extensions - yarn preprocess:bootloader - forge build --zksync --zk-enable-eravm-extensions + yarn build:foundry yarn build - name: Create cache @@ -69,37 +66,38 @@ jobs: - name: Run lint run: yarn lint:check - test-bootloader: - needs: [build, lint] - runs-on: ubuntu-latest - - steps: - - name: Checkout the repository - uses: actions/checkout@v4 - - - name: Install rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - toolchain: nightly-2023-04-17 - - - name: Restore artifacts cache - uses: actions/cache/restore@v3 - with: - fail-on-cache-miss: true - key: artifacts-system-${{ github.sha }} - path: | - system-contracts/zkout - system-contracts/cache-forge - system-contracts/bootloader/build - system-contracts/artifacts-zk - system-contracts/cache-zk - system-contracts/typechain - system-contracts/contracts-preprocessed - - - name: Run bootloader tests - run: | - cd system-contracts/bootloader/test_infra - cargo run + # FIXME: recover when used multivm is updated + # test-bootloader: + # needs: [build, lint] + # runs-on: ubuntu-latest + + # steps: + # - name: Checkout the repository + # uses: actions/checkout@v4 + + # - name: Install rust + # uses: actions-rust-lang/setup-rust-toolchain@v1 + # with: + # toolchain: nightly-2023-04-17 + + # - name: Restore artifacts cache + # uses: actions/cache/restore@v3 + # with: + # fail-on-cache-miss: true + # key: artifacts-system-${{ github.sha }} + # path: | + # system-contracts/zkout + # system-contracts/cache-forge + # system-contracts/bootloader/build + # system-contracts/artifacts-zk + # system-contracts/cache-zk + # system-contracts/typechain + # system-contracts/contracts-preprocessed + + # - name: Run bootloader tests + # run: | + # cd system-contracts/bootloader/test_infra + # cargo run test-contracts: needs: [build, lint] @@ -143,3 +141,36 @@ jobs: - name: Print output logs of era_test_node if: always() run: cat era_test_node.log +# FIXME: restore check hashes as it does not +# work in the base branch as well +# check-hashes: +# needs: [build] +# runs-on: ubuntu-latest + +# steps: +# - name: Checkout the repository +# uses: actions/checkout@v4 + +# - name: Use Node.js +# uses: actions/setup-node@v3 +# with: +# node-version: 18.18.0 +# cache: yarn + +# - name: Install dependencies +# run: yarn + +# - name: Restore artifacts cache +# uses: actions/cache/restore@v3 +# with: +# fail-on-cache-miss: true +# key: artifacts-system-${{ github.sha }} +# path: | +# system-contracts/artifacts-zk +# system-contracts/cache-zk +# system-contracts/typechain +# system-contracts/contracts-preprocessed +# system-contracts/bootloader/build + +# - name: Check hashes +# run: yarn sc calculate-hashes:check diff --git a/.gitignore b/.gitignore index 21c173828..2128686e0 100644 --- a/.gitignore +++ b/.gitignore @@ -22,9 +22,16 @@ l1-contracts/lcov.info l1-contracts/report/* l1-contracts/coverage/* l1-contracts/out/* +l1-contracts/zkout/* l1-contracts/broadcast/* l1-contracts/script-config/* !l1-contracts/script-config/artifacts l1-contracts/script-out/* +l1-contracts/test/foundry/l1/integration/deploy-scripts/script-out/*.toml !l1-contracts/script-out/.gitkeep *.timestamp +l1-contracts/test/foundry/l1/integration/deploy-scripts/script-out/* +l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-zk-chain-*.toml +l1-contracts/test/foundry/integration/deploy-scripts/script-out/* +l1-contracts/test/foundry/l1/integration/upgrade-envs/script-out/*.toml +l1-contracts/zkout/* diff --git a/.gitmodules b/.gitmodules index 3451bd884..f94071e53 100644 --- a/.gitmodules +++ b/.gitmodules @@ -12,3 +12,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/@matterlabs/zksync-contracts"] + path = lib/@matterlabs/zksync-contracts + url = https://github.com/matter-labs/v2-testnet-contracts diff --git a/.solhintignore b/.solhintignore index abcb64f98..7ba03ff3e 100644 --- a/.solhintignore +++ b/.solhintignore @@ -8,6 +8,7 @@ l1-contracts/lib l1-contracts/node_modules l1-contracts/contracts/dev-contracts l1-contracts/test +l1-contracts/deploy-scripts # l1-contracts-foundry l1-contracts-foundry/cache @@ -16,6 +17,8 @@ l1-contracts-foundry/lib # l2-contracts l2-contracts/cache-zk l2-contracts/node_modules +l2-contracts/contracts/dev-contracts +l2-contracts/test # system-contracts system-contracts/contracts/openzeppelin diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 46bdeebac..dbefde5c1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,7 +13,7 @@ There are many ways to contribute to the ZK Stack: issues. 3. Resolve issues: either by showing an issue isn't a problem and the current state is ok as is or by fixing the problem and opening a PR. -4. Report security issues, see [our security policy](./github/SECURITY.md). +4. Report security issues, see [our security policy](./.github/SECURITY.md). 5. [Join the team!](https://matterlabs.notion.site/Shape-the-future-of-Ethereum-at-Matter-Labs-dfb3b5a037044bb3a8006af2eb0575e0) ## Fixing issues @@ -34,7 +34,7 @@ We aim to make it as easy as possible to contribute to the mission. This is stil and suggestions here too. Some resources to help: 1. [In-repo docs aimed at developers](docs) -2. [ZKsync Era docs!](https://era.zksync.io/docs/) +2. [ZKsync Era docs!](https://docs.zksync.io/zk-stack) 3. Company links can be found in the [repo's readme](README.md) ## Code of Conduct diff --git a/README.md b/README.md index cc1425b5b..8c776af4c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ or re-auditing a single line of code. ZKsync Era also uses an LLVM-based compile write smart contracts in C++, Rust and other popular languages. This repository contains both L1 and L2 ZKsync smart contracts. For their description see the -[system overview](docs/Overview.md). +[system overview](docs/overview.md). ## Disclaimer diff --git a/SECURITY.md b/SECURITY.md index 2f2871cea..9b136eac8 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -5,7 +5,7 @@ We truly appreciate efforts to discover and disclose security issues responsibly ## Vulnerabilities If you'd like to report a security issue in the repositories of matter-labs organization, please proceed to our -[Bug Bounty Program on Immunefi](https://era.zksync.io/docs/reference/troubleshooting/audit-bug-bounty.html#bug-bounty-program). +[Bug Bounty Program on Immunefi](https://immunefi.com/bug-bounty/zksyncera/information/). ## Other Security Issues diff --git a/SystemConfig.json b/SystemConfig.json index 73d75b2b0..e66dd60ee 100644 --- a/SystemConfig.json +++ b/SystemConfig.json @@ -13,7 +13,7 @@ "L1_TX_DELTA_FACTORY_DEPS_PUBDATA": 64, "L2_TX_INTRINSIC_GAS": 14070, "L2_TX_INTRINSIC_PUBDATA": 0, - "MAX_NEW_FACTORY_DEPS": 32, + "MAX_NEW_FACTORY_DEPS": 64, "MAX_GAS_PER_TRANSACTION": 80000000, "KECCAK_ROUND_COST_GAS": 40, "SHA256_ROUND_COST_GAS": 7, diff --git a/da-contracts/.env b/da-contracts/.env new file mode 100644 index 000000000..59a2db08b --- /dev/null +++ b/da-contracts/.env @@ -0,0 +1,2 @@ +CHAIN_ETH_NETWORK=hardhat +ETH_CLIENT_WEB3_URL=http://127.0.0.1:8545 diff --git a/da-contracts/contracts/CalldataDA.sol b/da-contracts/contracts/CalldataDA.sol new file mode 100644 index 000000000..f49a63d99 --- /dev/null +++ b/da-contracts/contracts/CalldataDA.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {OperatorDAInputTooSmall, InvalidNumberOfBlobs, InvalidL2DAOutputHash, OnlyOneBlobWithCalldataAllowed, PubdataInputTooSmall, PubdataLengthTooBig, InvalidPubdataHash} from "./DAContractsErrors.sol"; + +/// @dev Total number of bytes in a blob. Blob = 4096 field elements * 31 bytes per field element +/// @dev EIP-4844 defines it as 131_072 but we use 4096 * 31 within our circuits to always fit within a field element +/// @dev Our circuits will prove that a EIP-4844 blob and our internal blob are the same. +uint256 constant BLOB_SIZE_BYTES = 126_976; + +/// @dev The state diff hash, hash of pubdata + the number of blobs. +uint256 constant BLOB_DATA_OFFSET = 65; + +/// @dev The size of the commitment for a single blob. +uint256 constant BLOB_COMMITMENT_SIZE = 32; + +/// @notice Contract that contains the functionality for process the calldata DA. +/// @dev The expected l2DAValidator that should be used with it `RollupL2DAValidator`. +abstract contract CalldataDA { + /// @notice Parses the input that the L2 DA validator has provided to the contract. + /// @param _l2DAValidatorOutputHash The hash of the output of the L2 DA validator. + /// @param _maxBlobsSupported The maximal number of blobs supported by the chain. + /// @param _operatorDAInput The DA input by the operator provided on L1. + function _processL2RollupDAValidatorOutputHash( + bytes32 _l2DAValidatorOutputHash, + uint256 _maxBlobsSupported, + bytes calldata _operatorDAInput + ) + internal + pure + returns ( + bytes32 stateDiffHash, + bytes32 fullPubdataHash, + bytes32[] memory blobsLinearHashes, + uint256 blobsProvided, + bytes calldata l1DaInput + ) + { + // The preimage under the hash `_l2DAValidatorOutputHash` is expected to be in the following format: + // - First 32 bytes are the hash of the uncompressed state diff. + // - Then, there is a 32-byte hash of the full pubdata. + // - Then, there is the 1-byte number of blobs published. + // - Then, there are linear hashes of the published blobs, 32 bytes each. + + // Check that it accommodates enough pubdata for the state diff hash, hash of pubdata + the number of blobs. + if (_operatorDAInput.length < BLOB_DATA_OFFSET) { + revert OperatorDAInputTooSmall(_operatorDAInput.length, BLOB_DATA_OFFSET); + } + + stateDiffHash = bytes32(_operatorDAInput[:32]); + fullPubdataHash = bytes32(_operatorDAInput[32:64]); + blobsProvided = uint256(uint8(_operatorDAInput[64])); + + if (blobsProvided > _maxBlobsSupported) { + revert InvalidNumberOfBlobs(blobsProvided, _maxBlobsSupported); + } + + // Note that the API of the contract requires that the returned blobs linear hashes have length of + // the `_maxBlobsSupported` + blobsLinearHashes = new bytes32[](_maxBlobsSupported); + + if (_operatorDAInput.length < BLOB_DATA_OFFSET + 32 * blobsProvided) { + revert OperatorDAInputTooSmall(_operatorDAInput.length, BLOB_DATA_OFFSET + 32 * blobsProvided); + } + + _cloneCalldata(blobsLinearHashes, _operatorDAInput[BLOB_DATA_OFFSET:], blobsProvided); + + uint256 ptr = BLOB_DATA_OFFSET + 32 * blobsProvided; + + // Now, we need to double check that the provided input was indeed returned by the L2 DA validator. + if (keccak256(_operatorDAInput[:ptr]) != _l2DAValidatorOutputHash) { + revert InvalidL2DAOutputHash(_l2DAValidatorOutputHash); + } + + // The rest of the output was provided specifically by the operator + l1DaInput = _operatorDAInput[ptr:]; + } + + /// @notice Verify that the calldata DA was correctly provided. + /// @param _blobsProvided The number of blobs provided. + /// @param _fullPubdataHash Hash of the pubdata preimage. + /// @param _maxBlobsSupported Maximum number of blobs supported. + /// @param _pubdataInput Full pubdata + an additional 32 bytes containing the blob commitment for the pubdata. + /// @dev We supply the blob commitment as part of the pubdata because even with calldata the prover will check these values. + function _processCalldataDA( + uint256 _blobsProvided, + bytes32 _fullPubdataHash, + uint256 _maxBlobsSupported, + bytes calldata _pubdataInput + ) internal pure virtual returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { + if (_blobsProvided != 1) { + revert OnlyOneBlobWithCalldataAllowed(); + } + if (_pubdataInput.length < BLOB_COMMITMENT_SIZE) { + revert PubdataInputTooSmall(_pubdataInput.length, BLOB_COMMITMENT_SIZE); + } + + // We typically do not know whether we'll use calldata or blobs at the time when + // we start proving the batch. That's why the blob commitment for a single blob is still present in the case of calldata. + + blobCommitments = new bytes32[](_maxBlobsSupported); + + _pubdata = _pubdataInput[:_pubdataInput.length - BLOB_COMMITMENT_SIZE]; + + if (_pubdata.length > BLOB_SIZE_BYTES) { + revert PubdataLengthTooBig(_pubdata.length, BLOB_SIZE_BYTES); + } + if (_fullPubdataHash != keccak256(_pubdata)) { + revert InvalidPubdataHash(_fullPubdataHash, keccak256(_pubdata)); + } + blobCommitments[0] = bytes32(_pubdataInput[_pubdataInput.length - BLOB_COMMITMENT_SIZE:_pubdataInput.length]); + } + + /// @notice Method that clones a slice of calldata into a bytes32[] memory array. + /// @param _dst The destination array. + /// @param _input The input calldata. + /// @param _len The length of the slice in 32-byte words to clone. + function _cloneCalldata(bytes32[] memory _dst, bytes calldata _input, uint256 _len) internal pure { + assembly { + // The pointer to the allocated memory above. We skip 32 bytes to avoid overwriting the length. + let dstPtr := add(_dst, 0x20) + let inputPtr := _input.offset + calldatacopy(dstPtr, inputPtr, mul(_len, 32)) + } + } +} diff --git a/da-contracts/contracts/DAContractsErrors.sol b/da-contracts/contracts/DAContractsErrors.sol new file mode 100644 index 000000000..73ee16dca --- /dev/null +++ b/da-contracts/contracts/DAContractsErrors.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +// 0x53dee67b +error PubdataCommitmentsEmpty(); +// 0x53e6d04d +error InvalidPubdataCommitmentsSize(); +// 0xafd53e2f +error BlobHashCommitmentError(uint256 index, bool blobHashEmpty, bool blobCommitmentEmpty); +// 0xfc7ab1d3 +error EmptyBlobVersionHash(uint256 index); +// 0x92290acc +error NonEmptyBlobVersionHash(uint256 index); +// 0x8d5851de +error PointEvalCallFailed(bytes); +// 0x4daa985d +error PointEvalFailed(bytes); + +// 0x885ae069 +error OperatorDAInputTooSmall(uint256 operatorDAInputLength, uint256 minAllowedLength); + +// 0xbeb96791 +error InvalidNumberOfBlobs(uint256 blobsProvided, uint256 maxBlobsSupported); + +// 0xd2531c15 +error InvalidL2DAOutputHash(bytes32 l2DAValidatorOutputHash); + +// 0x04e05fd1 +error OnlyOneBlobWithCalldataAllowed(); + +// 0x2dc9747d +error PubdataInputTooSmall(uint256 pubdataInputLength, uint256 totalBlobsCommitmentSize); + +// 0x9044dff9 +error PubdataLengthTooBig(uint256 pubdataLength, uint256 totalBlobSizeBytes); + +// 0x5513177c +error InvalidPubdataHash(bytes32 fullPubdataHash, bytes32 providedPubdataHash); + +// 0xc771423e +error BlobCommitmentNotPublished(); + +// 0x5717f940 +error InvalidPubdataSource(uint8 pubdataSource); +// 0x52595598 +error ValL1DAWrongInputLength(uint256 inputLength, uint256 expectedLength); diff --git a/da-contracts/contracts/DAUtils.sol b/da-contracts/contracts/DAUtils.sol new file mode 100644 index 000000000..f79e609e9 --- /dev/null +++ b/da-contracts/contracts/DAUtils.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +/// @dev Total number of bytes in a blob. Blob = 4096 field elements * 31 bytes per field element +/// @dev EIP-4844 defines it as 131_072 but we use 4096 * 31 within our circuits to always fit within a field element +/// @dev Our circuits will prove that a EIP-4844 blob and our internal blob are the same. +uint256 constant BLOB_SIZE_BYTES = 126_976; + +/// @dev Enum used to determine the source of pubdata. At first we will support calldata and blobs but this can be extended. +enum PubdataSource { + Calldata, + Blob +} + +/// @dev BLS Modulus value defined in EIP-4844 and the magic value returned from a successful call to the +/// point evaluation precompile +uint256 constant BLS_MODULUS = 52435875175126190479447740508185965837690552500527637822603658699938581184513; + +/// @dev Packed pubdata commitments. +/// @dev Format: list of: opening point (16 bytes) || claimed value (32 bytes) || commitment (48 bytes) || proof (48 bytes)) = 144 bytes +uint256 constant PUBDATA_COMMITMENT_SIZE = 144; + +/// @dev Offset in pubdata commitment of blobs for claimed value +uint256 constant PUBDATA_COMMITMENT_CLAIMED_VALUE_OFFSET = 16; + +/// @dev Offset in pubdata commitment of blobs for kzg commitment +uint256 constant PUBDATA_COMMITMENT_COMMITMENT_OFFSET = 48; + +/// @dev For each blob we expect that the commitment is provided as well as the marker whether a blob with such commitment has been published before. +uint256 constant BLOB_DA_INPUT_SIZE = PUBDATA_COMMITMENT_SIZE + 32; + +/// @dev Address of the point evaluation precompile used for EIP-4844 blob verification. +address constant POINT_EVALUATION_PRECOMPILE_ADDR = address(0x0A); + +/// @dev The address of the special smart contract that can send arbitrary length message as an L2 log +address constant L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR = address(0x8008); diff --git a/da-contracts/contracts/IL1DAValidator.sol b/da-contracts/contracts/IL1DAValidator.sol new file mode 100644 index 000000000..cb2b640e5 --- /dev/null +++ b/da-contracts/contracts/IL1DAValidator.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +struct L1DAValidatorOutput { + /// @dev The hash of the uncompressed state diff. + bytes32 stateDiffHash; + /// @dev The hashes of the blobs on L1. The array is dynamic to account for forward compatibility. + /// The length of it must be equal to `maxBlobsSupported`. + bytes32[] blobsLinearHashes; + /// @dev The commitments to the blobs on L1. The array is dynamic to account for forward compatibility. + /// Its length must be equal to the length of blobsLinearHashes. + /// @dev If the system supports more blobs than returned, the rest of the array should be filled with zeros. + bytes32[] blobsOpeningCommitments; +} + +interface IL1DAValidator { + /// @notice The function that checks the data availability for the given batch input. + /// @param _chainId The chain id of the chain that is being committed. + /// @param _batchNumber The batch number for which the data availability is being checked. + /// @param _l2DAValidatorOutputHash The hash of that was returned by the l2DAValidator. + /// @param _operatorDAInput The DA input by the operator provided on L1. + /// @param _maxBlobsSupported The maximal number of blobs supported by the chain. + /// We provide this value for future compatibility. + /// This is needed because the corresponding `blobsLinearHashes`/`blobsOpeningCommitments` + /// in the `L1DAValidatorOutput` struct will have to have this length as it is required + /// to be static by the circuits. + function checkDA( + uint256 _chainId, + uint256 _batchNumber, + bytes32 _l2DAValidatorOutputHash, + bytes calldata _operatorDAInput, + uint256 _maxBlobsSupported + ) external returns (L1DAValidatorOutput memory output); +} diff --git a/da-contracts/contracts/IL1Messenger.sol b/da-contracts/contracts/IL1Messenger.sol new file mode 100644 index 000000000..a2aa35fa0 --- /dev/null +++ b/da-contracts/contracts/IL1Messenger.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; +/** + * @author Matter Labs + * @custom:security-contact security@matterlabs.dev + * @notice The interface of the L1 Messenger contract, responsible for sending messages to L1. + */ +interface IL1Messenger { + function sendToL1(bytes calldata _message) external returns (bytes32); +} diff --git a/da-contracts/contracts/RollupL1DAValidator.sol b/da-contracts/contracts/RollupL1DAValidator.sol new file mode 100644 index 000000000..bf2e3dba9 --- /dev/null +++ b/da-contracts/contracts/RollupL1DAValidator.sol @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IL1DAValidator, L1DAValidatorOutput} from "./IL1DAValidator.sol"; + +import {CalldataDA} from "./CalldataDA.sol"; + +import {PubdataSource, BLS_MODULUS, PUBDATA_COMMITMENT_SIZE, PUBDATA_COMMITMENT_CLAIMED_VALUE_OFFSET, PUBDATA_COMMITMENT_COMMITMENT_OFFSET, BLOB_DA_INPUT_SIZE, POINT_EVALUATION_PRECOMPILE_ADDR} from "./DAUtils.sol"; + +import {InvalidPubdataSource, PubdataCommitmentsEmpty, InvalidPubdataCommitmentsSize, BlobHashCommitmentError, EmptyBlobVersionHash, NonEmptyBlobVersionHash, PointEvalCallFailed, PointEvalFailed, BlobCommitmentNotPublished} from "./DAContractsErrors.sol"; + +uint256 constant BLOBS_SUPPORTED = 6; + +/// @dev The number of blocks within each we allow blob to be used for DA. +/// On Ethereum blobs expire within 4096 epochs, i.e. 4096 * 32 blocks. We reserve +/// half of the time in order to ensure reader's ability to read the blob's content. +uint256 constant BLOB_EXPIRATION_BLOCKS = (4096 * 32) / 2; + +contract RollupL1DAValidator is IL1DAValidator, CalldataDA { + /// @notice The published blob commitments. Note, that the correctness of blob commitment with relation to the linear hash + /// is *not* checked in this contract, but is expected to be checked at the verification stage of the ZK contract. + mapping(bytes32 blobCommitment => uint256 blockOfPublishing) public publishedBlobCommitments; + + /// @notice Publishes certain blobs, marking commitments to them as published. + /// @param _pubdataCommitments The commitments to the blobs to be published. + /// `_pubdataCommitments` is a packed list of commitments of the following format: + /// opening point (16 bytes) || claimed value (32 bytes) || commitment (48 bytes) || proof (48 bytes) + function publishBlobs(bytes calldata _pubdataCommitments) external { + if (_pubdataCommitments.length == 0) { + revert PubdataCommitmentsEmpty(); + } + if (_pubdataCommitments.length % PUBDATA_COMMITMENT_SIZE != 0) { + revert InvalidPubdataCommitmentsSize(); + } + + uint256 versionedHashIndex = 0; + // solhint-disable-next-line gas-length-in-loops + for (uint256 i = 0; i < _pubdataCommitments.length; i += PUBDATA_COMMITMENT_SIZE) { + bytes32 blobCommitment = _getPublishedBlobCommitment( + versionedHashIndex, + _pubdataCommitments[i:i + PUBDATA_COMMITMENT_SIZE] + ); + publishedBlobCommitments[blobCommitment] = block.number; + ++versionedHashIndex; + } + } + + function isBlobAvailable(bytes32 _blobCommitment) public view returns (bool) { + uint256 blockOfPublishing = publishedBlobCommitments[_blobCommitment]; + + // While `block.number` on all used L1 networks is much higher than `BLOB_EXPIRATION_BLOCKS`, + // we still check that `blockOfPublishing > 0` just in case. + return blockOfPublishing > 0 && block.number - blockOfPublishing <= BLOB_EXPIRATION_BLOCKS; + } + + /// @inheritdoc IL1DAValidator + function checkDA( + uint256, // _chainId + uint256, // _batchNumber + bytes32 _l2DAValidatorOutputHash, + bytes calldata _operatorDAInput, + uint256 _maxBlobsSupported + ) external view returns (L1DAValidatorOutput memory output) { + ( + bytes32 stateDiffHash, + bytes32 fullPubdataHash, + bytes32[] memory blobsLinearHashes, + uint256 blobsProvided, + bytes calldata l1DaInput + ) = _processL2RollupDAValidatorOutputHash(_l2DAValidatorOutputHash, _maxBlobsSupported, _operatorDAInput); + + uint8 pubdataSource = uint8(l1DaInput[0]); + bytes32[] memory blobCommitments; + + if (pubdataSource == uint8(PubdataSource.Blob)) { + blobCommitments = _processBlobDA(blobsProvided, _maxBlobsSupported, l1DaInput[1:]); + } else if (pubdataSource == uint8(PubdataSource.Calldata)) { + (blobCommitments, ) = _processCalldataDA(blobsProvided, fullPubdataHash, _maxBlobsSupported, l1DaInput[1:]); + } else { + revert InvalidPubdataSource(pubdataSource); + } + + // We verify that for each set of blobHash/blobCommitment are either both empty + // or there are values for both. + // This is mostly a sanity check and it is not strictly required. + for (uint256 i = 0; i < _maxBlobsSupported; ++i) { + if ( + (blobsLinearHashes[i] == bytes32(0) && blobCommitments[i] != bytes32(0)) || + (blobsLinearHashes[i] != bytes32(0) && blobCommitments[i] == bytes32(0)) + ) { + revert BlobHashCommitmentError(i, blobsLinearHashes[i] == bytes32(0), blobCommitments[i] == bytes32(0)); + } + } + + output.stateDiffHash = stateDiffHash; + output.blobsLinearHashes = blobsLinearHashes; + output.blobsOpeningCommitments = blobCommitments; + } + + /// @notice Generated the blob commitemnt to be used in the cryptographic proof by calling the point evaluation precompile. + /// @param _index The index of the blob in this transaction. + /// @param _commitment The packed: opening point (16 bytes) || claimed value (32 bytes) || commitment (48 bytes) || proof (48 bytes)) = 144 bytes + /// @return The commitment to be used in the cryptographic proof. + function _getPublishedBlobCommitment(uint256 _index, bytes calldata _commitment) internal view returns (bytes32) { + bytes32 blobVersionedHash = _getBlobVersionedHash(_index); + + if (blobVersionedHash == bytes32(0)) { + revert EmptyBlobVersionHash(_index); + } + + // First 16 bytes is the opening point. While we get the point as 16 bytes, the point evaluation precompile + // requires it to be 32 bytes. The blob commitment must use the opening point as 16 bytes though. + bytes32 openingPoint = bytes32( + uint256(uint128(bytes16(_commitment[:PUBDATA_COMMITMENT_CLAIMED_VALUE_OFFSET]))) + ); + + _pointEvaluationPrecompile( + blobVersionedHash, + openingPoint, + _commitment[PUBDATA_COMMITMENT_CLAIMED_VALUE_OFFSET:PUBDATA_COMMITMENT_SIZE] + ); + + // Take the hash of the versioned hash || opening point || claimed value + return keccak256(abi.encodePacked(blobVersionedHash, _commitment[:PUBDATA_COMMITMENT_COMMITMENT_OFFSET])); + } + + /// @notice Verify that the blob DA was correctly provided. + /// @param _blobsProvided The number of blobs provided. + /// @param _maxBlobsSupported Maximum number of blobs supported. + /// @param _operatorDAInput Input used to verify that the blobs contain the data we expect. + function _processBlobDA( + uint256 _blobsProvided, + uint256 _maxBlobsSupported, + bytes calldata _operatorDAInput + ) internal view returns (bytes32[] memory blobsCommitments) { + blobsCommitments = new bytes32[](_maxBlobsSupported); + + // For blobs we expect to receive the commitments in the following format: + // 144 bytes for commitment data + // 32 bytes for the prepublished commitment. If it is non-zero, it means that it is expected that + // such commitment was published before. Otherwise, it is expected that it is published in this transaction + if (_operatorDAInput.length != _blobsProvided * BLOB_DA_INPUT_SIZE) { + revert InvalidPubdataCommitmentsSize(); + } + + uint256 versionedHashIndex = 0; + + // we iterate over the `_operatorDAInput`, while advancing the pointer by `BLOB_DA_INPUT_SIZE` each time + for (uint256 i = 0; i < _blobsProvided; ++i) { + bytes calldata commitmentData = _operatorDAInput[:PUBDATA_COMMITMENT_SIZE]; + bytes32 prepublishedCommitment = bytes32(_operatorDAInput[PUBDATA_COMMITMENT_SIZE:BLOB_DA_INPUT_SIZE]); + + if (prepublishedCommitment != bytes32(0)) { + // We double check that this commitment has indeed been published. + // If that is the case, we do not care about the actual underlying data. + if (!isBlobAvailable(prepublishedCommitment)) { + revert BlobCommitmentNotPublished(); + } + blobsCommitments[i] = prepublishedCommitment; + } else { + blobsCommitments[i] = _getPublishedBlobCommitment(versionedHashIndex, commitmentData); + ++versionedHashIndex; + } + + // Advance the pointer + _operatorDAInput = _operatorDAInput[BLOB_DA_INPUT_SIZE:]; + } + + // This check is required because we want to ensure that there aren't any extra blobs trying to be published. + // Calling the BLOBHASH opcode with an index > # blobs - 1 yields bytes32(0) + bytes32 versionedHash = _getBlobVersionedHash(versionedHashIndex); + if (versionedHash != bytes32(0)) { + revert NonEmptyBlobVersionHash(versionedHashIndex); + } + } + + /// @notice Calls the point evaluation precompile and verifies the output + /// Verify p(z) = y given commitment that corresponds to the polynomial p(x) and a KZG proof. + /// Also verify that the provided commitment matches the provided versioned_hash. + /// + function _pointEvaluationPrecompile( + bytes32 _versionedHash, + bytes32 _openingPoint, + bytes calldata _openingValueCommitmentProof + ) internal view { + bytes memory precompileInput = abi.encodePacked(_versionedHash, _openingPoint, _openingValueCommitmentProof); + + (bool success, bytes memory data) = POINT_EVALUATION_PRECOMPILE_ADDR.staticcall(precompileInput); + + // We verify that the point evaluation precompile call was successful by testing the latter 32 bytes of the + // response is equal to BLS_MODULUS as defined in https://eips.ethereum.org/EIPS/eip-4844#point-evaluation-precompile + if (!success) { + revert PointEvalCallFailed(precompileInput); + } + (, uint256 result) = abi.decode(data, (uint256, uint256)); + if (result != BLS_MODULUS) { + revert PointEvalFailed(abi.encode(result)); + } + } + + function _getBlobVersionedHash(uint256 _index) internal view virtual returns (bytes32 versionedHash) { + assembly { + versionedHash := blobhash(_index) + } + } +} diff --git a/da-contracts/contracts/da-layers/avail/AvailAttestationLib.sol b/da-contracts/contracts/da-layers/avail/AvailAttestationLib.sol new file mode 100644 index 000000000..2f6942446 --- /dev/null +++ b/da-contracts/contracts/da-layers/avail/AvailAttestationLib.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.24; + +import {IVectorx} from "./IVectorx.sol"; +import {IAvailBridge} from "./IAvailBridge.sol"; + +abstract contract AvailAttestationLib { + struct AttestationData { + uint32 blockNumber; + uint128 leafIndex; + } + + IAvailBridge public bridge; + IVectorx public vectorx; + + /// @dev Mapping from attestation leaf to attestation data. + /// It is necessary for recovery of the state from the onchain data. + mapping(bytes32 => AttestationData) public attestations; + + error InvalidAttestationProof(); + + constructor(IAvailBridge _bridge) { + bridge = _bridge; + vectorx = bridge.vectorx(); + } + + function _attest(IAvailBridge.MerkleProofInput memory input) internal virtual { + if (!bridge.verifyBlobLeaf(input)) revert InvalidAttestationProof(); + attestations[input.leaf] = AttestationData( + vectorx.rangeStartBlocks(input.rangeHash) + uint32(input.dataRootIndex) + 1, + uint128(input.leafIndex) + ); + } +} diff --git a/da-contracts/contracts/da-layers/avail/AvailL1DAValidator.sol b/da-contracts/contracts/da-layers/avail/AvailL1DAValidator.sol new file mode 100644 index 000000000..55783bf12 --- /dev/null +++ b/da-contracts/contracts/da-layers/avail/AvailL1DAValidator.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +// solhint-disable gas-custom-errors, reason-string + +import {IL1DAValidator, L1DAValidatorOutput} from "../../IL1DAValidator.sol"; +import {IAvailBridge} from "./IAvailBridge.sol"; +import {AvailAttestationLib} from "./AvailAttestationLib.sol"; + +contract AvailL1DAValidator is IL1DAValidator, AvailAttestationLib { + error InvalidValidatorOutputHash(); + + constructor(IAvailBridge _availBridge) AvailAttestationLib(_availBridge) {} + + function checkDA( + uint256, // _chainId + uint256, // _batchNumber + bytes32 l2DAValidatorOutputHash, // _l2DAValidatorOutputHash + bytes calldata operatorDAInput, + uint256 maxBlobsSupported + ) external returns (L1DAValidatorOutput memory output) { + output.stateDiffHash = bytes32(operatorDAInput[:32]); + + IAvailBridge.MerkleProofInput memory input = abi.decode(operatorDAInput[32:], (IAvailBridge.MerkleProofInput)); + if (l2DAValidatorOutputHash != keccak256(abi.encodePacked(output.stateDiffHash, input.leaf))) + revert InvalidValidatorOutputHash(); + _attest(input); + + // The rest of the fields that relate to blobs are empty. + output.blobsLinearHashes = new bytes32[](maxBlobsSupported); + output.blobsOpeningCommitments = new bytes32[](maxBlobsSupported); + } +} diff --git a/da-contracts/contracts/da-layers/avail/DummyAvailBridge.sol b/da-contracts/contracts/da-layers/avail/DummyAvailBridge.sol new file mode 100644 index 000000000..8fe184439 --- /dev/null +++ b/da-contracts/contracts/da-layers/avail/DummyAvailBridge.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IAvailBridge} from "./IAvailBridge.sol"; +import {IVectorx} from "./IVectorx.sol"; +import {DummyVectorX} from "./DummyVectorX.sol"; + +contract DummyAvailBridge is IAvailBridge { + IVectorx public vectorxContract; + + constructor() { + vectorxContract = new DummyVectorX(); + } + + function vectorx() external view returns (IVectorx) { + return vectorxContract; + } + + function verifyBlobLeaf(MerkleProofInput calldata) external view returns (bool) { + return true; + } +} diff --git a/da-contracts/contracts/da-layers/avail/DummyVectorX.sol b/da-contracts/contracts/da-layers/avail/DummyVectorX.sol new file mode 100644 index 000000000..eccee235b --- /dev/null +++ b/da-contracts/contracts/da-layers/avail/DummyVectorX.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IVectorx} from "./IVectorx.sol"; + +contract DummyVectorX is IVectorx { + function rangeStartBlocks(bytes32) external view returns (uint32 startBlock) { + return 1; + } +} diff --git a/da-contracts/contracts/da-layers/avail/IAvailBridge.sol b/da-contracts/contracts/da-layers/avail/IAvailBridge.sol new file mode 100644 index 000000000..fb44b91b2 --- /dev/null +++ b/da-contracts/contracts/da-layers/avail/IAvailBridge.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.24; + +import {IVectorx} from "./IVectorx.sol"; + +interface IAvailBridge { + // solhint-disable-next-line gas-struct-packing + struct Message { + // single-byte prefix representing the message type + bytes1 messageType; + // address of message sender + bytes32 from; + // address of message receiver + bytes32 to; + // origin chain code + uint32 originDomain; + // destination chain code + uint32 destinationDomain; + // data being sent + bytes data; + // nonce + uint64 messageId; + } + + struct MerkleProofInput { + // proof of inclusion for the data root + bytes32[] dataRootProof; + // proof of inclusion of leaf within blob/bridge root + bytes32[] leafProof; + // abi.encodePacked(startBlock, endBlock) of header range commitment on vectorx + bytes32 rangeHash; + // index of the data root in the commitment tree + uint256 dataRootIndex; + // blob root to check proof against, or reconstruct the data root + bytes32 blobRoot; + // bridge root to check proof against, or reconstruct the data root + bytes32 bridgeRoot; + // leaf being proven + bytes32 leaf; + // index of the leaf in the blob/bridge root tree + uint256 leafIndex; + } + + function vectorx() external view returns (IVectorx vectorx); + function verifyBlobLeaf(MerkleProofInput calldata input) external view returns (bool); +} diff --git a/da-contracts/contracts/da-layers/avail/IVectorx.sol b/da-contracts/contracts/da-layers/avail/IVectorx.sol new file mode 100644 index 000000000..5b7951a52 --- /dev/null +++ b/da-contracts/contracts/da-layers/avail/IVectorx.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.24; + +interface IVectorx { + function rangeStartBlocks(bytes32 rangeHash) external view returns (uint32 startBlock); +} diff --git a/da-contracts/foundry.toml b/da-contracts/foundry.toml new file mode 100644 index 000000000..6f29a31cc --- /dev/null +++ b/da-contracts/foundry.toml @@ -0,0 +1,32 @@ +[profile.default] +src = 'contracts' +out = 'out' +libs = ['node_modules', 'lib'] +remappings = [ + "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", + "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/", + "l2-contracts/=../l2-contracts/contracts/" +] +allow_paths = ["../l2-contracts/contracts"] +fs_permissions = [ + { access = "read", path = "../system-contracts/bootloader/build/artifacts" }, + { access = "read", path = "../system-contracts/artifacts-zk/contracts-preprocessed" }, + { access = "read", path = "../l2-contracts/artifacts-zk/" }, + { access = "read", path = "./script-config" }, + { access = "read-write", path = "./script-out" }, + { access = "read", path = "./out" } +] +cache_path = 'cache-forge' +test = 'test/foundry' +solc_version = "0.8.24" +evm_version = "cancun" +ignored_error_codes = [ + "missing-receive-ether", + "code-size", +] +ignored_warnings_from = [ + "test", + "contracts/dev-contracts" +] + +# See more config options https://github.com/foundry-rs/foundry/tree/master/crates/config diff --git a/da-contracts/hardhat.config.ts b/da-contracts/hardhat.config.ts new file mode 100644 index 000000000..884dc43d3 --- /dev/null +++ b/da-contracts/hardhat.config.ts @@ -0,0 +1,57 @@ +import "@nomiclabs/hardhat-ethers"; +import "@nomiclabs/hardhat-etherscan"; +import "@nomiclabs/hardhat-waffle"; +import "hardhat-contract-sizer"; +import "hardhat-gas-reporter"; +import "hardhat-typechain"; +import "solidity-coverage"; + +// If no network is specified, use the default config +if (!process.env.CHAIN_ETH_NETWORK) { + // eslint-disable-next-line @typescript-eslint/no-var-requires + require("dotenv").config(); +} + +export default { + defaultNetwork: "env", + solidity: { + version: "0.8.24", + settings: { + optimizer: { + enabled: true, + runs: 9999999, + }, + outputSelection: { + "*": { + "*": ["storageLayout"], + }, + }, + evmVersion: "cancun", + }, + }, + contractSizer: { + runOnCompile: false, + except: ["dev-contracts", "zksync/libraries", "common/libraries"], + }, + paths: { + sources: "./contracts", + }, + networks: { + env: { + url: process.env.ETH_CLIENT_WEB3_URL?.split(",")[0], + }, + hardhat: { + allowUnlimitedContractSize: false, + forking: { + url: "https://eth-goerli.g.alchemy.com/v2/" + process.env.ALCHEMY_KEY, + enabled: process.env.TEST_CONTRACTS_FORK === "1", + }, + }, + }, + etherscan: { + apiKey: process.env.MISC_ETHERSCAN_API_KEY, + }, + gasReporter: { + enabled: true, + }, +}; diff --git a/da-contracts/package.json b/da-contracts/package.json new file mode 100644 index 000000000..ec4ca3b45 --- /dev/null +++ b/da-contracts/package.json @@ -0,0 +1,66 @@ +{ + "name": "da-contracts", + "version": "0.1.0", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "devDependencies": { + "@nomiclabs/hardhat-ethers": "^2.0.0", + "@nomiclabs/hardhat-etherscan": "^3.1.0", + "@nomiclabs/hardhat-waffle": "^2.0.0", + "@openzeppelin/contracts": "4.9.5", + "@openzeppelin/contracts-upgradeable": "4.9.5", + "@typechain/ethers-v5": "^2.0.0", + "@types/argparse": "^1.0.36", + "@types/chai": "^4.2.21", + "@types/chai-as-promised": "^7.1.4", + "@types/mocha": "^8.2.3", + "argparse": "^1.0.10", + "axios": "^0.21.1", + "chai": "^4.3.10", + "chai-as-promised": "^7.1.1", + "chalk": "^4.1.0", + "collections": "^5.1.12", + "commander": "^8.3.0", + "eslint": "^8.51.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-prettier": "^5.0.1", + "ethereum-waffle": "^4.0.10", + "ethereumjs-abi": "^0.6.8", + "ethers": "^5.7.0", + "ethjs": "^0.4.0", + "fs": "^0.0.1-security", + "handlebars": "^4.7.6", + "hardhat": "=2.22.2", + "hardhat-contract-sizer": "^2.0.2", + "hardhat-gas-reporter": "^1.0.9", + "hardhat-typechain": "^0.3.3", + "jsonwebtoken": "^8.5.1", + "markdownlint-cli": "^0.33.0", + "merkletreejs": "^0.3.11", + "mocha": "^9.0.2", + "path": "^0.12.7", + "querystring": "^0.2.0", + "solc": "0.8.17", + "solhint": "^3.6.2", + "solidity-coverage": "^0.8.5", + "ts-generator": "^0.1.1", + "ts-node": "^10.1.0", + "typechain": "^4.0.0", + "typescript": "^4.6.4", + "zksync-ethers": "5.8.0-beta.5" + }, + "scripts": { + "build": "hardhat compile ", + "build:foundry": "forge build", + "clean": "hardhat clean", + "clean:foundry": "forge clean", + "verify": "hardhat run --network env scripts/verify.ts" + }, + "dependencies": { + "dotenv": "^16.0.3", + "solhint-plugin-prettier": "^0.0.5" + } +} diff --git a/da-contracts/slither.config.json b/da-contracts/slither.config.json new file mode 100644 index 000000000..0db8465a6 --- /dev/null +++ b/da-contracts/slither.config.json @@ -0,0 +1,11 @@ +{ + "filter_paths": "(contracts/dev-contracts|lib|node_modules)", + "detectors_to_exclude": "assembly,solc-version,low-level-calls,conformance-to-solidity-naming-conventions,incorrect-equality,uninitialized-local", + "exclude_dependencies": true, + "compile_force_framework": "foundry", + "exclude_medium": false, + "exclude_high": false, + "exclude_low": true, + "exclude_informational": true, + "exclude_optimization": true +} diff --git a/da-contracts/tsconfig.json b/da-contracts/tsconfig.json new file mode 100644 index 000000000..41d8e3fa8 --- /dev/null +++ b/da-contracts/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "types": ["node", "mocha"], + "downlevelIteration": true, + "resolveJsonModule": true + } +} diff --git a/docs/Overview.md b/docs/Overview.md deleted file mode 100644 index 1db994729..000000000 --- a/docs/Overview.md +++ /dev/null @@ -1,304 +0,0 @@ -# Overview - -ZKsync Era is a permissionless general-purpose ZK rollup. Similar to many L1 blockchains and sidechains it enables -deployment and interaction with Turing-complete smart contracts. - -- L2 smart contracts are executed on a zkEVM. -- zkEVM bytecode is different from the L1 EVM. -- There is a Solidity and Vyper compilers for L2 smart contracts. -- There is a standard way to pass messages between L1 and L2. That is a part of the protocol. -- There is no escape hatch mechanism yet, but there will be one. - -All data that is needed to restore the L2 state are also pushed on-chain. There are two approaches, publishing inputs of -L2 transactions on-chain and publishing the state transition diff. ZKsync follows the second option. - -See the [documentation](https://era.zksync.io/docs/dev/fundamentals/rollups.html) to read more! - -## Glossary - -- **Governor** - a privileged address that controls the upgradability of the network and sets other privileged - 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. -- **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. - -### L1 Smart contracts - -#### Diamond - -Technically, this L1 smart contract acts as a connector between Ethereum (L1) and ZKsync (L2). This contract checks the -validity proof and data availability, handles L2 <-> L1 communication, finalizes L2 state transition, and more. - -There are also important contracts deployed on the L2 that can also execute logic called _system contracts_. Using L2 -<-> L1 communication can affect both the L1 and the L2. - -#### DiamondProxy - -The main contract uses [EIP-2535](https://eips.ethereum.org/EIPS/eip-2535) diamond proxy pattern. It is an in-house -implementation that is inspired by the [mudgen reference implementation](https://github.com/mudgen/Diamond). It has no -external functions, only the fallback that delegates a call to one of the facets (target/implementation contract). So -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 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 - -It is a one-function contract that implements the logic of initializing a diamond proxy. It is called only once on the -diamond constructor and is not saved in the diamond as a facet. - -Implementation detail - function returns a magic value just like it is designed in -[EIP-1271](https://eips.ethereum.org/EIPS/eip-1271), but the magic value is 32 bytes in size. - -#### 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. - -#### AdminFacet - -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. - -Each upgrade consists of two steps: - -- Upgrade Proposal - The governor can schedule upgrades in two different manners: - - Fully transparent data. All implementation contracts and migration contracts are known to the community. The governor must wait - for the timelock to execute the upgrade. - - Shadow upgrade. The governor only shows the commitment for the upgrade. The upgrade can be executed only with security council - approval without timelock. -- Upgrade execution - perform the upgrade that was proposed. - -#### MailboxFacet - -The facet that handles L2 <-> L1 communication, an overview for which can be found in -[docs](https://era.zksync.io/docs/dev/developer-guides/bridging/l1-l2-interop.html). - -The Mailbox performs three functions: - -- L1 <-> L2 communication. -- Bridging native Ether to the L2. -- Censorship resistance mechanism (not yet implemented). - -L1 -> L2 communication is implemented as requesting an L2 transaction on L1 and executing it on L2. This means a user -can call the function on the L1 contract to save the data about the transaction in some queue. Later on, a validator can -process it on L2 and mark them as processed on the L1 priority queue. Currently, it is used for sending information from -L1 to L2 or implementing multi-layer protocols. - -_NOTE_: While user requests the transaction from L1, the initiated transaction on L2 will have such a `msg.sender`: - -```solidity - address sender = msg.sender; - if (sender != tx.origin) { - sender = AddressAliasHelper.applyL1ToL2Alias(msg.sender); - } -``` - -where - -```solidity -uint160 constant offset = uint160(0x1111000000000000000000000000000000001111); - -function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) { - unchecked { - l2Address = address(uint160(l1Address) + offset); - } -} -``` - -For most of the rollups the address aliasing needs to prevent cross-chain exploits that would otherwise be possible if -we simply reused the same L1 addresses as the L2 sender. In ZKsync Era address derivation rule is different from the -Ethereum, so cross-chain exploits are already impossible. However, ZKsync Era may add full EVM support in the future, so -applying address aliasing leave room for future EVM compatibility. - -The L1 -> L2 communication is also used for bridging ether. The user should include a `msg.value` when initiating a -transaction request on the L1 contract. Before executing a transaction on L2, the specified address will be credited -with the funds. To withdraw funds user should call `withdraw` function on the `L2EtherToken` system contracts. This will -burn the funds on L2, allowing the user to reclaim them through the `finalizeEthWithdrawal` function on the -`MailboxFacet`. - -L2 -> L1 communication, in contrast to L1 -> L2 communication, is based only on transferring the information, and not on -the transaction execution on L1. - -From the L2 side, there is a special zkEVM opcode that saves `l2ToL1Log` in the L2 batch. A validator will send all -`l2ToL1Logs` when sending an L2 batch to the L1 (see `ExecutorFacet`). Later on, users will be able to both read their -`l2ToL1logs` on L1 and _prove_ that they sent it. - -From the L1 side, for each L2 batch, a Merkle root with such logs in leaves is calculated. Thus, a user can provide -Merkle proof for each `l2ToL1Logs`. - -_NOTE_: For each executed L1 -> L2 transaction, the system program necessarily sends an L2 -> L1 log. To verify the -execution status user may use the `proveL1ToL2TransactionStatus`. - -_NOTE_: The `l2ToL1Log` structure consists of fixed-size fields! Because of this, it is inconvenient to send a lot of -data from L2 and to prove that they were sent on L1 using only `l2ToL1log`. To send a variable-length message we use -this trick: - -- One of the system contracts accepts an arbitrary length message and sends a fixed length message with parameters - `senderAddress == this`, `isService == true`, `key == msg.sender`, `value == keccak256(message)`. -- The contract on L1 accepts all sent messages and if the message came from this system contract it requires that the - preimage of `value` be provided. - -#### L1 -> L2 Transaction filtering - -There is a mechanism for applying custom filters to the L1 -> L2 communication. It is achieved by having an address of -the `TransactionFilterer` contract in the `ZkSyncHyperchainStorage`. If the filterer exists, it is being called in -the `Mailbox` facet with the tx details and has to return whether the transaction can be executed or not. The filterer -has to implement the `ITransactionFilterer` interface. The ones intended to use this feature, have to deploy the -contract that implements `ITransactionFilterer` and use `setTransactionFilterer` function of `AdminFacet` to set the -address of the transaction filterer. The same function called with `0` address will disable the filtering. - -#### ExecutorFacet - -A contract that accepts L2 batches, enforces data availability and checks the validity of zk-proofs. - -The state transition is divided into three stages: - -- `commitBatches` - check L2 batch timestamp, process the L2 logs, save data for a batch, and prepare data for zk-proof. -- `proveBatches` - validate zk-proof. -- `executeBatches` - finalize the state, marking L1 -> L2 communication processing, and saving Merkle tree with L2 logs. - -Each L2 -> L1 system log will have a key that is part of the following: - -```solidity -enum SystemLogKey { - L2_TO_L1_LOGS_TREE_ROOT_KEY, - TOTAL_L2_TO_L1_PUBDATA_KEY, - STATE_DIFF_HASH_KEY, - PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY, - PREV_BATCH_HASH_KEY, - CHAINED_PRIORITY_TXN_HASH_KEY, - NUMBER_OF_LAYER_1_TXS_KEY, - EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY -} -``` - -When a batch is committed, we process L2 -> L1 system logs. Here are the invariants that are expected there: - -- In a given batch there will be either 7 or 8 system logs. The 8th log is only required for a protocol upgrade. -- There will be a single log for each key that is contained within `SystemLogKey` -- Three logs from the `L2_TO_L1_MESSENGER` with keys: -- `L2_TO_L1_LOGS_TREE_ROOT_KEY` -- `TOTAL_L2_TO_L1_PUBDATA_KEY` -- `STATE_DIFF_HASH_KEY` -- Two logs from `L2_SYSTEM_CONTEXT_SYSTEM_CONTRACT_ADDR` with keys: - - `PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY` - - `PREV_BATCH_HASH_KEY` -- Two or three logs from `L2_BOOTLOADER_ADDRESS` with keys: - - `CHAINED_PRIORITY_TXN_HASH_KEY` - - `NUMBER_OF_LAYER_1_TXS_KEY` - - `EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY` -- None logs from other addresses (may be changed in the future). - -#### Bridges - -Bridges are completely separate contracts from the Diamond. They are a wrapper for L1 <-> L2 communication on contracts -on both L1 and L2. Upon locking assets on one layer, a request is sent to mint these bridged assets on the other layer. -Upon burning assets on one layer, a request is sent to unlock them on the other. - -Unlike the native Ether bridging, all other assets can be bridged by the custom implementation relying on the trustless -L1 <-> L2 communication. - -##### L1ERC20Bridge - -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. - -##### L1SharedBridge - -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. - -- `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 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. - -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. - -Thus, the deposit is made in one transaction, and the user receives L2 WETH that can be unwrapped to ETH. - -##### L2SharedBridge - -The L2 counterpart of the L1 Shared 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 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 - -An intermediate smart contract between the validator EOA account and the ZKsync smart contract. Its primary purpose is -to provide a trustless means of delaying batch execution without modifying the main ZKsync contract. 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. - -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. - -When the validator calls `commitBatches`, the same calldata will be propagated 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 committed by the validator to enforce a delay between committing and -execution of batches. Then, the validator can prove the already committed batches regardless of the mentioned timestamp, -and again the same calldata (related to the `proveBatches` function) will be propagated to the ZKsync contract. After, -the `delay` is elapsed, the validator is allowed to call `executeBatches` to propagate the same calldata to ZKsync -contract. - -### L2 specifics - -#### Deployment - -The L2 deployment process is different from Ethereum. - -In L1, the deployment always goes through two opcodes `create` and `create2`, each of which provides its address -derivation. The parameter of these opcodes is the so-called "init bytecode" - a bytecode that returns the bytecode to be -deployed. This works well in L1 but is suboptimal for L2. - -In the case of L2, there are also two ways to deploy contracts - `create` and `create2`. However, the expected input -parameters for `create` and `create2` are different. It accepts the hash of the bytecode, rather than the full bytecode. -Therefore, users pay less for contract creation and don't need to send the full contract code by the network upon -deploys. - -A good question could be, _how does the validator know the preimage of the bytecode hashes to execute the code?_ Here -comes the concept of factory dependencies! Factory dependencies are a list of bytecode hashes whose preimages were shown -on L1 (data is always available). Such bytecode hashes can be deployed, others - no. Note that they can be added to the -system by either L2 transaction or L1 -> L2 communication, where you can specify the full bytecode and the system will -mark it as known and allow you to deploy it. - -Besides that, due to the bytecode differences for L1 and L2 contracts, address derivation is different. This applies to -both `create` and `create2` and means that contracts deployed on the L1 cannot have a collision with contracts deployed -on the L2. Please note that EOA address derivation is the same as on Ethereum. - -Thus: - -- L2 contracts are deployed by bytecode hash, not by full bytecode -- Factory dependencies - list of bytecode hashes that can be deployed on L2 -- Address derivation for `create`/`create2` on L1 and L2 is different diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..1dd0dd635 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,64 @@ +# ZK Stack contracts specs + +The order of the files here only roughly represents the order of reading. A lot of topics are intertwined, so it is recommended to read everything first to have a complete picture and then refer to specific documents for more details. + +- [Glossary](./glossary.md) +- [Overview](./overview.md) +- Contracts of an individual chain + - [ZK Chain basics](./settlement_contracts/zkchain_basics.md) + - Data availability + - [Custom DA support](./settlement_contracts/data_availability/custom_da.md) + - [Rollup DA support](./settlement_contracts/data_availability/rollup_da.md) + - [Standard pubdata format](./settlement_contracts/data_availability/standard_pubdata_format.md) + - [State diff compression v1 spec](./settlement_contracts/data_availability/state_diff_compression_v1_spec.md) + - L1->L2 transaction handling + - [Processing of L1->L2 transactions](./settlement_contracts/priority_queue/processing_of_l1-l2_txs.md) + - [Priority queue](./settlement_contracts/priority_queue/priority-queue.md) + - Consensus + - [Consensus Registry](./consensus/consensus-registry.md) +- Chain Management + - [Chain type manager](./chain_management/chain_type_manager.md) + - [Admin role](./chain_management/admin_role.md) + - [Chain genesis](./chain_management/chain_genesis.md) + - [Standard Upgrade process](./chain_management/upgrade_process.md) +- Bridging + - Bridgehub + - [Overview of the bridgehub functionality](./bridging/bridgehub/overview.md) + - [Asset Router](./bridging/asset_router/overview.md) +- L2 System Contracts + - [System contracts bootloader description](./l2_system_contracts/system_contracts_bootloader_description.md) + - [Batches and blocks on ZKsync](./l2_system_contracts/batches_and_blocks_on_zksync.md) + - [Elliptic curve precompiles](./l2_system_contracts/elliptic_curve_precompiles.md) + - [ZKsync fee model](./l2_system_contracts/zksync_fee_model.md) +- Gateway + - [General overview](./gateway/overview.md) + - [Chain migration](./gateway/chain_migration.md) + - [L1->L3 messaging via gateway](./gateway/messaging_via_gateway.md) + - [L3->L1 messaging via gateway](./gateway/nested_l3_l1_messaging.md) + - [Gateway protocol versioning](./gateway/gateway_protocol_upgrades.md) + - [DA handling on Gateway](./gateway/gateway_da.md) +- Upgrade history + - [Gateway upgrade diff](./upgrade_history/gateway_upgrade/gateway_diff_review.md) + - [Gateway upgrade process](./upgrade_history/gateway_upgrade/upgrade_process.md) + +![Reading order](./img/reading_order.png) + +## Repo structure + +The repository contains the following sections: + +- [gas-bound-caller](../gas-bound-caller) that contains `GasBoundCaller` utility contract implementation. You can read more about it in its README. +- [da-contracts](../da-contracts/). There are implementations for [DA validation](./settlement_contracts/data_availability/custom_da.md) contracts that should be deployed on L1 only. +- [l1-contracts](../l1-contracts/). Despite the legacy name, it contains contracts that are deployed both on L1 and on L2. This folder encompasses bridging, ZK chain contracts, the contracts for chain admin, etc. The name is historical due to the fact that these contracts were usually deployed on L1 only. However with Gateway, settlement and bridging-related contracts will be deployed on both EVM and eraVM environment. Also, bridging has been unified between L1 and L2 in many places and so keeping everything in one project allows to avoid code duplication. +- [l2-contracts](../l2-contracts/). Contains contracts that are deployed only on L2. +- [system-contracts](../system-contracts/). Contains system contracts or predeployed L2 contracts. + +## For auditors: Invariants/tricky places to look out for + +This section is for auditors of the codebase. It includes some of the important invariants that the system relies on and which if broken could have bad consequences. + +- Assuming that the accepting CTM is correct & efficient, the L1→GW part of the L1→GW→L3 transaction never fails. It is assumed that the provided max amount for gas is always enough for any transaction that can realistically come from L1. +- GW → L1 migration never fails. If it is possible to get into a state where the migration is not possible to finish, then the chain is basically lost. There are some exceptions where for now it is the expected behavior. (check out the “Migration invariants & protocol upgradability” section) +- The general consistency of chains when migration between different settlement layers is done. Including the feasibility of emergency upgrades, etc. I.e. whether the whole system is thought-through. +- Preimage attacks in the L3→L1 tree, we apply special prefixes to ensure that the tree structure is fixed, i.e. all logs are 88 bytes long (this is for backwards compatibility reasons). For batch leaves and chain id leaves we use special prefixes. +- Data availability guarantees. Whether rollup users can always restore all their storage slots, etc. An example of a potential tricky issue can be found in “Security notes for Gateway-based rollups” [in this document](./gateway/gateway_da.md). diff --git a/docs/bridging/asset_router/asset_router.md b/docs/bridging/asset_router/asset_router.md new file mode 100644 index 000000000..99f678f58 --- /dev/null +++ b/docs/bridging/asset_router/asset_router.md @@ -0,0 +1,47 @@ +# AssetRouters (L1/L2) and NativeTokenVault + +[back to readme](../../README.md) + +The main job of the asset router is to be the central point of coordination for bridging. All crosschain token bridging is done between asset routers only and once the message reaches asset router, it then routes it to the corresponding asset handler. + +In order to make this easier, all L2 chains have the asset router located on the same address on every chain. It is `0x10003` and it is pre-deployed contract. More on how it is deployed can be seen in the [Chain Genesis](../../chain_management/chain_genesis.md) section. + +The endgame is to have L1 asset router have the same functionality as the L2 one. This is not the case yet, but some progress has been made: L2AssetRouter can now bridge L2-native assets to L1, from which it could be bridged to other chains in the ecosystem. + +The specifics of the L2AssetRouter is the need to interact with the previously deployed L2SharedBridgeLegacy if it was already present. It has less “rights” than the L1AssetRouter: at the moment it is assumed that all asset deployment trackers are from L1, the only way to register an asset handler on L2 is to make an L1→L2 transaction. + +> Note, that today registering new asset deployment trackers will be permissioned, but the plan is to make it permissionless in the future + +The specifics of the L1AssetRouter come from the need to be backwards compatible with the old L1SharedBridge. Yes, it will not share the same storage, but it will inherit the need to be backwards compatible with the current SDK. Also, L1AssetRouter needs to facilitate L1-only operations, such as recovering from failed deposits. + +Also, L1AssetRouter is the only base token bridge contract that can participate in initiation of cross chain transactions via the bridgehub. This will change in the future with the support of interop. + +### L1Nullifier + +While the endgoal is to unify L1 and L2 asset routers, in reality, it may not be that easy: while L2 asset routers get called by L1→L2 transactions, L1 ones don't and require manual finalization of transactions, which involves proof verification, etc. To move this logic outside of the L1AssetRouter, it was moved into a separate L1Nullifier contract. + +_This is the contract the previous L1SharedBridge will be upgraded to, so it should have the backwards compatible storage._ + +### NativeTokenVault (L1/L2) + +NativeTokenVault is an asset handler that is available on all chains and is also predeployed. It is provides the functionality of the most basic bridging: locking funds on one chain and minting the bridged equivalent on the other one. On L2 chains NTV is predeployed at the `0x10004` address. + +The L1 and L2 versions of the NTV are almost identical in functionality, the main differences come from the differences of the deployment functionality in L1 and L2 envs, where the former uses standard CREATE2 and the latter uses low level calls to `CONTRACT_DEPLOYER`system contract. + +Also, the L1NTV has the following specifics: + +- It operates the `chainBalance` mapping, ensuring that the chains do not go beyond their balances. +- It allows recovering from failed L1→L2 transfers. +- It needs to both be able to retrieve funds from the former L1SharedBridge (now this contract has L1Nullifier in its place), but also needs to support the old SDK that gives out allowance to the “l1 shared bridge” value returned from the API, i.e. in our case this is will the L1AssetRouter. + +### L2SharedBridgeLegacy + +L2AssetRouter has to be pre-deployed onto a specific address. The old L2SharedBridge will be upgraded to L2SharedBridgeLegacy contract. The main purpose of this contract is to ensure compatibility with the incoming deposits and re-route them to the shared bridge. + +This contract is never deployed for new chains. + +### Summary + +![image.png](./img/bridge_contracts.png) + +> New bridge contracts diff --git a/docs/bridging/asset_router/img/bridge_contracts.png b/docs/bridging/asset_router/img/bridge_contracts.png new file mode 100644 index 000000000..f3f6802cd Binary files /dev/null and b/docs/bridging/asset_router/img/bridge_contracts.png differ diff --git a/docs/bridging/asset_router/img/custom_asset_handler_registration.png b/docs/bridging/asset_router/img/custom_asset_handler_registration.png new file mode 100644 index 000000000..a57e69f92 Binary files /dev/null and b/docs/bridging/asset_router/img/custom_asset_handler_registration.png differ diff --git a/docs/bridging/asset_router/overview.md b/docs/bridging/asset_router/overview.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/bridging/bridgehub/img/L1_L2_tx_processing_on_L2.png b/docs/bridging/bridgehub/img/L1_L2_tx_processing_on_L2.png new file mode 100644 index 000000000..cfe75d5cc Binary files /dev/null and b/docs/bridging/bridgehub/img/L1_L2_tx_processing_on_L2.png differ diff --git a/docs/bridging/bridgehub/img/gateway_architecture.png b/docs/bridging/bridgehub/img/gateway_architecture.png new file mode 100644 index 000000000..a9302ec7e Binary files /dev/null and b/docs/bridging/bridgehub/img/gateway_architecture.png differ diff --git a/docs/bridging/bridgehub/img/requestL2TransactionDirect.png b/docs/bridging/bridgehub/img/requestL2TransactionDirect.png new file mode 100644 index 000000000..95621fb7b Binary files /dev/null and b/docs/bridging/bridgehub/img/requestL2TransactionDirect.png differ diff --git a/docs/bridging/bridgehub/img/requestL2TransactionTwoBridges_depositEthToUSDC.png b/docs/bridging/bridgehub/img/requestL2TransactionTwoBridges_depositEthToUSDC.png new file mode 100644 index 000000000..12f2f116c Binary files /dev/null and b/docs/bridging/bridgehub/img/requestL2TransactionTwoBridges_depositEthToUSDC.png differ diff --git a/docs/bridging/bridgehub/img/requestL2TransactionTwoBridges_token.png b/docs/bridging/bridgehub/img/requestL2TransactionTwoBridges_token.png new file mode 100644 index 000000000..6cc290fde Binary files /dev/null and b/docs/bridging/bridgehub/img/requestL2TransactionTwoBridges_token.png differ diff --git a/docs/bridging/bridgehub/overview.md b/docs/bridging/bridgehub/overview.md new file mode 100644 index 000000000..a8d4ca5ba --- /dev/null +++ b/docs/bridging/bridgehub/overview.md @@ -0,0 +1,243 @@ +# BridgeHub & Asset Routers + +[back to readme](../../README.md) + +## Bridgehub as the main chain registry + +Bridgehub is the most important contract in the system, that stores: + +- A mapping from chainId to chains address +- A mapping from chainId to the CTM it belongs to. +- A mapping from chainId to its base token (i.e. the token that is used for paying fees) +- etc + +> Note sure what CTM is? Check our the [overview](../../settlement_contracts/zkchain_basics.md) for contracts for settlement layer. + +Overall, it is the main registry for all the contracts. Note, that a clone of Bridgehub is also deployed on each L2 chain, but this clone is only used on settlement layers. All the in all, the architecture of the entire ecosystem can be seen below: + +![Contracts](./img/gateway_architecture.png) + +> This document will not cover how ZK Gateway works, you can check it out in [a separate doc](../../gateway/overview.md). + +## Asset router as the main asset bridging entrypoint + +The main entry for passing value between chains is the AssetRouter, it is responsible for facilitating bridging between multiple asset types. To read more in detail on how it works, please refer to custom [asset bridging documentation](../asset_router/overview.md). + +For the purpose of this document, it is enough to treat the Asset Router as a blackbox that is responsible for processing escrowing funds on the source chain and minting them on the destination chain. + +> For those that are aware of the [previous ZKsync architecture](https://github.com/code-423n4/2024-03-zksync/blob/main/docs/Smart%20contract%20Section/L1%20ecosystem%20contracts.md), its role is similar to L1SharedBridge that we had before. Note, however, that it is a different contract with much enhanced functionality. Also, note that the L1SharedBridge will NOT be upgraded to the L1AssetRouter. For more details about migration, please check out [the migration doc](../../upgrade_history/gateway_upgrade/gateway_diff_review.md). + +### Handling base tokens + +On L2, _a base token_ (not to be consfused with a _native token_, i.e. an ERC20 token with a main contract on the chain) is the one that is used for `msg.value` and it is managed at `L2BaseToken` system contract. We need its logic to be strictly defined in `L2BaseToken`, since the base asset is expected to behave the exactly the same as ether on EVM. For now this token contract does not support base minting and burning of the asset, nor further customization. + +In other words, in the current release base assets can only be transferred through `msg.value`. They can also only be minted when they are backed 1-1 on L1. + +## L1→L2 communication via `Bridgehub.requestL2TransactionDirect` + +L1→L2 communication allows users on L1 to create a request for a transaction to happen on L2. This is the primary censorship resistance mechanism. If you are interested, you can read more on L1→L2 communications [here](../../settlement_contracts/priority_queue/processing_of_l1-l2_txs.md), but for now just understanding that L1→L2 communication allows to request transactions to happen on L2 is enough. + +The L1→L2 communication is also the only way to mint a base asset at the moment. Fees to the operator as well as `msg.value` will be minted on `L2BaseToken` after the corresponding L1→L2 tx has been processed. + +To request an L1→L2 transaction, the `BridgeHub.requestL2TransactionDirect` function needs to be invoked. The user should pass the struct with the following parameters: + +```solidity +struct L2TransactionRequestDirect { + uint256 chainId; + uint256 mintValue; + address l2Contract; + uint256 l2Value; + bytes l2Calldata; + uint256 l2GasLimit; + uint256 l2GasPerPubdataByteLimit; + bytes[] factoryDeps; + address refundRecipient; +} +``` + +Most of the params are self-explanatory & replicate the logic of ZKsync Era. The only non-trivial fields are: + +- `mintValue` is the total amount of the base tokens that should be minted on L2 as the result of this transaction. The requirement is that `request.mintValue >= request.l2Value + request.l2GasLimit * derivedL2GasPrice(...)`, where `derivedL2GasPrice(...)` is the gas price to be used by this L1→L2 transaction. The exact price is defined by the ZKChain. + +Here is a quick guide on how this transaction is routed through the bridgehub. + +1. The bridgehub retrieves the `baseTokenAssetId` of the chain with the corresponding `chainId` and calls `L1AssetRouter.bridgehubDepositBaseToken` method. The `L1AssetRouter` will then use standard token depositing mechanism to burn/escrow the respective amount of the `baseTokenAssetId`. You can read more about it in [the asset router doc](../asset_router/overview.md). This step ensures that the baseToken will be backed 1-1 on L1. + +2. After that, it just routes the corresponding call to the ZKChain with the corresponding `chainId` . It is now the responsibility of the ZKChain to validate that the transaction is correct and can be accepted by it. This validation includes, but not limited to: + + - The fact that the user paid enough funds for the transaction (basically `request.l2GasLimit * derivedL2GasPrice(...) + request.l2Value >= request.mintValue`. + - The fact the transaction is always executable (the `request.l2GasLimit` is not high enough). + - etc. + +3. After the ZKChain validates the tx, it includes it into its priority queue. Once the operator executes this transaction on L2, the `mintValue` of the baseToken will be minted on L2. The `derivedL2GasPrice(...) * gasUsed` will be given to the operator’s balance. The other funds can be routed either of the following way: + +If the transaction is successful, the `request.l2Value` will be minted on the `request.l2Contract` address (it can potentially transfer these funds within the transaction). The rest are minted to the `request.refundRecipient` address. In case the transaction is not successful, all of the base token will be minted to the `request.refundRecipient` address. These are the same rules as for the ZKsync Era. + +**_Diagram of the L1→L2 transaction flow on L1 for direct user calls, the baseToken can be ETH or an ERC20:_** + +![requestL2TransactionDirect (ETH) (2).png](./img/requestL2TransactionDirect.png) + +**_Diagram of the L1→L2 transaction flow on L2 (it is the same regardless of the baseToken):_** + +![requestL2TransactionTwoBridges](./img/requestL2TransactionTwoBridges_token.png) + +![L1-_L2 tx processing on L2.png](./img/L1_L2_tx_processing_on_L2.png) + +### Limitations of custom base tokens in the current release + +ZKsync Era uses ETH as a base token. Upon creation of an ZKChain other chains may want to use their own custom base tokens. Note, that for the current release all the possible base tokens are whitelisted. The other limitation is that all the base tokens must be backed 1-1 on L1 as well as they are solely implemented with `L2BaseToken` contract. In other words: + +- No custom logic is allowed on L2 for base tokens +- Base tokens can not be minted on L2 without being backed by the corresponding L1 amount. + +If someone wants to build a protocol that mints base tokens on L2, the option for now is to “mint” an infinite amount of those on L1, deposit on L2 and then give those out as a way to “mint”. We will update this in the future. + +## General architecture and initialization of SharedBridge for a new ZKChain + +Once the chain is created, its L2AssetRouter will be automatically deployed upon genesis. You can read more about it in the [Chain creation flow](../../chain_management/chain_genesis.md). + +## `requestL2TransactionTwoBridges` + +`L1AssetRouter` is used as the main "glue" for value bridging across chains. Whenever a token that is not native needs to be bridged between two chains an L1<>L2 transaction out of the name of an AssetRouter needs to be performed. For more details, check out the [asset router documentation](../asset_router/overview.md). But for this section it is enough to understand that we need to somehow make a transaction out of the name of `L1AssetRouter` to its L2 counterpart to deliver the message about certain amount of asset being bridged. + +> In the next paragraphs we will often refer to `L1AssetRouter` as performing something. It is good enough for understanding of how bridgehub functionality works. Under the hood though, it mainly serves as common entry that calls various asset handlers that are chosen based on asset id. You can read more about it in the [asset router documentation](../asset_router/asset_router.md). + +Let’s say that a ZKChain has ETH as its base token. Let’s say that the depositor wants to bridge USDC to that chain. We can not use `BridgeHub.requestL2TransactionDirect`, because it only takes base token `mintValue` and then starts an L1→L2 transaction rightaway out of the name of the user and not the `L1AssetRouter`. + +We need some way to atomically deposit both ETH and USDC to the shared bridge + start a transaction from `L1AssetRouter`. For that we have a separate function on `Bridgehub`: `BridgeHub.requestL2TransactionTwoBridges`. The reason behind the name “two bridges” is a bit historical: the transaction supposed compose to do actions with two bridges: the bridge responsible for base tokens and the second bridge responsible for any other token. + +Note, however, that only `L1AssetRouter` can be used to bridge base tokens. And the role of the second bridge can be played by any contract that supports the protocol desrcibed below. + +When calling `BridgeHub.requestL2TransactionTwoBridges` the following struct needs to be provided: + +```solidity +struct L2TransactionRequestTwoBridgesOuter { + uint256 chainId; + uint256 mintValue; + uint256 l2Value; + uint256 l2GasLimit; + uint256 l2GasPerPubdataByteLimit; + address refundRecipient; + address secondBridgeAddress; + uint256 secondBridgeValue; + bytes secondBridgeCalldata; +} +``` + +The first few fields are the same as for the simple L1→L2 transaction case. However there are three new fields: + +- `secondBridgeAddress` is the address of the bridge (or contract in general) which will need to perform the L1->L2 transaction. In this case it should be the same `L1AssetRouter` +- `secondBridgeValue` is the `msg.value` to be sent to the bridge which is responsible for the asset being deposited (in this case it is `L1AssetRouter` ). This can be used to deposit ETH to ZKChains that have base token that is not ETH. +- `secondBridgeCalldata` is the data to pass to the second contract. `L1AssetRouter` supports multiple formats of calldata, the list can be seen in the `bridgehubDeposit` function of the `L1AssetRouter`. + +The function will do the following: + +#### L1 + +1. It will deposit the `request.mintValue` of the ZKChain’s base token the same way as during a simple L1→L2 transaction. These funds will be used for funding the `l2Value` and the fee to the operator. +2. It will call the `secondBridgeAddress` (`L1AssetRouter`) once again and this time it will deposit the funds to the `L1AssetRouter`, but this time it will be deposit not to pay the fees, but rather for the sake of bridging the desired token. + +This call will return the parameters to call the l2 contract with (the address of the L2 bridge counterpart, the calldata and factory deps to call it with). 3. After the BridgeHub will call the ZKChain to add the corresponding L1→L2 transaction to the priority queue. 4. The BridgeHub will call the `SharedBridge` once again so that it can remember the hash of the corresponding deposit transaction. [This is needed in case the deposit fails](#claiming-failed-deposits). + +#### L2 + +1. After some time, the corresponding L1→L2 is created. +2. The L2AssetRouter will receive the message and re-route it to the asset handler of the bridged token. To read more about how it works, check out the [asset router documentation](../asset_router/overview.md). + +**_Diagram of a depositing ETH onto a chain with USDC as the baseToken. Note that some contract calls (like `USDC.transferFrom` are omitted for the sake of consiceness):_** + +![requestL2TransactionTwoBridges (SharedBridge) (1).png](./img/requestL2TransactionTwoBridges_depositEthToUSDC.png) + +## Generic usage of `BridgeHub.requestL2TransactionTwoBridges` + +`L1AssetRouter` is the only bridge that can handle base tokens. However, the `BridgeHub.requestL2TransactionTwoBridges` could be used by `secondBridgeAddress` on L1. A notable example of how it is done is how our [CTMDeploymentTracker](../../../l1-contracts/contracts/bridgehub/CTMDeploymentTracker.sol) uses it to register the correct CTM address on Gateway. You can read more about how Gateway works in [its documentation](../../gateway/overview.md). + +Let’s do a quick recap on how it works: + +When calling `BridgeHub.requestL2TransactionTwoBridges` the following struct needs to be provided: + +```solidity +struct L2TransactionRequestTwoBridgesOuter { + uint256 chainId; + uint256 mintValue; + uint256 l2Value; + uint256 l2GasLimit; + uint256 l2GasPerPubdataByteLimit; + address refundRecipient; + address secondBridgeAddress; + uint256 secondBridgeValue; + bytes secondBridgeCalldata; +} +``` + +- `secondBridgeAddress` is the address of the L1 contract that needs to perform the L1->L2 transaction. +- `secondBridgeValue` is the `msg.value` to be sent to the `secondBridgeAddress`. +- `secondBridgeCalldata` is the data to pass to the `secondBridgeAddress`. This can be interpreted any way it wants. + +1. Firstly, the Bridgehub will deposit the `request.mintValue` the same way as during a simple L1→L2 transaction. These funds will be used for funding the `l2Value` and the fee to the operator. +2. After that, the `secondBridgeAddress.bridgehubDeposit` with the following signature is called + +```solidity +struct L2TransactionRequestTwoBridgesInner { + // Should be equal to a constant `keccak256("TWO_BRIDGES_MAGIC_VALUE")) - 1` + bytes32 magicValue; + // The L2 contract to call + address l2Contract; + // The calldata to call it with + bytes l2Calldata; + // The factory deps to call it with + bytes[] factoryDeps; + // Just some 32-byte value that can be used for later processing + // It is called `txDataHash` as it *should* be used as a way to facilitate + // reclaiming failed deposits. + bytes32 txDataHash; +} + +function bridgehubDeposit( + uint256 _chainId, + // The actual user that does the deposit + address _prevMsgSender, + // The msg.value of the L1->L2 transaction to be created + uint256 _l2Value, + // Custom bridge-specific data + bytes calldata _data +) external payable returns (L2TransactionRequestTwoBridgesInner memory request); +``` + +Now the job of the contract will be to “validate” whether they are okay with the transaction to come. For instance, the `CTMDeploymentTracker` checks that the `_prevMsgSender` is the owner of `CTMDeploymentTracker` and has the necessary rights to perform the transaction out of the name of it. + +Ultimately, the correctly processed `bridgehubDeposit` function basically grants `BridgeHub` the right to create an L1→L2 transaction out of the name of the `secondBridgeAddress`. Since it is so powerful, the first returned value must be a magical constant that is equal to `keccak256("TWO_BRIDGES_MAGIC_VALUE")) - 1`. The fact that it was a somewhat non standard signature and a struct with the magical value is the major defense against “accidental” approvals to start a transaction out of the name of an account. + +Aside from the magical constant, the method should also return the information an L1→L2 transaction will start its call with: the `l2Contract` , `l2Calldata`, `factoryDeps`. It also should return the `txDataHash` field. The meaning `txDataHash` will be needed in the next paragraphs. But generally it can be any 32-byte value the bridge wants. + +1. After that, an L1→L2 transaction is invoked. Note, that the “trusted” `L1AssetRouter` has enforced that the baseToken was deposited correctly (again, the step (1) can _only_ be handled by the `L1AssetRouter`), while the second bridge can provide any data to call its L2 counterpart with. +2. As a final step, following function is called: + +```solidity +function bridgehubConfirmL2Transaction( + // `chainId` of the ZKChain + uint256 _chainId, + // the same value that was returned by `bridgehubDeposit` + bytes32 _txDataHash, + // the hash of the L1->L2 transaction + bytes32 _txHash +) external; +``` + +This function is needed for whatever actions are needed to be done after the L1→L2 transaction has been invoked. + +On `L1AssetRouter` it is used to remember the hash of each deposit transaction, so that later on, the funds could be returned to user if the `L1->L2` transaction fails. The `_txDataHash` is stored so that the whenever the users will want to reclaim funds from a failed deposit, they would provide the token and the amount as well as the sender to send the money to. + +## Claiming failed deposits + +In case a deposit fails, the `L1AssetRouter` allows users to recover the deposited funds by providing a proof that the corresponding transaction indeed failed. The logic is the same as in the current Era implementation. + +## Withdrawing funds from L2 + +Funds withdrawal is a similar way to how it is done currently on Era. + +The user needs to call the `L2AssetRouter.withdraw` function on L2, while providing the token they want to withdraw. This function would then calls the corresponding L2 asset handler and ask him to burn the funds. We expand a bit more about it in the [asset router documentation](../asset_router/overview.md). + +Note, however, that it is not the way to withdraw base token. To withdraw base token, `L2BaseToken.withdraw` needs to be called. + +After the batch with the withdrawal request has been executed, the user can finalize the withdrawal on L1 by calling `L1AssetRouter.finalizeWithdrawal`, where the user provides the proof of the corresponding withdrawal message. diff --git a/docs/chain_management/admin_role.md b/docs/chain_management/admin_role.md new file mode 100644 index 000000000..81585f748 --- /dev/null +++ b/docs/chain_management/admin_role.md @@ -0,0 +1,114 @@ +# Safe ChainAdmin management + +[back to readme](../README.md) + +While the ecosystem does a [decentralized trusted governance](https://blog.zknation.io/introducing-zk-nation/), each chain has its own Chain Admin. While the upgrade parameters are chosen by the governance, chain admin is still a powerful role and should be managed carefully. + +In this document we will explore what are the abilities of the ChainAdmin, how dangerous they are and how to mitigate potential issues. + +## General guidelines + +The system does not restrict in any way how the admin of the chain should be implemented. However special caution should be taken to keep it safe. + +The general guideline is that an admin of a ZK chain should be _at least_ a well-distributed multisig. Having it as an EOA is definitely a bad idea since having this address stolen can lead to [chain being permanently frozen](#setting-da-layer). + +Additional measures may be taken [to self-restrict](#proposed-modular-chainadmin-implementation) the ChainAdmin to ensure that some operations can be only done in safe fashion. + +Generally all the functionality of chain admin should be treated with maximal security and caution, and having hotkey separate roles in rare circuimstances, e.g. to call `setTokenMultiplier` in case of an ERC-20 based chain. + +## Chain Admin functionality + +### Setting validators for a chain + +The admin of a chain can call `ValidatorTimelock` on the settlement layer to add or remove validators, i.e. addresses that have the right to `commit`/`verify`/`execute` batches etc. + +The system is protected against malicious validators, they can never steal funds from users. However, this role is still relatively powerful: If the DA layer is not reliable, and a batch does get executed, the funds may be frozen. This is why the chains should be [cautious about DA layers that they use](#setting-da-layer). Note, that on L1 the `ValidatorTimelock` has 21h delay, while on Gateway this timelock will not be present. + +In case the malicious block has not been executed yet, it can be reverted. + +### Setting DA layer + +This is one of the most powerful settings that a chain can have: setting a custom DA layer. The dangers of doing this wrong are obvious: lack of proper data availability solution may lead to funds being frozen. (Note: that funds can never be _stolen_ due to ZKP checks of the VM execution). + +Sometimes, users may need assurances that a chain will never become frozen even under a malicious chain admin. A general though unstable approach is discussed [here](#proposed-modular-chainadmin-implementation), however this release comes with a solution specially tailored for rollups: the `isPermanentRollup` setting. + +#### `isPermanentRollup` setting + +Chain also exposes the `AdminFacet.makePermanentRollup` function. It will turn a chain into a permanent rollup, ensuring that DA validator pairs can be only set to values that are approved by decentralized governance to be used for rollups. + +This functionality is obviously dangerous in a sense that it is permanent and revokes the right of the chain to change its DA layer. On the other hand, it ensures perpetual safety for users. This is the option that ZKsync Era plans to use. + +This setting is preserved even when migrating to [gateway](../gateway/overview.md). If this setting was set while chain is on top of Gateway, and it migrates back to L1, it will keep this status, i.e. it is fully irrevocable. + +### `changeFeeParams` method + +This method allows to change how the fees are charged for priority operations. + +The worst impact of setting this value wrongly is having L1->L2 transactions underpriced. + +### `setTokenMultiplier` method + +This method allows to set the token multiplier, i.e. the ratio between the price of ETH and the price of the token. It will be used for L1->L2 priority transactions. + +Typically, `ChainAdmin`s of ERC20 chains will have a special hotkey responsible for calling this function to keep the price up to date. An example on how it is implemented in the current system can be seen [here](https://github.com/matter-labs/era-contracts/blob/aafee035db892689df3f7afe4b89fd6467a39313/l1-contracts/contracts/governance/ChainAdmin.sol#L23). + +The worst impact of setting this value wrongly is having L1->L2 transactions underpriced. + +### `setPubdataPricingMode` + +This method allows to set whether the pubdata price will be taken into account for priority operations. + +The worst impact of setting this value wrongly is having L1->L2 transactions underpriced. + +### `setTransactionFilterer` + +This method allows to set a transaction filterer, i.e. an additional validator for all incoming L1->L2 transactions. The worst impact is users' transactions being censored. + +### Migration to another settlement layer + +The upgrade can start migration of a chain to another settlement layer. Currently all the settlement layers are whitelisted, so generally this operation is harmless (except for the inconvenience in case the migration was unplanned). + +However, some caution needs to be applied to migrate properly as described in the section below. + +## Chain admin when migrating to gateway + +When a chain migrates to gateway, it provides the address of the new admin on L2. The following rules apply: + +- If a ZK chain has already been deployed on a settlement layer, its admin stays the same. +- If a ZK chain has not been deployed yet, then the new admin is set. + +The above means that in the current release the admin of the chain on the new settlement layer is "detached" from the admin on L1. It is the responsibility of the chain to set the L2 admin correctly: either it should have the same signers or, even better in the long run, put the aliased L1 admin to have most of the abilities inside the L2 chain admin. + +Since most of the Admin's functionality above are related to L1->L2 operations, the L1 chain admin will continue playing a crucial role even after the chain migrates to Gateway. However, some of the new functionality are relevant on the chain admin on the settlement layer only: + +- Managing DA +- Managing new validators +- It is the admin of the settlement layer that do migrations of chains + +As such, the choice of the L2 Admin is very important. Also, if the chain admin on the new settlement layer is not accessible (e.g. accidentally wrong address was chosen), the chain is lost: + +- No validators will be set +- The chain can not migrate back + +Overall **very special care** needs to be taken when selecting an admin for the migration to a new settlement layer. + +## Proposed modular `ChainAdmin` implementation + +> **Warning**. The proposed implementation here will likely **not** be used by the Matter Labs team for ZKsync Era due to the issues listed in the issues section. This code, however, is still in scope of the audit and may serve as a future basis of a more long term solution. + +In order to ensure that the architecture here flexible enough for future other chains to use, it uses a modular architecture to ensure that other chains could fit it to their needs. By default, this contract is not even `Ownable`, and anyone can execute transactions out of the name of it. In order to add new features such as restricting calling dangerous methods and access control, _restrictions_ should be added there. Each restriction is a contract that implements the `IRestriction` interface. The following restrictions have been implemented so far: + +- `AccessControlRestriction` that allows to specify which addresses can call which methods. In the case of Era, only the `DEFAULT_ADMIN_ROLE` will be able to call any methods. Other chains with non-ETH base token may need an account that would periodically call the L1 contract to update the ETH price there. They may create the `SET_TOKEN_MULTIPLIER_ROLE` role that is required to update the token price and give its rights to some hot private key. + +- `PermanentRestriction` that ensures that: + +a) This restriction could not be lifted, i.e. the chain admin of the chain must forever have it. Even if the address of the `ChainAdmin` changes, it ensures that the new admin has this restriction turned on. +b) It specifies the calldata this which certain methods can be called. For instance, in case a chain wants to keep itself permanently tied to certain DA, it will ensure that the only DA validation method that can be used is rollup. Some sort of decentralized governance could be chosen to select which DA validation pair corresponds to this DA method. + +The approach above does not only helps to protect the chain, but also provides correct information for chains that are present in our ecosystem. For instance, if a chain claims to perpetually have a certain property, having the `PermanentRestriction` as part of the chain admin can ensure all observers of that. + +### Issues and limitations + +Due to specifics of [migration to another settlement layers](#migration-to-another-settlement-layer) (i.e. that migrations do not overwrite the admin), maintaining the same `PermanentRestriction` becomes hard in case a restriction has been added on top of the chain admin inside one chain, but not the other. + +While very flexible, this modular approach should still be polished enough before recommending it as a generic solution for everyone. However, the provided new [ChainAdmin](../../l1-contracts/contracts/governance/ChainAdmin.sol) can still be helpful for new chains as with the `AccessControlRestriction` it provides a ready-to-use framework for role-based managing of the chain. Using `PermanentRestriction` for now is discouraged however. diff --git a/docs/chain_management/chain_genesis.md b/docs/chain_management/chain_genesis.md new file mode 100644 index 000000000..8431868a9 --- /dev/null +++ b/docs/chain_management/chain_genesis.md @@ -0,0 +1,72 @@ +# Creating new chains with BridgeHub + +[back to readme](../README.md) + +The main contract of the whole hyperchain ecosystem is called _`BridgeHub`_. It contains: + +- the registry from chainId to CTMs that is responsible for that chainId +- the base token for each chainId. +- the whitelist of CTMs +- the whitelist of tokens allowed to be `baseTokens` of chains. +- the whitelist of settlement layers +- etc + +BridgeHub is responsible for creating new STs. It is also the main point of entry for L1→L2 transactions for all the STs. Users won't be able to interact with STs directly, all the actions must be done through the BridgeHub, which will ensure that the fees have been paid and will route the call to the corresponding ST. One of the reasons it was done this way was to have the unified interface for all STs that will ever be included in the hyperchain ecosystem. + +To create a chain, the `BridgeHub.createNewChain` function needs to be called: + +```solidity +/// @notice register new chain. New chains can be only registered on Bridgehub deployed on L1. Later they can be moved to any other layer. +/// @notice for Eth the baseToken address is 1 +/// @param _chainId the chainId of the chain +/// @param _chainTypeManager the state transition manager address +/// @param _baseTokenAssetId the base token asset id of the chain +/// @param _salt the salt for the chainId, currently not used +/// @param _admin the admin of the chain +/// @param _initData the fixed initialization data for the chain +/// @param _factoryDeps the factory dependencies for the chain's deployment +function createNewChain( + uint256 _chainId, + address _chainTypeManager, + bytes32 _baseTokenAssetId, + // solhint-disable-next-line no-unused-vars + uint256 _salt, + address _admin, + bytes calldata _initData, + bytes[] calldata _factoryDeps +) external +``` + +BridgeHub will check that the CTM as well as the base token are whitelisted and route the call to the State + +![newChain (2).png](./img/create_new_chain.png) + +### Creation of a chain in the current release + +In the future, ST creation will be permissionless. A securely random `chainId` will be generated for each chain to be registered. However, generating 32-byte chainId is not feasible with the current SDK expectations on EVM and so for now chainId is of type `uint48`. And so it has to be chosen by the admin of `BridgeHub`. Also, for the current release we would want to avoid chains being able to choose their own initialization parameter to prevent possible malicious input. + +For this reason, there will be an entity called `admin` which is basically a hot key managed by us and it will be used to deploy new STs. + +So the flow for deploying their own ST for users will be the following: + +1. Users tell us that they want to deploy a ST with certain governance, CTM (we’ll likely allow only one for now), and baseToken. +2. Our server will generate a chainId not reserved by any other major chain and the `admin` will call the `BridgeHub.createNewChain` . This will call the `CTM.createNewChain` that will deploy the instance of the rollup as well as initialize the first transaction there — the system upgrade transaction needed to set the chainId on L2. + +After that, the ST is ready to be used. Note, that the admin of the newly created chain (this will be the organization that will manage this chain from now on) will have to conduct certain configurations before the chain [can be used securely](../chain_management/admin_role.md). + +## Built-in contracts and their initialization + +Each single ZK Chain has a set of the following contracts that, while not belong to kernel space, are built-in and provide important functionality: + +- Bridgehub (the source code is identical to the L1 one). The role of bridgehub is to facilitate cross chain transactions. It contains a mapping from chainId to the address of the diamond proxy of the chain. It is really used only on the L1 and Gateway, i.e. layers that can serve as a settlement layer. +- L2AssetRouter. The new iteration of the SharedBridge. +- L2NativeTokenVault. The Native token vault on L2. +- MessageRoot (the source code is identical to the L1 one). Similar to bridgehub, it facilitates cross-chain communication, but is practically unused on all chains except for L1/GW. + +To reuse as much code as possible from L1 and also to allow easier initialization, most of these contracts are not initialized as just part of the genesis storage root. Instead, the data for their initialization is part of the original diamondcut for the chain. In the same initial upgrade transaction when the chainId is initialized, these contracts are force-deployed and initialized also. An important part in it plays the new `L2GenesisUpgrade` contract, which is pre-deployed in a user-space contract, but it is delegate-called by the `ComplexUpgrader` system contract (already exists as part of genesis and existed before this upgrade). + +## Additional limitations for the current version + +In the current version creating new chains will not be permissionless. That is needed to ensure that no malicious input can be provided there. + +Also, since in the current release, there will be little benefits from shared liquidity, i.e. the there will be no direct ZKChain<>ZKChain transfers supported, as a measure of additional security we’ll also keep track of balances for each individual ZKChain and will not allow it to withdraw more than it has deposited into the system. diff --git a/docs/chain_management/chain_type_manager.md b/docs/chain_management/chain_type_manager.md new file mode 100644 index 000000000..26a01f604 --- /dev/null +++ b/docs/chain_management/chain_type_manager.md @@ -0,0 +1,70 @@ +# Chain Type Manager (CTM) + +[back to readme](../README.md) + +> If someone is already familiar with the [previous version](https://github.com/code-423n4/2024-03-zksync) of ZKsync architecture, this contract was previously known as "State Transition Manager (CTM)". + +Currently bridging between different zk rollups requires the funds to pass through L1. This is slow & expensive. + +The vision of seamless internet of value requires transfers of value to be _both_ seamless and trustless. This means that for instance different STs need to share the same L1 liquidity, i.e. a transfer of funds should never touch L1 in the process. However, it requires some sort of trust between two chains. If a malicious (or broken) rollup becomes a part of the shared liquidity pool it can steal all the funds. + +However, can two instances of the same zk rollup trust each other? The answer is yes, because no new additions of rollups introduce new trust assumptions. Assuming there are no bugs in circuits, the system will work as intended. + +How can two rollups know that they are two different instances of the same system? We can create a factory of such contracts (and so we would know that each new rollup created by this instance is correct one). But just creating correct contracts is not enough. Ethereum changes, new bugs may be found in the original system & so an instance that does not keep itself up-to-date with the upgrades may exploit some bug from the past and jeopardize the entire system. Just deploying is not enough. We need to constantly make sure that all STs are up to date and maintain whatever other invariants are needed for these STs to trust each other. + +Let’s define as _Chain Type Manager_ (CTM) \*\*as a contract that is responsible for the following: + +- It serves as a factory to deploy STs (new ZK chains) +- It is responsible for ensuring that all the STs deployed by it are up-to-date. + +Note, that this means that STs have a “weaker” governance. I.e. governance can only do very limited number of things, such as setting the validator. ST admin can not set its own upgrades and it can only “execute” the upgrade that has already been prepared by the CTM. + +In the long term vision STs deployment will be permissionless, however CTM will always remain the main point of trust and will have to be explicitly whitelisted by the decentralized governance of the entire ecosystem before its ST can get the access to the shared liquidity. + +## Configurability in the current release + +For now, only one CTM will be supported — the one that deploys instances of ZKsync Era, possibly using other DA layers. To read more about different DA layers, check out [this document](../settlement_contracts/data_availability/custom_da.md). + +The exact process of deploying & registering a ST can be [read here](./chain_genesis.md). Overall, each ST in the current release will have the following parameters: + +| ST parameter | Updatability | Comment | +| --------------------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| chainId | Permanent | Permanent identifier of the ST. Due to wallet support reasons, for now chainId has to be small (48 bits). This is one of the reasons why for now we’ll deploy STs manually, to prevent STs from having the same chainId as some another popular chain. In the future it will be trustlessly assigned as a random 32-byte value. | +| baseTokenAssetId | Permanent | Each ST can have their own custom base token (i.e. token used for paying the fees). It is set once during creation and can never be changed. Note, that we refer to and "asset id" here instead of an L1 address. To read more about what is assetId and how it works check out the document for [asset router](../bridging/asset_router/overview.md) | +| chainTypeManager | Permanent | The CTM that deployed the ST. In principle, it could be possible to migrate between CTMs (assuming both CTMs support that). However, in practice it may be very hard and as of now such functionality is not supported. | +| admin | By admin of ST | The admin of the ST. It has some limited powers to govern the chain. To read more about which powers are available to a chain admin and which precautions should be taken, check [out this document](../chain_management/admin_role.md) | +| validatorTimelock | CTM | For now, we want all the chains to use the same 21h timelock period before their batches are finalized. Only CTM can update the address that can submit state transitions to the rollup (that is, the validatorTimelock). | +| validatorTimelock.validator | By admin of ST | The admin of ST can choose who can submit new batches to the ValidatorTimelock. | +| priorityTx FeeParams | By admin of ST | The admin of a ZK chain can amend the priority transaction fee params. | +| transactionFilterer | By admin of ST | A chain may put an additional filter to the incoming L1->L2 transactions. This may be needed by a permissioned chain (e.g. a Validium bank-lile corporate chain). | +| DA validation / permanent rollup status | By admin of ST | A chain can decide which DA layer to use. You check out more about [safe DA management here](./admin_role.md) | +| executing upgrades | By admin of ST | While exclusively CTM governance can set the content of the upgrade, STs will typically be able to choose suitable time for them to actually execute it. In the current release, STs will have to follow our upgrades. | +| settlement layer | By admin of ST | The admin of the chain can enact migrations to other settlement layers. | + +> Note, that if we take a look at the access control for the corresponding functions inside the [AdminFacet](../../l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol), the may see that a lot of methods from above that are marked as "By admin of ST" could be in theory amended by the ChainTypeManager. However, this sort of action requires approval from decentralized governance. Also, in case of an urgent high risk situation, the decentralized governance might force upgrade the contract via CTM. + +## Upgradability in the current release + +In the current release, each chain will be an instance of ZKsync Era and so the upgrade process of each individual ST will be similar to that of ZKsync Era. + +1. Firstly, the governance of the CTM will publish the server (including sequencer, prover, etc) that support the new version . This is done offchain. Enough time should be given to various zkStack devs to update their version. +2. The governance of the CTM will publish the upgrade onchain by automatically executing the following three transactions: + + - `setChainCreationParams` ⇒ to ensure that new chains will be created with the version + - `setValidatorTimelock` (if needed) ⇒ to ensure that the new chains will use the new validator timelock right-away + - `setNewVersionUpgrade` ⇒ to save the upgrade information that each ST will need to follow to conduct the upgrade on their side. + +3. After that, each ChainAdmin can upgrade to the new version in suitable time for them. + +> Note, that while the governance does try to give the maximal possible time for chains to upgrade, the governance will typically put restrictions (aka deadlines) on the time by which the chain has to be upgraded. If the deadline is passed, the chain can not commit new batches until the upgrade is executed. + +### Emergency upgrade + +In case of an emergency, the [security council](https://blog.zknation.io/introducing-zk-nation/) has the ability to freeze the ecosystem and conduct an emergency upgrade. + +In case we are aware that some of the committed batches on an ST are dangerous to be executed, the CTM can call `revertBatches` on that ST. For faster reaction, the admin of the ChainTypeManager has the ability to do so without waiting for govenrnace approval that may take a lot of time. This action does not lead to funds being lost, so it is considered suitable for the partially trusted role of the admin of the ChainTypeManager. + +### Issues & caveats + +- If an ZK chain skips an upgrade (i.e. it has version X, it did not upgrade to `X + 1` and now the latest protocol version is `X + 2` there is no built-in way to upgrade). This team will require manual intervention from us to upgrade. +- The approach of calling `revertBatches` for malicious STs is not scalable (O(N) of the number of chains). The situation is very rare, so it is fine in the short term, but not in the long run. diff --git a/docs/chain_management/img/create_new_chain.png b/docs/chain_management/img/create_new_chain.png new file mode 100644 index 000000000..b71fecfeb Binary files /dev/null and b/docs/chain_management/img/create_new_chain.png differ diff --git a/docs/chain_management/upgrade_process.md b/docs/chain_management/upgrade_process.md new file mode 100644 index 000000000..894bd2405 --- /dev/null +++ b/docs/chain_management/upgrade_process.md @@ -0,0 +1,41 @@ +# Upgrade process document + +[back to readme](../README.md) + +## Intro + +This document assumes that you have understanding about [the structure](../settlement_contracts/zkchain_basics.md) on individual chains' L1 contracts. + +Upgrading the ecosystem of ZKChains is a complicated process. ZKSync is a complex ecosystem with many chains and contracts and each upgrade is unique, but there are some steps that repeat for most upgrades. These are mostly how we interact with the CTM, the diamond facets, the L1→L2 upgrade, how we update the verification keys. + +Where each upgrade consists of two parameters: + +- Facet cuts - change of the internal implementation of the diamond proxy +- Diamond Initialization - delegate call to the specified address wit`h specified data + +The second parameter is very powerful and flexible enough to move majority of upgrade logic there. + +## Upgrade structure + +Upgrade information is composed in the form of a [DiamondCutData](../../l1-contracts/contracts/state-transition/libraries/Diamond.sol#L75) struct. During the upgrade, the chain's DiamondProxy will delegateCall the `initAddress` with the provided `initCalldata`, while the facets that the `DiamondProxy` will be changed according to the `facetCuts`. This scheme is very powerful and it allows to change anything in the contract. However, we typically have a very specific set of changes that we need to do. To facilitate these, two contracts have been created: + +1. [BaseZkSyncUpgrade](../../l1-contracts/contracts/upgrades/BaseZkSyncUpgrade.sol) - Generic template with function that can be useful for upgrades +2. [DefaultUpgrade](../../l1-contracts/contracts/upgrades/DefaultUpgrade.sol) - Default implementation of the `BaseZkSyncUpgrade`, contract that is most often planned to be used as diamond initialization when doing upgrades. + +> Note, that the Gateway upgrade will be more complex than the usual ones and so a similar, but separate [process](../upgrade_history/gateway_upgrade/upgrade_process.md) will be used for it. It will also use its own custom implementation of the `BaseZkSyncUpgrade`: [GatewayUpgrade](../../l1-contracts/contracts/upgrades/GatewayUpgrade.sol). + +### Protocol version + +For tracking upgrade versions on different networks (private testnet, public testnet, mainnet) we use protocol version, which is basically just a number denoting the deployed version. The protocol version is different from Diamond Cut `proposalId`, since `protocolId` only shows how much upgrade proposal was proposed/executed, but nothing about the content of upgrades, while the protocol version is needed to understand what version is deployed. + +In the [BaseZkSyncUpgrade](../../l1-contracts/contracts/upgrades/BaseZkSyncUpgrade.sol) & [DefaultUpgrade](../../l1-contracts/contracts/upgrades/DefaultUpgrade.sol) we allow to arbitrarily increase the proposal version while upgrading a system, but only increase it. We are doing that since we can skip some protocol versions if for example found a bug there (but it was deployed on another network already). + +## Protocol upgrade transaction + +During upgrade, we typically need not only update the L1 contracts, but also the L2 ones. This is achieved by creating an upgrade transactions. More details on how those are processed inside the system can be read [here](../settlement_contracts/priority_queue/processing_of_l1-l2_txs.md). + +## Whitelisting and executing upgrade + +Note, that due to how powerful the upgrades are, if we allowed any [chain admin](../chain_management/admin_role.md) to inact any upgrade it wants, it could allow malicious chains to potentially break some of the ecosystem invariants. Because of that, any upgrade should be firstly whitelisted by the decentralized governance through calling the `setNewVersionUpgrade` function of the [ChainTypeManager](../../l1-contracts/contracts/state-transition/ChainTypeManager.sol). + +In order to execute the upgrade, the chain admin would call the `upgradeChainFromVersion` function from the [Admin](../../l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol) facet. diff --git a/docs/consensus/consensus-registry.md b/docs/consensus/consensus-registry.md new file mode 100644 index 000000000..d2a37e994 --- /dev/null +++ b/docs/consensus/consensus-registry.md @@ -0,0 +1,20 @@ +# Consensus Registry + +As part of the decentralization effort we plan to introduce two new roles into the system: + +- Validators, which are nodes that are meant to receive L2 blocks from the sequencer, execute them locally and broadcast their signature over the block if it’s valid. If the sequencer receives enough of these signatures, the L2 block is considered finalized. Nodes that are following the chain or syncing will only accept blocks that are finalized. +- Attesters, which basically do the same thing as validators but for L1 batches instead of L2 blocks. Just like with L2 blocks, if a L1 batch is accompanied with enough attester signatures then it’s considered finalized. How these signature are used is different from validators signatures though. These signatures are meant to be submitted to L1 together with the L1 batch when it’s committed. And the L1 contracts are meant to only accept L1 batches that come with enough signatures from the correct attesters. But that functionality is not implemented yet. + +The `ConsensusRegistry` contract implements a small part of that entire flow. In order to verify the L2 block and L1 batch signatures we need to know the public keys of the validators and attesters that signed them. And we also want that set of validators and attesters to be dynamic. The `ConsensusRegistry` contract is going to store and manage the current set of validators and attesters and expose methods to add, remove and modify validators/attesters. + +## Users + +There are basically three types of users that will call this contract: + +- The contract owner. This is generally meant to be some multisig or governance contract. In this case, it will initially be Matter Labs multisig and later it will be changed to be ZKsync’s governance. It can call any method in the contract and basically can modify the validator and attester sets at will. There are methods that are exclusive to it though. Namely add nodes, remove nodes, change validator/attester weights (the relative voting power of each validator/attester) and commit validator/attester committees (creates a snapshot of the current nodes and that updates the validator/attester committees). +- The node owners. The entities that will run the validators and attesters. They change over time as nodes get added/removed. They can only activate/deactivate their nodes (deactivated nodes do not get selected to be part of committees) and change their validator/attester public keys. +- The sequencer plus anyone running an external node. They need to verify L1 batch and L2 block signatures so they need to get the attester and validator committees for each batch. There are getter methods for this. + +## Future integration + +Currently `ConsensusRegistry` contract is not directly connected to the protocol. The plan is to read the validator committee from the consensus registry contract on each new batch. And, with upcoming protocol upgrades, start verifying the validator signatures onchain in each submitted batch. diff --git a/docs/gateway/chain_migration.md b/docs/gateway/chain_migration.md new file mode 100644 index 000000000..2fe254e7b --- /dev/null +++ b/docs/gateway/chain_migration.md @@ -0,0 +1,43 @@ +# Chain migration + +[back to readme](../README.md) + +## Ecosystem Setup + +Chain migration reuses lots of logic from standard custom asset bridging which is enabled by the AssetRouter. The easiest way to imagine is that ZKChains are NFTs that are being migrated from one chain to another. Just like in case of the NFT contract, an CTM is assumed to have an `assetId := keccak256(abi.encode(L1_CHAIN_ID, address(ctmDeployer), bytes32(uint256(uint160(_ctmAddress)))))`. I.e. these are all assets with ADT = ctmDeployer contract on L1. + +CTMDeployer is a very lightweight contract used to facilitate chain migration. Its main purpose is to server as formal asset deployment tracker for CTMs. It serves two purposes: + +- Assign bridgehub as the asset handler for the “asset” of the CTM on the supported settlement layer. + +Currently, it can only be done by the owner of the CTMDeployer, but in the future, this method can become either permissionless or callable by the CTM owner. + +- Tell bridgehub which address on the L2 should serve as the L2 representation of the CTM on L1. Currently, it can only be done by the owner of the CTMDeployer, but in the future, this method can become callable by the CTM owner. + +![image.png](./img/ctm_gw_registration.png) + +## The process of migration L1→GW + +![image.png](./img/migrate_to_gw.png) + +## Chain migration GW → L1 + +Chain migration from from L1 to GW works similar to how NFT bridging from L1 to another chain would work. Migrating back will use the same mechanism as for withdrawals. + +Note, that for L2→L1 withdrawals via bridges we never provide a recovery mechanism. The same is the case with GW → L1 messaging, i.e. it is assumed that such migrations are always executable on L1. + +You can read more about how the safety is ensured in the “Migration invariants & protocol upgradability” section. + +![image.png](./img/migrate_from_gw.png) + +## Chain migration GW_1 → GW_2 + +In this release we plan to only support a single whitelisted settlement layer, but if in the future more will be supported, as of now the plan is to migrate the chain firstly to L1 and then to GW. + +## Chain migration invariants & protocol upgradability + +Note, that once a chain migrates to a new settlement layer, there are two deployments of contracts for the same ZKChain. What’s more, the L1 part will always be used. + +There is a need to ensure that the chains work smoothly during migration and there are not many issues during the protocol upgrade. + +You can read more about it [here](./gateway_protocol_upgrades.md). diff --git a/docs/gateway/gateway_da.md b/docs/gateway/gateway_da.md new file mode 100644 index 000000000..67bd2081b --- /dev/null +++ b/docs/gateway/gateway_da.md @@ -0,0 +1,27 @@ +# Custom DA layers + +[back to readme](../README.md) + +## Prerequisites + +To better understand this document, it is better to have grasp on how [custom DA handling protocol](../settlement_contracts/data_availability/custom_da.md) works. + +## Rollup DA + +If a chain intends to be a rollup, it needs to relay its pubdata to L1 via L1Messenger system contract. Thus, typically the L1DAValidator will be different from the one that they used on Ethereum. + +For chains that use our [standard pubdata format](../settlement_contracts/data_availability/rollup_da.md), we provide the [following relayed L1 DA validator](../../l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol) that relays all the data to L1. + +### Security notes for Gateway-based rollups + +An important note is that when reading the state diffs from L1, the observer will read messages that come from the L2DAValidator. To be more precise, the contract used is `RelayedSLDAValidator` which reads the data and publishes it to L1 by calling the L1Messenger contract. + +If anyone could call this contract, the observer from L1 could get wrong data for pubdata for this particular batch. To prevent this, it ensures that only the chain can call it. + +## Validium DA + +Validiums can reuse [the same DA validator](../../l1-contracts/contracts/state-transition/data-availability/ValidiumL1DAValidator.sol) that they used on L1. Note, that it has to be redeployed on the Gateway. + +## Custom DA + +As already stated before, the DA validation is done on the settlement layer. Thus, if you use a custom DA layer you need to ensure that its verification can be done on Gateway. diff --git a/docs/gateway/gateway_protocol_upgrades.md b/docs/gateway/gateway_protocol_upgrades.md new file mode 100644 index 000000000..11558d533 --- /dev/null +++ b/docs/gateway/gateway_protocol_upgrades.md @@ -0,0 +1,159 @@ +# Gateway protocol versioning and upgradability + +[back to readme](../README.md) + +One of the hardest part about gateway (GW) is how do we synchronize interaction between L1 and L2 parts that can potentially have different versions of contracts. This synchronization should be compatible with any future CTM that may be present on the gateway. + +Here we describe various scenarios of standard/emergency upgrades and how will those play out in the gateway setup. + +## General idea + +We do not enshrine any particular approach on the protocol level of the GW. The following is the approach used by the standard Era CTM, which also manages GW. + +Upgrades will be split into two parts: + +- “Inactive chain upgrades” ⇒ intended to update contract code only and not touch the state or touch it very little. The main motivation is to be able to upgrade the L1 contracts without e.g. adding new upgrade transactions. +- “Active chain upgrades” ⇒ same as the ones that we have today: full-on upgrade that also updates bootloader, insert system upgrade transaction and so on. + +In other words: + +`active upgrade = inactive upgrade + bootloader changes + setting upgrade tx` + +The other difference is that while “active chain upgrades” are usually always needed to be forced in order to ensure that contracts/protocol are up to date, the “inactive chain upgrades” typically involve changes in the facets’ bytecode and will only be needed before migration is complete to ensure that contracts are compatible. + +To reduce the boilerplate / make management of the upgrades easier, the abstraction will be basically implemented at the upgrade implementation level, that will check `if block.chainid == s.settlementLayer { ... perform active upgrade stuff } else { ... perform inactive upgrade stuff, typically nothing m}.` + +## Lifecycle of a chain + +While the chain settles on L1 only, it will just do “active chain upgrades”. Everything is the same as now. + +When a chain starts its migration to a new settlement layer (regardless of whether it is gateway or not): + +1. It will be checked the that the protocolVersion is the latest in the CTM in the current settlement layer (just in case to not have to bother with backwards compatibility). +2. The `s.settlementLayer` will be set for the chain. Now the chain becomes inactive and it can only take “inactive” upgrades. +3. When migration finishes, it will be double checked that the `protocolVersion` is the same as the one in the target chains’ CTM. + +If the chain has already been deployed there, it will be checked that the `protocolVersion` of the deployed contracts there is the same as the one of the chain that is being moved. 4. All “inactive” instances of a chain can receive “inactive” upgrades of a chain. The single “active” instance of a chain (the one on the settlement layer) can receive only active upgrades. + +In case step (3) fails (or for any other reason the chain fails), the migration recovery process should be available. (`L1AssetRouter.bridgeRecoverFailedTransfer` method). Recovering a chain id basically just changing its `settlementLayerId` to the current block.chainid. It will be double checked that the chain has not conducted any inactive upgrades in the meantime, i.e. the `protocolVersion` of the chain is the same as the one when the chain started its migration. + +In case we ever do need to do more than simply resetting `settlementLayerId` for a chain in case of a failed migration, it is the responsibility of the CTM to ensure that the logic is compatible for all the versions. + +## Stuck state for L1→GW migration + +The only unrecoverable state that a chain can achieve is: + +- It tries to migrate and it fails. +- While the migration has happening an “inactive” upgrade has been conducted. +- Now recovery of the chain is not possible as the “protocol version” check will fail. + +This is considered to be a rare event, but it will be strongly recommended that before conducting any inactive upgrades the migration transaction should be finalized. + +In the future, we could actively force it, i.e. require confirmation of a successful migration before any upgrades on a migrated chain could be done. + +## Safety guards for GW→L1 migrations + +Migrations from GW to L1 do not have any chain recovery mechanism, i.e. if the step (3) from the above fails for some reason (e.g. a new protocol version id is available on the CTM), then the chain is basically lost. + +### Protocol version safety guards + +- Before a new protocol version is released, all the migrations will be paused, i.e. the `pauseMigration` function will be called by the owner of the Bridgehub on both L1 and L2. It should prevent migrations happening in the risky period when the new version is published to the CTM. +- Assuming that no new protocol versions are published to CTM during the migration, the migration must succeed, since both CTM on GW and on L1 will have the same version and so the checks will work fine. +- The finalization of any chain withdrawal is permissionless and so in the short term the team could help finalize the outstanding migrations to prevent funds loss. + +> The approach above is somewhat tricky as it requires careful coordination with the governance to ensure that at the time of when the new protocol version is published to CTM, there are no outstanding migrations. + +In the future we will either make it more robust or add a recovery mechanism for failed GW → L1 migrations. + +> + +### Batch number safety guards + +Another potential place that may lead for a chain to not be migratable to L1 is if the number of outstanding batches is very high, which can lead to migration to cost too much gas and being not executable no L1. + +To prevent that, it is required for chains that migrate from GW that all their batches are executed. This ensures that the number of batches’ hashes to be copied on L1 is constant (i.e. just 1 last batch). + +## Motivation + +The job of this proposal is to reduce the number of potential states in which the system can find itself in to a minimum. The cases that are removed: + +- Need to be able to migrate to a chain that has contracts from a different protocol version +- Need to be able for CTM to support migration of chains with different versions. Only `bridgeRecoverFailedTransfer` has to be supported for all the versions, but its logic is very trivial. + +The reason why we can not conduct “active” upgrades everywhere on both L1 and L2 part is that for the settlement layer we need to write the new protocol upgrade tx, while NOT allowing to override it. On other hand, for the “inactive” chain contracts, we need to ignore the upgrade transaction. + +## Forcing “active chain upgrade” + +For L1-based chains, forcing those upgrades will work exactly same as before. Just during `commitBatches` the CTM double checks that the protocol version is up to date. + +The admin of the CTM (GW) will call the CTM (GW) with the new protocol version’s data. This transaction should not fail, but even if it does fail, we should be able to just re-try. For now, the GW operator will be trusted to not be malicious + +### Case of malicious Gateway operator + +In the future, malicious Gateway operator may try to exploit a known vulnerability in an CTM. + +The recommended approach here is the following: + +- Admin of the CTM (GW) will firstly commit to the upgrade (for example, preemptively freeze all the chains). +- Once the chains are frozen, it can use L1→L2 communication to pass the new protocol upgrade to CTM. + +> The approach above basically states that “if operator is censoring, we’ll be able to use standard censorship-resistance mechanism of a chain to bypass it”. The freezing part is just a way to not tell to the world the issue before all chains are safe from exploits. + +It is the responsibility of the CTM to ensure that all the supported settlement layers are trusted enough to uphold to the above protocol. Using any sort of Validiums will be especially discouraged, since in theory those could get frozen forever without any true censorship resistance mechanisms. + +Also, note that the freezing period should be long enough to ensure that censorship resistance mechanisms have enough time to kick in + +> + +## Forcing “inactive chain upgrade” + +Okay, imagine that there is a bug in an L1 implementation of a chain that has migrated to Gateway. This is a rather rare event as most of the action happens on the settlement layer, together with the ability to steal the most of funds. + +In case such situation does happen however, the current plan is just to: + +- Freeze the ecosystem. +- Ask the admins nicely to upgrade their implementation. Decentralized token governance can also force-upgrade those via CTM on L1. + +## Backwards compatibility + +With this proposal the protocol version on the L1 part and on the settlement layer part is completely out of sync. This means that all new mailboxes need to support both accepting and sending all versions of relayed (L1 → GW → L2) transactions. + +For now, this is considered okay. In the future, some stricter versioning could apply. + +## Notes + +### Regular chain migration moving chain X from Y to Z (where Y is Z’s settlement layer) + +So assume that Y is L1, and Z is ‘Gateway’. + +Definition: + +`ZKChain(X)` - ‘a.k.a ST / DiamondProxy’ for a given chain id X + +`CTM(X)` - the State transition manager for a given chain id X + +1. check that `ZKChain(X).protocol_version == CTM(X).protocol_version` on chain Y. +2. Start ‘burn’ process (on chain Y) + 1. collect `‘payload’` from `ZKChain(X)` and `CTM(X)` and `protocol_version` on chain Y. + 2. set `ZKChain(X).settlement_layer` to `address(ZKChain(Z))` on chain Y. +3. Start ‘mint’ process (on chain Z) + 1. check that `CTM(X).protocol_version == payload.protocol_version` + 2. Create new `ZKChain(X)` on chain Z and register in the local bridgehub & CTM. + 3. pass `payload` to `ZKChain(X)` and `CTM(X)` to initialize the state. +4. If ‘mint’ fails - recover (on chain Y) + 1. check that `ZKChain(X).protocol_version == payload.protocol_version` + 1. important, here we’re actually looking at the ‘HYPERCHAIN’ protocol version and not necessarily CTM protocol version. + 2. set `ZKChain(X).settlement_layer` to `0` on chain Y. + 3. pass `payload` to `IZKChain(X)` and `CTM(X)` to initialize the state. + +### ‘Reverse’ chain migration - moving chain X ‘back’ from Z to Y + +(moving back from gateway to L1). + +1. Same as above (check protocol version - but on chain Z) +2. Same as above (start burn process - but on chain Z) +3. Same as above (start ‘mint’ - but on chain Y) + 1. same as above + 2. creation is probably not needed - as the contract was already there in a first place. + 3. same as above - but the state is ‘re-initialized’ +4. Same as above - but on chain ‘Z’ diff --git a/docs/gateway/img/ctm_gw_registration.png b/docs/gateway/img/ctm_gw_registration.png new file mode 100644 index 000000000..03dc68518 Binary files /dev/null and b/docs/gateway/img/ctm_gw_registration.png differ diff --git a/docs/gateway/img/gateway_architecture.png b/docs/gateway/img/gateway_architecture.png new file mode 100644 index 000000000..a9302ec7e Binary files /dev/null and b/docs/gateway/img/gateway_architecture.png differ diff --git a/docs/gateway/img/l1_l2_messaging.png b/docs/gateway/img/l1_l2_messaging.png new file mode 100644 index 000000000..886c13174 Binary files /dev/null and b/docs/gateway/img/l1_l2_messaging.png differ diff --git a/docs/gateway/img/l1_l3_messaging.png b/docs/gateway/img/l1_l3_messaging.png new file mode 100644 index 000000000..a2b4db935 Binary files /dev/null and b/docs/gateway/img/l1_l3_messaging.png differ diff --git a/docs/gateway/img/migrate_from_gw.png b/docs/gateway/img/migrate_from_gw.png new file mode 100644 index 000000000..b30576043 Binary files /dev/null and b/docs/gateway/img/migrate_from_gw.png differ diff --git a/docs/gateway/img/migrate_to_gw.png b/docs/gateway/img/migrate_to_gw.png new file mode 100644 index 000000000..6615791e1 Binary files /dev/null and b/docs/gateway/img/migrate_to_gw.png differ diff --git a/docs/gateway/img/nested_l3_l1_messaging.png b/docs/gateway/img/nested_l3_l1_messaging.png new file mode 100644 index 000000000..c85845396 Binary files /dev/null and b/docs/gateway/img/nested_l3_l1_messaging.png differ diff --git a/docs/gateway/img/nested_l3_l1_messaging_2.png b/docs/gateway/img/nested_l3_l1_messaging_2.png new file mode 100644 index 000000000..5426e8e01 Binary files /dev/null and b/docs/gateway/img/nested_l3_l1_messaging_2.png differ diff --git a/docs/gateway/img/new_bridging_contracts.png b/docs/gateway/img/new_bridging_contracts.png new file mode 100644 index 000000000..f3f6802cd Binary files /dev/null and b/docs/gateway/img/new_bridging_contracts.png differ diff --git a/docs/gateway/messaging_via_gateway.md b/docs/gateway/messaging_via_gateway.md new file mode 100644 index 000000000..fc6681ad3 --- /dev/null +++ b/docs/gateway/messaging_via_gateway.md @@ -0,0 +1,50 @@ +# Messaging via Gateway + +[back to readme](../README.md) + +## Deeper dive into MessageRoot contract and how L3→L1 communication works + +Before, when were just settling on L1, a chain’s message root was just the merkle tree of L2→L1 logs that were sent within this batch. However, this new model will have to be amended to be able to perform messages to L1 coming from an L3 that settles on top of Gateway. + +The description of how L3→L1 messages are aggregated in the MessageRoots and proved on L1 can be read in the [nested l3 l1 messaging](./nested_l3_l1_messaging.md) section. + +## L1→L3 messaging + +As a recap, here is how messaging works for chains that settle on L1: + +![Direct L1->L2 messaging](./img/l1_l2_messaging.png) + +- The user calls the bridgehub, which routes the message to the chain. +- The operator eventually sees the transaction via an event on L1 and it will process it on L2. + +With gateway, the situation will be a bit more complex: + +![L1->L2 messaging via the Gateway](./img/l1_l3_messaging.png) + +Since now, the contracts responsible for batch processing were moved to Gateway, now all the priority transactions have to be relayed to that chain so that the validation could work. + +- (Steps 1-3) The user calls Bridgehub. The base token needs to be deposited via L1AssetRouter (usually the NTV will be used). +- (Step 4-5). The Bridgehub calls the chain where the transaction is targeted to. The chain sees that its settlement layer is another chain and so it calls it and asks to relay this transaction to gateway +- (Steps 6-7). priority transaction from `SETTLEMENT_LAYER_RELAY_SENDER` to the Bridgehub is added to the Gateway chain’s priority queue. Once the Gateway operator sees the transaction from L1, it processed it. The transaction itself will eventually call the DiamondProxy of the initial called chain. +- (Step 8) At some point, the operator of the chain will see that the priority transaction has been included to the gateway and it will process it on the L3. +- Step 9 from the picture above is optional and in case the callee of the L1→L3 transaction is the L2AssetRouter (i.e. the purpose of the transaction was bridging funds), then the L2AssetRouter will call asset handler of the asset (in case of standard bridged tokens, it will be the NativeTokenVault). It will be responsible for minting the corresponding number of tokens to the user. + +So under the hood there are 2 cross chain transactions happening: + +1. One from L1 to GW +2. The second one from GW to the L3. + +From another point with bridging we have methods that allow users to recover funds in case of a failed L1→L2 transaction. E.g. if the user tried to bridge USDC to a Zk Chain, but did not provide enough L2 gas, it can still recover the funds. + +This functionality works by letting user prove that the bridging transaction failed and then the funds are released back to the original sender on L1. With the approach above where there are multiple cross chain transactions involved, it could become 2x hard to maintain: now both of these could fail. + +To simplify things, for now, we provide the L1→GW with a large amount of gas (72kk, i.e. the maximal amount allowed to be passed on L2). We believe that it is not possible to create a relayed transaction that would fail, assuming that a non malicious recipient CTM is used on L2. + +> Note, that the above means that we currently rely on the following two facts: + +- The recipient CTM is honest and efficient. +- Creating a large transaction on L1 that would cause the L1→GW part to fail is not possible due to high L1 gas costs that would be required to create such a tx. + +Both of the assumptions above will be removed in subsequent releases, but for now this is how things are. + +> diff --git a/docs/gateway/nested_l3_l1_messaging.md b/docs/gateway/nested_l3_l1_messaging.md new file mode 100644 index 000000000..7338c939a --- /dev/null +++ b/docs/gateway/nested_l3_l1_messaging.md @@ -0,0 +1,184 @@ +# Nested L3→L1 messages tree design for Gateway + +[back to readme](../README.md) + +## Introduction + +This document assumes that the reader is already aware of what SyncLayer (or how it is now called Gateway) is. To reduce the interactions with L1, on SyncLayer we will gather all the batch roots from all the chains into the tree with following structure: + +![NestedL3L1Messaging.png](./img/nested_l3_l1_messaging.png) +![NestedL3L1Messaging.png](./img/nested_l3_l1_messaging_2.png) + +> Note: + +“Multiple arrows” from `AggregatedRoot` to `chainIdRoot` and from each `chainIdRoot` to `batchRoot` are for illustrational purposes only. + +In fact, the tree above will be a binary merkle tree, where the `AggregatedRoot` will be the root of the tree of `chainIdRoot`, while `chainIdRoot` is the merkle root of a binary merkle tree of `batchRoot`. + +> + +For each chain that settles on L1, the root will have the following format: + +`settledMessageRoot = keccak256(LocalRoot, AggregatedRoot)` + +where `localRoot` is the root of the tree of messages that come from the chain itself, while the `AggregatedRoot` is the root of aggregated messages from all of the chains that settle on top of the chain. + +In reality, `AggregatedRoot` will have a meaningful value only on SyncLayer and L1. On other chains it will be a root of an empty tree. + +The structure has the following recursive format: + +- `settledMessageRoot = keccak256(LocalRoot, AggregatedRoot)` +- `LocalRoot` — the root of the binary merkle tree over `UserLog[]`. (the same as the one we have now). It only contains messages from the current batch. +- `AggregatedRoot` — the root of the binary merkle tree over `ChainIdLeaf[]`. +- `ChainIdLeaf = keccak256(CHAIN_ID_LEAF_PADDING, chain_id, ChainIdRoot`) +- `CHAIN_ID_LEAF_PADDING` — it is a constant padding, needed to ensure that the preimage of the ChainIdLeaf is larger than 64 bytes and so it can not be an internal node. +- `chain_id` — the chain id of the chain the batches of which are aggregated. +- `ChainIdRoot` = the root of the binary merkle tree `BatchRootLeaf[]`. +- `BatchRootLeaf = keccak256(BATCH_LEAF_HASH_PADDING, batch_number, SettledRootOfBatch).` + +In other words, we get the recursive structure, where for leaves of it, i.e. chains that do not aggregate any other chains, have empty `AggregatedRoot`. + +## Appending new batch root leaves + +At the execution stage of every batch, the ZK Chain would call the `MessageRoot.addChainBatchRoot` function, while providing the `SettledRootOfBatch` for the chain. Then, the `BatchRootLeaf` will be calculated and appended to the incremental merkle tree with which the `ChainIdRoot` & `ChainIdLeaf` is calculated, which will be updated in the merkle tree of `ChainIdLeaf`s. + +At the end of the batch, the L1Messenger system contract would query the MessageRoot contract for the total aggregated root, i.e. the root of all `ChainIdLeaf`s . Calculate the settled root `settledMessageRoot = keccak256(LocalRoot, AggregatedRoot)` and propagate it to L1. + +Only the final aggregated root will be stored on L1. + +## Proving that a message belongs to a chain on top of SyncLayer + +The process will consist of two steps: + +1. Construct the needed `SettledRootOfBatch` for the current chain’s batch. +2. Prove that it belonged to the gateway. + +If the depth of recursion is larger than 1, then step (1) could be repeated multiple times. + +Right now for proving logs the following interface is exposed on L1 side: + +```solidity +struct L2Log { + uint8 l2ShardId; + bool isService; + uint16 txNumberInBatch; + address sender; + bytes32 key; + bytes32 value; +} + +function proveL2LogInclusion( + uint256 _chainId, + uint256 _batchNumber, + uint256 _index, + L2Log calldata _log, + bytes32[] calldata _proof +) external view override returns (bool) { + address hyperchain = getHyperchain(_chainId); + return IZkSyncHyperchain(hyperchain).proveL2LogInclusion(_batchNumber, _index, _log, _proof); +} +``` + +Let’s define a new function: + +```solidity +function proveL2LeafInclusion( + uint256 _chainId, + uint256 _batchNumber, + uint256 _mask, + bytes32 _leaf, + bytes32[] calldata _proof +) external view override returns (bool) {} +``` + +This function will prove that a certain 32-byte leaf belongs to the tree. Note, that the fact that the `leaf` is 32-bytes long means that the function could work successfully for internal leaves also. To prevent this it will be the callers responsibility to ensure that the preimage of the leaf is larger than 32-bytes long and/or use other ways to ensuring that the function will be called securely. + +This function will be internally used by the existing `_proveL2LogInclusion` function to prove that a certain log existed + +We want to avoid breaking changes to SDKs, so we will modify the `zks_getL2ToL1LogProof` to return the data in the following format (the results of it are directly passed into the `proveL2LeafInclusion` method, so returned value must be supported by the contract): + +First `bytes32` corresponds to the metadata of the proof. The zero-th byte should tell the version of the metadata and must be equal to the `SUPPORTED_PROOF_METADATA_VERSION` (a constant of `0x01`). + +Then, it should contain the number of 32-byte words that are needed to restore the current `BatchRootLeaf` , i.e. `logLeafProofLen` (it is called this way as it proves that a leaf belongs to the `SettledRootOfBatch`). The second byte contains the `batchLeafProofLen` . It is the length of the merkle path to prove that the `BatchRootLeaf` belonged to the `ChainIdRoot` . + +Then, the following happens: + +- We consume the `logLeafProofLen` items to produce the `SettledRootOfBatch`. The last word is typically the aggregated root for the chain. + +If the settlement layer of the chain is the chain itself, we can just end here by verifying that the provided batch message root is correct. + +If the chain is not a settlement layer of itself, we then need to calculate: + +- `BatchRootLeaf = keccak256(BATCH_LEAF_HASH_PADDING, SettledRootOfBatch, batch_number).` +- Consume one element from the `_proofs` array to get the mask for the merkle path of the batch leaf in the chain id tree. +- Consume `batchLeafProofLen` elements to construct the `ChainIdRoot` +- After that, we calculate the `chainIdLeaf = keccak256(CHAIN_ID_LEAF_PADDING, chainIdRoot, chainId` + +Now, we have the _supposed_ `chainIdRoot` for the chain inside its settlement layer. The only thing left to prove is that this root belonged to some batch of the settlement layer. + +Then, the following happens: + +- One element from `_proof` array is consumed and expected to maintain the batchNumber of the settlement layer when this chainid root was present as well as mask for the reconstruction of the merkle tree. +- The other element from the `_proof` contains the address of the settlement layer, where the address will be checked. + +Now, we can call the function to verify that the batch belonged to the settlement layer: + +```solidity + IMailbox(settlementLayerAddress).proveL2LeafInclusion( + settlementLayerBatchNumber, + settlementLayerBatchRootMask, + chainIdLeaf, + // Basically pass the rest of the `_proof` array + extractSliceUntilEnd(_proof, ptr) + ); +``` + +The other slice of the `_proof` array is expected to have the same structure as before: + +- Metadata +- Merkle path to construct the `SettledRootOfBatch` +- In case there are any more aggregation layers, additional info to prove that the batch belonged to it. + +## Trust assumptions + +Note, that the `_proof` field is provided by potentially malicious users. The only part that really checks anything with L1 state is the final step of the aggregated proof verification, i.e. that the settled root of batch of the final top layer was present on L1. + +It puts a lot of trust in the settlement layers as it can steal funds from chains and “verify” incorrect L3→L1 logs if it wants to. It is the job of the chain itself to ensure that it trusts the aggregation layer. It is also the job of the STM to ensure that the settlement layers that are used by its chains are secure. + +Also, note that that `address` of the settlement layer is provided by the user. Assuming that the settlement layer is trusted, this scheme works fine, since the `chainIdLeaf` belongs to it only if the chain really ever settled there. I.e. so the protection from maliciously chosen settlement layers is the fact that the settlement layers are trusted to never include batches that they did not have. + +## Additional notes on security + +### Redundance of data + +Currently, we never clear the `MessageRoot` in other words, the aggregated root contains more and more batches’ settlement roots, leading to the following two facts: + +- The aggregated proofs’ length starts to logarithmically depend on the number of total batches ever finalized on top of this settlement layer (it also depends logarithmically on the number of chains in the settlement layer). I.e. it is `O(log(total_chains) + log(total_batches) + log(total_logs_in_the_batch))` in case of a single aggregation layer. +- The same data may be referenced from multiple final aggregated roots. + +It is the responsibility of the chain to ensure that each message has a unique id and can not be replayed. Currently a tuple of `chain_batch_number, chain_message_id` is used. While there are multiple message roots from which such a tuple could be proven from, it is still okay as it will be nullified only once. + +Another notable example of the redundancy of data, is that we also have total `MessageRoot` on L1, which contains the aggregated root of all chains, while for chains that settle on L1, we still store the `settledBatchRoot` for the efficiency. + +### Data availability guarantees + +We want to maintain the security invariant that users can always withdraw their funds from rollup chains. In other words, all L3→L1 logs that come from rollups should be eventually propagated to L1, and also regardless of how other chains behave an honest chain should always provide the ability for their users to withdraw. + +Firstly, unless the chain settles on L1, this requires a trusted settlement layer. That is, not trusted operator of the gateway, but it works properly, i.e. appends messages correctly, publishes the data that it promises to publish, etc. This is already the case for the Gateway as it is a ZK rollup fork of Era, and while the operator may censor transactions, it can not lie and is always forced to publish all state diffs. + +Secondly, we guarantee that all the stored `ChainIdLeaf`s are published on L1, even for Validiums. Publishing a single 32 byte value per relatively big Gateway batch has little price for Validiums, but it ensures that the settlement root of the gateway can always be constructed. And, assuming that the preimage for the chain root could be constructed, this gives an ability to ability to recover the proof for any L3→L1 coming from a rollup. + +But how can one reconstruct the total chain tree for a particular rollup chain? A rollup would relay all of its pubdata to L1, meaning that by observing L1, the observer would know all the L3→L1 logs that happened in a particular batch. It means that for each batch it can restore the `LocalRoot` (in case the `AggregatedRoot` is non-zero, it could be read from e.g. the storage which is available via the standard state diffs). This allows to calculate the `BatchRootLeaf` for the chain. The only thing missing is understanding which batches were finalized on gateway in order to construct the merkle path to the `ChainRootLeaf`. + +To understand which SL was used by a batch for finalization, one could simply brute force over all settlement layers ever used to find out where the settledBatchRoot is stored.. This number is expected to be rather small. + +## Legacy support + +In order to ease the server migration, we support legacy format of L2→L1 logs proving, i.e. just provide a proof that assumes that stored `settledMessageRoot` is identical to local root, i.e. the hash of logs in the batch. + +To differentiate between legacy format and the one, the following approach is used; + +- Except for the first 3 bytes the first word in the new format contains 0s, which is unlikely in the old format, where leaves are hashed. +- I.e. if the last 29 bytes are zeroes, then it is assumed to be the new format and vice versa. + +In the next release the old format will be removed. diff --git a/docs/gateway/overview.md b/docs/gateway/overview.md new file mode 100644 index 000000000..ea50dc459 --- /dev/null +++ b/docs/gateway/overview.md @@ -0,0 +1,25 @@ +# Gateway + +[back to readme](../README.md) + +Gateway is a proof aggregation layer, created to solve the following problems: + +- Fast interop (interchain communication) would require quick proof generation and verification. The latter can be very expensive on L1. Gateway provides an L1-like interface for chains, while giving a stable price for compute. +- Generally proof aggregation can reduce costs for users, if there are multiple chains settling on top of the same layer. It can reduce the costs of running a Validium even further. + +In this release, Gateway is basically a fork of Era, that will be deployed within the same CTM as other ZK Chains. This allows us to reuse most of the existing code for Gateway. + +> In some places in code you can meet words such as “settlement layer” or the abbreviation “sl”. “Settlement layer” is a general term that describes a chain that other chains can settle to. Right now, the list of settlement layers is whitelisted and only Gateway will be allowed to be a settlement layer (along with L1). + +## High level gateway architecture + +![image.png](./img/gateway_architecture.png) + +## Read more + +- [General overview](overview.md) +- [Chain migration](chain_migration.md) +- [L1->L3 messaging via gateway](messaging_via_gateway.md) +- [L3->L1 messaging via gateway](nested_l3_l1_messaging.md) +- [Gateway protocol versioning](gateway_protocol_upgrades.md) +- [DA handling on Gateway](gateway_da.md) diff --git a/docs/glossary.md b/docs/glossary.md new file mode 100644 index 000000000..c0a1b29a8 --- /dev/null +++ b/docs/glossary.md @@ -0,0 +1,11 @@ +# Glossary + +[back to readme](./README.md) + +- **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. +- **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. diff --git a/docs/img/reading_order.png b/docs/img/reading_order.png new file mode 100644 index 000000000..c5309e681 Binary files /dev/null and b/docs/img/reading_order.png differ diff --git a/docs/l2_system_contracts/batches_and_blocks_on_zksync.md b/docs/l2_system_contracts/batches_and_blocks_on_zksync.md new file mode 100644 index 000000000..723317e64 --- /dev/null +++ b/docs/l2_system_contracts/batches_and_blocks_on_zksync.md @@ -0,0 +1,103 @@ +# Batches & L2 blocks on ZKsync + +[back to readme](../README.md) + +## Glossary + +- Batch - a set of transactions that the bootloader processes (`commitBatches`, `proveBatches`, and `executeBatches` work with it). A batch consists of multiple transactions. +- L2 blocks - non-intersecting sub-sets of consecutively executed transactions in a batch. This is the kind of block you see in the API. This is the one that is used for `block.number`/`block.timestamp`/etc. + +> Note that sometimes in code you can see notion of "virtual blocks". In the past, we returned batch information for `block.number`/`block.timestamp`. However due to DevEx issues we decided to move to returned these values for L2 blocks. Virtual blocks were used during migration, but are not used anymore. You consider that there is one virtual block per one L2 block and it has exactly the same properties. + +## Motivation + +L2 blocks were created for fast soft confirmation in wallets and block explorer. For example, MetaMask shows transactions as confirmed only after the block in which transaction execution was mined. So if the user needs to wait for the batch confirmation it would take at least a few minutes (for soft confirmation) and hours for full confirmation which is very bad UX. But API could return soft confirmation much earlier through L2 blocks. + +## Adapting for Solidity + +In order to get the returned value for `block.number`, `block.timestamp`, `blockhash` our compiler used the following functions: + +- `getBlockNumber` +- `getBlockTimestamp` +- `getBlockHashEVM` + +These return values for L2 blocks. + +## Blocks’ processing and consistency checks + +Our `SystemContext` contract allows to get information about batches and L2 blocks. Some of the information is hard to calculate onchain. For instance, time. The timing information (for both batches and L2 blocks) are provided by the operator. In order to check that the operator provided some realistic values, certain checks are done on L1. Generally though, we try to check as much as we can on L2. + +### Initializing L1 batch + +At the start of the batch, the operator [provides](../../system-contracts/bootloader/bootloader.yul#L3935) the timestamp of the batch, its number and the hash of the previous batch. The root hash of the Merkle tree serves as the root hash of the batch. + +The SystemContext can immediately check whether the provided number is the correct batch number. It also immediately sends the previous batch hash to L1, where it will be checked during the commit operation. Also, some general consistency checks are performed. This logic can be found [here](../../system-contracts/contracts/SystemContext.sol#L469). + +### L2 blocks processing and consistency checks + +#### `setL2Block` + +Before each transaction, we call `setL2Block` [method](../../system-contracts/bootloader/bootloader.yul#L2884). There we will provide some data about the L2 block that the transaction belongs to: + +- `_l2BlockNumber` The number of the new L2 block. +- `_l2BlockTimestamp` The timestamp of the new L2 block. +- `_expectedPrevL2BlockHash` The expected hash of the previous L2 block. +- `_isFirstInBatch` Whether this method is called for the first time in the batch. +- `_maxVirtualBlocksToCreate` The maximum number of virtual block to create with this L2 block. This is a legacy field that is always equal either to 0 or 1. + +If two transactions belong to the same L2 block, only the first one may have non-zero `_maxVirtualBlocksToCreate`. The rest of the data must be same. + +The `setL2Block` [performs](../../system-contracts/contracts/SystemContext.sol#L355) a lot of similar consistency checks to the ones for the L1 batch. + +#### L2 blockhash calculation and storage + +Unlike L1 batch’s hash, the L2 blocks’ hashes can be checked on L2. + +The hash of an L2 block is `keccak256(abi.encode(_blockNumber, _blockTimestamp, _prevL2BlockHash, _blockTxsRollingHash))`. Where `_blockTxsRollingHash` is defined in the following way: + +`_blockTxsRollingHash = 0` for an empty block. + +`_blockTxsRollingHash = keccak(0, tx1_hash)` for a block with one tx. + +`_blockTxsRollingHash = keccak(keccak(0, tx1_hash), tx2_hash)` for a block with two txs, etc. + +To add a transaction hash to the current miniblock we use the `appendTransactionToCurrentL2Block` function of the `SystemContext` contract. + +Since ZKsync is a state-diff based rollup, there is no way to deduce the hashes of the L2 blocks based on the transactions’ in the batch (because there is no access to the transaction’s hashes). At the same time, in order to execute `blockhash` method, the VM requires the knowledge of some of the previous L2 block hashes. In order to save up on pubdata (by making sure that the same storage slots are reused, i.e. we only have repeated writes) we [store](../../system-contracts/contracts/SystemContext.sol#L73) only the last 257 block hashes. You can read more on what are the repeated writes and how the pubdata is processed [here](../settlement_contracts/data_availability/standard_pubdata_format.md). + +We store only the last 257 blocks, since the EVM requires only 256 previous ones and we use 257 as a safe margin. + +#### Legacy blockhash + +For L2 blocks that were created before we switched to the formulas from above, we use the following formula for their hash: + +`keccak256(abi.encodePacked(uint32(_blockNumber)))` + +These are only very old blocks on ZKsync Era and other ZK chains don't have such blocks. + +#### Timing invariants + +While the timestamp of each L2 block is provided by the operator, there are some timing invariants that the system preserves: + +- For each L2 block its timestamp should be > the timestamp of the previous L2 block +- For each L2 block its timestamp should be ≥ timestamp of the batch it belongs to +- Each batch must start with a new L2 block (i.e. an L2 block can not span across batches). +- The timestamp of a batch must be ≥ the timestamp of the latest L2 block which belonged to the previous batch. +- The timestamp of the last miniblock in batch can not go too far into the future. This is enforced by publishing an L2→L1 log, with the timestamp which is then checked on L1. + +### Fictive L2 block & finalizing the batch + +At the end of the batch, the bootloader calls the `setL2Block` [one more time](../../system-contracts/bootloader/bootloader.yul#L4110) to allow the operator to create a new empty block. This is done purely for some of the technical reasons inside the node, where each batch ends with an empty L2 block. + +We do not enforce that the last block is empty explicitly as it complicates the development process and testing, but in practice, it is, and either way, it should be secure. + +Also, at the end of the batch we send the timestamps of the batch as well as the timestamp of the last miniblock in order to check on L1 that both of these are realistic. Checking any other L2 block’s timestamp is not required since all of them are enforced to be between those two. + +## Additional note on blockhashes + +In the past, we had to apply different formulas based on whether or not the migration from batch environment info to L2 block info has finished. You can find these checks [here](../../system-contracts/contracts/SystemContext.sol#L137). But note, that the migration has ended quite some time ago, so in reality only the two cases above can be met: + +- When the block is out of the readable range. +- When it is a normal L2 block and so its hash has to be used. + +The only edge case is when we ask for miniblock block number for which the base hash is returned. This edge case will be removed in future releases. diff --git a/docs/l2_system_contracts/elliptic_curve_precompiles.md b/docs/l2_system_contracts/elliptic_curve_precompiles.md new file mode 100644 index 000000000..a3caa1f01 --- /dev/null +++ b/docs/l2_system_contracts/elliptic_curve_precompiles.md @@ -0,0 +1,251 @@ +# Elliptic curve precompiles + +[back to readme](../README.md) + +Precompiled contracts for elliptic curve operations are required in order to perform zkSNARK verification. + +The operations that you need to be able to perform are elliptic curve point addition, elliptic curve point scalar multiplication, and elliptic curve pairing. + +This document explains the precompiles responsible for elliptic curve point addition and scalar multiplication and the design decisions. You can read the specification [here](https://eips.ethereum.org/EIPS/eip-196). + +## Introduction + +On top of having a set of opcodes to choose from, the EVM also offers a set of more advanced functionalities through precompiled contracts. These are a special kind of contracts that are bundled with the EVM at fixed addresses and can be called with a determined gas cost. The addresses start from 1, and increment for each contract. New hard forks may introduce new precompiled contracts. They are called from the opcodes like regular contracts, with instructions like CALL. The gas cost mentioned here is purely the cost of the contract and does not consider the cost of the call itself nor the instructions to put the parameters in memory. + +For Go-Ethereum, the code being run is written in Go, and the gas costs are defined in each precompile spec. + +In the case of ZKsync Era, ecAdd and ecMul precompiles are written as a smart contract for two reasons: + +- zkEVM needs to be able to prove their execution (and at the moment it cannot do that if the code being run is executed outside the VM) +- Writing custom circuits for Elliptic curve operations is hard, and time-consuming, and after all such code is harder to maintain and audit. + +## Field Arithmetic + +The BN254 (also known as alt-BN128) is an elliptic curve defined by the equation $y^2 = x^3 + 3$ over the finite field $\mathbb{F}_p$, being $p = 21888242871839275222246405745257275088696311157297823662689037894645226208583. The modulus is less than 256 bits, which is why every element in the field is represented as a `uint256`. + +The arithmetic is carried out with the field elements encoded in the Montgomery form. This is done not only because operating in the Montgomery form speeds up the computation but also because the native modular multiplication, which is carried out by Yul's `mulmod` opcode, is very inefficient. + +Instructions set on ZKsync and EVM are different, so the performance of the same Yul/Solidity code can be efficient on EVM, but not on zkEVM and opposite. + +One such very inefficient command is `mulmod`. On EVM there is a native opcode that makes modulo multiplication and it costs only 8 gas, which compared to the other opcodes costs is only 2-3 times more expensive. On zkEVM we don’t have native `mulmod` opcode, instead, the compiler does full-with multiplication (e.g. it multiplies two `uint256`s and gets as a result an `uint512`). Then the compiler performs long division for reduction (but only the remainder is kept), in the generic form it is an expensive operation and costs many opcode executions, which can’t be compared to the cost of one opcode execution. The worst thing is that `mulmod` is used a lot for the modulo inversion, so optimizing this one opcode gives a huge benefit to the precompiles. + +### Multiplication + +As said before, multiplication was carried out by implementing the Montgomery reduction, which works with general moduli and provides a significant speedup compared to the naïve approach. + +The squaring operation is obtained by multiplying a number by itself. However, this operation can have an additional speedup by implementing the SOS Montgomery squaring. + +### Inversion + +Inversion was performed using the extended binary Euclidean algorithm (also known as extended binary greatest common divisor). This algorithm is a modification of Algorithm 3 `MontInvbEEA` from [Montgomery inversion](https://cetinkayakoc.net/docs/j82.pdf). + +### Exponentiation + +The exponentiation was carried out using the square and multiply algorithm, which is a standard technique for this operation. + +## Montgomery Form + +Let’s take a number `R`, such that `gcd(N, R) == 1` and `R` is a number by which we can efficiently divide and take module over it (for example power of two or better machine word, aka 2^256). Then transform every number to the form of `x * R mod N` / `y * R mod N` and then we get efficient modulo addition and multiplication. The only thing is that before working with numbers we need to transform them to the form from `x mod N` to the `x * R mod N` and after performing operations transform the form back. + +For the latter, we will assume that `N` is the module that we use in computations, and `R` is $2^{256}$, since we can efficiently divide and take module over this number and it practically satisfies the property of `gcd(N, R) == 1`. + +### Montgomery Reduction Algorithm (REDC) + +> Reference: + +```solidity +/// @notice Implementation of the Montgomery reduction algorithm (a.k.a. REDC). +/// @dev See +/// @param lowestHalfOfT The lowest half of the value T. +/// @param higherHalfOfT The higher half of the value T. +/// @return S The result of the Montgomery reduction. +function REDC(lowestHalfOfT, higherHalfOfT) -> S { + let q := mul(lowestHalfOfT, N_PRIME()) + let aHi := add(higherHalfOfT, getHighestHalfOfMultiplication(q, P())) + let aLo, overflowed := overflowingAdd(lowestHalfOfT, mul(q, P())) + if overflowed { + aHi := add(aHi, 1) + } + S := aHi + if iszero(lt(aHi, P())) { + S := sub(aHi, P()) + } +} + +``` + +By choosing $R = 2^{256}$ we avoided 2 modulo operations and one division from the original algorithm. This is because in Yul, native numbers are uint256 and the modulo operation is native, but for the division, as we work with a 512-bit number split into two parts (high and low part) dividing by $R$ means shifting 256 bits to the right or what is the same, discarding the low part. + +### Montgomery Addition/Subtraction + +Addition and subtraction in Montgomery form are the same as ordinary modular addition and subtraction because of the distributive law + +$$ +\begin{align*} +aR+bR=(a+b)R,\\ +aR-bR=(a-b)R. +\end{align*} +$$ + +```solidity +/// @notice Computes the Montgomery addition. +/// @dev See for further details on the Montgomery multiplication. +/// @param augend The augend in Montgomery form. +/// @param addend The addend in Montgomery form. +/// @return ret The result of the Montgomery addition. +function montgomeryAdd(augend, addend) -> ret { + ret := add(augend, addend) + if iszero(lt(ret, P())) { + ret := sub(ret, P()) + } +} + +/// @notice Computes the Montgomery subtraction. +/// @dev See for further details on the Montgomery multiplication. +/// @param minuend The minuend in Montgomery form. +/// @param subtrahend The subtrahend in Montgomery form. +/// @return ret The result of the Montgomery subtraction. +function montgomerySub(minuend, subtrahend) -> ret { + ret := montgomeryAdd(minuend, sub(P(), subtrahend)) +} + +``` + +We do not use `addmod`. That's because in most cases the sum does not exceed the modulus. + +### Montgomery Multiplication + +The product of $aR \mod N$ and $bR \mod N$ is $REDC((aR \mod N)(bR \mod N))$. + +```solidity +/// @notice Computes the Montgomery multiplication using the Montgomery reduction algorithm (REDC). +/// @dev See for further details on the Montgomery multiplication. +/// @param multiplicand The multiplicand in Montgomery form. +/// @param multiplier The multiplier in Montgomery form. +/// @return ret The result of the Montgomery multiplication. +function montgomeryMul(multiplicand, multiplier) -> ret { + let hi := getHighestHalfOfMultiplication(multiplicand, multiplier) + let lo := mul(multiplicand, multiplier) + ret := REDC(lo, hi) +} + +``` + +### Montgomery Inversion + +```solidity +/// @notice Computes the Montgomery modular inverse skipping the Montgomery reduction step. +/// @dev The Montgomery reduction step is skipped because a modification in the binary extended Euclidean algorithm is used to compute the modular inverse. +/// @dev See the function `binaryExtendedEuclideanAlgorithm` for further details. +/// @param a The field element in Montgomery form to compute the modular inverse of. +/// @return invmod The result of the Montgomery modular inverse (in Montgomery form). +function montgomeryModularInverse(a) -> invmod { + invmod := binaryExtendedEuclideanAlgorithm(a) +} +``` + +As said before, we use a modified version of the bEE algorithm that lets us “skip” the Montgomery reduction step. + +The regular algorithm would be $REDC((aR \mod N)^{−1}(R^3 \mod N))$ which involves a regular inversion plus a multiplication by a value that can be precomputed. + +## ECADD + +Precompile for computing elliptic curve point addition. The points are represented in affine form, given by a pair of coordinates $(x,y)$. + +Affine coordinates are the conventional way of expressing elliptic curve points, which use 2 coordinates. The math is concise and easy to follow. + +For a pair of constants $a$ and $b$, an elliptic curve is defined by the set of all points $(x,y)$ that satisfy the equation $y^2=x^3+ax+b$, plus a special “point at infinity” named $O$. + +### Point Doubling + +To compute $2P$ (or $P+P$), there are three cases: + +- If $P = O$, then $2P = O$. +- Else $P = (x, y)$ + + - If $y = 0$, then $2P = O$. + - Else $y≠0$, then + + $$ + \begin{gather*} \lambda = \frac{3x_{p}^{2} + a}{2y_{p}} \\ x_{r} = \lambda^{2} - 2x_{p} \\ y_{r} = \lambda(x_{p} - x_{r}) - y_{p}\end{gather*} + $$ + +The complicated case involves approximately 6 multiplications, 4 additions/subtractions, and 1 division. There could also be 4 multiplications, 6 additions/subtractions, and 1 division, and if you want you could trade a multiplication with 2 more additions. + +### Point Addition + +To compute $P + Q$ where $P \neq Q$, there are four cases: + +- If $P = 0$ and $Q \neq 0$, then $P + Q = Q$. +- If $Q = 0$ and $P \neq 0$, then $P + Q = P$. +- Else $P = (x_{p},\ y_{p})$ and$Q = (x_{q},\ y_{q})$ + + - If $x_{p} = x_{q}$ (and necessarily $y_{p} \neq y_{q}$), then $P + Q = O$. + - Else $x_{p} \neq x_{q}$, then + + $$ + \begin{gather*} \lambda = \frac{y_{2} - y_{1}}{x_{2} - x_{1}} \\ x_{r} = \lambda^{2} - x_{p} - x_{q} \\ y_{r} = \lambda(x_{p} - x_{r}) - y_{p}\end{gather*} + $$ + + and $P + Q = R = (x_{r},\ y_{r})$. + +The complicated case involves approximately 2 multiplications, 6 additions/subtractions, and 1 division. + +## ECMUL + +Precompile for computing elliptic curve point scalar multiplication. The points are represented in homogeneous projective coordinates, given by the coordinates $(x , y , z)$. Transformation into affine coordinates can be done by applying the following transformation: +$(x,y) = (X.Z^{-1} , Y.Z^{-1} )$ if the point is not the point at infinity. + +The key idea of projective coordinates is that instead of performing every division immediately, we defer the divisions by multiplying them into a denominator. The denominator is represented by a new coordinate. Only at the very end, do we perform a single division to convert from projective coordinates back to affine coordinates. + +In affine form, each elliptic curve point has 2 coordinates, like $(x,y)$. In the new projective form, each point will have 3 coordinates, like $(X,Y,Z)$, with the restriction that $Z$ is never zero. The forward mapping is given by $(x,y)→(xz,yz,z)$, for any non-zero $z$ (usually chosen to be 1 for convenience). The reverse mapping is given by $(X,Y,Z)→(X/Z,Y/Z)$, as long as $Z$ is non-zero. + +### Point Doubling + +The affine form case $y=0$ corresponds to the projective form case $Y/Z=0$. This is equivalent to $Y=0$, since $Z≠0$. + +For the interesting case where $P=(X,Y,Z)$ and $Y≠0$, let’s convert the affine arithmetic to projective arithmetic. + +After expanding and simplifying the equations ([demonstration here](https://www.nayuki.io/page/elliptic-curve-point-addition-in-projective-coordinates)), the following substitutions come out + +$$ +\begin{align*} T &= 3X^{2} + aZ^{2},\\ U &= 2YZ,\\ V &= 2UXY,\\ W &= T^{2} - 2V \end{align*} +$$ + +Using them, we can write + +$$ +\begin{align*} X_{r} &= UW \\ Y_{r} &= T(V−W)−2(UY)^{2} \\ Z_{r} &= U^{3} \end{align*} +$$ + +As we can see, the complicated case involves approximately 18 multiplications, 4 additions/subtractions, and 0 divisions. + +### Point Addition + +The affine form case $x_{p} = x_{q}$ corresponds to the projective form case $X_{p}/Z_{p} = X_{q}/Z_{q}$. This is equivalent to $X_{p}Z_{q} = X_{q}Z_{p}$, via cross-multiplication. + +For the interesting case where $P = (X_{p},\ Y_{p},\ Z_{p})$ , $Q = (X_{q},\ Y_{q},\ Z_{q})$, and $X_{p}Z_{q} ≠ X_{q}Z_{p}$, let’s convert the affine arithmetic to projective arithmetic. + +After expanding and simplifying the equations ([demonstration here](https://www.nayuki.io/page/elliptic-curve-point-addition-in-projective-coordinates)), the following substitutions come out + +$$ +\begin{align*} +T_{0} &= Y_{p}Z_{q}\\ +T_{1} &= Y_{q}Z_{p}\\ +T &= T_{0} - T_{1}\\ +U_{0} &= X_{p}Z_{q}\\ +U_{1} &= X_{q}Z_{p}\\ +U &= U_{0} - U_{1}\\ +U_{2} &= U^{2}\\ +V &= Z_{p}Z_{q}\\ +W &= T^{2}V−U_{2}(U_{0}+U_{1}) \\ +\end{align*} +$$ + +Using them, we can write + +$$ +\begin{align*} X_{r} &= UW \\ Y_{r} &= T(U_{0}U_{2}−W)−T_{0}U^{3} \\ Z_{r} &= U^{3}V \end{align*} +$$ + +As we can see, the complicated case involves approximately 15 multiplications, 6 additions/subtractions, and 0 divisions. diff --git a/docs/l2_system_contracts/system_contracts_bootloader_description.md b/docs/l2_system_contracts/system_contracts_bootloader_description.md new file mode 100644 index 000000000..9a4a28903 --- /dev/null +++ b/docs/l2_system_contracts/system_contracts_bootloader_description.md @@ -0,0 +1,728 @@ +# System contracts/bootloader description (VM v1.5.0) + +[back to readme](../README.md) + +## Bootloader + +On standard Ethereum clients, the workflow for executing blocks is the following: + +1. Pick a transaction, validate the transactions & charge the fee, execute it +2. Gather the state changes (if the transaction has not reverted), apply them to the state. +3. Go back to step (1) if the block gas limit has not been yet exceeded. + +However, having such flow on ZKsync (i.e. processing transaction one-by-one) would be too inefficient, since we have to run the entire proving workflow for each individual transaction. That’s what we need the _bootloader_ for: instead of running N transactions separately, we run the entire batch (set of blocks, more can be found [here](./batches_and_blocks_on_zksync.md)) as a single program that accepts the array of transactions as well as some other batch metadata and processes them inside a single big “transaction”. The easiest way to think about bootloader is to think in terms of EntryPoint from EIP4337: it also accepts the array of transactions and facilitates the Account Abstraction protocol. + +The hash of the code of the bootloader is stored on L1 and can only be changed as a part of a system upgrade. Note, that unlike system contracts, the bootloader’s code is not stored anywhere on L2. That’s why we may sometimes refer to the bootloader’s address as formal. It only exists for the sake of providing some value to `this` / `msg.sender`/etc. When someone calls the bootloader address (e.g. to pay fees) the EmptyContract’s code is actually invoked. + +## System contracts + +While most of the primitive EVM opcodes can be supported out of the box (i.e. zero-value calls, addition/multiplication/memory/storage management, etc), some of the opcodes are not supported by the VM by default and they are implemented via “system contracts” — these contracts are located in a special _kernel space,_ i.e. in the address space in range `[0..2^16-1]`, and they have some special privileges, which users’ contracts don’t have. These contracts are pre-deployed at the genesis and updating their code can be done only via system upgrade, managed from L1. + +The use of each system contract will be explained down below. + +### Pre-deployed contracts + +Some of the contracts need to be predeployed at the genesis, but they do not need the kernel space rights. To give them minimal permissiones, we predeploy them at consecutive addresses that start right at the `2^16`. These will be described in the following sections. + +## zkEVM internals + +Full specification of the zkEVM is beyond the scope of this document. However, this section will give you most of the details needed for understanding the L2 system smart contracts & basic differences between EVM and zkEVM. + +### Registers and memory management + +On EVM, during transaction execution, the following memory areas are available: + +- `memory` itself. +- `calldata` the immutable slice of parent memory. +- `returndata` the immutable slice returned by the latest call to another contract. +- `stack` where the local variables are stored. + +Unlike EVM, which is stack machine, zkEVM has 16 registers. Instead of receiving input from `calldata`, zkEVM starts by receiving a _pointer_ in its first register (_basically a packed struct with 4 elements: the memory page id, start and length of the slice to which it points to_) to the calldata page of the parent. Similarly, a transaction can receive some other additional data within its registers at the start of the program: whether the transaction should invoke the constructor ([more about deployments here](#contractdeployer--immutablesimulator)), whether the transaction has `isSystem` flag, etc. The meaning of each of these flags will be expanded further in this section. + +_Pointers_ are separate type in the VM. It is only possible to: + +- Read some value within a pointer. +- Shrink the pointer by reducing the slice to which pointer points to. +- Receive the pointer to the returndata/as a calldata. +- Pointers can be stored only on stack/registers to make sure that the other contracts can not read memory/returndata of contracts they are not supposed to. +- A pointer can be converted to the u256 integer representing it, but an integer can not be converted to a pointer to prevent unallowed memory access. +- It is not possible to return a pointer that points to a memory page with id smaller than the one for the current page. What this means is that it is only possible to `return` only pointer to the memory of the current frame or one of the pointers returned by the subcalls of the current frame. + +#### Memory areas in zkEVM + +For each frame, the following memory areas are allocated: + +- _Heap_ (plays the same role as `memory` on Ethereum). +- _AuxHeap_ (auxiliary heap). It has the same properties as Heap, but it is used for the compiler to encode calldata/copy the returndata from the calls to system contracts to not interfere with the standard Solidity memory alignment. +- _Stack_. Unlike Ethereum, stack is not the primary place to get arguments for opcodes. The biggest difference between stack on zkEVM and EVM is that on ZKsync stack can be accessed at any location (just like memory). While users do not pay for the growth of stack, the stack can be fully cleared at the end of the frame, so the overhead is minimal. +- _Code_. The memory area from which the VM executes the code of the contract. The contract itself can not read the code page, it is only done implicitly by the VM. + +Also, as mentioned in the previous section, the contract receives the pointer to the calldata. + +#### Managing returndata & calldata + +Whenever a contract finishes its execution, the parent’s frame receives a _pointer_ as `returndata`. This pointer may point to the child frame’s Heap/AuxHeap or it can even be the same `returndata` pointer that the child frame received from some of its child frames. + +The same goes with the `calldata`. Whenever a contract starts its execution, it receives the pointer to the calldata. The parent frame can provide any valid pointer as the calldata, which means it can either be a pointer to the slice of parent’s frame memory (heap or auxHeap) or it can be some valid pointer that the parent frame has received before as calldata/returndata. + +Contracts simply remember the calldata pointer at the start of the execution frame (it is by design of the compiler) and remembers the latest received returndata pointer. + +Some important implications of this is that it is now possible to do the following calls without any memory copying: + +A → B → C + +where C receives a slice of the calldata received by B. + +The same goes for returning data: + +A ← B ← C + +There is no need to copy returned data if the B returns a slice of the returndata returned by C. + +Note, that you can _not_ use the pointer that you received via calldata as returndata (i.e. return it at the end of the execution frame). Otherwise, it would be possible that returndata points to the memory slice of the active frame and allow editing the `returndata`. It means that in the examples above, C could not return a slice of its calldata without memory copying. + +Note, that the rule above is implemented by the principle "it is not possible to return a slice of data with memory page id lower than the memory page id of the current heap", since a memory page with smaller id could only be created before the call. That's why a user contract can usually safely return a slice of previously returned returndata (since it is guaranteed to have a higher memory page id). However, system contracts have an exemption from the rule above. It is needed in particular to the correct functionality of the `CodeOracle` system contract. You can read more about it [here](#codeoracle). So the rule of thumb is that returndata from `CodeOracle` should never be passed along. + +Some of these memory optimizations can be seen utilized in the [EfficientCall](../../system-contracts/contracts/libraries/EfficientCall.sol#L34) library that allows to perform a call while reusing the slice of calldata that the frame already has, without memory copying. + +#### Returndata & precompiles + +Some of the operations which are opcodes on Ethereum, have become calls to some of the system contracts. The most notable examples are `Keccak256`, `SystemContext`, etc. Note, that, if done naively, the following lines of code would work differently on ZKsync and Ethereum: + +```solidity +pop(call(...)) +keccak(...) +returndatacopy(...) +``` + +Since the call to keccak precompile would modify the `returndata`. To avoid this, our compiler does not override the latest `returndata` pointer after calls to such opcode-like precompiles. + +### ZKsync specific opcodes + +While some Ethereum opcodes are not supported out of the box, some of the new opcodes were added to facilitate the development of the system contracts. + +Note, that this lists does not aim to be specific about the internals, but rather explain methods in the [SystemContractHelper.sol](../../system-contracts/contracts/libraries/SystemContractHelper.sol#L44) + +#### **Only for kernel space** + +These opcodes are allowed only for contracts in kernel space (i.e. system contracts). If executed in other places they result in `revert(0,0)`. + +- `mimic_call`. The same as a normal `call`, but it can alter the `msg.sender` field of the transaction. +- `to_l1`. Sends a system L2→L1 log to Ethereum. The structure of this log can be seen [here](../../l1-contracts/contracts/common/Messaging.sol#L23). +- `event`. Emits an L2 log to ZKsync. Note, that L2 logs are not equivalent to Ethereum events. Each L2 log can emit 64 bytes of data (the actual size is 88 bytes, because it includes the emitter address, etc). A single Ethereum event is represented with multiple `event` logs constitute. This opcode is only used by `EventWriter` system contract. +- `precompile_call`. This is an opcode that accepts two parameters: the uint256 representing the packed parameters for it as well as the ergs to burn. Besides the price for the precompile call itself, it burns the provided ergs and executes the precompile. The action that it does depend on `this` during execution: + - If it is the address of the `ecrecover` system contract, it performs the ecrecover operation + - If it is the address of the `sha256`/`keccak256` system contracts, it performs the corresponding hashing operation. + - It does nothing (i.e. just burns ergs) otherwise. It can be used to burn ergs needed for L2→L1 communication or publication of bytecodes onchain. +- `setValueForNextFarCall` sets `msg.value` for the next `call`/`mimic_call`. Note, that it does not mean that the value will be really transferred. It just sets the corresponding `msg.value` context variable. The transferring of ETH should be done via other means by the system contract that uses this parameter. Note, that this method has no effect on `delegatecall` , since `delegatecall` inherits the `msg.value` of the previous frame. +- `increment_tx_counter` increments the counter of the transactions within the VM. The transaction counter used mostly for the VM’s internal tracking of events. Used only in bootloader after the end of each transaction. +- `decommit` will return a pointer to a slice with the corresponding bytecode hash preimage. If this bytecode has been unpacked before, the memory page where it was unpacked will be reused. If it has never been unpacked before, it will be unpacked into the current heap. + +Note, that currently we do not have access to the `tx_counter` within VM (i.e. for now it is possible to increment it and it will be automatically used for logs such as `event`s as well as system logs produced by `to_l1`, but we can not read it). We need to read it to publish the _user_ L2→L1 logs, so `increment_tx_counter` is always accompanied by the corresponding call to the [SystemContext](#systemcontext) contract. + +More on the difference between system and user logs can be read [here](../settlement_contracts/data_availability/standard_pubdata_format.md). + +#### **Generally accessible** + +Here are opcodes that can be generally accessed by any contract. Note that while the VM allows to access these methods, it does not mean that this is easy: the compiler might not have convenient support for some use-cases yet. + +- `near_call`. It is basically a “framed” jump to some location of the code of your contract. The difference between the `near_call` and ordinary jump are: + 1. It is possible to provide an ergsLimit for it. Note, that unlike “`far_call`”s (i.e. calls between contracts) the 63/64 rule does not apply to them. + 2. If the near call frame panics, all state changes made by it are reversed. Please note, that the memory changes will **not** be reverted. +- `getMeta`. Returns an u256 packed value of [ZkSyncMeta](../../system-contracts/contracts/libraries/SystemContractHelper.sol#L18) struct. Note that this is not tight packing. The struct is formed by the [following rust code](https://github.com/matter-labs/zksync-protocol/blob/main/crates/zkevm_opcode_defs/src/definitions/abi/meta.rs#L4). +- `getCodeAddress` — receives the address of the executed code. This is different from `this` , since in case of delegatecalls `this` is preserved, but `codeAddress` is not. + +#### Flags for calls + +Besides the calldata, it is also possible to provide additional information to the callee when doing `call` , `mimic_call`, `delegate_call`. The called contract will receive the following information in its first 12 registers at the start of execution: + +- _r1_ — the pointer to the calldata. +- _r2_ — the pointer with flags of the call. This is a mask, where each bit is set only if certain flags have been set to the call. Currently, two flags are supported: 0-th bit: `isConstructor` flag. This flag can only be set by system contracts and denotes whether the account should execute its constructor logic. Note, unlike Ethereum, there is no separation on constructor & deployment bytecode. More on that can be read [here](#contractdeployer--immutablesimulator). 1-st bit: `isSystem` flag. Whether the call intends a system contracts’ function. While most of the system contracts’ functions are relatively harmless, accessing some with calldata only may break the invariants of Ethereum, e.g. if the system contract uses `mimic_call`: no one expects that by calling a contract some operations may be done out of the name of the caller. This flag can be only set if the callee is in kernel space. +- The rest r3..r12 registers are non-empty only if the `isSystem` flag is set. There may be arbitrary values passed, which we call `extraAbiParams`. + +The compiler implementation is that these flags are remembered by the contract and can be accessed later during execution via special [simulations](https://github.com/code-423n4/2024-03-zksync/blob/main/docs/VM%20Section/How%20compiler%20works/instructions/extensions/overview.md). + +If the caller provides inappropriate flags (i.e. tries to set `isSystem` flag when callee is not in the kernel space), the flags are ignored. + +#### `onlySystemCall` modifier + +Some of the system contracts can act on behalf of the user or have a very important impact on the behavior of the account. That’s why we wanted to make it clear that users can not invoke potentially dangerous operations by doing a simple EVM-like `call`. Whenever a user wants to invoke some of the operations which we considered dangerous, they must provide “`isSystem`” flag with them. + +The `onlySystemCall` flag checks that the call was either done with the “isSystemCall” flag provided or the call is done by another system contract (since Matter Labs is fully aware of system contracts). + +#### Simulations via our compiler + +In the future, we plan to introduce our “extended” version of Solidity with more supported opcodes than the original one. However, right now it was beyond the capacity of the team to do, so in order to represent accessing ZKsync-specific opcodes, we use `call` opcode with certain constant parameters that will be automatically replaced by the compiler with zkEVM native opcode. + +Example: + +```solidity +function getCodeAddress() internal view returns (address addr) { + address callAddr = CODE_ADDRESS_CALL_ADDRESS; + assembly { + addr := staticcall(0, callAddr, 0, 0xFFFF, 0, 0) + } +} +``` + +In the example above, the compiler will detect that the static call is done to the constant `CODE_ADDRESS_CALL_ADDRESS` and so it will replace it with the opcode for getting the code address of the current execution. + +Full list of opcode simulations can be found [here](https://github.com/code-423n4/2024-03-zksync/blob/main/docs/VM%20Section/How%20compiler%20works/instructions/extensions/call.md). + +We also use [verbatim-like](https://github.com/code-423n4/2024-03-zksync/blob/main/docs/VM%20Section/How%20compiler%20works/instructions/extensions/verbatim.md) statements to access ZKsync-specific opcodes in the bootloader. + +All the usages of the simulations in our Solidity code are implemented in the [SystemContractHelper](../../system-contracts/contracts/libraries//SystemContractHelper.sol) library and the [SystemContractsCaller](../../system-contracts/contracts//libraries/SystemContractsCaller.sol) library. + +#### Simulating `near_call` (in Yul only) + +In order to use `near_call` i.e. to call a local function, while providing a limit of ergs (gas) that this function can use, the following syntax is used: + +The function should contain `ZKSYNC_NEAR_CALL` string in its name and accept at least 1 input parameter. The first input parameter is the packed ABI of the `near_call`. Currently, it is equal to the number of ergs to be passed with the `near_call`. + +Whenever a `near_call` panics, the `ZKSYNC_CATCH_NEAR_CALL` function is called. + +_Important note:_ the compiler behaves in a way that if there is a `revert` in the bootloader, the `ZKSYNC_CATCH_NEAR_CALL` is not called and the parent frame is reverted as well. The only way to revert only the `near_call` frame is to trigger VM’s _panic_ (it can be triggered with either invalid opcode or out of gas error). + +_Important note 2:_ The 63/64 rule does not apply to `near_call`. Also, if 0 gas is provided to the near call, then actually all of the available gas will go to it. + +#### Notes on security + +To prevent unintended substitution, the compiler requires `--system-mode` flag to be passed during compilation for the above substitutions to work. + +> Note, that in the more recent compiler versions this the `--system-mode` has been renamed to `enable_eravm_extensions` (this can be seen in e.g. our [foundry.toml](../../l1-contracts/foundry.toml)) + +### Bytecode hashes + +On ZKsync the bytecode hashes are stored in the following format: + +- The 0th byte denotes the version of the format. Currently the only version that is used is “1”. +- The 1st byte is `0` for deployed contracts’ code and `1` for the contract code [that is being constructed](#constructing-vs-non-constructing-code-hash). +- The 2nd and 3rd bytes denote the length of the contract in 32-byte words as big-endian 2-byte number. +- The next 28 bytes are the last 28 bytes of the sha256 hash of the contract’s bytecode. + +The bytes are ordered in little-endian order (i.e. the same way as for `bytes32` ). + +#### Bytecode validity + +A bytecode is valid if it: + +- Has its length in bytes divisible by 32 (i.e. consists of an integer number of 32-byte words). +- Has a length of less than 2^16 words (i.e. its length in words fits into 2 bytes). +- Has an odd length in words (i.e. the 3rd byte is an odd number). + +Note, that it does not have to consist of only correct opcodes. In case the VM encounters an invalid opcode, it will simply revert (similar to how EVM would treat them). + +A call to a contract with invalid bytecode can not be proven. That is why it is **essential** that no contract with invalid bytecode is ever deployed on ZKsync. It is the job of the [KnownCodesStorage](#knowncodestorage) to ensure that all allowed bytecodes in the system are valid. + +## Account abstraction + +One of the other important features of ZKsync is the support of account abstraction. It is highly recommended to read the documentation on our AA protocol here: [https://docs.zksync.io/zk-stack/concepts/account-abstraction](https://docs.zksync.io/zk-stack/concepts/account-abstraction) + +#### Account versioning + +Each account can also specify which version of the account abstraction protocol do they support. This is needed to allow breaking changes of the protocol in the future. + +Currently, two versions are supported: `None` (i.e. it is a simple contract and it should never be used as `from` field of a transaction), and `Version1`. + +#### Nonce ordering + +Accounts can also signal to the operator which nonce ordering it should expect from these accounts: `Sequential` or `Arbitrary`. + +`Sequential` means that the nonces should be ordered in the same way as in EOAs. This means, that, for instance, the operator will always wait for a transaction with nonce `X` before processing a transaction with nonce `X+1`. + +`Arbitrary` means that the nonces can be ordered in arbitrary order. It is supported by the server right now, i.e. if there is a contract with arbitrary nonce ordering, its transactions will likely either be rejected or get stuck in the mempool due to nonce mismatch. + +Note, that this is not enforced by system contracts in any way. Some sanity checks may be present, but the accounts are allowed to do however they like. It is more of a suggestion to the operator on how to manage the mempool. + +#### Returned magic value + +Now, both accounts and paymasters are required to return a certain magic value upon validation. This magic value will be enforced to be correct on the mainnet, but will be ignored during fee estimation. Unlike Ethereum, the signature verification + fee charging/nonce increment are not included as part of the intrinsic costs of the transaction. These are paid as part of the execution and so they need to be estimated as part of the estimation for the transaction’s costs. + +Generally, the accounts are recommended to perform as many operations as during normal validation, but only return the invalid magic in the end of the validation. This will allow to correctly (or at least as correctly as possible) estimate the price for the validation of the account. + +## Bootloader + +Bootloader is the program that accepts an array of transactions and executes the entire ZKsync batch. This section will expand on its invariants and methods. + +### Playground bootloader vs proved bootloader + +For convenience, we use the same implementation of the bootloader both in the mainnet batches and for emulating ethCalls or other testing activities. _Only_ _proved_ bootloader is ever used for batch-building and thus this document describes only it. + +### Start of the batch + +It is enforced by the ZKPs, that the state of the bootloader is equivalent to the state of a contract transaction with empty calldata. The only difference is that it starts with all the possible memory pre-allocated (to avoid costs for memory expansion). + +For additional efficiency (and our convenience), the bootloader receives its parameters inside its memory. This is the only point of non-determinism: the bootloader _starts with its memory pre-filled with any data the operator wants_. That’s why it is responsible for validating the correctness of it and it should never rely on the initial contents of the memory to be correct & valid. + +For instance, for each transaction, we check that it is [properly ABI-encoded](../../system-contracts/bootloader/bootloader.yul#L4044) and that the transactions [go exactly one after another](../../system-contracts/bootloader/bootloader.yul#L4037). We also ensure that transactions do not exceed the limits of the memory space allowed for transactions. + +### Transaction types & their validation + +While the main transaction format is the internal `Transaction` [format](../../system-contracts/contracts/libraries/TransactionHelper.sol#L25), it is a struct that is used to represent various kinds of transactions types. It contains a lot of `reserved` fields that could be used depending in the future types of transactions without need for AA to change the interfaces of their contracts. + +The exact type of the transaction is marked by the `txType` field of the transaction type. There are 6 types currently supported: + +- `txType`: 0. It means that this transaction is of legacy transaction type. The following restrictions are enforced: +- `maxFeePerErgs=getMaxPriorityFeePerErg` since it is pre-EIP1559 tx type. +- `reserved1..reserved4` as well as `paymaster` are 0. `paymasterInput` is zero. +- Note, that unlike type 1 and type 2 transactions, `reserved0` field can be set to a non-zero value, denoting that this legacy transaction is EIP-155-compatible and its RLP encoding (as well as signature) should contain the `chainId` of the system. +- `txType`: 1. It means that the transaction is of type 1, i.e. transactions access list. ZKsync does not support access lists in any way, so no benefits of fulfilling this list will be provided. The access list is assumed to be empty. The same restrictions as for type 0 are enforced, but also `reserved0` must be 0. +- `txType`: 2. It is EIP1559 transactions. The same restrictions as for type 1 apply, but now `maxFeePerErgs` may not be equal to `getMaxPriorityFeePerErg`. +- `txType`: 113. It is ZKsync transaction type. This transaction type is intended for AA support. The only restriction that applies to this transaction type: fields `reserved0..reserved4` must be equal to 0. +- `txType`: 254. It is a transaction type that is used for upgrading the L2 system. This is the only type of transaction is allowed to start a transaction out of the name of the contracts in kernel space. +- `txType`: 255. It is a transaction that comes from L1. There are almost no restrictions explicitly imposed upon this type of transaction, since the bootloader at the end of its execution sends the rolling hash of the executed priority transactions. The L1 contract ensures that the hash did indeed match the [hashes of the priority transactions on L1](../../l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol#L376). + +You can also read more on L1->L2 transactions and upgrade transactions [here](../settlement_contracts/priority_queue/processing_of_l1-l2_txs.md). + +However, as already stated, the bootloader’s memory is not deterministic and the operator is free to put anything it wants there. For all of the transaction types above the restrictions are imposed in the following ([method](../../system-contracts/bootloader/bootloader.yul#L3107)), which is called before starting processing the transaction. + +### Structure of the bootloader’s memory + +The bootloader expects the following structure of the memory (here by word we denote 32-bytes, the same machine word as on EVM): + +#### **Batch information** + +The first 8 words are reserved for the batch information provided by the operator. + +- `0` word — the address of the operator (the beneficiary of the transactions). +- `1` word — the hash of the previous batch. Its validation will be explained later on. +- `2` word — the timestamp of the current batch. Its validation will be explained later on. +- `3` word — the number of the new batch. +- `4` word — the fair pubdata price. More on how our pubdata is calculated can be read [here](./zksync_fee_model.md). +- `5` word — the “fair” price for L2 gas, i.e. the price below which the `baseFee` of the batch should not fall. For now, it is provided by the operator, but it in the future it may become hardcoded. +- `6` word — the base fee for the batch that is expected by the operator. While the base fee is deterministic, it is still provided to the bootloader just to make sure that the data that the operator has coincides with the data provided by the bootloader. +- `7` word — reserved word. Unused on proved batch. + +The batch information slots [are used at the beginning of the batch](../../system-contracts/bootloader/bootloader.yul#3921). Once read, these slots can be used for temporary data. + +#### **Temporary data for debug & transaction processing purposes** + +- `[8..39]` – reserved slots for debugging purposes +- `[40..72]` – slots for holding the paymaster context data for the current transaction. The role of the paymaster context is similar to the [EIP4337](https://eips.ethereum.org/EIPS/eip-4337)’s one. You can read more about it in the account abstraction documentation. +- `[73..74]` – slots for signed and explorer transaction hash of the currently processed L2 transaction. +- `[75..142]` – 68 slots for the calldata for the KnownCodesContract call. +- `[143..10142]` – 10000 slots for the refunds for the transactions. +- `[10143..20142]` – 10000 slots for the overhead for batch for the transactions. This overhead is suggested by the operator, i.e. the bootloader will still double-check that the operator does not overcharge the user. +- `[20143..30142]` – slots for the “trusted” gas limits by the operator. The user’s transaction will have at its disposal `min(MAX_TX_GAS(), trustedGasLimit)`, where `MAX_TX_GAS` is a constant guaranteed by the system. Currently, it is equal to 80 million gas. In the future, this feature will be removed. +- `[30143..70146]` – slots for storing L2 block info for each transaction. You can read more on the difference L2 blocks and batches [here](./batches_and_blocks_on_zksync.md). +- `[70147..266754]` – slots used for compressed bytecodes each in the following format: + - 32 bytecode hash + - 32 zeroes (but then it will be modified by the bootloader to contain 28 zeroes and then the 4-byte selector of the `publishCompressedBytecode` function of the `BytecodeCompressor`) + - The calldata to the bytecode compressor (without the selector). +- `[266755..266756]` – slots where the hash and the number of current priority ops is stored. More on it in the priority operations [section](../settlement_contracts/priority_queue/processing_of_l1-l2_txs.md). + +#### L1Messenger Pubdata + +- `[266757..1626756]` – slots where the final batch pubdata is supplied to be verified by the [L2DAValidator](../settlement_contracts/data_availability/custom_da.md). + +But briefly, this space is used for the calldata to the L1Messenger’s `publishPubdataAndClearState` function, which accepts the address of the L2DAValidator as well as the pubdata for it to check. The L2DAValidator is a contract that is responsible to ensure efficiency [when handling pubdata](../settlement_contracts/data_availability/custom_da.md). Typically, the calldata `L2DAValidator` would include uncompressed preimages for bytecodes, L2->L1 messages, L2->L1 logs, etc as their compressed counterparts. However, the exact implementation may vary across various ZK chains. + +Note, that while the realistic number of pubdata that can be published in a batch is ~780kb, the size of the calldata to L1Messenger may be a lot larger due to the fact that this method also accepts the original uncompressed state diff entries. These will not be published to L1, but will be used to verify the correctness of the compression. + +One of "worst case" scenarios for the number of state diffs in a batch is when 780kb of pubdata is spent on repeated writes, that are all zeroed out. In this case, the number of diffs is 780kb / 5 = 156k. This means that they will have accoomdate 42432000 bytes of calldata for the uncompressed state diffs. Adding 780kb on top leaves us with roughly 43212000 bytes needed for calldata. 1350375 slots are needed to accommodate this amount of data. We round up to 1360000 slots just in case. + +In theory though much more calldata could be used (if for instance 1 byte is used for enum index). It is the responsibility of the +operator to ensure that it can form the correct calldata for the L1Messenger. + +#### **Transaction’s meta descriptions** + +- `[1626756..1646756]` words — 20000 slots for 10000 transaction’s meta descriptions (their structure is explained below). + +For internal reasons related to possible future integrations of zero-knowledge proofs about some of the contents of the bootloader’s memory, the array of the transactions is not passed as the ABI-encoding of the array of transactions, but: + +- We have a constant maximum number of transactions. At the time of this writing, this number is 10000. +- Then, we have 10000 transaction descriptions, each ABI encoded as the following struct: + +```solidity +struct BootloaderTxDescription { + // The offset by which the ABI-encoded transaction's data is stored + uint256 txDataOffset; + // Auxiliary data on the transaction's execution. In our internal versions + // of the bootloader it may have some special meaning, but for the + // bootloader used on the mainnet it has only one meaning: whether to execute + // the transaction. If 0, no more transactions should be executed. If 1, then + // we should execute this transaction and possibly try to execute the next one. + uint256 txExecutionMeta; +} +``` + +#### **Reserved slots for the calldata for the paymaster’s postOp operation** + +- `[1646756..1646795]` words — 40 slots which could be used for encoding the calls for postOp methods of the paymaster. + +To avoid additional copying of transactions for calls for the account abstraction, we reserve some of the slots which could be then used to form the calldata for the `postOp` call for the account abstraction without having to copy the entire transaction’s data. + +#### **The actual transaction’s descriptions** + +- `[1646796..1967599]` + +Starting from the 487312 word, the actual descriptions of the transactions start. (The struct can be found by this [link](../../system-contracts/contracts/libraries/TransactionHelper.sol#L25)). The bootloader enforces that: + +- They are correctly ABI encoded representations of the struct above. +- They are located without any gaps in memory (the first transaction starts at word 653 and each transaction goes right after the next one). +- The contents of the currently processed transaction (and the ones that will be processed later on are untouched). Note, that we do allow overriding data from the already processed transactions as it helps to preserve efficiency by not having to copy the contents of the `Transaction` each time we need to encode a call to the account. + +#### **VM hook pointers** + +- `[1967600..1967602]` + +These are memory slots that are used purely for debugging purposes (when the VM writes to these slots, the server side can catch these calls and give important insight information for debugging issues). + +#### **Result ptr pointer** + +- `[1967602..1977602]` + +These are memory slots that are used to track the success status of a transaction. If the transaction with number `i` succeeded, the slot `937499 - 10000 + i` will be marked as 1 and 0 otherwise. + +### General flow of the bootloader’s execution + +1. At the start of the batch it [reads the initial batch information](../../system-contracts/bootloader/bootloader.yul#L3928) and [sends the information](../../system-contracts/bootloader/bootloader.yul#L2857) about the current batch to the SystemContext system contract. +2. It goes through each of [transaction’s descriptions](../../system-contracts/bootloader/bootloader.yul#L4016) and checks whether the `execute` field is set. If not, it ends processing of the transactions and ends execution of the batch. If the execute field is non-zero, the transaction will be executed and it goes to step 3. +3. Based on the transaction’s type it decides whether the transaction is an L1 or L2 transaction and processes them accordingly. More on the processing of the L1 transactions can be read [here](#l1-l2-transactions). More on L2 transactions can be read [here](#l2-transactions). + +### L2 transactions + +On ZKsync, every address is a contract. Users can start transactions from their EOA accounts, because every address that does not have any contract deployed on it implicitly contains the code defined in the [DefaultAccount.sol](../../system-contracts/contracts/DefaultAccount.sol) file. Whenever anyone calls a contract that is not in kernel space (i.e. the address is ≥ 2^16) and does not have any contract code deployed on it, the code for `DefaultAccount` will be used as the contract’s code. + +Note, that if you call an account that is in kernel space and does not have any code deployed there, right now, the transaction will revert. + +We process the L2 transactions according to our account abstraction protocol: [https://docs.zksync.io/build/developer-reference/account-abstraction](https://docs.zksync.io/build/developer-reference/account-abstraction). + +1. We [deduct](../../system-contracts/bootloader/bootloader.yul#L1263) the transaction’s upfront payment for the overhead for the block’s processing. You can read more on how that works in the fee model [description](./zksync_fee_model.md). +2. Then we calculate the gasPrice for these transactions according to the EIP1559 rules. +3. We [conduct the validation step](../../system-contracts/bootloader/bootloader.yul#L1287) of the AA protocol: + + - We calculate the hash of the transaction. + - If enough gas has been provided, we near_call the validation function in the bootloader. It sets the tx.origin to the address of the bootloader, sets the ergsPrice. It also marks the factory dependencies provided by the transaction as marked and then invokes the validation method of the account and verifies the returned magic. + - Calls the accounts and, if needed, the paymaster to receive the payment for the transaction. Note, that accounts may not use `block.baseFee` context variable, so they have no way to know what exact sum to pay. That’s why the accounts typically firstly send `tx.maxFeePerErg * tx.ergsLimit` and the bootloader [refunds](../../system-contracts/bootloader/bootloader.yul#L792) for any excess funds sent. + +4. [We perform the execution of the transaction](../../system-contracts/bootloader/bootloader.yul#L1352). Note, that if the sender is an EOA, tx.origin is set equal to the `from` the value of the transaction. During the execution of the transaction, the publishing of the compressed bytecodes happens: for each factory dependency if it has not been published yet and its hash is currently pointed to in the compressed bytecodes area of the bootloader, a call to the bytecode compressor is done. Also, at the end the call to the KnownCodeStorage is done to ensure all the bytecodes have indeed been published. +5. We [refund](../../system-contracts/bootloader/bootloader.yul#L1206) the user for any excess funds he spent on the transaction: + + - Firstly, the `postTransaction` operation is called to the paymaster. + - The bootloader asks the operator to provide a refund. During the first VM run without proofs the provide directly inserts the refunds in the memory of the bootloader. During the run for the proved batches, the operator already knows what which values have to be inserted there. You can read more about it in the [documentation](./zksync_fee_model.md) of the fee model. + - The bootloader refunds the user. + +6. We notify the operator about the [refund](../../system-contracts/bootloader/bootloader.yul#L1217) that was granted to the user. It will be used for the correct displaying of gasUsed for the transaction in explorer. + +### L1->L2 transactions + +L1->L2 transactions are transactions that were initiated on L1. We assume that `from` has already authorized the L1→L2 transactions. It also has its L1 pubdata price as well as ergsPrice set on L1. + +Most of the steps from the execution of L2 transactions are omitted and we set `tx.origin` to the `from`, and `ergsPrice` to the one provided by transaction. After that, we use [mimicCall](#zksync-specific-opcodes) to provide the operation itself from the name of the sender account. + +Note, that for L1→L2 transactions, `reserved0` field denotes the amount of ETH that should be minted on L2 as a result of this transaction. `reserved1` is the refund receiver address, i.e. the address that would receive the refund for the transaction as well as the msg.value if the transaction fails. + +There are two kinds of L1->L2 transactions: + +- Priority operations, initiated by users (they have type `255`). +- Upgrade transactions, that can be initiated during system upgrade (they have type `254`). + +You can read more about differences between those in the corresponding [document](../settlement_contracts/priority_queue/processing_of_l1-l2_txs.md). + +### End of the batch + +At the end of the batch we set `tx.origin` and `tx.gasprice` context variables to zero to save L1 gas on calldata and send the entire bootloader balance to the operator, effectively sending fees to him. + +Also, we [set](../../system-contracts/bootloader/bootloader.yul#L4110) the fictive L2 block’s data. Then, we call the system context to ensure that it publishes the timestamp of the L2 block as well as L1 batch. We also reset the `txNumberInBlock` counter to avoid its state diffs from being published on L1. You can read more about block processing on ZKsync [here](./batches_and_blocks_on_zksync.md). + +After that, we publish the hash as well as the number of priority operations in this batch. More on it [here](../settlement_contracts/priority_queue/processing_of_l1-l2_txs.md). + +Then, we call the L1Messenger system contract for it to compose the pubdata to be published on L1. You can read more about the pubdata processing [here](../settlement_contracts/data_availability/standard_pubdata_format.md). + +## System contracts + +Most of the details on the implementation and the requirements for the execution of system contracts can be found in the doc-comments of their respective code bases. This chapter serves only as a high-level overview of such contracts. + +All the codes of system contracts (including `DefaultAccount`s) are part of the protocol and can only be change via a system upgrade through L1. + +### SystemContext + +This contract is used to support various system parameters not included in the VM by default, i.e. `chainId`, `origin`, `ergsPrice`, `blockErgsLimit`, `coinbase`, `difficulty`, `baseFee`, `blockhash`, `block.number`, `block.timestamp.` + +It is important to note that the constructor is **not** run for this contract upon genesis, i.e. the constant context values are set on genesis explicitly. Notably, if in the future we want to upgrade the contracts, we will do it via [ContractDeployer](#contractdeployer--immutablesimulator) and so the constructor will be run. + +This contract is also responsible for ensuring validity and consistency of batches, L2 blocks. The implementation itself is rather straightforward, but to better understand this contract, please take a look at the [page](./batches_and_blocks_on_zksync.md) about the block processing on ZKsync. + +### AccountCodeStorage + +The code hashes of accounts are stored inside the storage of this contract. Whenever a VM calls a contract with address `address` it retrieves the value under storage slot `address` of this system contract, if this value is non-zero, it uses this as the code hash of the account. + +Whenever a contract is called, the VM asks the operator to provide the preimage for the codehash of the account. That is why data availability of the code hashes is paramount. + +#### Constructing vs Non-constructing code hash + +In order to prevent contracts from being able to call a contract during its construction, we set the marker (i.e. second byte of the bytecode hash of the account) as `1`. This way, the VM will ensure that whenever a contract is called without the `isConstructor` flag, the bytecode of the default account (i.e. EOA) will be substituted instead of the original bytecode. + +### BootloaderUtilities + +This contract contains some of the methods which are needed purely for the bootloader functionality but were moved out from the bootloader itself for the convenience of not writing this logic in Yul. + +### DefaultAccount + +Whenever a contract that does **not** both: + +- belong to kernel space +- have any code deployed on it (the value stored under the corresponding storage slot in `AccountCodeStorage` is zero) + +The code of the default account is used. The main purpose of this contract is to provide EOA-like experience for both wallet users and contracts that call it, i.e. it should not be distinguishable (apart of spent gas) from EOA accounts on Ethereum. + +### Ecrecover + +The implementation of the ecrecover precompile. It is expected to be used frequently, so written in pure yul with a custom memory layout. + +The contract accepts the calldata in the same format as EVM precompile, i.e. the first 32 bytes are the hash, the next 32 bytes are the v, the next 32 bytes are the r, and the last 32 bytes are the s. + +It also validates the input by the same rules as the EVM precompile: + +- The v should be either 27 or 28, +- The r and s should be less than the curve order. + +After that, it makes a precompile call and returns empty bytes if the call failed, and the recovered address otherwise. + +### Empty contracts + +Some of the contracts are relied upon to have EOA-like behaviour, i.e. they can be always called and get the success value in return. An example of such address is 0 address. We also require the bootloader to be callable so that the users could transfer ETH to it. + +For these contracts, we insert the `EmptyContract` code upon genesis. It is basically a noop code, which does nothing and returns `success=1`. + +### SHA256 & Keccak256 + +Note that, unlike Ethereum, keccak256 is a precompile (_not an opcode_) on ZKsync. + +These system contracts act as wrappers for their respective crypto precompile implementations. They are expected to be used frequently, especially keccak256, since Solidity computes storage slots for mapping and dynamic arrays with its help. That's why we wrote contracts on pure yul with optimizing the short input case. In the past both `sha256` and `keccak256` performed padding within the smart contracts, this is no longer true with `sha256` performing padding in the smart contracts and `keccak256` in the zk-circuits. Hashing is then completed for both within the zk-circuits. + +It's important to note that the crypto part of the `sha256` precompile expects to work with padded data. This means that a bug in applying padding may lead to an unprovable transaction. + +### EcAdd & EcMul + +These precompiles simulate the behaviour of the EVM's EcAdd and EcMul precompiles and are fully implemented in Yul without circuit counterparts. You can read more about them [here](./elliptic_curve_precompiles.md). + +### L2BaseToken & MsgValueSimulator + +Unlike Ethereum, zkEVM does not have any notion of any special native token. That’s why we have to simulate operations with the native token (in which fees are charged) via two contracts: `L2BaseToken` & `MsgValueSimulator`. + +`L2BaseToken` is a contract that holds the balances of native token for the users. This contract does NOT provide ERC20 interface. The only method for transferring native token is `transferFromTo`. It permits only some system contracts to transfer on behalf of users. This is needed to ensure that the interface is as close to Ethereum as possible, i.e. the only way to transfer native token is by doing a call to a contract with some `msg.value`. This is what `MsgValueSimulator` system contract is for. + +Whenever anyone wants to do a non-zero value call, they need to call `MsgValueSimulator` with: + +- The calldata for the call equal to the original one. +- Pass `value` and whether the call should be marked with `isSystem` in the first extra abi params. +- Pass the address of the callee in the second extraAbiParam. + +More information on the extraAbiParams can be read [here](#flags-for-calls). + +#### Support for `.send/.transfer` + +On Ethereum, whenever a call with non-zero value is done, some additional gas is charged from the caller's frame and in return a `2300` gas stipend is given out to the callee frame. This stipend is usually enough to emit a small event, but it is enforced that it is not possible to change storage within these `2300` gas. This also means that in practice some users might opt to do `call` with 0 gas provided, relying on the `2300` stipend to be passed to the callee. This is the case for `.call/.transfer`. + +While using `.send/.transfer` is generally not recommended, as a step towards better EVM compatibility, since vm1.5.0 a _partial_ support of these functions is present with ZKsync Era. It is the done via the following means: + +- Whenever a call is done to the `MsgValueSimulator` system contract, `27000` gas is deducted from the caller's frame and it passed to the `MsgValueSimulator` on top of whatever gas the user has originally provided. The number was chosen to cover for the execution of the transferring of the balances as well as other constant size operations by the `MsgValueSimulator`. Note, that since it will be the frame of `MsgValueSimulator` that will actually call the callee, the constant must also include the cost for decommitting the code of the callee. Decoding bytecode of any size would be prohibitevely expensive and so we support only callees of size up to `100000` bytes. +- `MsgValueSimulator` ensures that no more than `2300` out of the stipend above gets to the callee, ensuring the reentrancy protection invariant for these functions holds. + +Note, that unlike EVM any unused gas from such calls will be refunded. + +The system preserves the following guarantees about `.send/.transfer`: + +- No more than `2300` gas will be received by the callee. Note, [that a smaller, but a close amount](../../system-contracts/contracts/test-contracts/TransferTest.sol#L33) may be passed. +- It is not possible to do any storage changes within this stipend. This is enforced by having cold write cost more than `2300` gas. Also, cold write cost always has to be prepaid whenever executing storage writes. More on it can be read [here](../l2_system_contracts/zksync_fee_model.md#io-pricing). +- Any callee with bytecode size of up to `100000` will work. + +The system does not guarantee the following: + +- That callees with bytecode size larger than `100000` will work. Note, that a malicious operator can fail any call to a callee with large bytecode even if it has been decommitted before. More on it can be read [here](../l2_system_contracts/zksync_fee_model.md#io-pricing). + +As a conclusion, using `.send/.transfer` should be generally avoided, but when avoiding is not possible it should be used with small callees, e.g. EOAs, which implement [DefaultAccount](../../system-contracts/contracts/DefaultAccount.sol). + +### KnownCodeStorage + +This contract is used to store whether a certain code hash is “known”, i.e. can be used to deploy contracts. On ZKsync, the L2 stores the contract’s code _hashes_ and not the codes themselves. Therefore, it must be part of the protocol to ensure that no contract with unknown bytecode (i.e. hash with an unknown preimage) is ever deployed. + +The factory dependencies field provided by the user for each transaction contains the list of the contract’s bytecode hashes to be marked as known. We can not simply trust the operator to “know” these bytecodehashes as the operator might be malicious and hide the preimage. We ensure the availability of the bytecode in the following way: + +- If the transaction comes from L1, i.e. all its factory dependencies have already been published on L1, we can simply mark these dependencies as “known”. +- If the transaction comes from L2, i.e. (the factory dependencies are yet to publish on L1), we make the user pays by burning ergs proportional to the bytecode’s length. After that, we send the L2→L1 log with the bytecode hash of the contract. It is the responsibility of the L1 contracts to verify that the corresponding bytecode hash has been published on L1. + +It is the responsibility of the [ContractDeployer](#contractdeployer--immutablesimulator) system contract to deploy only those code hashes that are known. + +The KnownCodesStorage contract is also responsible for ensuring that all the “known” bytecode hashes are also [valid](#bytecode-validity). + +### ContractDeployer & ImmutableSimulator + +`ContractDeployer` is a system contract responsible for deploying contracts on ZKsync. It is better to understand how it works in the context of how the contract deployment works on ZKsync. Unlike Ethereum, where `create`/`create2` are opcodes, on ZKsync these are implemented by the compiler via calls to the ContractDeployer system contract. + +For additional security, we also distinguish the deployment of normal contracts and accounts. That’s why the main methods that will be used by the user are `create`, `create2`, `createAccount`, `create2Account`, which simulate the CREATE-like and CREATE2-like behavior for deploying normal and account contracts respectively. + +#### **Address derivation** + +Each rollup that supports L1→L2 communications needs to make sure that the addresses of contracts on L1 and L2 do not overlap during such communication (otherwise it would be possible that some evil proxy on L1 could mutate the state of the L2 contract). Generally, rollups solve this issue in two ways: + +- XOR/ADD some kind of constant to addresses during L1→L2 communication. That’s how rollups closer to full EVM-equivalence solve it, since it allows them to maintain the same derivation rules on L1 at the expense of contract accounts on L1 having to redeploy on L2. +- Have different derivation rules from Ethereum. That is the path that ZKsync has chosen, mainly because since we have different bytecode than on EVM, CREATE2 address derivation would be different in practice anyway. + +You can see the rules for our address derivation in `getNewAddressCreate2`/ `getNewAddressCreate` methods in the ContractDeployer. + +Note, that we still add a certain constant to the addresses during L1→L2 communication in order to allow ourselves some way to support EVM bytecodes in the future. + +#### **Deployment nonce** + +On Ethereum, the same nonce is used for CREATE for accounts and EOA wallets. On ZKsync this is not the case, we use a separate nonce called “deploymentNonce” to track the nonces for accounts. This was done mostly for consistency with custom accounts and for having multicalls feature in the future. + +#### **General process of deployment** + +- After incrementing the deployment nonce, the contract deployer must ensure that the bytecode that is being deployed is available. +- After that, it puts the bytecode hash with a [special constructing marker](#constructing-vs-non-constructing-code-hash) as code for the address of the to-be-deployed contract. +- Then, if there is any value passed with the call, the contract deployer passes it to the deployed account and sets the `msg.value` for the next as equal to this value. +- Then, it uses `mimic_call` for calling the constructor of the contract out of the name of the account. +- It parses the array of immutables returned by the constructor (we’ll talk about immutables in more details later). +- Calls `ImmutableSimulator` to set the immutables that are to be used for the deployed contract. + +Note how it is different from the EVM approach: on EVM when the contract is deployed, it executes the initCode and returns the deployedCode. On ZKsync, contracts only have the deployed code and can set immutables as storage variables returned by the constructor. + +#### **Constructor** + +On Ethereum, the constructor is only part of the initCode that gets executed during the deployment of the contract and returns the deployment code of the contract. On ZKsync, there is no separation between deployed code and constructor code. The constructor is always a part of the deployment code of the contract. In order to protect it from being called, the compiler-generated contracts invoke constructor only if the `isConstructor` flag provided (it is only available for the system contracts). You can read more about flags [here](#flags-for-calls). + +After execution, the constructor must return an array of: + +```solidity +struct ImmutableData { + uint256 index; + bytes32 value; +} +``` + +basically denoting an array of immutables passed to the contract. + +#### **Immutables** + +Immutables are stored in the `ImmutableSimulator` system contract. The way how `index` of each immutable is defined is part of the compiler specification. This contract treats it simply as mapping from index to value for each particular address. + +Whenever a contract needs to access a value of some immutable, they call the `ImmutableSimulator.getImmutable(getCodeAddress(), index)`. Note that on ZKsync it is possible to get the current execution address (you can read more about `getCodeAddress()` [here](#zksync-specific-opcodes). + +#### **Return value of the deployment methods** + +If the call succeeded, the address of the deployed contract is returned. If the deploy fails, the error bubbles up. + +### DefaultAccount + +The implementation of the default account abstraction. This is the code that is used by default for all addresses that are not in kernel space and have no contract deployed on them. This address: + +- Contains minimal implementation of our account abstraction protocol. Note that it supports the [built-in paymaster flows](https://docs.zksync.io/build/developer-reference/account-abstraction/paymasters). +- When anyone (except bootloader) calls it, it behaves in the same way as a call to an EOA, i.e. it always returns `success = 1, returndatasize = 0` for calls from anyone except for the bootloader. + +### L1Messenger + +A contract used for sending arbitrary length L2→L1 messages from ZKsync to L1. While ZKsync natively supports a rather limited number of L1→L2 logs, which can transfer only roughly 64 bytes of data a time, we allowed sending nearly-arbitrary length L2→L1 messages with the following trick: + +The L1 messenger receives a message, hashes it and sends only its hash as well as the original sender via L2→L1 log. Then, it is the duty of the L1 smart contracts to make sure that the operator has provided full preimage of this hash in the commitment of the batch. + +Note, that L1Messenger is calls the L2DAValidator and plays an important role in facilitating the [DA validation protocol](../settlement_contracts/data_availability/custom_da.md). + +### NonceHolder + +Serves as storage for nonces for our accounts. Besides making it easier for operator to order transactions (i.e. by reading the current nonces of account), it also serves a separate purpose: making sure that the pair (address, nonce) is always unique. + +It provides a function `validateNonceUsage` which the bootloader uses to check whether the nonce has been used for a certain account or not. Bootloader enforces that the nonce is marked as non-used before validation step of the transaction and marked as used one afterwards. The contract ensures that once marked as used, the nonce can not be set back to the “unused” state. + +Note that nonces do not necessarily have to be monotonic (this is needed to support more interesting applications of account abstractions, e.g. protocols that can start transactions on their own, tornado-cash like protocols, etc). That’s why there are two ways to set a certain nonce as “used”: + +- By incrementing the `minNonce` for the account (thus making all nonces that are lower than `minNonce` as used). +- By setting some non-zero value under the nonce via `setValueUnderNonce`. This way, this key will be marked as used and will no longer be allowed to be used as nonce for accounts. This way it is also rather efficient, since these 32 bytes could be used to store some valuable information. + +The accounts upon creation can also provide which type of nonce ordering do they want: Sequential (i.e. it should be expected that the nonces grow one by one, just like EOA) or Arbitrary, the nonces may have any values. This ordering is not enforced in any way by system contracts, but it is more of a suggestion to the operator on how it should order the transactions in the mempool. + +### EventWriter + +A system contract responsible for emitting events. + +It accepts in its 0-th extra abi data param the number of topics. In the rest of the extraAbiParams he accepts topics for the event to emit. Note, that in reality the event the first topic of the event contains the address of the account. Generally, the users should not interact with this contract directly, but only through Solidity syntax of `emit`-ing new events. + +### Compressor + +One of the most expensive resource for a rollup is data availability, so in order to reduce costs for the users we compress the published pubdata in several ways: + +- We compress published bytecodes. +- We compress state diffs. + +The contract provides two methods: + +- `publishCompressedBytecode` that verifies the correctness of the bytecode compression and publishes it in form of a message to the DA layer. +- `verifyCompressedStateDiffs` that can verify the correctness of our standard state diff compression. This method can be used by common L2DAValidators and it is for instance utilized by the [RollupL2DAValidator](../../l2-contracts/contracts/data-availability/RollupL2DAValidator.sol). + +You can read more about how custom DA is handled [here](../settlement_contracts/data_availability/custom_da.md). + +### Pubdata Chunk Publisher + +This contract is responsible for separating pubdata into chunks that each fit into a [4844 blob](../settlement_contracts/data_availability/rollup_da.md) and calculating the hash of the preimage of said blob. If a chunk's size is less than the total number of bytes for a blob, we pad it on the right with zeroes as the circuits will require that the chunk is of exact size. + +This contract can be utilized by L2DAValidators, e.g. [RollupL2DAValidator](../../l2-contracts/contracts/data-availability/RollupL2DAValidator.sol) uses it to compress the pubdata into blobs. + +### CodeOracle + +It is a contract that accepts the versioned hash of a bytecode and returns the preimage of it. It is similar to the `extcodecopy` functionality on Ethereum. + +It works the following way: + +1. It accepts a versioned hash and double checks that it is marked as “known”, i.e. the operator must know the preimage for such hash. +2. After that, it uses the `decommit` opcode, which accepts the versioned hash and the number of ergs to spent, which is proportional to the length of the preimage. If the preimage has been decommitted before, the requested cost will be refunded to the user. + + Note, that the decommitment process does not only happen using the `decommit` opcode, but during calls to contracts. Whenever a contract is called, its code is decommitted into a memory page dedicated to contract code. We never decommit the same preimage twice, regardless of whether it was decommitted via an explicit opcode or during a call to another contract, the previous unpacked bytecode memory page will be reused. When executing `decommit` inside the `CodeOracle` contract, the user will be firstly precharged with maximal possible price and then it will be refunded in case the bytecode has been decommitted before. + +3. The `decommit` opcode returns to the slice of the decommitted bytecode. Note, that the returned pointer always has length of 2^21 bytes, regardless of the length of the actual bytecode. So it is the job of the `CodeOracle` system contract to shrink the length of the returned data. + +### P256Verify + +This contract exerts the same behavior as the P256Verify precompile from [RIP-7212](https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md). Note, that since Era has different gas schedule, we do not comply with the gas costs, but otherwise the interface is identical. + +### GasBoundCaller + +This is not a system contract, but it will be predeployed on a fixed user space address. This contract allows users to set an upper bound of how much pubdata can a subcall take, regardless of the gas per pubdata. More on how pubdata works on ZKsync can be read [here](./zksync_fee_model.md). + +Note, that it is a deliberate decision not to deploy this contract in the kernel space, since it can relay calls to any contracts and so may break the assumption that all system contracts can be trusted. + +### ComplexUpgrader + +Usually an upgrade is performed by calling the `forceDeployOnAddresses` function of ContractDeployer out of the name of the `FORCE_DEPLOYER` constant address. However some upgrades may require more complex interactions, e.g. query something from a contract to determine which calls to make etc. + +For cases like this `ComplexUpgrader` contract has been created. The assumption is that the implementation of the upgrade is predeployed and the `ComplexUpgrader` would delegatecall to it. + +> Note, that while `ComplexUpgrader` existed even in the previous upgrade, it lacked `forceDeployAndUpgrade` function. This caused some serious limitations. More on how the gateway upgrade process will look like can be read [here](../upgrade_history/gateway_upgrade/upgrade_process.md). + +### Predeployed contracts + +There are some contracts need to predeployed, but having kernel space rights is not desirable for them. Such contracts are usuallypredeployed at sequential addresses starting from `2^16`. + +### Create2Factory + +Just a built-in Create2Factory. It allows to deterministically deploy contracts to the samme address on multiple chains. + +### L2GenesisUpgrade + +A contract that is responsible for facilitating initialization of a newly created chain. This is part of a [chain creation flow](../chain_management/chain_genesis.md). + +### Bridging-related contracts + +`L2Bridgehub`, `L2AssetRouter`, `L2NativeTokenVault`, as well as `L2MessageRoot`. + +These contracts are used to facilitate cross-chain communication as well value bridging. You can read more about then in [the asset router spec](../bridging/asset_router/overview.md). + +Note, that [L2AssetRouter](../../l1-contracts/contracts/bridge/asset-router/L2AssetRouter.sol) and [L2NativeTokenVault](../../l1-contracts/contracts/bridge/ntv/L2NativeTokenVault.sol) have unique code, the L2Bridgehub and L2MessageRoot share the same source code with their L1 precompiles, i.e. the L2Bridgehub has [this](../../l1-contracts/contracts/bridgehub/Bridgehub.sol) code and L2MessageRoot has [this](../../l1-contracts/contracts/bridgehub/MessageRoot.sol) code. + +### SloadContract + +During the L2GatewayUpgrade, the system contracts will need to read the storage of some other contracts, despite those lacking getters. The how it is implemented can be read in the `forcedSload` function of the [SystemContractHelper](../../system-contracts/contracts/libraries/SystemContractHelper.sol) contract. + +While it is only used for the upgrade, it was decided to leave it as a predeployed contract for future use-cases as well. + +### L2WrappedBaseTokenImplementation + +While bridging wrapped base tokens (e.g. WETH) is not yet supported. The address of it is enshrined within the native token vault (both the L1 and L2 one). For consistency with other networks, our WETH token is deployed as a TransparentUpgradeableProxy. To have the deployment process easier, we predeploy the implementation. + +## Known issues to be resolved + +The protocol, while conceptually complete, contains some known issues which will be resolved in the short to middle term. + +- Fee modeling is yet to be improved. More on it in the [document](./zksync_fee_model.md) on the fee model. +- We may add some kind of default implementation for the contracts in the kernel space (i.e. if called, they wouldn’t revert but behave like an EOA). diff --git a/docs/l2_system_contracts/zksync_fee_model.md b/docs/l2_system_contracts/zksync_fee_model.md new file mode 100644 index 000000000..5f3c44fe2 --- /dev/null +++ b/docs/l2_system_contracts/zksync_fee_model.md @@ -0,0 +1,309 @@ +# ZKsync fee model + +[back to readme](../README.md) + +This document will assume that you already know how gas & fees work on Ethereum. + +On Ethereum, all the computational, as well as storage costs, are represented via one unit: gas. Each operation costs a certain amount of gas, which is generally constant (though it may change during [upgrades](https://blog.ethereum.org/2021/03/08/ethereum-berlin-upgrade-announcement)). + +## Main differences from EVM + +ZKsync as well as other L2s have the issue that does not allow the adoption of the same model as the one for Ethereum so easily: the main reason is the requirement for publishing the pubdata on Ethereum. This means that prices for L2 transactions will depend on the volatile L1 gas prices and can not be simply hard coded. + +Also, ZKsync, being a zkRollup is required to prove every operation with zero-knowledge proofs. That comes with a few nuances. + +### Different opcode pricing + +The operations tend to have different “complexity”/”pricing” in zero-knowledge proof terms than in standard CPU terms. For instance, `keccak256` which was optimized for CPU performance, will cost more to prove. + +That’s why you will find the prices for operations on ZKsync a lot different from the ones on Ethereum. + +### I/O pricing + +On Ethereum, whenever a storage slot is read/written to for the first time, a certain amount of gas is charged for the fact that the slot has been accessed for the first time. A similar mechanism is used for accounts: whenever an account is accessed for the first time, a certain amount of gas is charged for reading the account's data. On EVM, an account's data includes its nonce, balance, and code. We use a similar mechanism but with a few differences. + +#### Storage costs + +Just like EVM, we also support "warm" and "cold" storage slots. However, the flow is a bit different: + +1. The user is firstly precharged with the maximum (cold) cost. +2. The operator is asked for a refund. +3. Then, the refund is given out to the user in place. + +In other words, unlike EVM, the user should always have enough gas for the worst case (even if the storage slot is "warm"). Also, the control of the refunds is currently enforced by the operator only and not by the circuits. + +#### Code decommitment and account access costs + +Unlike EVM, our storage does not couple accounts' balances, nonces, and bytecodes. Balance, nonce, and code hash are three separate storage variables that use standard storage "warm" and "cold" mechanisms. A different approach is used for accessing bytecodes though. + +We call the process of unpacking the bytecode as, _code decommitment_, since it is a process of transforming a commitment to code (i.e., the versioned code hash) into its preimage. Whenever a contract with a certain code hash is called, the following logic is executed: + +1. The operator is asked whether this is the first time this bytecode has been decommitted. +2. If the operator returns "yes", then the user is charged the full cost. Otherwise, the user does not pay for decommit. +3. If needed, the code is decommitted to the code page. + +Unlike storage interactions, the correctness of this process is _partially_ enforced by circuits, i.e., if step (3) is reached, i.e., the code is being decommitted, it will be proven that the operator responded correctly on step (1). However, if the program runs out of gas on step (2), the correctness of the first statement won't be proven. The reason for that is it is hard to prove in circuits at the time the decommitment is invoked whether it is indeed the first decommitment or not. + +Note that in the case of an honest operator, this approach offers a better UX, since there is no need to be precharged with the full cost beforehand. However, no program should rely on this fact. + +#### Conclusion + +As a conclusion, ZKsync Era supports a similar "cold"/"warm" mechanism to EVM, but for now, these are only enforced by the operator, i.e., the users of the applications should not rely on these. The execution is guaranteed to be correct as long as the user has enough gas to pay for the worst, i.e. "cold" scenario. + +### Memory pricing + +ZKsync Era has different memory pricing rules: + +- Whenever a user contract is called, `2^12` bytes of memory are given out for free, before starting to charge users linearly according to its length. +- Whenever a kernel space (i.e., a system) contract is called, `2^21` bytes of memory are given out for free, before starting to charge users linearly according to the length. + Note that, unlike EVM, we never use a quadratic component of the price for memory expansion. + +### Different intrinsic costs + +Unlike Ethereum, where the intrinsic cost of transactions (`21000` gas) is used to cover the price of updating the balances of the users, the nonce and signature verification, on ZKsync these prices are _not_ included in the intrinsic costs for transactions, due to the native support of account abstraction, meaning that each account type may have their own transaction cost. In theory, some may even use more zk-friendly signature schemes or other kinds of optimizations to allow cheaper transactions for their users. + +That being said, ZKsync transactions do come with some small intrinsic costs, but they are mostly used to cover costs related to the processing of the transaction by the bootloader which can not be easily measured in code in real-time. These are measured via testing and are hard coded. + +### Charging for pubdata + +An important cost factor for users is the pubdata. ZKsync Era is a state diff-based rollup, meaning that the pubdata is published not for the transaction data, but for the state changes: modified storage slots, deployed bytecodes, L2->L1 messages. This allows for applications that modify the same storage slot multiple times such as oracles, to update the storage slots multiple times while maintaining a constant footprint on L1 pubdata. Correctly a state diff rollups requires a special solution to charging for pubdata. It is explored in the next section. + +## How L2 gas price works + +### Batch overhead & limited resources of the batch + +To process the batch, the ZKsync team has to pay for proving the batch, committing to it, etc. Processing a batch involves some operational costs as well. All of these values we call “Batch overhead”. It consists of two parts: + +- The L2 requirements for proving the circuits (denoted in L2 gas). +- The L1 requirements for the proof verification as well as general batch processing (denoted in L1 gas). + +We generally try to aggregate as many transactions as possible and each transaction pays for the batch overhead proportionally to how close the transaction brings the batch to being _sealed,_ i.e. closed and prepared for proof verification and submission on L1. A transaction gets closer to sealing a batch by using the batch’s _limited resources_. + +While on Ethereum, the main reason for the existence of a batch gas limit is to keep the system decentralized & load low, i.e. assuming the existence of the correct hardware, only time would be a requirement for a batch to adhere to. In the case of ZKsync batches, there are some limited resources the batch should manage: + +- **Time.** The same as on Ethereum, the batch should generally not take too much time to be closed to provide better UX. To represent the time needed we use a batch gas limit, note that it is higher than the gas limit for a single transaction. +- **Slots for transactions.** The bootloader has a limited number of slots for transactions, i.e. it can not take more than a certain transactions per batch. +- **The memory of the bootloader.** The bootloader needs to store the transaction’s ABI encoding in its memory & this fills it up. In practical terms, it serves as a penalty for having transactions with large calldata/signatures in the case of custom accounts. +- **Pubdata bytes.** To fully appreciate the gains from the storage diffs, i.e. the fact that changes in a single slot happening in the same batch need to be published only once, we need to publish all the batch’s public data only after the transaction has been processed. Right now, we publish all the data with the storage diffs as well as L2→L1 messages, etc in a single transaction at the end of the batch. Most nodes have a limit of 128kb per transaction, so this is the limit that each ZKsync batch should adhere to. + +Each transaction spends the batch overhead proportionally to how closely it consumes the resources above. + +Note, that before the transaction is executed, the system can not know how many of the limited system resources the transaction will take, so we need to charge for the worst case and provide the refund at the end of the transaction. + +### `MAX_TRANSACTION_GAS_LIMIT` + +A recommended maximal amount of gas that a transaction can spend on computation is `MAX_TRANSACTION_GAS_LIMIT`. But in case the operator trusts the user, the operator may provide the [trusted gas limit](../../system-contracts/bootloader/bootloader.yul#L1242), i.e. the limit which exceeds `MAX_TRANSACTION_GAS_LIMIT` assuming that the operator knows what he is doing. This can be helpful in the case of a hyperchain with different parameters. + +### Derivation of `baseFee` and `gasPerPubdata` + +At the start of each batch, the operator provides the following two parameters: + +1. `FAIR_L2_GAS_PRICE`. This variable should denote what is the minimal L2 gas price that the operator is willing to accept. It is expected to cover the cost of proving/executing a single unit of zkEVM gas, the potential contribution of usage of a single gas towards sealing the batch, as well as congestion. +2. `FAIR_PUBDATA_PRICE`, which is the price of a single pubdata byte in Wei. Similar to the variable about, it is expected to cover the cost of publishing a single byte as well as the potential contribution of usage of a single pubdata byte towards sealing the batch. + +In the descriptions above by "contribution towards sealing the batch" we referred to the fact that if a batch is most often closed by a certain resource (e.g. pubdata), then the pubdata price should include this cost. + +The `baseFee` and `gasPerPubdata` are then calculated as: + +```yul +baseFee := max( + fairL2GasPrice, + ceilDiv(fairPubdataPrice, MAX_L2_GAS_PER_PUBDATA()) +) +gasPerPubdata := ceilDiv(pubdataPrice, baseFee) +``` + +While the way how we [charge for pubdata](#how-we-charge-for-pubdata) in theory allows for any `gasPerPubdata`, some SDKs expect the `gasLimit` by a transaction to be a uint64 number. We would prefer `gasLimit` for transactions to stay within JS's safe "number" range in case someone uses `number` type to denote gas there. For this reason, we will bind the `MAX_L2_GAS_PER_PUBDATA` to `2^20` gas per 1 pubdata byte. The number is chosen such that `MAX_L2_GAS_PER_PUBDATA * 2^32` is a safe JS integer. The `2^32` part is the maximal possible value for pubdata counter that could be in theory used. It is unrealistic that this value will ever appear under an honest operator, but it is needed just in case. + +Note, however, that it means that the total under high L1 gas prices `gasLimit` may be larger than `u32::MAX` and it is recommended that no more than `2^20` bytes of pubdata can be published within a transaction. + +#### Recommended calculation of `FAIR_L2_GAS_PRICE`/`FAIR_PUBDATA_PRICE` + +Let's define the following constants: + +- `BATCH_OVERHEAD_L1_GAS` - The L1 gas overhead for a batch (proof verification, etc). +- `COMPUTE_OVERHEAD_PART` - The constant that represents the possibility that a batch can be sealed because of overuse of computation resources. It has range from 0 to 1. If it is 0, the compute will not depend on the cost of closing the batch. If it is 1, the gas limit per batch will have to cover the entire cost of closing the batch. +- `MAX_GAS_PER_BATCH` - The maximum amount of gas that can be used by the batch. This value is derived from the circuits' limitation per batch. +- `PUBDATA_OVERHEAD_PART` - The constant that represents the possibility that a batch can be sealed because of overuse of pubdata. It has range from 0 to 1. If it is 0, the pubdata will not depend on the cost of closing the batch. If it is 1, the pubdata limit per batch will have to cover the entire cost of closing the batch. +- `MAX_PUBDATA_PER_BATCH` - The maximum amount of pubdata that can be used by the batch. Note that if the calldata is used as pubdata, this variable should not exceed 128kb. + +And the following fluctuating variables: + +- `MINIMAL_L2_GAS_PRICE` - The minimal acceptable L2 gas price, i.e. the price that should include the cost of computation/proving as well as potential premium for congestion. +- `PUBDATA_BYTE_ETH_PRICE` - The minimal acceptable price in ETH per each byte of pubdata. It should generally be equal to the expected price of a single blob byte or calldata byte (depending on the approach used). + +Then: + +1. `FAIR_L2_GAS_PRICE = MINIMAL_L2_GAS_PRICE + COMPUTE_OVERHEAD_PART * BATCH_OVERHEAD_L1_GAS / MAX_GAS_PER_BATCH` +2. `FAIR_PUBDATA_PRICE = PUBDATA_BYTE_ETH_PRICE + PUBDATA_OVERHEAD_PART * BATCH_OVERHEAD_L1_GAS / MAX_PUBDATA_PER_BATCH` + +For L1→L2 transactions, the `MAX_GAS_PER_BATCH` variable is equal to `L2_TX_MAX_GAS_LIMIT` (since this amount of gas is enough to publish the maximal number of pubdata in the batch). Also, for additional security, for L1->L2 transactions the `COMPUTE_OVERHEAD_PART = PUBDATA_OVERHEAD_PART = 1`, i.e. since we are not sure what exactly will be the reason for us closing the batch. For L2 transactions, typically `COMPUTE_OVERHEAD_PART = 0`, since, unlike L1→L2 transactions, in case of an attack, the operator can simply censor bad transactions or increase the `FAIR_L2_GAS_PRICE` and so the operator can use average values for better UX. + +#### Note on operator’s responsibility + +To reiterate, the formulas above are used for L1→L2 transactions on L1 to protect the operator from malicious transactions. However, for L2 transactions, it is solely the responsibility of the operator to provide the correct values. It is designed this way for more fine-grained control over the system for the zkStack operators (including Validiums, maybe Era on top of another L1, etc). + +This fee model also provides a very high degree of flexibility to the operator & so if we find out that we earn too much with a certain part, we could amend how the fair l2 gas price and fair pubdata price are generated and that’s it (there will be no further enforcements on the bootloader side). + +In the long run, the consensus will ensure the correctness of these values on the main ZKsync Era (or maybe we’ll transition to a different system). + +#### Overhead for transaction slot and memory + +We also have a limit on the number of memory that can be consumed within a batch as well as the number of transactions that can be included there. + +To simplify the codebase we've chosen the following constants: + +- `TX_OVERHEAD_GAS = 10000` -- the overhead in gas for including a transaction into a batch. +- `TX_MEMORY_OVERHEAD_GAS = 10` -- the overhead for consuming a single byte of bootloader memory. + +We've used roughly the following formulae to derive these values: + +1. `TX_OVERHEAD_GAS = MAX_GAS_PER_BATCH / MAX_TXS_IN_BATCH`. For L1->L2 transactions we used the `MAX_GAS_PER_BATCH = 80kk` and `MAX_TXS_IN_BATCH = 10k`. `MAX_GAS_PER_BATCH / MAX_TXS_IN_BATCH = 8k`, while we decided to use the 10k value to better take into account the load on the operator from storing the information about the transaction. +2. `TX_MEMORY_OVERHEAD_GAS = MAX_GAS_PER_BATCH / MAX_MEMORY_FOR_BATCH`. For L1->L2 transactions we used the `MAX_GAS_PER_BATCH = 80kk` and `MAX_MEMORY_FOR_BATCH = 32 * 600_000`. + +`MAX_GAS_PER_BATCH / MAX_MEMORY_FOR_BATCH = 4`, while we decided to use the `10` gas value to better take into account the load on the operator from storing the information about the transaction. + +Future work will focus on removing the limit on the number of transactions’ slots completely as well as increasing the memory limit. + +#### Note on L1→L2 transactions + +The formulas above apply to L1→L2 transactions. However, note that the `gas_per_pubdata` is still kept as constant as `800`. This means that a higher `baseFee` could be used for L1->L2 transactions to ensure that `gas_per_pubdata` remains at that value regardless of the price of the pubdata. + +#### Refunds + +Note, that the used constants for the fee model are probabilistic, i.e. we never know in advance the exact reason why a batch is going to be sealed. These constants are meant to cover the expenses of the operator over a longer period so we do not refund the fact that the transaction might've been charged for overhead above the level at which the transaction has brought the batch to being closed, since these funds are used to cover transactions that did not pay in full for the limited batch's resources that they used. + +#### Refunds for repeated writes + +ZKsync Era is a state diff-based rollup, i.e. the pubdata is published not for transactions, but for storage changes. This means that whenever a user writes into a storage slot, it incurs a certain amount of pubdata. However, not all writes are equal: + +- If a slot has been already written to in one of the previous batches, the slot has received a short ID, which allows it to require less pubdata in the state diff. +- Depending on the `value` written into a slot, various compression optimizations could be used and so we should reflect that too. +- Maybe the slot has been already written to in this batch so we don’t have to charge anything for it. + +You can read more about how we treat the pubdata [here](../settlement_contracts/data_availability/standard_pubdata_format.md). + +The important part here is that while such refunds are inlined (i.e. unlike the refunds for overhead they happen in place during execution and not after the whole transaction has been processed), they are enforced by the operator. Right now, the operator is the one who decides what refund to provide. + +## How we charge for pubdata + +ZKsync Era is a state diff-based rollup. It means that it is not possible to know how much pubdata a transaction will take before its execution. We _could_ charge for pubdata the following way: whenever a user does an operation that emits pubdata (writes to storage, publishes an L2->L1 message, etc.), we charge `pubdata_bytes_published * gas_per_pubdata` directly from the context of the execution. + +However, such an approach has the following disadvantages: + +- This would inherently make execution very divergent from EVM. +- It is prone to unneeded overhead. For instance, in the case of reentrancy locks, the user will still have to pay the initial price for marking the lock as used. The price will get refunded in the end, but it still worsens the UX. +- If we want to impose any sort of limit on how much computation a transaction could take (let's call this limit `MAX_TX_GAS_LIMIT`), it would mean that no more than `MAX_TX_GAS_LIMIT / gas_per_pubdata` could be published in a transaction, making this limit either too small or forcing us to increase `baseFee` to prevent the number from growing too much. + +To avoid the issues above we need to somehow decouple the gas spent on pubdata from the gas spent on execution. While calldata-based rollups precharge for calldata, we cannot do it, since the exact state diffs are known only after the transaction is finished. We'll use the approach of _post-charging._ Basically, we'll keep a counter that tracks how much pubdata has been spent and charge the user for the calldata at the end of the transaction. + +A problem with post-charging is that the user may spend all their gas within the transaction so we'll have no gas to charge for pubdata from. Note, however, that if the transaction is reverted, all the state changes that were related to it will be reverted too. That's why whenever we need to charge the user for pubdata, but it doesn't provide enough gas, the transaction will get reverted. The user will pay for the computation, but no state changes (and thus, pubdata) will be produced by the transaction. + +So it will work the following way: + +1. Firstly, we fix the amount of pubdata published so far. Let's denote it as `basePubdataSpent`. +2. We execute the validation of the transaction. +3. We check whether `(getPubdataSpent() - basePubdataSpent) * gasPerPubdata <= gasLeftAfterValidation`. If it is not, then the transaction does not cover enough funds for itself, so it should be _rejected_ (unlike revert, which means that the transaction is not even included in the block). +4. We execute the transaction itself. +5. We do the same check as in (3), but now if the transaction does not have enough gas for pubdata, it is reverted, i.e., the user still pays the fee to cover the computation for its transaction. +6. (optional, in case a paymaster is used). We repeat steps (4-5), but now for the `postTransaction` method of the paymaster. + +On the internal level, the pubdata counter is modified in the following way: + +- When there is a storage write, the operator is asked to provide by how much to increment the pubdata counter. Note that this value can be negative if, as in the example with a reentrancy guard, the storage diff is being reversed. There is currently no limit on how much the operator can charge for the pubdata. +- Whenever there is a need to publish a blob of bytes to L1 (for instance, when publishing a bytecode), the responsible system contract would increment the pubdata counter by `bytes_to_publish`. +- Whenever there is a revert in a frame, the pubdata counter gets reverted too, similar to storage & events. + +The approach with post-charging removes the unneeded overhead and decouples the gas used for the execution from the gas used for data availability, which removes any caps on `gasPerPubdata`. + +### Security considerations for protocol + +Now it has become easier for a transaction to use up more pubdata than what can be published within a batch. In such a case, we'll revert the transaction as well. + +### Security considerations for users + +The approach with post-charging introduces one distinctive feature: it is not trivial to know the final price for a transaction at the time of its execution. When a user does `.call{gas: some_gas}` the final impact on the price of the transaction may be higher than `some_gas` since the pubdata counter will be incremented during the execution and charged only at the end of the transaction. + +While for the average user, this limitation is not relevant, some specific applications may receive certain issues. + +#### Example for a queue of withdrawals + +Imagine that there is the following contract: + +```solidity +struct Withdrawal { + address token; + address to; + uint256 amount; +} + +Withdrawals[] queue; +uint256 lastProcessed; + +function processNWithdrawals(uint256 N) external nonReentrant { + uint256 current = lastProcessed + 1; + uint256 lastToProcess = current + N - 1; + + while(current <= lastToProcess) { + // If the user provided some bad token that takes more than MAX_WITHDRAWAL_GAS + // to transfer, it is the problem of the user and it will stall the queue, so + // the `_success` value is ignored. + Withdrawal storage currentQueue = queue[current]; + (bool _success, ) = currentQueue.token.call{gas: MAX_WITHDRAWAL_GAS}(abi.encodeWithSignature("transfer(to,amount)", currentQueue.to, currentQueue.amount)); + current += 1; + } + lastProcessed = lastToProcess; +} +``` + +The contract above supports a queue of withdrawals. This queue supports any type of token, including potentially malicious ones. However, the queue will never get stuck, since the `MAX_WITHDRAWAL_GAS` ensures that even if the malicious token does a lot of computation, it will be bound by this number and so the caller of the `processNWithdrawals` won't spend more than `MAX_WITHDRAWAL_GAS` per token. + +The above assumptions work in the pre-charge model (calldata based rollups) or pay-as-you-go model (pre-1.5.0 Era). However, in the post-charge model, the `MAX_WITHDRAWAL_GAS`` limits the amount of computation that can be done within the transaction, but it does not limit the amount of pubdata that can be published. Thus, if such a function publishes a very large L1→L2 message, it might make the entire top transaction fail. This effectively means that such a queue would be stalled. + +#### How to prevent this issue on the users' side + +If a user really needs to limit the amount of gas that the subcall takes, all the subcalls should be routed through a special contract, that will guarantee that the total cost of the subcall wont be larger than the gas provided (by reverting if needed). + +An implementation of this special contract can be seen [here](../../gas-bound-caller/contracts/GasBoundCaller.sol). Note, that this contract is _not_ a system one and it will be deployed on some fixed, but not kernel space address. + +#### 1. Case of when a malicious contract consumes a large, but processable amount of pubdata\*\* + +In this case, the topmost transaction will be able to sponsor such subcalls. When a transaction is processed, at most 80M gas is allowed to be passed to the execution. The rest can only be spent on pubdata during the post-charging. + +#### 2. Case of when a malicious contract consumes an unprocessable amount of pubdata\*\* + +In this case, the malicious callee published so much pubdata, that such a transaction can not be included into a batch. This effectively means that no matter how much money the topmost transaction willing to pay, the queue is stalled. + +The only way how it is combated is by setting some minimal amount of ergs that still have to be consumed with each emission of pubdata (basically to make sure that it is not possible to publish large chunks of pubdata while using negligible computation). Unfortunately, setting this minimal amount to cover the worst possible case (i.e. 80M ergs spent with maximally 100k of pubdata available, leading to 800 L2 gas / pubdata byte) would likely be too harsh and will negatively impact average UX. Overall, this _is_ the way to go, however for now the only guarantee will be that a subcall of 1M gas is always processable, which will mean that at least 80 gas will have to be spent for each published pubdata byte. Even if higher than real L1 gas costs, it is reasonable even in the long run, since all the things that are published as pubdata are state-related and so they have to be well-priced for long-term storage. + +In the future, we will guarantee the processability of subcalls of larger size by increasing the number of pubdata that can be published per batch. + +### Limiting the `gas_per_pubdata` + +As already mentioned, the transactions on ZKsync depend on volatile L1 gas costs to publish the pubdata for batch, verify proofs, etc. For this reason, ZKsync-specific EIP712 transactions contain the `gas_per_pubdata_limit` field, denoting the maximum `gas_per_pubdata` that the operator can charge the user for a single byte of pubdata. + +For Ethereum transactions (which do not contain this field), the block's `gas_per_pubdata` is used. + +## Improvements in the upcoming releases + +The fee model explained above, while fully functional, has some known issues. These will be tackled with the following upgrades. + +### L1->L2 transactions do not pay for their execution on L1 + +The `executeBatches` operation on L1 is executed in `O(N)` where N is the number of priority ops that we have in the batch. Each executed priority operation will be popped and so it incurs cost for storage modifications. As of now, we do not charge for it. + +## ZKsync Era Fee Components (Revenue & Costs) + +- On-Chain L1 Costs + - L1 Commit Batches + - The commit batch transaction submits pubdata (which is the list of updated storage slots) to L1. The cost of a commit transaction is calculated as `constant overhead + price of pubdata`. The `constant overhead` cost is evenly distributed among L2 transactions in the L1 commit transaction, but only at higher transaction loads. As for the `price of pubdata`, it is known how much pubdata each L2 transaction consumed, therefore, they are charged directly for that. Multiple L1 batches can be included in a single commit transaction. + - L1 Prove Batches + - Once the off-chain proof is generated, it is submitted to L1 to make the rollup batch final. Currently, each proof contains only one L1 batch. + - L1 Execute Batches + - The execute batches transaction processes L2 -> L1 messages and marks executed priority operations as such. Multiple L1 batches can be included in a single execute transaction. + - L1 Finalize Withdrawals + - While not strictly part of the L1 fees, the cost to finalize L2 → L1 withdrawals are covered by Matter Labs. The finalize withdrawals transaction processes user token withdrawals from ZKsync Era to Ethereum. Multiple L2 withdrawal transactions are included in each finalize withdrawal transaction. +- On-Chain L2 Revenue + - L2 Transaction Fee + - This fee is what the user pays to complete a transaction on ZKsync Era. It is calculated as `gasLimit x baseFeePerGas - refundedGas x baseFeePerGas`, or more simply, `gasUsed x baseFeePerGas`. +- Profit = L2 Revenue - L1 Costs - Off-Chain Infrastructure Costs diff --git a/docs/overview.md b/docs/overview.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/settlement_contracts/data_availability/custom_da.md b/docs/settlement_contracts/data_availability/custom_da.md new file mode 100644 index 000000000..b1780eb4a --- /dev/null +++ b/docs/settlement_contracts/data_availability/custom_da.md @@ -0,0 +1,49 @@ +# Custom DA support + +[back to readme](../../README.md) + +## Intro + +We introduced modularity into our contracts to support multiple DA layers, easier support for Validium and Rollup mode, and to settlement via the Gateway. + +![The contracts for the rollup case](./img/custom_da.png) +![The general architecture](./img/Custom-da-external.png) + +### Background + +**Pubdata** - information published by the ZK Chain that can be used to reconstruct its state, it consists of l2→l1 logs, l2→l1 messages, contract bytecodes, and compressed state diffs. + +```solidity +struct PubdataInput { + pub(crate) user_logs: Vec, + pub(crate) l2_to_l1_messages: Vec>, + pub(crate) published_bytecodes: Vec>, + pub(crate) state_diffs: Vec, +} +``` + +The current version of ZK Chains supports the following DataAvailability(DA) modes: + +- `Calldata` - uses Ethereum tx calldata as pubdata storage +- `Blobs` - uses Ethereum blobs calldata as pubdata storage +- `No DA Validium` - posting pubdata is not enforced + +The goal is to create a general purpose solution, that would ensure DA consistency and verifiability, on top of which we would build what is requested by many partners and covers many use cases like on-chain games and DEXes: **Validium with Abstract DA.** + +This means that a separate solution like AvailDA, EigenDA, Celestia, etc. would be used to store the pubdata. The idea is that every solution like that (`DA layer`) provides a proof of inclusion of our pubdata to their storage, and this proof can later be verified on Ethereum. This results in an approach that has more security guarantees than `No DA Validium`, but lower fees than `Blobs`(assuming that Ethereum usage grows and blobs become more expensive). + +## Proposed solution + +The proposed solution is to introduce an abstract 3rd party DA layer, that the sequencer would publish the data to. When the batch is sealed, the hashes of the data related to that batch will be made available on L1. Then, after the DA layer has confirmed that its state is synchronized, the sequencer calls a `commitBatches` function with the proofs required to verify the DA inclusion on L1. + +### Challenges + +On the protocol level, the complexity is in introducing two new components: L1 and L2 DA verifiers. They are required to ensure the verifiable delivery of the DA inclusion proofs to L1 and consequent verification of these proofs. + +The L2 verifier would validate the pubdata correctness and compute a final commitment for DA called `outputHash`. It consists of hashes of `L2→L1 logs and messages`, `bytecodes`, and `compressed state diffs`(blob hashes in case of blobs). This contract has to be deployed by the chain operator and it has to be tied to the DA layer logic, e.g. DA layer accepts 256kb blobs → on the final hash computation stage, the pubdata has to be packed into the chunks of <256kb, and a either the hashes of all blobs, or a rolling hash has to be be part of the `outputHash` preimage. + +The `outputHash` will be sent to L1 as a L2→L1 log, so this process is a part of a bootloader execution and can be trusted. + +The hashes of data chunks alongside the inclusion proofs have to be provided in the calldata of the L1 diamond proxy’s `commitBatches` function. + +L1 contracts have to recalculate the `outputHash` and make sure it matches the one from the logs, after which the abstract DA verification contract is called. In general terms, it would accept the set of chunk’s hashes (by chunk here I mean DA blob, not to be confused with 4844 blob) and a set of inclusion proofs, that should be enough to verify that the preimage (chunk data) is included in the DA layer. This verification would be done by specific contract e.g. `Attestation Bridge`, which holds the state tree information and can perform verification against it. diff --git a/docs/settlement_contracts/data_availability/img/Custom-da-external.png b/docs/settlement_contracts/data_availability/img/Custom-da-external.png new file mode 100644 index 000000000..0c318dd53 Binary files /dev/null and b/docs/settlement_contracts/data_availability/img/Custom-da-external.png differ diff --git a/docs/settlement_contracts/data_availability/img/Rollup_DA.png b/docs/settlement_contracts/data_availability/img/Rollup_DA.png new file mode 100644 index 000000000..6f6ad084a Binary files /dev/null and b/docs/settlement_contracts/data_availability/img/Rollup_DA.png differ diff --git a/docs/settlement_contracts/data_availability/img/custom_da.png b/docs/settlement_contracts/data_availability/img/custom_da.png new file mode 100644 index 000000000..0a17eb662 Binary files /dev/null and b/docs/settlement_contracts/data_availability/img/custom_da.png differ diff --git a/docs/settlement_contracts/data_availability/rollup_da.md b/docs/settlement_contracts/data_availability/rollup_da.md new file mode 100644 index 000000000..901e47148 --- /dev/null +++ b/docs/settlement_contracts/data_availability/rollup_da.md @@ -0,0 +1,80 @@ +# Rollup DA + +[back to readme](../../README.md) + +FIXME: run a spellchecker + +## Prerequisites + +Before reading this document, it is better to understand how [custom DA](./custom_da.md) in general works. + +## EIP4844 support + +EIP-4844, commonly known as Proto-Danksharding, is an upgrade to the ethereum protocol that introduces a new data availability solution embedded in layer 1. More information about it can be found [here](https://ethereum.org/en/roadmap/danksharding/). + +To facilitate EIP4844 blob support, our circuits allow providing two arrays in our public input to the circuit: + +- `blobCommitments` -- this is the commitment that helps to check the correctness of the blob content. The formula on how it is computed will be explained below in the document. +- `blobHash` -- the `keccak256` hash of the inner contents of the blob. + +Note, that our circuits require that each blob contains exactly `4096 * 31` bytes. The maximal number of blobs that are supported by our proving system is 16, but the system contracts support only 6 blobs at most for now. + +When committing a batch, the L1DAValidator is called with the data provided by the operator and it should return the two arrays described above. These arrays be put inside the batch commitment and then the correctness of the commitments will be verified at the proving stage. + +Note, that the `Executor.sol` (and the contract itself) is not responsible for checking that the provided `blobHash` and `blobCommitments` in any way correspond to the pubdata inside the batch as it is the job of the DA Validator pair. + +## Publishing pubdata to L1 + +Let's see an example of how the approach above works in rollup DA validators. + +### RollupL2DAValidator + +![RollupL2DAValidator.png](./img/Rollup_DA.png) + +`RollupL2DAValidator` accepts the preimages for the data to publishes as well as their compressed format. After verifying the compression, it forms the `_totalPubdata` bytes array, which represents the entire blob of data that should be published to L1. + +It calls the `PubdataChunkPublisher` system contract to split this pubdata into multiple "chunks" of size `4096 * 31` bytes and return the `keccak256` hash of those, These will be the `blobHash` of from the section before. + +To give the flexibility of checking different DA, we send the following data to L1: + +- State diff hash. As it will be used on L1 to confirm the correctness of the provided uncompressed storage diifs. +- The hash of the `_totalPubdata`. In case the size of pubdata is small, it will allow the operator also use just standard Ethereum calldata for the DA. +- Send the `blobHash` array. + +### RollupL1DAValidator + +When committing the batch, the operator will provide the preimage of the fields that the RollupL2DAValidator has sent before, and also some `l1DaInput` along with it. This `l1DaInput` will be used to prove that the pubdata was indeed provided in this batch. + +The first byte of the `l1DaInput` denotes which way of pubdata publishing was used: Calldata or Blobs. + +In case it is Calldata it will be just checked that the provided calldata matches the hash of the `_totalPubdata` that was sent by the L2 counterpart. Note, that Calldata may still contain the blob information as we typically start generating proves before we know which way of calldata will be used. Note, that in case the Calldata is used for DA, we do not verify the `blobCommitments` as the presence of the correct pubdata has been verified already. + +In case it is blobs, we need to construct the `blobCommitment`s correctly for each of the blob of data. + +For each of the `blob`s the operator provides so called `_commitment` that consists of the following packed structure: `opening point (16 bytes) || claimed value (32 bytes) || commitment (48 bytes) || proof (48 bytes)`. + +The verification of the `_commitment` can be summarized in the following snippet: + +```solidity +// The opening point is passed as 16 bytes as that is what our circuits expect and use when verifying the new batch commitment +// PUBDATA_COMMITMENT_SIZE = 144 bytes +pubdata_commitments <- [opening point (16 bytes) || claimed value (32 bytes) || commitment (48 bytes) || proof (48 bytes)] from calldata +opening_point = bytes32(pubdata_commitments[:16]) +versioned_hash <- from BLOBHASH opcode + +// Given that we needed to pad the opening point for the precompile, append the data after. +point_eval_input = versioned_hash || opening_point || pubdataCommitments[16: PUBDATA_COMMITMENT_SIZE] + +// this part handles the following: +// verify versioned_hash == hash(commitment) +// verify P(z) = y +res <- point_valuation_precompile(point_eval_input) + +assert uint256(res[32:]) == BLS_MODULUS +``` + +The final `blobCommitment` is calculated as the hash between the `blobVersionedHash`, `opening point` and the `claimed value`. The zero knowledge circuits will verify that the opening point and the claimed value were calculated correctly and correspond to the data that was hashed under the `blobHash`. + +## Structure of the pubdata + +Rollups maintain the same structure of pubdata and apply the same rules for compresison as those that were used in the previous versions of the system. These can be read [here](./state_diff_compression_v1_spec.md). diff --git a/docs/settlement_contracts/data_availability/standard_pubdata_format.md b/docs/settlement_contracts/data_availability/standard_pubdata_format.md new file mode 100644 index 000000000..4db784b2a --- /dev/null +++ b/docs/settlement_contracts/data_availability/standard_pubdata_format.md @@ -0,0 +1,286 @@ +# Standard pubdata format + +[back to readme](../../README.md) + +While with the introduction of [custom DA validators](./custom_da.md), any pubdata logic could be applied for each chain (including calldata-based pubdata), ZK chains are generally optimized for using state-diffs based rollup model. + +This document will describe how the standard pubdata format looks like. This is the format that is enforced for [permanent rollup chains](../../chain_management/admin_role.md#ispermanentrollup-setting). + +Pubdata in ZKsync can be divided up into 4 different categories: + +1. L2 to L1 Logs +2. L2 to L1 Messages +3. Smart Contract Bytecodes +4. Storage writes + +Using data corresponding to these 4 facets, across all executed batches, we’re able to reconstruct the full state of L2. To restore the state we just need to filter all of the transactions to the L1 ZKsync contract for only the `commitBatches` transactions where the proposed block has been referenced by a corresponding `executeBatches` call (the reason for this is that a committed or even proven block can be reverted but an executed one cannot). Once we have all the committed batches that have been executed, we then will pull the transaction input and the relevant fields, applying them in order to reconstruct the current state of L2. + +## L2→L1 communication + +We will implement the calculation of the Merkle root of the L2→L1 messages via a system contract as part of the `L1Messenger`. Basically, whenever a new log emitted by users that needs to be Merklized is created, the `L1Messenger` contract will append it to its rolling hash and then at the end of the batch, during the formation of the blob it will receive the original preimages from the operator, verify their consistency, and send those to the L2DAValidator to facilitate the DA protocol. + +We will now call the logs that are created by users and are Merklized _user_ logs and the logs that are emitted by natively by VM _system_ logs. Here is a short comparison table for better understanding: + +| System logs | User logs | +| --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Emitted by VM via an opcode. | VM knows nothing about them. | +| Consistency and correctness is enforced by the verifier on L1 (i.e. their hash is part of the block commitment. | Consistency and correctness is enforced by the L1Messenger system contract. The correctness of the behavior of the L1Messenger is enforced implicitly by prover in a sense that it proves the correctness of the execution overall. | +| We don’t calculate their Merkle root. | We calculate their Merkle root on the L1Messenger system contract. | +| We have constant small number of those. | We can have as much as possible as long as the commitBatches function on L1 remains executable (it is the job of the operator to ensure that only such transactions are selected) | +| In EIP4844 they will remain part of the calldata. | In EIP4844 they will become part of the blobs. | + +### Backwards-compatibility + +Note, that to maintain a unified interface with the previous version of the protocol, the leaves of the Merkle tree will have to maintain the following structure: + +```solidity +struct L2Log { + uint8 l2ShardId; + bool isService; + uint16 txNumberInBlock; + address sender; + bytes32 key; + bytes32 value; +} +``` + +While the leaf will look the following way: + +```solidity +bytes32 hashedLog = keccak256( + abi.encodePacked(_log.l2ShardId, _log.isService, _log.txNumberInBlock, _log.sender, _log.key, _log.value) +); +``` + +`keccak256` will continue being the function for the merkle tree. + +To put it shortly, the proofs for L2→L1 log inclusion will continue having exactly the same format as they did in the pre-Boojum system, which avoids breaking changes for SDKs and bridges alike. + +### Implementation of `L1Messenger` + +The L1Messenger contract will maintain a rolling hash of all the L2ToL1 logs `chainedLogsHash` as well as the rolling hashes of messages `chainedMessagesHash`. Whenever a contract wants to send an L2→L1 log, the following operation will be [applied](../../../system-contracts/contracts/L1Messenger.sol#L73): + +`chainedLogsHash = keccak256(chainedLogsHash, hashedLog)`. L2→L1 logs have the same 88-byte format as in the current version of ZKsync. + +Note, that the user is charged for necessary future the computation that will be needed to calculate the final merkle root. It is roughly 4x higher than the cost to calculate the hash of the leaf, since the eventual tree might have be 4x times the number nodes. In any case, this will likely be a relatively negligible part compared to the cost of the pubdata. + +At the end of the execution, the bootloader will [provide](../../../system-contracts/bootloader/bootloader.yul#L2676) a list of all the L2ToL1 logs (this will be provided by the operator in the memory of the bootloader). The L1Messenger checks that the rolling hash from the provided logs is the same as in the `chainedLogsHash` and calculate the merkle tree of the provided messages. Right now, we always build the Merkle tree of size `16384`, but we charge the user as if the tree was built dynamically based on the number of leaves in there. The implementation of the dynamic tree has been postponed until the later upgrades. + +> Note, that unlike most other parts of pubdata, the user L2->L1 must always be validated by the trusted `L1Messenger` system contract. If we moved this responsibility to L2DAValidator it would be possible that a malicious operator provided incorrect data and forged transactions out of names of certain users. + +### Long L2→L1 messages & bytecodes + +If the user wants to send an L2→L1 message, its preimage is [appended](../../../system-contracts/contracts/L1Messenger.sol#L126) to the message’s rolling hash too `chainedMessagesHash = keccak256(chainedMessagesHash, keccak256(message))`. + +A very similar approach for bytecodes is used, where their rolling hash is calculated and then the preimages are provided at the end of the batch to form the full pubdata for the batch. + +Note, that in for backward compatibility, just like before any long message or bytecode is accompanied by the corresponding user L2→L1 log. + +### Using system L2→L1 logs vs the user logs + +The content of the L2→L1 logs by the L1Messenger will go to the blob of EIP4844. Meaning, that all the data that belongs to the tree by L1Messenger’s L2→L1 logs should not be needed during block commitment. Also, note that in the future we will remove the calculation of the Merkle root of the built-in L2→L1 messages. + +The only places where the built-in L2→L1 messaging should continue to be used: + +- Logs by SystemContext (they are needed on commit to check the previous block hash). +- Logs by L1Messenger for the merkle root of the L2→L1 tree as well the data needed for L1DAValidator. +- `chainedPriorityTxsHash` and `numberOfLayer1Txs` from the bootloader (read more about it below). + +### Obtaining `txNumberInBlock` + +To have the same log format, the `txNumberInBlock` must be obtained. While it is internally counted in the VM, there is currently no opcode to retrieve this number. We will have a public variable `txNumberInBlock` in the `SystemContext`, which will be incremented with each new transaction and retrieve this variable from there. It is [zeroed out](../../../system-contracts/contracts/SystemContext.sol#L515) at the end of the batch. + +### Bootloader implementation + +The bootloader has a memory segment dedicated to the ABI-encoded data of the L1ToL2Messenger to perform the `publishPubdataAndClearState` call. + +At the end of the execution of the batch, the operator should provide the corresponding data into the bootloader memory, i.e user L2→L1 logs, long messages, bytecodes, etc. After that, the [call](../../../system-contracts/bootloader/bootloader.yul#L2676) is performed to the `L1Messenger` system contract, that would call the L2DAValidator that should check the adherence of the pubdata to the specified format. + +## Bytecode Publishing + +Within pubdata, bytecodes are published in 1 of 2 ways: (1) uncompressed as part of the bytecodes array and (2) compressed via long l2 → l1 messages. + +### Uncompressed Bytecode Publishing + +Uncompressed bytecodes are included within the `totalPubdata` bytes and have the following format: `number of bytecodes || forEachBytecode (length of bytecode(n) || bytecode(n))` . + +### Compressed Bytecode Publishing + +Unlike uncompressed bytecode which are published as part of `factoryDeps`, compressed bytecodes are published as long l2 → l1 messages which can be seen [here](../../../system-contracts/contracts/Compressor.sol#L78). + +#### Bytecode Compression Algorithm — Server Side + +This is the part that is responsible for taking bytecode, that has already been chunked into 8 byte words, performing validation, and compressing it. + +Each 8 byte word from the chunked bytecode is assigned a 2 byte index (constraint on size of dictionary of chunk → index is 2^16 - 1 elements). The length of the dictionary, dictionary entries (index assumed through order), and indexes are all concatenated together to yield the final compressed version. + +For bytecode to be considered valid it must satisfy the following: + +1. Bytecode length must be less than 2097120 ((2^16 - 1) \* 32) bytes. +2. Bytecode length must be a multiple of 32. +3. Number of 32-byte words cannot be even. + +The following is a simplified version of the algorithm: + +```python +statistic: Map[chunk, (count, first_pos)] +dictionary: Map[chunk, index] +encoded_data: List[index] + +for position, chunk in chunked_bytecode: + if chunk is in statistic: + statistic[chunk].count += 1 + else: + statistic[chunk] = (count=1, first_pos=pos) + +# We want the more frequently used bytes to have smaller ids to save on calldata (zero bytes cost less) +statistic.sort(primary=count, secondary=first_pos, order=desc) + +for index, chunk in enumerated(sorted_statistics): + dictionary[chunk] = index + +for chunk in chunked_bytecode: + encoded_data.append(dictionary[chunk]) + +return [len(dictionary), dictionary.keys(order=index asc), encoded_data] +``` + +#### Verification And Publishing — L2 Contract + +The function `publishCompressBytecode` takes in both the original `_bytecode` and the `_rawCompressedData` , the latter of which comes from the output of the server’s compression algorithm. Looping over the encoded data, derived from `_rawCompressedData` , the corresponding chunks are pulled from the dictionary and compared to the original byte code, reverting if there is a mismatch. After the encoded data has been verified, it is published to L1 and marked accordingly within the `KnownCodesStorage` contract. + +Pseudo-code implementation: + +```python +length_of_dict = _rawCompressedData[:2] +dictionary = _rawCompressedData[2:2 + length_of_dict * 8] # need to offset by bytes used to store length (2) and multiply by 8 for chunk size +encoded_data = _rawCompressedData[2 + length_of_dict * 8:] + +assert(len(dictionary) % 8 == 0) # each element should be 8 bytes +assert(num_entries(dictionary) <= 2^16) +assert(len(encoded_data) * 4 == len(_bytecode)) # given that each chunk is 8 bytes and each index is 2 bytes they should differ by a factor of 4 + +for (index, dict_index) in list(enumerate(encoded_data)): + encoded_chunk = dictionary[dict_index] + real_chunk = _bytecode.readUint64(index * 8) # need to pull from index * 8 to account for difference in element size + verify(encoded_chunk == real_chunk) + +# Sending the compressed bytecode to L1 for data availability +sendToL1(_rawCompressedBytecode) +markAsPublished(hash(_bytecode)) +``` + +## Storage diff publishing + +ZKsync is a statediff-based rollup and so publishing the correct state diffs plays an integral role in ensuring data availability. + +### Difference between initial and repeated writes + +ZKsync publishes state changes that happened within the batch instead of transactions themselves. Meaning, that for instance some storage slot `S` under account `A` has changed to value `V`, we could publish a triple of `A,S,V`. Users by observing all the triples could restore the state of ZKsync. However, note that our tree unlike Ethereum’s one is not account based (i.e. there is no first layer of depth 160 of the merkle tree corresponding to accounts and second layer of depth 256 of the merkle tree corresponding to users). Our tree is “flat”, i.e. a slot `S` under account `A` is just stored in the leaf number `H(S,A)`. Our tree is of depth 256 + 8 (the 256 is for these hashed account/key pairs and 8 is for potential shards in the future, we currently have only one shard and it is irrelevant for the rest of the document). + +We call this `H(S,A)` _derived key_, because it is derived from the address and the actual key in the storage of the account. Since our tree is flat, whenever a change happens, we can publish a pair `DK, V`, where `DK=H(S,A)`. + +However, these is an optimization that could be done: + +- Whenever a change to a key is used for the first time, we publish a pair of `DK,V` and we assign some sequential id to this derived key. This is called an _initial write_. It happens for the first time and that’s why we must publish the full key. +- If this storage slot is published in some of the subsequent batches, instead of publishing the whole `DK`, we can use the sequential id instead. This is called a _repeated write_. + +For instance, if the slots `A`,`B` (I’ll use latin letters instead of 32-byte hashes for readability) changed their values to `12`,`13` accordingly, in the batch it happened they will be published in the following format: + +- `(A, 12), (B, 13)`. Let’s say that the last sequential id ever used is 6. Then, `A` will receive the id of `7` and B will receive the id of `8`. + +Let’s say that in the next block, they changes their values to `13`,`14`. Then, their diff will be published in the following format: + +- `(7, 13), (8,14)`. + +The id is permanently assigned to each storage key that was ever published. While in the description above it may not seem like a huge boost, however, each `DK` is 32 bytes long and id is at most 8 bytes long. + +We call this id _enumeration_index_. + +Note, that the enumeration indexes are assigned in the order of sorted array of (address, key), i.e. they are internally sorted. The enumeration indexes are part of the state merkle tree, it is **crucial** that the initial writes are published in the correct order, so that anyone could restore the correct enum indexes for the storage slots. In addition, an enumeration index of `0` indicates that the storage write is an initial write. + +### State diffs structure + +Firstly, let’s define what we mean by _state diffs_. A _state diff_ is an element of the following structure. + +[State diff structure](https://github.com/matter-labs/zksync-protocol/blob/main/crates/circuit_encodings/src/state_diff_record.rs#L8). + +Basically, it contains all the values which might interest us about the state diff: + +- `address` where the storage has been changed. +- `key` (the original key inside the address) +- `derived_key` — `H(key, address)` as described in the previous section. + - Note, the hashing algorithm currently used here is `Blake2s` +- `enumeration_index` — Enumeration index as explained above. It is equal to 0 if the write is initial and contains the non-zero enumeration index if it is the repeated write (indexes are numerated starting from 1). +- `initial_value` — The value that was present in the key at the start of the batch +- `final_value` — The value that the key has changed to by the end of the batch. + +We will consider `stateDiffs` an array of such objects, sorted by (address, key). + +This is the internal structure that is used by the circuits to represent the state diffs. The most basic “compression” algorithm is the one described above: + +- For initial writes, write the pair of (`derived_key`, `final_value`) +- For repeated writes write the pair of (`enumeration_index`, `final_value`). + +Note, that values like `initial_value`, `address` and `key` are not used in the "simplified" algorithm above, but they will be helpful for the more advanced compression algorithms in the future. The [algorithm](#state-diff-compression-format) for Boojum already utilizes the difference between the `initial_value` and `final_value` for saving up on pubdata. + +### How the new pubdata verification works + +#### **L2** + +1. The operator provides both full `stateDiffs` (i.e. the array of the structs above) and the compressed state diffs (i.e. the array which contains the state diffs, compressed by the algorithm explained [below](#state-diff-compression-format)). +2. The L2DAValidator must verify that the compressed version is consistent with the original stateDiffs and send the the _hash_ of the original state diff to its L1 counterpart. It will also include the compressed state diffs into the totalPubdata to be published onto L1. + +#### **L1** + +1. During committing the block, the standard DA protocol follows and the L1DAValidator is responsible to check that the operator has provided the preimage for the `_totalPubdata`. More on how this is checked can be seen [here](./rollup_da.md). +2. The block commitment [includes](../../../l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol#L550) \*the hash of the `stateDiffs`. Thus, during ZKP verification will fail if the provided stateDiff hash is not correct. + +It is a secure construction because the proof can be verified only if both the execution was correct and the hash of the provided hash of the `stateDiffs` is correct. This means that the L2DAValidator indeed received the array of correct `stateDiffs` and, assuming the L2DAValidator is working correctly, double-checked that the compression is of the correct format, while L1 contracts on the commit stage double checked that the operator provided the preimage for the compressed state diffs. + +### State diff compression format + +The following algorithm is used for the state diff compression: + +[State diff compression v1 spec](./state_diff_compression_v1_spec.md) + +## General pubdata format + +The `totalPubdata` has the following structure: + +1. First 4 bytes — the number of user L2→L1 logs in the batch +2. Then, the concatenation of packed L2→L1 user logs. +3. Next, 4 bytes — the number of long L2→L1 messages in the batch. +4. Then, the concatenation of L2→L1 messages, each in the format of `<4 byte length || actual_message>`. +5. Next, 4 bytes — the number of uncompressed bytecodes in the batch. +6. Then, the concatenation of uncompressed bytecodes, each in the format of `<4 byte length || actual_bytecode>`. +7. Next, 4 bytes — the length of the compressed state diffs. +8. Then, state diffs are compressed by the spec [above](#state-diff-compression-format). + +The interface for committing batches is the following one: + +```solidity +/// @notice Data needed to commit new batch +/// @param batchNumber Number of the committed batch +/// @param timestamp Unix timestamp denoting the start of the batch execution +/// @param indexRepeatedStorageChanges The serial number of the shortcut index that's used as a unique identifier for storage keys that were used twice or more +/// @param newStateRoot The state root of the full state tree +/// @param numberOfLayer1Txs Number of priority operations to be processed +/// @param priorityOperationsHash Hash of all priority operations from this batch +/// @param bootloaderHeapInitialContentsHash Hash of the initial contents of the bootloader heap. In practice it serves as the commitment to the transactions in the batch. +/// @param eventsQueueStateHash Hash of the events queue state. In practice it serves as the commitment to the events in the batch. +/// @param systemLogs concatenation of all L2 -> L1 system logs in the batch +/// @param totalL2ToL1Pubdata Total pubdata committed to as part of bootloader run. Contents are: l2Tol1Logs <> l2Tol1Messages <> publishedBytecodes <> stateDiffs +struct CommitBatchInfo { + uint64 batchNumber; + uint64 timestamp; + uint64 indexRepeatedStorageChanges; + bytes32 newStateRoot; + uint256 numberOfLayer1Txs; + bytes32 priorityOperationsHash; + bytes32 bootloaderHeapInitialContentsHash; + bytes32 eventsQueueStateHash; + bytes systemLogs; + bytes totalL2ToL1Pubdata; +} +``` diff --git a/docs/settlement_contracts/data_availability/state_diff_compression_v1_spec.md b/docs/settlement_contracts/data_availability/state_diff_compression_v1_spec.md new file mode 100644 index 000000000..5fae9cedf --- /dev/null +++ b/docs/settlement_contracts/data_availability/state_diff_compression_v1_spec.md @@ -0,0 +1,89 @@ +# State diff compression v1 spec + +[back to readme](../../README.md) + +The most basic strategy to publish state diffs is to publish those in either of the following two forms: + +- When a key is updated for the first time — ``, where key is 32-byte derived key and the value is new 32-byte value of the slot. +- When a key is updated for the second time and more — ``, where the `enumeration_index` is an 8-byte id of the slot and the value is the new 32-byte value of the slot. + +This compression strategy will utilize a similar idea for treating keys and values separately and it will be focused on the efficient compression of keys and values separately. + +## Keys + +Keys will be packed in the same way as they were before. The only change is that we’ll avoid using the 8-byte enumeration index and will pack it to the minimal necessary number of bytes. This number will be part of the pubdata. Once a key has been used, it can already use the 4 or 5 byte enumeration index and it is very hard to have something cheaper for keys that has been used already. The opportunity comes when remembering the ids for accounts to spare some bytes on nonce/balance key, but ultimately the complexity may not be worth it. + +There is some room for optimization of the keys that are being written for the first time, however, optimizing those is more complex and achieves only a one-time effect (when the key is published for the first time), so they may be in scope of the future upgrades. + +## Values + +Values are much easier to compress since they usually contain only zeroes. Also, we can leverage the nature of how those values are changed. For instance, if nonce has been increased only by 1, we do not need to write the entire 32-byte new value, we can just tell that the slot has been _increased_ and then supply only the 1-byte value by which it was increased. This way instead of 32 bytes we need to publish only 2 bytes: first byte to denote which operation has been applied and the second by to denote the number by which the addition has been made. + +We have the following 4 types of changes: `Add`, `Sub,` `Transform`, `NoCompression` where: + +- `NoCompression` denotes that the whole 32 byte will be provided. +- `Add` denotes that the value has been increased. (modulo 2^256) +- `Sub` denotes that the value has been decreased. (modulo 2^256) +- `Transform` denotes the value just has been changed (i.e. we disregard any potential relation between the previous and the new value, though the new value might be small enough to save up on the number of bytes). + +Where the byte size of the output can be anywhere from 0 to 31 (also 0 makes sense for `Transform`, since it denotes that it has been zeroed out). For `NoCompression` the whole 32 byte value is used. + +So the format of the pubdata is the following: + +**Part 1. Header.** + +- `` — this will enable easier automated unpacking in the future. Currently, it will be only equal to `1`. +- `` — we need only 3 bytes to describe the total length of the L2→L1 logs. +- ``. It should be equal to the minimal required bytes to represent the enum indexes for repeated writes. + +**Part 2. Initial writes.** + +- `` - the number of initial writes. Since each initial write publishes at least 32 bytes for key, then `2^16 * 32 = 2097152` will be enough for a lot of time (right now with the limit of 120kb it will take more than 15 L1 txs to use up all the space there). +- Then for each `` pair for each initial write: + - print key as 32-byte derived key. + - packing type as a 1 byte value, which consists of 5 bits to denote the length of the packing and 3 bits to denote the type of the packing (either `Add`, `Sub`, `Transform` or `NoCompression`). + - The packed value itself. + +**Part 3. Repeated writes.** + +Note, that there is no need to write the number of repeated writes, since we know that until the end of the pubdata, all the writes will be repeated ones. + +- For each `` pair for each repeated write: + - print key as derived key by using the number of bytes provided in the header. + - packing type as a 1 byte value, which consists of 5 bits to denote the length of the packing and 3 bits to denote the type of the packing (either `Add`, `Sub`, `Transform` or `NoCompression`). + - The packed value itself. + +## Impact + +This setup allows us to achieve nearly 75% packing for values, and 50% gains overall in terms of the storage logs based on historical data. + +## Encoding of packing type + +Since we have `32 * 3 + 1` ways to pack a state diff, we need at least 7 bits to present the packing type. To make parsing easier, we will use 8 bits, i.e. 1 byte. + +We will use the first 5 bits to represent the length of the bytes (from 0 to 31 inclusive) to be used. The other 3 bits will be used to represent the type of the packing: `Add`, `Sub` , `Transform`, `NoCompression`. + +## Worst case scenario + +The worst case scenario for such packing is when we have to pack a completely random new value, i.e. it will take us 32 bytes to pack + 1 byte to denote which type it is. However, for such a write the user will anyway pay at least for 32 bytes. Adding an additional byte is roughly 3% increase, which will likely be barely felt by users, most of which use storage slots for balances, etc, which will consume only 7-9 bytes for packed value. + +## Why do we need to repeat the same packing method id + +You might have noticed that for each pair `` to describe value we always first write the packing type and then write the packed value. However, the reader might ask, it is more efficient to just supply the packing id once and then list all the pairs `` which use such packing. + +I.e. instead of listing + +(key = 0, type = 1, value = 1), (key = 1, type = 1, value = 3), (key = 2, type = 1, value = 4), … + +Just write: + +type = 1, (key = 0, value = 1), (key = 1, value = 3), (key = 2, value = 4), … + +There are two reasons for it: + +- A minor reason: sometimes it is less efficient in case the packing is used for very few slots (since for correct unpacking we need to provide the number of slots for each packing type). +- A fundamental reason: currently enum indices are stored directly in the merkle tree & have very strict order of incrementing enforced by the circuits and (they are given in order by pairs `(address, key)`), which are generally not accessible from pubdata. + +All this means that we are not allowed to change the order of “first writes” above, so indexes for them are directly recoverable from their order, and so we can not permute them. If we were to reorder keys without supplying the new enumeration indices for them, the state would be unrecoverable. Always supplying the new enum index may add additional 5 bytes for each key, which might negate the compression benefits in a lot of cases. Even if the compression will still be beneficial, the added complexity may not be worth it. + +That being said, we _could_ rearange those for _repeated_ writes, but for now we stick to the same value compression format for simplicity. diff --git a/docs/settlement_contracts/img/Diamond-scheme.png b/docs/settlement_contracts/img/Diamond-scheme.png new file mode 100644 index 000000000..eac56be5a Binary files /dev/null and b/docs/settlement_contracts/img/Diamond-scheme.png differ diff --git a/docs/settlement_contracts/priority_queue/img/PQ1.png b/docs/settlement_contracts/priority_queue/img/PQ1.png new file mode 100644 index 000000000..0f3602371 Binary files /dev/null and b/docs/settlement_contracts/priority_queue/img/PQ1.png differ diff --git a/docs/settlement_contracts/priority_queue/img/PQ2.png b/docs/settlement_contracts/priority_queue/img/PQ2.png new file mode 100644 index 000000000..92a3e3002 Binary files /dev/null and b/docs/settlement_contracts/priority_queue/img/PQ2.png differ diff --git a/docs/settlement_contracts/priority_queue/img/PQ3.png b/docs/settlement_contracts/priority_queue/img/PQ3.png new file mode 100644 index 000000000..8cd5fd847 Binary files /dev/null and b/docs/settlement_contracts/priority_queue/img/PQ3.png differ diff --git a/docs/settlement_contracts/priority_queue/priority-queue.md b/docs/settlement_contracts/priority_queue/priority-queue.md new file mode 100644 index 000000000..59384bb0a --- /dev/null +++ b/docs/settlement_contracts/priority_queue/priority-queue.md @@ -0,0 +1,137 @@ +# Priority Queue to Merkle Tree + +[back to readme](../../README.md) + +## Overview of the current implementation + +Priority queue is a data structure in Era contracts that is used to handle L1->L2 priority operations. It supports the following: + +- inserting a new operation into the end of the queue +- checking that an newly executed batch executed some n first priority operations from the queue (and not some other ones) in correct order + +The queue itself only stores the following: + +```solidity +struct PriorityOperation { + bytes32 canonicalTxHash; + uint64 expirationTimestamp; + uint192 layer2Tip; +} +``` + +of which we only care about the canonical hash. + +### Inserting new operations + +The queue is implemented as a [library](../../../l1-contracts/contracts/state-transition/libraries/PriorityQueue.sol#L22). +For each incoming priority operation, we simply `pushBack` its hash, expiration and layer2Tip. + +### Checking validity + +When a new batch is executed, we need to check that operations that were executed there match the operations in the priority queue. The batch header contains `numberOfLayer1Txs` and `priorityOperationsHash` which is a rolling hash of all priority operations that were executed in the batch. Bootloader check that this hash indeed corresponds to all priority operations that have been executed in that batch. The contract only checks that this hash matches the operations stored in the queue: + +```solidity +/// @dev Pops the priority operations from the priority queue and returns a rolling hash of operations +function _collectOperationsFromPriorityQueue(uint256 _nPriorityOps) internal returns (bytes32 concatHash) { + concatHash = EMPTY_STRING_KECCAK; + + for (uint256 i = 0; i < _nPriorityOps; i = i.uncheckedInc()) { + PriorityOperation memory priorityOp = s.priorityQueue.popFront(); + concatHash = keccak256(abi.encode(concatHash, priorityOp.canonicalTxHash)); + } +} + +bytes32 priorityOperationsHash = _collectOperationsFromPriorityQueue(_storedBatch.numberOfLayer1Txs); +require(priorityOperationsHash == _storedBatch.priorityOperationsHash); // priority operations hash does not match to expected +``` + +As can be seen, this is done in `O(n)` compute, where `n` is the number of priority operations in the batch. + +## Motivation for migration to Merkle Tree + +Since we will be introducing Sync Layer, we will need to support one more operation: + +- migrating priority queue from L1 to SL (and back) + +Current implementation takes `O(n)` space and is vulnerable to spam attacks during migration +(e.g. an attacker can insert a lot of priority operations and we won't be able to migrate all of them due to gas limits). + +Hence, we need an implementation with a small (constant- or log-size) space imprint that we can migrate to SL and back that would still allow us to perform the other 2 operations. + +Merkle tree of priority operations is perfect for this since we can simply migrate the latest root hash to SL and back. + +- It can still efficiently (in `O(height)`) insert new operations. +- It can also still efficiently (in `O(n)` compute and `O(n + height)` calldata) check that the batch’s `priorityOperationsHash` corresponds to the operations from the queue. + +Note that `n` here is the number of priority operations in the batch, not `2^height`. + +The implementation details are described below. + +### FAQ + +- Q: Why can't we just migrate the rolling hash of the operations in the existing priority queue? +- A: The rolling hash is not enough to check that the operations from the executed batch are indeed from the priority queue. We would need to store all historical rolling hashes, which would be `O(n)` space and would not solve the spam attack problem. + +## Implementation + +The implementation will consist of two parts: + +- Merkle tree on L1 contracts, to replace the existing priority queue (while still supporting the existing operations) +- Merkle tree off-chain on the server, to generate the merkle proofs for the executed priority operations. + +### Contracts + +On the contracts, the Merkle tree will be implemented as an Incremental (append-only) Merkle Tree ([example implementation](https://github.com/tornadocash/tornado-core/blob/master/contracts/MerkleTreeWithHistory.sol)), meaning that it can efficiently (in `O(height)` compute) append new elements to the right, while only storing `O(height)` nodes at all times. + +It will also be dynamically sized, meaning that it will double in size when the current size is not enough to store the new element. + +### Server + +On the server, the Merkle tree will be implemented as an extension of `MiniMerkleTree` currently used for L2->L1 logs. + +It will have the following properties: + +- in-memory: the tree will be stored in memory and will be rebuilt on each restart (details below). +- dynamically sized (to match the contracts implementation) +- append-only (to match the contracts implementation) + +The tree does not need to be super efficient, since we process on average 7 operations per batch. + +### Why in-memory? + +Having the tree in-memory means rebuilding the tree on each restart. This is fine because on mainnet after >1 year since release we have only 3.2M priority operations. We only have to fully rebuild the tree _once_ and then simply cache the already executed operations (which are the majority). Having the tree in-memory has an added benefit of not having to have additional infrastructure to store it on disk and not having to be bothered to rollback its state manually if we ever have to (as we do for e.g. for the storage logs tree). + +Note: If even rebuilding it once becomes a problem, it can be easily mitigated by only persisting the cache nodes. + +### Caching + +**Why do we need caching?** After a batch is successfully executed, we will no longer need to have the ability to generate merkle paths for those operations. This means that we can save space and compute by only fully storing the operations that are not yet executed, and caching the leaves +corresponding to the already executed operations. + +We will only cache some prefix of the tree, meaning nodes in the interval [0; N) where N is the number of executed priority operations. The cache will store the rightmost cached left-child node on each level of the tree (see diagrams). + +![Untitled](./img/PQ1.png) + +![Untitled](./img/PQ2.png) + +![Untitled](./img/PQ3.png) + +This means that we will not be able to generate merkle proofs for the cached nodes (and since they are already executed, we don't need to). This structure allows us to save a lot of space, since it only takes up `O(height)` space instead of linear space for all executed operations. This is a big optimization since there are currently 3.2M total operations but <10 non-executed operations in the mainnet priority queue, which means most of the tree will be cached. + +This also means we don’t really have to store non-leaf nodes other than cache, since we can calculate merkle root / merkle paths in `O(n)` where `n` is the number of non-executed operations (and not total number of operations), and since `n` is so small, it is really fast. + +### Adding new operations + +On the contracts, appending a new operation to the tree is done by simply calling `append` on the Incremental Merkle Tree, which will update at most `height` slots. Actually, it works almost exactly like the cache described above. Once again: [tornado-cash implementation](https://github.com/tornadocash/tornado-core/blob/1ef6a263ac6a0e476d063fcb269a9df65a1bd56a/contracts/MerkleTreeWithHistory.sol#L68). + +On the server, `eth_watch` will listen for `NewPriorityOperation` events as it does now, and will append the new operation to the tree on the server. + +### Checking validity + +To check that the executed batch indeed took its priority operations from the queue, we have to make sure that if we take first `numberOfL1Txs` non-executed operations from the tree, their rolling hash will match `priorityOperationsHash` . Since will not be storing the hashes of these operations onchain anymore, we will have to provide them as calldata. Additionally in calldata, we should provide merkle proofs for the **first and last** operations in that batch (hence `O(n + height)` calldata). This will make it possible to prove onchain that that contiguous interval of hashes indeed exists in the merkle tree. + +This can be done simply by constructing the part of the tree above this interval using the provided paths to first and last elements of the interval checking that computed merkle root matches with stored one (in `O(n)` where `n` is number of priority operations in a batch). We will also need to track the `index` of the first unexecuted operation onchain to properly calculate the merkle root and ensure that batches don’t execute some operations out of order or multiple times. + +We will also need to prove that the rolling hash of provided hashes matches with `priorityOperationsHash` which is also `O(n)` + +It is important to note that we should store some number of historical root hashes, since the Merkle tree on the server might lag behind the contracts a bit, and hence merkle paths generated on the server-side might become invalid if we compare them to the latest root hash on the contracts. These historical root hashes are not necessary to migrate to and from SL though. diff --git a/docs/settlement_contracts/priority_queue/processing_of_l1-l2_txs.md b/docs/settlement_contracts/priority_queue/processing_of_l1-l2_txs.md new file mode 100644 index 000000000..c6af14c61 --- /dev/null +++ b/docs/settlement_contracts/priority_queue/processing_of_l1-l2_txs.md @@ -0,0 +1,98 @@ +# Handling L1→L2 ops on ZKsync + +[back to readme](../../README.md) + +The transactions on ZKsync can be initiated not only on L2, but also on L1. There are two types of transactions that can be initiated on L1: + +- Priority operations. These are the kind of operations that any user can create. +- Upgrade transactions. These can be created only during upgrades. + +## Prerequisites + +Please read the full [article](../../l2_system_contracts/system_contracts_bootloader_description.md) on the general system contracts / bootloader structure as well as the pubdata structure to understand [the difference](../data_availability/standard_pubdata_format.md) between system and user logs. + +## Priority operations + +### Initiation + +A new priority operation can be appended by calling the `requestL2TransactionDirect` or `requestL2TransactionTwoBridges` methods on `BridgeHub` smart contract. `BridgeHub` will ensure that the base token is deposited via `L1AssetRouter` and send transaction request to the specified state transition contract (selected by the chainID). State transition contract will perform several checks for the transaction, making sure that it is processable and provides enough fee to compensate the operator for this transaction. Then, this transaction will be [appended](../../../l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol#569) to the priority tree (and optionally to the legacy priority queue). + +> In the previous system, priority operations were structured in a queue. However, now they will be stored in an incremental merkle tree. The motivation for the tree structure can be read [here](./priority-queue.md). + +The difference between `requestL2TransactionDirect` and `requestL2TransactionTwoBridges` is that the `msg.sender` on the L2 Transaction is the second bridge in the `requestL2TransactionTwoBridges` case, while it is the `msg.sender` of the `requestL2TransactionDirect` in the first case. For more details read the [bridgehub documentation](../../bridging/bridgehub/overview.md) + +### Bootloader + +Whenever an operator sees a priority operation, it can include the transaction into the batch. While for normal L2 transaction the account abstraction protocol will ensure that the `msg.sender` has indeed agreed to start a transaction out of this name, for L1→L2 transactions there is no signature verification. In order to verify that the operator includes only transactions that were indeed requested on L1, the bootloader maintains](../../system-contracts/bootloader/bootloader.yul#L1052-L1053) two variables: + +- `numberOfPriorityTransactions` (maintained at `PRIORITY_TXS_L1_DATA_BEGIN_BYTE` of bootloader memory) +- `priorityOperationsRollingHash` (maintained at `PRIORITY_TXS_L1_DATA_BEGIN_BYTE + 32` of the bootloader memory) + +Whenever a priority transaction is processed, the `numberOfPriorityTransactions` gets incremented by 1, while `priorityOperationsRollingHash` is assigned to `keccak256(priorityOperationsRollingHash, processedPriorityOpHash)`, where `processedPriorityOpHash` is the hash of the priority operations that has been just processed. + +Also, for each priority transaction, we [emit](../../../system-contracts/bootloader/bootloader.yul#L1046) a user L2→L1 log with its hash and result, which basically means that it will get Merklized and users will be able to prove on L1 that a certain priority transaction has succeeded or failed (which can be helpful to reclaim your funds from bridges if the L2 part of the deposit has failed). + +Then, at the end of the batch, we [submit](../../../system-contracts/bootloader/bootloader.yul#L4117-L4118) 2 L2→L1 log system log with these values. + +### Batch commit + +During batch commit, the contract will remember those values, but not validate them in any way. + +### Batch execution + +During batch execution, the will check that the `priorityOperationsRollingHash` rolling hash provided before was correct. There are two ways to do it: + +- [Legacy one that uses priority queue](../../../l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol#L397). We will pop `numberOfPriorityTransactions` from the top of priority queue and verify that the hashes match. +- [The new one that uses priority tree](../../../l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol#L397). The operator would have to provide the hashes of these priority operations in an array, as well as proof that this entire segment belongs to the merkle tree. After it is verified that this array of leaves is correct, it will be checked whether the rolling hash of those is equal to the `priorityOperationsRollingHash`. + +## Upgrade transactions + +### Initiation + +Upgrade transactions can only be created during a system upgrade. It is done if the `DiamondProxy` delegatecalls to the implementation that manually puts this transaction into the storage of the DiamondProxy, this could happen on calling `upgradeChainFromVersion` function in `Admin.sol` on the State Transition contract. Note, that since it happens during the upgrade, there is no “real” checks on the structure of this transaction. We do have [some validation](../../../l1-contracts/contracts/upgrades/BaseZkSyncUpgrade.sol#L193), but it is purely on the side of the implementation which the `DiamondProxy` delegatecalls to and so may be lifted if the implementation is changed. + +The hash of the currently required upgrade transaction is stored under `l2SystemContractsUpgradeTxHash` variable. + +We will also track the batch where the upgrade has been committed in the `l2SystemContractsUpgradeBatchNumber` variable. + +We can not support multiple upgrades in parallel, i.e. the next upgrade should start only after the previous one has been complete. + +### Bootloader + +The upgrade transactions are processed just like with priority transactions, with only the following differences: + +- We can have only one upgrade transaction per batch & this transaction must be the first transaction in the batch. +- The system contracts upgrade transaction is not appended to `priorityOperationsRollingHash` and doesn’t increment `numberOfPriorityTransactions`. Instead, its hash is calculated via a system L2→L1 log _before_ it gets executed. Note, that it is an important property. More on it [below](#security-considerations). + +### Commit + +After an upgrade has been initiated, it will be required that the next commit batches operation already contains the system upgrade transaction. It is [checked](../../../l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol#L223) by verifying the corresponding L2→L1 log. + +We also remember that the upgrade transaction has been processed in this batch (by amending the `l2SystemContractsUpgradeBatchNumber` variable). + +### Revert + +In a very rare event when the team needs to revert the batch with the upgrade on ZKsync, the `l2SystemContractsUpgradeBatchNumber` is reset. + +Note, however, that we do not “remember” that certain batches had a version before the upgrade, i.e. if the reverted batches will have to be reexecuted, the upgrade transaction must still be present there, even if some of the deleted batches were committed before the upgrade and thus didn’t contain the transaction. + +### Execute + +Once batch with the upgrade transaction has been executed, we [delete](../../../l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol#L486) them from storage for efficiency to signify that the upgrade has been fully processed and that a new upgrade can be initiated. + +### Security considerations + +Since the operator can put any data into the bootloader memory and for L1→L2 transactions the bootloader has to blindly trust it and rely on L1 contracts to validate it, it may be a very powerful tool for a malicious operator. Note, that while the governance mechanism is trusted, we try to limit our trust for the operator as much as possible, since in the future anyone would be able to become an operator. + +Some time ago, we _used to_ have a system where the upgrades could be done via L1→L2 transactions, i.e. the implementation of the `DiamondProxy` upgrade would include a priority transaction (with `from` equal to for instance `FORCE_DEPLOYER`) with all the upgrade params. + +In the current system though having such logic would be dangerous and would allow for the following attack: + +- Let’s say that we have at least 1 priority operations in the priority queue. This can be any operation, initiated by anyone. +- The operator puts a malicious priority operation with an upgrade into the bootloader memory. This operation was never included in the priority operations queue / and it is not an upgrade transaction. However, as already mentioned above the bootloader has no idea what priority / upgrade transactions are correct and so this transaction will be processed. + +The most important caveat of this malicious upgrade is that it may change implementation of the `Keccak256` precompile to return any values that the operator needs. + +- When the`priorityOperationsRollingHash` will be updated, instead of the “correct” rolling hash of the priority transactions, the one which would appear with the correct topmost priority operation is returned. The operator can’t amend the behaviour of `numberOfPriorityTransactions`, but it won’t help much, since the the `priorityOperationsRollingHash` will match on L1 on the execution step. + +That’s why the concept of the upgrade transaction is needed: this is the only transaction that can initiate transactions out of the kernel space and thus change bytecodes of system contracts. That’s why it must be the first one and that’s why bootloader [emits](../../../system-contracts/bootloader/bootloader.yul#L603) its hash via a system L2→L1 log before actually processing it. diff --git a/docs/settlement_contracts/zkchain_basics.md b/docs/settlement_contracts/zkchain_basics.md new file mode 100644 index 000000000..861af30f0 --- /dev/null +++ b/docs/settlement_contracts/zkchain_basics.md @@ -0,0 +1,172 @@ +# L1 smart contract of an individual chain + +[back to readme](../README.md) + +## Diamond (also mentioned as State Transition contract) + +Technically, this L1 smart contract acts as a connector between Ethereum (L1) and hyperchain (L2). It checks the +validity proof and data availability, handles L2 <-> L1 communication, finalizes L2 state transition, and more. + +There are also important contracts deployed on the L2 that can also execute logic called _system contracts_. Using L2 +<-> L1 communication can affect both the L1 and the L2. + +![diamondProxy.png](./img/Diamond-scheme.png) + +### DiamondProxy + +The main contract uses [EIP-2535](https://eips.ethereum.org/EIPS/eip-2535) diamond proxy pattern. It is an in-house +implementation that is inspired by the [mudgen reference implementation](https://github.com/mudgen/Diamond). It has no +external functions, only the fallback that delegates a call to one of the facets (target/implementation contract). So +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 admin +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. + +The diamond proxy pattern is very flexible and extendable. For now, it allows splitting implementation contracts by their logical meaning, removes the limit of bytecode size per contract and implements security features such as freezing. In the future, it can also be viewed as [EIP-6900](https://eips.ethereum.org/EIPS/eip-6900) for [zkStack](https://blog.matter-labs.io/introducing-the-zk-stack-c24240c2532a), where each hyperchain can implement a sub-set of allowed implementation contracts. + +### 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. + +### AdminFacet + +This facet responsible for the configuration setup and upgradability, handling tasks such as: + +- Privileged Address Management: Updating key roles, including the governor and validators. +- System Parameter Configuration: Adjusting critical system settings, such as the L2 bootloader bytecode hash, verifier address, changing DA layer or fee configurations. +- Freezability: Executing the freezing/unfreezing of facets within the diamond proxy to safeguard the ecosystem during upgrades or in response to detected vulnerabilities. + +Control over the AdminFacet is divided between two main entities: + +- CTM (Chain Type Manager, formerly known as `StateTransitionManager`) - Separate smart contract that can perform critical changes to the system as protocol upgrades. For more detailed information on its function and design, refer to [this document](../chain_management/chain_type_manager.md). Although currently only one version of the CTM exists, the architecture allows for future versions to be introduced via subsequent upgrades. The owner of the CTM is the [decentralized governance](https://blog.zknation.io/introducing-zk-nation/), while for non-critical an Admin entity is used (see details below). +- Chain Admin - Multisig smart contract managed by each individual chain that can perform non-critical changes to the system such as granting validator permissions. + +### MailboxFacet + +The facet that handles L2 <-> L1 communication. + +The Mailbox performs three functions: + +- L1 ↔ L2 Communication: Enables data and transaction requests to be sent from L1 to L2 and vice versa, supporting the implementation of multi-layer protocols. +- Bridging Native Tokens: Allows the bridging of either ether or ERC20 tokens to L2, enabling users to use these assets within the L2 ecosystem. +- Censorship Resistance Mechanism: Currently in the research stage. + +L1 -> L2 communication is implemented as requesting an L2 transaction on L1 and executing it on L2. This means a user +can call the function on the L1 contract to save the data about the transaction in some queue. Later on, a validator can +process it on L2 and mark it as processed on the L1 priority queue. Currently, it is used for sending information from +L1 to L2 or implementing multi-layer protocols. Users pays for the transaction execution in the native token when requests L1 -> L2 transaction. + +_NOTE_: While user requests the transaction from L1, the initiated transaction on L2 will have such a `msg.sender`: + +```solidity + address sender = msg.sender; + if (sender != tx.origin) { + sender = AddressAliasHelper.applyL1ToL2Alias(msg.sender); + } +``` + +where + +```solidity +uint160 constant offset = uint160(0x1111000000000000000000000000000000001111); + +function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) { + unchecked { + l2Address = address(uint160(l1Address) + offset); + } +} +``` + +For most of the rollups the address aliasing needs to prevent cross-chain exploits that would otherwise be possible if +we simply reused the same L1 addresses as the L2 sender. In ZKsync Era address derivation rule is different from the +Ethereum, so cross-chain exploits are already impossible. However, ZKsync Era may add full EVM support in the future, so +applying address aliasing leaves room for future EVM compatibility. + +The L1 -> L2 communication is also used for bridging **base tokens**. If base token is ether (the case for ZKsync Era) - user should include a `msg.value` when initiating a +transaction request on the L1 contract, if base token is an ERC20 then contract will spend users allowance. Before executing a transaction on L2, the specified address will be credited +with the funds. To withdraw funds user should call `withdraw` function on the `L2BaseToken` system contracts. This will +burn the funds on L2, allowing the user to reclaim them through the `finalizeWithdrawal` function on the +`SharedBridge` (more in hyperchain section). + +More about L1->L2 operations can be found [here](./priority_queue/processing_of_l1-l2_txs.md). + +L2 -> L1 communication, in contrast to L1 -> L2 communication, is based only on transferring the information, and not on +the transaction execution on L1. The full description of the mechanism for sending information from L2 to L1 can be found [here](./data_availability/standard_pubdata_format.md). + +The Mailbox facet also facilitates L1<>L3 communications for those chains that settle on top of Gateway. The user interfaces for those are identical to the L1<>L2 communication described above. To learn more about L1<>L3 communication works, check out [this document](../gateway/messaging_via_gateway.md) and [this one](../gateway/nested_l3_l1_messaging.md). + +### ExecutorFacet + +A contract that accepts L2 batches, enforces data availability via DA validators and checks the validity of zk-proofs. You can read more about DA validators [in this document](../settlement_contracts/data_availability/custom_da.md). + +The state transition is divided into three stages: + +- `commitBatches` - check L2 batch timestamp, process the L2 logs, save data for a batch, and prepare data for zk-proof. +- `proveBatches` - validate zk-proof. +- `executeBatches` - finalize the state, marking L1 -> L2 communication processing, and saving Merkle tree with L2 logs. + +Each L2 -> L1 system log will have a key that is part of the following: + +```solidity +enum SystemLogKey { + L2_TO_L1_LOGS_TREE_ROOT_KEY, + PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY, + CHAINED_PRIORITY_TXN_HASH_KEY, + NUMBER_OF_LAYER_1_TXS_KEY, + PREV_BATCH_HASH_KEY, + L2_DA_VALIDATOR_OUTPUT_HASH_KEY, + USED_L2_DA_VALIDATOR_ADDRESS_KEY, + EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY +} +``` + +When a batch is committed, we process L2 -> L1 system logs. Here are the invariants that are expected there: + +- In a given batch there will be either 7 or 8 system logs. The 8th log is only required for a protocol upgrade. +- There will be a single log for each key that is contained within `SystemLogKey` +- Three logs from the `L2_TO_L1_MESSENGER` with keys: +- `L2_TO_L1_LOGS_TREE_ROOT_KEY` +- `L2_DA_VALIDATOR_OUTPUT_HASH_KEY` +- `USED_L2_DA_VALIDATOR_ADDRESS_KEY` +- Two logs from `L2_SYSTEM_CONTEXT_SYSTEM_CONTRACT_ADDR` with keys: + - `PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY` + - `PREV_BATCH_HASH_KEY` +- Two or three logs from `L2_BOOTLOADER_ADDRESS` with keys: + - `CHAINED_PRIORITY_TXN_HASH_KEY` + - `NUMBER_OF_LAYER_1_TXS_KEY` + - `EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY` +- None logs from other addresses (may be changed in the future). + +### DiamondInit + +It is a one-function contract that implements the logic of initializing a diamond proxy. It is called only once on the +diamond constructor and is not saved in the diamond as a facet. + +Implementation detail - function returns a magic value just like it is designed in +[EIP-1271](https://eips.ethereum.org/EIPS/eip-1271), but the magic value is 32 bytes in size. + +## ValidatorTimelock + +An intermediate smart contract between the validator EOA account and the ZK chain diamond contract. Its primary purpose is +to provide a trustless means of delaying batch execution without modifying the main ZKsync contract. 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. + +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`, which can be called only by the validator. + +When the validator calls `commitBatches`, the same calldata will be propagated 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 committed by the validator to enforce a delay between committing and execution of batches. Then, the +validator can prove the already committed batches regardless of the mentioned timestamp, and again the same calldata (related +to the `proveBatches` function) will be propagated to the ZKsync contract. After the `delay` is elapsed, the validator +is allowed to call `executeBatches` to propagate the same calldata to ZKsync contract. + +The owner of the ValidatorTimelock contract is the decentralized governance. Note, that all the chains share the same ValidatorTimelock for simplicity. diff --git a/docs/upgrade_history/gateway_upgrade/gateway_diff_review.md b/docs/upgrade_history/gateway_upgrade/gateway_diff_review.md new file mode 100644 index 000000000..a1518242f --- /dev/null +++ b/docs/upgrade_history/gateway_upgrade/gateway_diff_review.md @@ -0,0 +1,100 @@ +# Gateway upgrade changes + +## Introduction & prerequisites + +[back to readme](../../README.md) + +This document assumes that the reader has general knowledge of how ZKsync Era works and how our ecosystem used to be like at the moment of shared bridge in general. + +To read the documentation about the current system, you can read [here](../../README.md). + +For more info about the previous one, you can reach out to the following documentation: + +[Code4rena Documentation Smart contract Section](https://github.com/code-423n4/2024-03-zksync/tree/main/docs/Smart%20contract%20Section) + +## Changes from the shared bridge design + +This section contains some of the important changes that happened since the shared bridge release in June. This section may not be fully complete and additional information will be provided in the sections that cover specific topics. + +### Bridgehub now has chainId → address mapping + +Before, Bridgehub contained a mapping from `chainId => stateTransitionManager`. The further resolution of the mapping should happen at the CTM level. +For more intuitive management of the chains, a new mapping `chainId => hyperchainAddress` was added. This is considered more intuitive since “bridgehub is the owner of all the chains” mentality is more applicable with this new design. + +The upside of the previous approach was potentially easier migration within the same CTM. However, in the end it was decided that the new approach is better. + +#### Migration + +This new mapping will have to be filled up after upgrading the bridgehub. It is done by repeatedly calling the `setLegacyChainAddress` for each of the deployed chains. It is assumed that their number is relatively low. Also, this function is permissionless and so can be called by anyone after the upgrade is complete. This function will call the old CTM and ask for the implementation of the chainId. + +Until the migration is done, all transactions with the old chains will not be working, but it is a short period of time. + +### baseTokenAssetId is used as a base token for the chains + +In order to facilitate future support of any type of asset a base token, including assets minted on L2, now chains will provide the `assetId` for their base token instead. The derivation & definition of the `assetId` is expanded in the CAB section of the doc. + +#### Migration & compatibility + +Today, there are some mappings of sort `chainId => baseTokenAddress`. These will no longer be filled for new chains. Instead, only assetId will be provided in a new `chainId => baseTokenAssetId` mapping. + +To initialize the new `baseTokenAssetId` mapping the following function should be called for each chain: `setLegacyBaseTokenAssetId`. It will encode each token as the assetId of an L1 token of the Native Token Vault. This method is permissionless. + +For the old tooling that may rely on getters of sort `getBaseTokenAddress(chainId)` working, we provide a getter method, but its exact behavior depends on the asset handler of the `setLegacyBaseTokenAssetId`, i.e. it is even possible that the method will revert for an incompatible assetId. + +### L2 Shared bridge (not L2AssetRouter) is deployed everywhere at the same address + +Before, for each new chain, we would have to initialize the mapping in the L1SharedBridge to remember the address of the l2 shared bridge on the corresponding L2 chain. + +Now, however, the L2AssetRouter is set on the same constant on all chains. + +#### L2SharedBridgeLegacy + +Note, that for the chains that contained the `L2SharedBridge` before the upgrade, it will be upgraded to the `L2SharedBridgeLegacy` code. The `L2AssetRouter` will have the same address on all chains, including old ones. + +### StateTransitionManager was renamed to ChainTypeManager + +CTM was renamed to CTM (ChainTypeManager). This was done to use more intuitive naming as the chains of the same “type” share the same CTM. + +### Hyperchains were renamed to ZK chains + +For consistency with the naming inside the blogs, the term “hyperchain” has been changed to “ZK chain”. + +## Changes in the structure of contracts + +While fully reusing contracts on both L1 and L2 is not always possible, it was done to a very high degree as now all bridging-related contracts are located inside the `l1-contracts` folder. + +## Priority tree + +[Migrating Priority Queue to Merkle Tree](../../settlement_contracts/priority_queue/priority-queue.md) + +In the currently deployed system, L1→L2 transactions are added as a part of a priority queue, i.e. all of them are stored 1-by-1 on L1 in a queue-like structure. + +Note, that the complexity of chain migrations in either of the directions depends on the size of the priority queue. However, the number of unprocessed priority transactions is potentially out of hands of both the operator of the chain and the chain admin as the users are free to add priority transactions in case there is no `transactionFilterer` contract, which is the case for any permissionless system, such as ZKsync Era. + +If someone tries to DDoS the priority queue, the chain can be blocked from migration. Even worse, for GW→L1 migrations, inability to finalize the migration can lead to a complete loss of chain. + +To combat all the issues above, it was decided to move from the priority queue to a priority tree, i.e. only the incremental merkle tree is stored on L1, while at the end of the batch the operator will provide a merkle proof for the inclusion of the priority transactions that were present in the batch. It does not impact the bootloader, but rather only how the L1 checks that the priority transactions did indeed belong to the chain + +## Custom DA layers + +Custom DA layer support was added. + +### Major changes + +In order to achieve CAB, we separated the liquidity managing logic from the Shared Bridge to `Asset Handlers`. The basic cases will be handled by `Native Token Vaults`, which are handling all of the standard `ERC20 tokens`, as well as `ETH`. + +## L1<>L2 token bridging considerations + +- We have the L2SharedBridgeLegacy on chains that are live before the upgrade. This contract will keep on working, and where it exists it will also be used to: + - deploy bridged tokens. This is so that the l2TokenAddress keeps working on the L1, and so that we have a predictable address for these tokens. + - send messages to L1. On the L1 finalizeWithdrawal does not specify the l2Sender. Legacy withdrawals will use the legacy bridge as their sender, while new withdrawals would use the L2_ASSET_ROUTER_ADDR. In the future we will add the sender to the L1 finalizeWithdrawal interface. Until the current method is deprecated we use the l2SharedBridgeAddress even for new withdrawals on legacy chains. + This also means that on the L1 side we set the L2AR address when calling the function via the legacy interface even if it is a baseToken withdrawal. Later when we learn if it baseToken or not, we override the value. +- We have the finalizeWithdrawal function on L1 AR, which uses the finalizeDeposit in the background. +- L1→L2 deposits need to use the legacy encoding for SDK compatibility. + - This means the legacy finalizeDeposit with tokenAddress which calls the new finalizeDeposit with assetId. + - On the other hand, new assets will use the new finalizeDeposit directly +- The originChainId will be tracked for each assetId in the NTVs. This will be the chain where the token is originally native to. This is needed to accurately track chainBalance (especially for l2 native tokens bridged to other chains via L1), and to verify the assetId is indeed an NTV asset id (i.e. has the L2_NATIVE_TOKEN_VAULT_ADDR as deployment tracker). + +## Upgrade process in detail + +You can read more about the upgrade process itself [here](./upgrade_process.md). diff --git a/docs/upgrade_history/gateway_upgrade/upgrade_process.md b/docs/upgrade_history/gateway_upgrade/upgrade_process.md new file mode 100644 index 000000000..8a36cc4e5 --- /dev/null +++ b/docs/upgrade_history/gateway_upgrade/upgrade_process.md @@ -0,0 +1,216 @@ +# The upgrade process to the new version + +[back to readme](../../README.md) + +Gateway system introduces a lot of new contracts and so conducting so to provide the best experience for ZK chains the multistage upgrade will be provided. The upgrade will require some auxiliary contracts that will exist only for the purpose of this upgrade. + +## Previous version + +The previous version can be found [here](https://github.com/matter-labs/era-contracts/tree/release-v25-protocol-defense). + +The documentation for the previous version can be found [here](https://github.com/code-423n4/2024-03-zksync). + +However, deep knowledge of the previous version should not be required for understanding. But this document _does_ require understanding of the new system, so it should be the last document for you to read. + +## Overall design motivation + +During design of this upgrade we followed two principles: + +- Trust minimization. I.e. once the voting has started, no party can do damage to the upgrade or change its course. For instance, no one should be able to prevent a usable chain from conducting the upgrade to the gateway. +- Minimal required preparation for chains. All of the required contracts for the upgrade (e.g. rollup L2DA validator, etc) will be deployed during the upgrade automatically. This allows to minimize risks of mistakes for each individual chain. + +There are four roles that will be mentioned within this document: + +- “Governance” — trusted entity that embodies the whole [decentralized voting process for ZK chain ecosystem](https://blog.zknation.io/introducing-zk-nation/). +- “Ecosystem admin” — relatively trusted role for non-critical operation. It will be typically implemented as a multisig, that is approved by the governance and can only do limited operations to facilitate the upgrade. It can not alter the content of the upgrade nor it should be able to somehow harm chains by weaponizing the upgrade. +- “Chain admin” — an admin of a ZK chain. An entity with limited ability to govern their own chain (they can choose who are the validators of the chain, but can not change the general behavior of the their chain). +- “Deployer”. This role may not be mentioned, but it is implicitly present during the preparation stage. This is a hot wallet that is responsible for deploying the implementation of new contracts, etc. The governance should validate all its actions at the start of the voting process and so no trust is assumed from this wallet. + +## Ecosystem preparation stage + +This stage involves everything that is done before the voting starts. At this stage, all the details of the upgrade must be fixed, including the chain id of the gateway. + +More precisely, the implementations for the contracts will have to be deployed. Also, all of the new contracts will have to be deployed along with their proxies, e.g. `CTMDeploymentTracker`, `L1AssetRouter`, etc. + +Also, at this stage the bytecodes all L2 contracts have to be fixed, this includes bytecode for the things like `L2DAValidators`, `GatewayCTMDeployer`, etc. + +### Ensuring Governance ownership + +Some of the new contracts (e.g. `CTMDeploymentTracker` ) have two sorts of admins: the admin of the their proxy as well the `owner` role inside the contract. Both should belong to governance (the former is indirectly controlled by governance via a `ProxyAdmin` contract). + +The governance needs to know that it will definitely retain the ownership of these contracts regardless of the actions of their deployer. This are multiple ways this is ensured: + +- For `TransparentUpgradaeableProxy` this is simple: we can just transfer the ownership in one step to the `ProxyAdmin` that is under control of the governance. +- For contracts that are deployed as standalone contracts (not proxies), then if we possible we provide the address of the owner of in the constructor. +- For proxies and for contracts for which transferring inside the constructor is not option, we would transfer the ownership to a `TransitionaryOwner` contract. This is a contract that is responsible for being a temporary owner until the voting ends and it can do only two things: accept ownership for a contract and atomically transfer it to the governance. This is a workaround we have to use since most of our contracts implement `Ownable2Step` and so it is not possible to transfer ownership in one go. + +PS: It may be possible that for more contracts, e.g. some of the proxies we could’ve avoided the `TransitionaryOwner` approach by e.g. providing the governance address inside of an initializer. But we anyway need the `TransitionaryOwner` for `ValidatorTimelock`, so we decided to use it in most places to keep the code simpler. + +### L2SharedBridge and L2WETH migration + +In the current system (i.e. before the gateway upgrade), the trusted admin of the L1SharedBridge is responsible for [setting the correct L2SharedBridge address for chains](https://github.com/matter-labs/era-contracts/blob/aafee035db892689df3f7afe4b89fd6467a39313/l1-contracts/contracts/bridge/L1SharedBridge.sol#L249) (note that the links points to the old code and not the one in the scope of the contest). This is done with no additional validation. The system is generally designed to protect chains in case when a malicious admin tries to attack a chain. There are two measures to do that: + +- The general assumption is that the L2 shared bridge is set for a chain as soon as possible. It is a realistic assumption, since without it no bridging of any funds except for the base token is possible. So if at an early stage the admin would put a malicious l2 shared bridge for a chain, it would lose its trust from the community and the chain should be discarded. +- Admin can not retroactively change L2 shared bridge for any chains. So once the correct L2 shared bridge is set, there is no way a bad admin can harm the chain. + +The mapping for L2SharedBridge will be used as a source for the address of `L2SharedBridgeLegacy` contract address during the migration. + +To correctly initialize the `L2NativeTokenVault` inside the gateway upgrade, we will need the address of the L2 Wrapped Base Token contract [as well](https://github.com/matter-labs/era-contracts/blob/main/l2-contracts/contracts/bridge/L2WrappedBaseToken.sol) (note that the link is intentionally for the current codebase to show that these are deployed even before the upgrade). + +The data to execute the upgrade with is gathered on L1, so we need to create a mapping on L1 from `chainId => l2WrappedBaseToken`. This is what the `L2WrappedBaseTokenStore` contract for. + +Some chains already have `L2WrappedBaseToken` implementation deployed. It will be the job of the admin of the contract to prepopulate the contract with the correct addresses of those. The governance will have to double check that for the existing chains this mapping has been populated correctly before proceeding with the upgrade. + +Since we do not want to stop new chain creation while the voting is in progress, the admin needs to have the ability to add both new `L2SharedBridges` and the new `L2WrappedBaseToken` addresses to the mappings above. The following protections are put in place: + +- In case the trusted admin maliciously populated the addresses for any chains that were created before the voting started, the governance should just reject the voting +- In case the trusted admin maliciously populated the addresses for a chain after the voting has ended, the same assumptions as the ones described for L2SharedBridge apply, i.e. the chain should have its `L2SharedBridge` and `L2WrappedBaseToken` deployed asap after the creation of the chain, in case the admin did something malicious, they should immediately discard the chain to prevent loss of value. + +### Publishing bytecodes for everyone + +Before a contract can be deployed with a bytecode, it must be marked as “known”. This includes system contracts. This caused some inconveniences during previous upgrades: + +- For each chain we would have to publish all factory dependencies for the upgrade separately, making it expensive and risk-prone process. +- If a chain forgets to publish bytecodes for a chain before it executes an upgrade, there is little way to recover without intervention from the Governance. + +This upgrade the different approach is used to ensure safe and riskless preparation for the upgrade: + +- All bytecodes that are needed for this upgrade must be published to the `BytecodesSupplier` contract. +- The protocol upgrade transaction will have all the required dependencies in its factory deps. During the upgrade they will be marked as known automatically by the system. The operator of a chain needs to grab the preimages for those from events emitted by the `BytecodesSupplier`. +- It will be the job of the governance to verify that all the bytecodes were published to this contract. + +## Voting stage + +### Things to validate by the governance + +- The L1/L2 bytecodes are correct and the calldata is correct. +- That the correct L2SharedBridge are populated in L1SharedBridge (note that it is a legacy contract from the current system that becomes L1Nullifer in the new upgrade) and that L2WrappedBaseTokenStore has been populated correctly. +- [That the ownership is correctly transferred to governance.](#ensuring-governance-ownership) +- That the bytecodes were published correctly to the `BytecodeSupplier` contract. + +### Things to sign by the governance + +The governance should sign all operations that will happen in all of the consecutive stages at this time. There will be no other voting. Unless stated otherwise, all the governance operations in this document are listed as dependencies for one another, i.e. must be executed in strictly sequential order. + +Note, that to support “Stage 3” it would also need to finalize all the details for Gateway, including its chain id, chain admin, etc. + +## Stage 1. Publishing of the new protocol upgrade + +### Txs by governance (in one multicall) + +1. The governance accepts ownership for all the contracts that used `TransitionaryOwner`. +2. The governance publishes the new version by calling `function setNewVersionUpgrade`. +3. The governance calls `setChainCreationParams` and sets temporary incorrect values there that use a contract that always reverts as the `genesisUpgrade`, ensuring that no new chains can be created until Stage 2. +4. The governance should call the `GovernanceUpgradeTimer.startTimer()` to ensure that the timer for the upgrade starts. + +### Impact + +The chains will get the ability to upgrade to the new protocol version. They will be advised to do so before the deadline for upgrade runs out. + +Also, new chains wont be deployable during this stage due to step (3). + +Chains, whether upgraded or not, should work as usual as the new L2 bridging ecosystem is fully compatible with the old L1SharedBridge. + +Chains that upgrade need to carefully coordinate this upgrade on the server side, since validator timelock changes and also there is a need to keep track of the number of already existing priority ops that were not included into the priority tree. + +## Chain Upgrade flow + +Let’s take a deeper look at how upgrading of an individual chain would look like. + +### Actions by Chain Admins + +As usual, the ChainAdmin should call `upgradeChainFromVersion`. What is unusual however: + +- ValidatorTimelock changes and so the admin should call the new ValidatorTimelock to set the old validators there. +- The new DA validation mechanism is there and so the ChainAdmin should set the new DA validator pair. +- If a chain should be a permanent rollup, the ChainAdmin should call the `makePermanentRollup()` function. + +It is preferable that all the steps above are executed in a multicall for greater convenience, though it is not mandatory. + +This upgrade adds a lot of new chain parameters and so these [should be managed carefully](../../chain_management/admin_role.md). + +### Upgrade flow in contracts + +Usually, we would perform an upgrade by simply doing a list of force deployments: basically providing an array of the contracts to deploy for the system. This array would be constant for all chains and it would work fine. + +However in this upgrade we have an issue that some of the constructor parameters (e.g. the address of the `L2SharedBridgeLegacy`) are specific to each chain. Thus, besides the standard parts of the upgrades each chain also has `ZKChainSpecificForceDeploymentsData` populated. Some of the params to conduct those actions are constant and so populate the `FixedForceDeploymentsData` struct. + +If the above could be composed on L1 to still reuse the old list of `(address, bytecodeHash, constructorData)` list, there are also other rather complex actions such as upgrading the L2SharedBridge to the L2SharedBridgeLegacy implementation that require rather complex logic. + +Due to the complexity of the actions above, it was decided to put all those into the [L2GatewayUpgrade](../../../system-contracts/contracts/L2GatewayUpgrade.sol) contract. It is supposed to be force-deployed with the constructor parameters containing the `ZKChainSpecificForceDeploymentsData` as well as `FixedForceDeploymentsData`. It will be forcedeployed to the ComplexUpgrader’s address to get the kernel space rights. + +So most of the system contracts will be deployed the old way (via force deployment), but for more complex thing the `L2GatewayUpgrade` will be temporarily put onto `ComplexUpgrader` address and initialize additional contracts inside the constructor. Then the correct will be put back there. + +So entire flow can be summarized by the following: + +1. On L1, when `AdminFacet.upgradeChainFromVersion` is called by the Chain Admin, the contract delegatecalls to the [GatewayUpgrade](../../../l1-contracts/contracts/upgrades/GatewayUpgrade.sol) contract. +2. The `GatewayUpgrade` gathers all the needed data to compose the `ZKChainSpecificForceDeploymentsData`, while the `FixedForceDeploymentsData` is part is hardcoded inside the upgrade transaction. +3. The combined upgrade transaction consists of many forced deployments (basically tuples of `(address, bytecodeHash, constructorInput)`) and one of these that is responsible for the temporary `L2GatewayUpgrade` gets its `constructorInput` set to contain the `ZKChainSpecificForceDeploymentsData` / `FixedForceDeploymentsData`. +4. When the upgrade is executed on L2, it iterates over the forced deployments, deploys most of the contracts and then executes the `L2GatewayUpgrade`. +5. `L2GatewayUpgrade` will deploy the L2 Bridgehub, MessageRoot, L2NativeTokenVault, L2AssetRouters. It will also deploy l2WrappedBaseToken if missing. It will also upgrade the implementations the L2SharedBridge as well as the UpgradaeableBeacon for these tokens. + +## Stage 2. Finalization of the upgrade + +### Txs by governance (in one multicall) + +- call the `GovernanceUpgradeTimer` to check whether the deadline has passed as only after that the upgrade can be finalized. +- set the protocol version deadline for the old version to 0, i.e. ensuring that all the contracts with the old version wont be able to commit any new batches. +- upgrade the old contracts to the new implementation. +- set the correct new chain creation params, upgrade the old contracts to the new one + +### Txs by anyone + +After the governance has finalized the upgrade above, anyone can do the following transactions to finalize the upgrade: + +For each chainId: + +- `Bridgehub.setLegacyBaseTokenAssetId` +- `Bridgehub.setLegacyChainAddress` + +For each token: + +- register token inside the L1NTV + +For each chain/token pair: + +- update chain balances from shared bridge for L1NTV + +The exact way these functions will be executed is out of scope of this document. It can be done via a trivial multicall. + +### Impact + +The ecosystem has been fully transformed to the new version. However, the gateway chain is not yet ready. + +All the chains should start returning the address of the `L1AssetRouter` as the address of the L1SharedBridge in the API. + +## Stage 3. Deploying of Gateway + +### Txs by governance (sequentially, potentially different txs) + +1. Call `Bridgehub.createNewChain` with the data for gateway. +2. It will have to register it via `bridgehub.registerSettlementLayer` +3. It will have to do the L1→L2 transactions to register the CTM on Gateway as the valid CTM inside the L2Bridgehub as well as to set the CTM inside Gateway as the asset handler for the chains. + +Note, that steps (2) and (3) can be executed before this CTM has been ever deployed. These are expected to be deployed via [c](../../../l1-contracts/contracts/state-transition/chain-deps/GatewayCTMDeployer.sol) that itself should be deployed deployed via Create2Factory. So the address of the CTM can be precomputed. + +In case anyone will try to migrate their chain on top of gateway while CTM is not yet deployed, the migration will fail due to the calls to this CTM failing (standard Solidity checks for non-empty code size when doing high level calls). + +### Txs by anyone + +Anyone with funds on Gateway can deploy the `GatewayCTMDeployer` and it will deploy inside its constructor the CTM described from above. + +It can not be done as part of the governance transactions from above since it requires pre-publishing CTM-specific bytecodes. + +## Security notes + +### Importance of preventing new batches being committed with the old version + +The new `L1AssetRouter` is not compatible with chains that do not support the new protocol version as they do not have `L2AssetRouter` deployed. Doing bridging to such new chains will lead to funds being lost without recovery (since formally the L1->L2 transaction won't fail as it is just a call to an empty address). + +This is why it is crucial that on step (2) we revoke the ability for outdated chains to push new batches as those might've been spawned using the `L1AssetRouter`. + +### Impact of malicious ecosystem admin + +In theory, the ecosystem admin can front-run this deployment and set potentially bad values for the Gateway chain. It will just mean that the first transaction will fail. Due to sequential dependencies between txs, the rest won’t be executed and so the malicious chain wont be whitelisted as the settlement layer. + +While unlikely, in this case the Governance will have to re-do the voting for the gateway chain, including voting to remove the admin rights from the malicious admin. diff --git a/gas-bound-caller/package.json b/gas-bound-caller/package.json index 96593e37a..1b144a8ff 100644 --- a/gas-bound-caller/package.json +++ b/gas-bound-caller/package.json @@ -57,7 +57,7 @@ "test-node": "hardhat node-zksync --tag v0.0.1-vm1.5.0", "check-canonical-bytecode": "ts-node ./scripts/check-canonical-bytecode.ts", "verify": "hardhat run scripts/verify.ts", - "deploy-on-hyperchain": "ts-node ./scripts/deploy-on-hyperchain.ts", + "deploy-on-zk-chain": "ts-node ./scripts/deploy-on-zk-chain.ts", "deploy-on-localhost": "hardhat deploy --network localhost" } } diff --git a/gas-bound-caller/scripts/deploy-on-hyperchain.ts b/gas-bound-caller/scripts/deploy-on-hyperchain.ts index 35d013fd7..228524de4 100644 --- a/gas-bound-caller/scripts/deploy-on-hyperchain.ts +++ b/gas-bound-caller/scripts/deploy-on-hyperchain.ts @@ -44,8 +44,8 @@ async function main() { program .version("0.1.0") - .name("Deploy on hyperchain") - .description("Deploys the GasBoundCaller on a predetermined Hyperchain network") + .name("Deploy on ZK chain") + .description("Deploys the GasBoundCaller on a predetermined ZK chain network") .option("--private-key ") .option("--l2Rpc ") .action(async (cmd) => { diff --git a/high-level-design-bridging.png b/high-level-design-bridging.png new file mode 100644 index 000000000..a8f8d8f49 Binary files /dev/null and b/high-level-design-bridging.png differ diff --git a/l1-contracts/.env b/l1-contracts/.env index 10bbdf102..4f40c9e49 100644 --- a/l1-contracts/.env +++ b/l1-contracts/.env @@ -24,8 +24,12 @@ CONTRACTS_TRANSPARENT_PROXY_ADMIN_ADDR=0x000000000000000000000000000000000000000 CONTRACTS_GOVERNANCE_ADDR=0x0000000000000000000000000000000000000000 CONTRACTS_L1_ERC20_BRIDGE_IMPL_ADDR=0x0000000000000000000000000000000000000000 CONTRACTS_L1_ERC20_BRIDGE_PROXY_ADDR=0x0000000000000000000000000000000000000000 +CONTRACTS_L1_NULLIFIER_IMPL_ADDR=0x0000000000000000000000000000000000000000 +CONTRACTS_L1_NULLIFIER_PROXY_ADDR=0x0000000000000000000000000000000000000000 CONTRACTS_L1_SHARED_BRIDGE_IMPL_ADDR=0x0000000000000000000000000000000000000000 CONTRACTS_L1_SHARED_BRIDGE_PROXY_ADDR=0x0000000000000000000000000000000000000000 +CONTRACTS_L1_BRIDGED_STANDARD_ERC20_IMPL_ADDR=0x0000000000000000000000000000000000000000 +CONTRACTS_L1_BRIDGED_TOKEN_BEACON_ADDR=0x0000000000000000000000000000000000000000 CONTRACTS_L1_ALLOW_LIST_ADDR=0x0000000000000000000000000000000000000000 CONTRACTS_CREATE2_FACTORY_ADDR=0x0000000000000000000000000000000000000000 CONTRACTS_VALIDATOR_TIMELOCK_ADDR=0x0000000000000000000000000000000000000000 @@ -33,4 +37,15 @@ CONTRACTS_VALIDATOR_TIMELOCK_EXECUTION_DELAY=0 ETH_SENDER_SENDER_OPERATOR_COMMIT_ETH_ADDR=0x0000000000000000000000000000000000000000 ETH_SENDER_SENDER_OPERATOR_BLOBS_ETH_ADDR=0x0000000000000000000000000000000000000001 CONTRACTS_SHARED_BRIDGE_UPGRADE_STORAGE_SWITCH=0 -CONTRACTS_MAX_NUMBER_OF_HYPERCHAINS=100 \ No newline at end of file +CONTRACTS_MAX_NUMBER_OF_ZK_CHAINS=100 +L1_CONFIG=/script-config/config-deploy-l1.toml +L2_CONFIG=/script-config/config-deploy-l2-contracts.toml +L1_OUTPUT=/script-out/output-deploy-l1.toml +L2_CONFIG=/script-config/config-deploy-l2-contracts.toml +TOKENS_CONFIG=/script-config/config-deploy-erc20.toml +ZK_TOKEN_CONFIG=/script-config/config-deploy-zk.toml +ZK_TOKEN_OUTPUT=/script-out/output-deploy-zk-token.toml +ZK_CHAIN_CONFIG=/script-config/register-zk-chain.toml +ZK_CHAIN_OUTPUT=/script-out/output-deploy-zk-chain-era.toml +FORCE_DEPLOYMENTS_CONFIG=/script-config/generate-force-deployments-data.toml +GATEWAY_PREPARATION_L1_CONFIG=/script-config/gateway-preparation-l1.toml diff --git a/l1-contracts/contracts/bridge/BridgeHelper.sol b/l1-contracts/contracts/bridge/BridgeHelper.sol new file mode 100644 index 000000000..9b2c8a6b9 --- /dev/null +++ b/l1-contracts/contracts/bridge/BridgeHelper.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IERC20Metadata} from "@openzeppelin/contracts-v4/token/ERC20/extensions/IERC20Metadata.sol"; +import {ETH_TOKEN_ADDRESS} from "../common/Config.sol"; +import {DataEncoding} from "../common/libraries/DataEncoding.sol"; + +/** + * @author Matter Labs + * @custom:security-contact security@matterlabs.dev + * @notice Helper library for working with native tokens on both L1 and L2. + */ +library BridgeHelper { + /// @dev Receives and parses (name, symbol, decimals) from the token contract + function getERC20Getters(address _token, uint256 _originChainId) internal view returns (bytes memory) { + bytes memory name; + bytes memory symbol; + bytes memory decimals; + if (_token == ETH_TOKEN_ADDRESS) { + // when depositing eth to a non-eth based chain it is an ERC20 + name = abi.encode("Ether"); + symbol = abi.encode("ETH"); + decimals = abi.encode(uint8(18)); + } else { + bool success; + /// note this also works on the L2 for the base token. + (success, name) = _token.staticcall(abi.encodeCall(IERC20Metadata.name, ())); + if (!success) { + // We ignore the revert data + name = hex""; + } + (success, symbol) = _token.staticcall(abi.encodeCall(IERC20Metadata.symbol, ())); + if (!success) { + // We ignore the revert data + symbol = hex""; + } + (success, decimals) = _token.staticcall(abi.encodeCall(IERC20Metadata.decimals, ())); + if (!success) { + // We ignore the revert data + decimals = hex""; + } + } + return + DataEncoding.encodeTokenData({_chainId: _originChainId, _name: name, _symbol: symbol, _decimals: decimals}); + } +} diff --git a/l2-contracts/contracts/bridge/L2StandardERC20.sol b/l1-contracts/contracts/bridge/BridgedStandardERC20.sol similarity index 74% rename from l2-contracts/contracts/bridge/L2StandardERC20.sol rename to l1-contracts/contracts/bridge/BridgedStandardERC20.sol index 90db45814..4d5a82566 100644 --- a/l2-contracts/contracts/bridge/L2StandardERC20.sol +++ b/l1-contracts/contracts/bridge/BridgedStandardERC20.sol @@ -6,14 +6,17 @@ import {ERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/tok import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; import {ERC1967Upgrade} from "@openzeppelin/contracts-v4/proxy/ERC1967/ERC1967Upgrade.sol"; -import {IL2StandardToken} from "./interfaces/IL2StandardToken.sol"; -import {ZeroAddress, Unauthorized, NonSequentialVersion} from "../errors/L2ContractErrors.sol"; +import {IBridgedStandardToken} from "./interfaces/IBridgedStandardToken.sol"; +import {Unauthorized, NonSequentialVersion, ZeroAddress} from "../common/L1ContractErrors.sol"; +import {L2_NATIVE_TOKEN_VAULT_ADDR} from "../common/L2ContractAddresses.sol"; +import {DataEncoding} from "../common/libraries/DataEncoding.sol"; +import {INativeTokenVault} from "../bridge/ntv/INativeTokenVault.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev /// @notice The ERC20 token implementation, that is used in the "default" ERC20 bridge. Note, that it does not /// support any custom token logic, i.e. rebase tokens' functionality is not supported. -contract L2StandardERC20 is ERC20PermitUpgradeable, IL2StandardToken, ERC1967Upgrade { +contract BridgedStandardERC20 is ERC20PermitUpgradeable, IBridgedStandardToken, ERC1967Upgrade { /// @dev Describes whether there is a specific getter in the token. /// @notice Used to explicitly separate which getters the token has and which it does not. /// @notice Different tokens in L1 can implement or not implement getter function as `name`/`symbol`/`decimals`, @@ -31,11 +34,45 @@ contract L2StandardERC20 is ERC20PermitUpgradeable, IL2StandardToken, ERC1967Upg /// @notice OpenZeppelin token represents `name` and `symbol` as storage variables and `decimals` as constant. uint8 private decimals_; + /// @notice The l2Bridge now is deprecated, use the L2AssetRouter and L2NativeTokenVault instead. /// @dev Address of the L2 bridge that is used as trustee who can mint/burn tokens address public override l2Bridge; - /// @dev Address of the L1 token that can be deposited to mint this L2 token - address public override l1Address; + /// @dev Address of the token on its origin chain that can be deposited to mint this bridged token + address public override originToken; + + /// @dev Address of the native token vault that is used as trustee who can mint/burn tokens + address public nativeTokenVault; + + /// @dev The assetId of the token. + bytes32 public assetId; + + /// @dev This also sets the native token vault to the default value if it is not set. + /// It is not set only on the L2s for legacy tokens. + modifier onlyNTV() { + address ntv = nativeTokenVault; + if (ntv == address(0)) { + ntv = L2_NATIVE_TOKEN_VAULT_ADDR; + nativeTokenVault = L2_NATIVE_TOKEN_VAULT_ADDR; + assetId = DataEncoding.encodeNTVAssetId( + INativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR).L1_CHAIN_ID(), + originToken + ); + } + if (msg.sender != ntv) { + revert Unauthorized(msg.sender); + } + _; + } + + modifier onlyNextVersion(uint8 _version) { + // The version should be incremented by 1. Otherwise, the governor risks disabling + // future reinitialization of the token by providing too large a version. + if (_version != _getInitializedVersion() + 1) { + revert NonSequentialVersion(); + } + _; + } /// @dev Contract is expected to be used as proxy implementation. constructor() { @@ -45,22 +82,25 @@ contract L2StandardERC20 is ERC20PermitUpgradeable, IL2StandardToken, ERC1967Upg /// @notice Initializes a contract token for later use. Expected to be used in the proxy. /// @dev Stores the L1 address of the bridge and set `name`/`symbol`/`decimals` getters that L1 token has. - /// @param _l1Address Address of the L1 token that can be deposited to mint this L2 token + /// @param _assetId The assetId of the token. + /// @param _originToken Address of the origin token that can be deposited to mint this bridged token /// @param _data The additional data that the L1 bridge provide for initialization. /// In this case, it is packed `name`/`symbol`/`decimals` of the L1 token. - function bridgeInitialize(address _l1Address, bytes calldata _data) external initializer { - if (_l1Address == address(0)) { + function bridgeInitialize(bytes32 _assetId, address _originToken, bytes calldata _data) external initializer { + if (_originToken == address(0)) { revert ZeroAddress(); } - l1Address = _l1Address; + originToken = _originToken; + assetId = _assetId; - l2Bridge = msg.sender; + nativeTokenVault = msg.sender; + bytes memory nameBytes; + bytes memory symbolBytes; + bytes memory decimalsBytes; // We parse the data exactly as they were created on the L1 bridge - (bytes memory nameBytes, bytes memory symbolBytes, bytes memory decimalsBytes) = abi.decode( - _data, - (bytes, bytes, bytes) - ); + // slither-disable-next-line unused-return + (, nameBytes, symbolBytes, decimalsBytes) = DataEncoding.decodeTokenData(_data); ERC20Getters memory getters; string memory decodedName; @@ -101,7 +141,7 @@ contract L2StandardERC20 is ERC20PermitUpgradeable, IL2StandardToken, ERC1967Upg } availableGetters = getters; - emit BridgeInitialize(_l1Address, decodedName, decodedSymbol, decimals_); + emit BridgeInitialize(_originToken, decodedName, decodedSymbol, decimals_); } /// @notice A method to be called by the governor to update the token's metadata. @@ -128,30 +168,14 @@ contract L2StandardERC20 is ERC20PermitUpgradeable, IL2StandardToken, ERC1967Upg __ERC20Permit_init(_newName); availableGetters = _availableGetters; - emit BridgeInitialize(l1Address, _newName, _newSymbol, decimals_); - } - - modifier onlyBridge() { - if (msg.sender != l2Bridge) { - revert Unauthorized(msg.sender); - } - _; - } - - modifier onlyNextVersion(uint8 _version) { - // The version should be incremented by 1. Otherwise, the governor risks disabling - // future reinitialization of the token by providing too large a version. - if (_version != _getInitializedVersion() + 1) { - revert NonSequentialVersion(); - } - _; + emit BridgeInitialize(originToken, _newName, _newSymbol, decimals_); } /// @dev Mint tokens to a given account. /// @param _to The account that will receive the created tokens. /// @param _amount The amount that will be created. /// @notice Should be called by bridge after depositing tokens from L1. - function bridgeMint(address _to, uint256 _amount) external override onlyBridge { + function bridgeMint(address _to, uint256 _amount) external override onlyNTV { _mint(_to, _amount); emit BridgeMint(_to, _amount); } @@ -160,11 +184,21 @@ contract L2StandardERC20 is ERC20PermitUpgradeable, IL2StandardToken, ERC1967Upg /// @param _from The account from which tokens will be burned. /// @param _amount The amount that will be burned. /// @notice Should be called by bridge before withdrawing tokens to L1. - function bridgeBurn(address _from, uint256 _amount) external override onlyBridge { + function bridgeBurn(address _from, uint256 _amount) external override onlyNTV { _burn(_from, _amount); emit BridgeBurn(_from, _amount); } + /// @dev External function to decode a string from bytes. + function decodeString(bytes calldata _input) external pure returns (string memory result) { + (result) = abi.decode(_input, (string)); + } + + /// @dev External function to decode a uint8 from bytes. + function decodeUint8(bytes calldata _input) external pure returns (uint8 result) { + (result) = abi.decode(_input, (uint8)); + } + function name() public view override returns (string memory) { // If method is not available, behave like a token that does not implement this method - revert on call. // solhint-disable-next-line reason-string, gas-custom-errors @@ -186,13 +220,13 @@ contract L2StandardERC20 is ERC20PermitUpgradeable, IL2StandardToken, ERC1967Upg return decimals_; } - /// @dev External function to decode a string from bytes. - function decodeString(bytes calldata _input) external pure returns (string memory result) { - (result) = abi.decode(_input, (string)); - } + /*////////////////////////////////////////////////////////////// + LEGACY FUNCTIONS + //////////////////////////////////////////////////////////////*/ - /// @dev External function to decode a uint8 from bytes. - function decodeUint8(bytes calldata _input) external pure returns (uint8 result) { - (result) = abi.decode(_input, (uint8)); + /// @notice Returns the address of the token on its native chain. + /// Legacy for the l2 bridge. + function l1Address() public view override returns (address) { + return originToken; } } diff --git a/l1-contracts/contracts/bridge/L1BridgeContractErrors.sol b/l1-contracts/contracts/bridge/L1BridgeContractErrors.sol new file mode 100644 index 000000000..d72cf85a2 --- /dev/null +++ b/l1-contracts/contracts/bridge/L1BridgeContractErrors.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.21; + +// 0x6d963f88 +error EthTransferFailed(); + +// 0x1c55230b +error NativeTokenVaultAlreadySet(); + +// 0x61cdb17e +error WrongMsgLength(uint256 expected, uint256 length); + +// 0xe4742c42 +error ZeroAmountToTransfer(); + +// 0xfeda3bf8 +error WrongAmountTransferred(uint256 balance, uint256 nullifierChainBalance); + +// 0x066f53b1 +error EmptyToken(); + +// 0x0fef9068 +error ClaimFailedDepositFailed(); + +// 0x636c90db +error WrongL2Sender(address providedL2Sender); + +// 0xb4aeddbc +error WrongCounterpart(); diff --git a/l1-contracts/contracts/bridge/L1ERC20Bridge.sol b/l1-contracts/contracts/bridge/L1ERC20Bridge.sol index ccd4a2a88..bb3f6dd56 100644 --- a/l1-contracts/contracts/bridge/L1ERC20Bridge.sol +++ b/l1-contracts/contracts/bridge/L1ERC20Bridge.sol @@ -6,23 +6,35 @@ import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; -import {IL1SharedBridge} from "./interfaces/IL1SharedBridge.sol"; +import {IL1Nullifier, FinalizeL1DepositParams} from "./interfaces/IL1Nullifier.sol"; +import {IL1NativeTokenVault} from "./ntv/IL1NativeTokenVault.sol"; +import {IL1AssetRouter} from "./asset-router/IL1AssetRouter.sol"; import {L2ContractHelper} from "../common/libraries/L2ContractHelper.sol"; import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; -import {Unauthorized, EmptyDeposit, TokensWithFeesNotSupported, WithdrawalAlreadyFinalized} from "../common/L1ContractErrors.sol"; +import {AssetRouterAllowanceNotZero, EmptyDeposit, WithdrawalAlreadyFinalized, TokensWithFeesNotSupported, ETHDepositNotSupported} from "../common/L1ContractErrors.sol"; +import {ETH_TOKEN_ADDRESS} from "../common/Config.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -/// @notice Smart contract that allows depositing ERC20 tokens from Ethereum to hyperchains +/// @notice Smart contract that allows depositing ERC20 tokens from Ethereum to ZK chains /// @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 The shared bridge that is now used for all bridging, replacing the legacy contract. - IL1SharedBridge public immutable override SHARED_BRIDGE; + IL1Nullifier public immutable override L1_NULLIFIER; + + /// @dev The asset router, which holds deposited tokens. + IL1AssetRouter public immutable override L1_ASSET_ROUTER; + + /// @dev The native token vault, which holds deposited tokens. + IL1NativeTokenVault public immutable override L1_NATIVE_TOKEN_VAULT; + + /// @dev The chainId of Era + uint256 public immutable ERA_CHAIN_ID; /// @dev A mapping L2 batch number => message number => flag. /// @dev Used to indicate that L2 -> L1 message was already processed for ZKsync Era withdrawals. @@ -58,34 +70,21 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { /// @dev Contract is expected to be used as proxy implementation. /// @dev Initialize the implementation to prevent Parity hack. - constructor(IL1SharedBridge _sharedBridge) reentrancyGuardInitializer { - SHARED_BRIDGE = _sharedBridge; + constructor( + IL1Nullifier _nullifier, + IL1AssetRouter _assetRouter, + IL1NativeTokenVault _nativeTokenVault, + uint256 _eraChainId + ) reentrancyGuardInitializer { + L1_NULLIFIER = _nullifier; + L1_ASSET_ROUTER = _assetRouter; + L1_NATIVE_TOKEN_VAULT = _nativeTokenVault; + ERA_CHAIN_ID = _eraChainId; } /// @dev Initializes the reentrancy guard. Expected to be used in the proxy. function initialize() external reentrancyGuardInitializer {} - /// @dev transfer token to shared bridge as part of upgrade - function transferTokenToSharedBridge(address _token) external { - if (msg.sender != address(SHARED_BRIDGE)) { - revert Unauthorized(msg.sender); - } - uint256 amount = IERC20(_token).balanceOf(address(this)); - IERC20(_token).safeTransfer(address(SHARED_BRIDGE), amount); - } - - /*////////////////////////////////////////////////////////////// - ERA LEGACY GETTERS - //////////////////////////////////////////////////////////////*/ - - /// @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))); - - return L2ContractHelper.computeCreate2Address(l2Bridge, salt, l2TokenProxyBytecodeHash, constructorInputHash); - } - /*////////////////////////////////////////////////////////////// ERA LEGACY FUNCTIONS //////////////////////////////////////////////////////////////*/ @@ -119,6 +118,36 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { }); } + /// @notice Finalize the withdrawal and release funds + /// @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 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes calldata _message, + bytes32[] calldata _merkleProof + ) external nonReentrant { + if (isWithdrawalFinalized[_l2BatchNumber][_l2MessageIndex]) { + revert WithdrawalAlreadyFinalized(); + } + // We don't need to set finalizeWithdrawal here, as we set it in the shared bridge + + FinalizeL1DepositParams memory finalizeWithdrawalParams = FinalizeL1DepositParams({ + chainId: ERA_CHAIN_ID, + l2BatchNumber: _l2BatchNumber, + l2MessageIndex: _l2MessageIndex, + l2Sender: L1_NULLIFIER.l2BridgeAddress(ERA_CHAIN_ID), + l2TxNumberInBatch: _l2TxNumberInBatch, + message: _message, + merkleProof: _merkleProof + }); + L1_NULLIFIER.finalizeDeposit(finalizeWithdrawalParams); + } + /// @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 @@ -152,18 +181,21 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { uint256 _l2TxGasPerPubdataByte, address _refundRecipient ) public payable nonReentrant returns (bytes32 l2TxHash) { - // empty deposit if (_amount == 0) { + // empty deposit amount revert EmptyDeposit(); } - uint256 amount = _depositFundsToSharedBridge(msg.sender, IERC20(_l1Token), _amount); - // The token has non-standard transfer logic + if (_l1Token == ETH_TOKEN_ADDRESS) { + revert ETHDepositNotSupported(); + } + uint256 amount = _approveFundsToAssetRouter(msg.sender, IERC20(_l1Token), _amount); if (amount != _amount) { + // The token has non-standard transfer logic revert TokensWithFeesNotSupported(); } - l2TxHash = SHARED_BRIDGE.depositLegacyErc20Bridge{value: msg.value}({ - _msgSender: msg.sender, + l2TxHash = L1_ASSET_ROUTER.depositLegacyErc20Bridge{value: msg.value}({ + _originalCaller: msg.sender, _l2Receiver: _l2Receiver, _l1Token: _l1Token, _amount: _amount, @@ -171,6 +203,10 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { _l2TxGasPerPubdataByte: _l2TxGasPerPubdataByte, _refundRecipient: _refundRecipient }); + // Ensuring that all the funds that were locked into this bridge were spent by the asset router / native token vault. + if (IERC20(_l1Token).allowance(address(this), address(L1_ASSET_ROUTER)) != 0) { + revert AssetRouterAllowanceNotZero(); + } depositAmount[msg.sender][_l1Token][l2TxHash] = _amount; emit DepositInitiated({ l2DepositTxHash: l2TxHash, @@ -181,12 +217,17 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { }); } - /// @dev Transfers tokens from the depositor address to the shared bridge address. + /*////////////////////////////////////////////////////////////// + ERA LEGACY FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @dev Transfers tokens from the depositor address to the native token vault 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(SHARED_BRIDGE)); - _token.safeTransferFrom(_from, address(SHARED_BRIDGE), _amount); - uint256 balanceAfter = _token.balanceOf(address(SHARED_BRIDGE)); + function _approveFundsToAssetRouter(address _from, IERC20 _token, uint256 _amount) internal returns (uint256) { + uint256 balanceBefore = _token.balanceOf(address(this)); + _token.safeTransferFrom(_from, address(this), _amount); + _token.forceApprove(address(L1_ASSET_ROUTER), _amount); + uint256 balanceAfter = _token.balanceOf(address(this)); return balanceAfter - balanceBefore; } @@ -215,7 +256,7 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { } delete depositAmount[_depositSender][_l1Token][_l2TxHash]; - SHARED_BRIDGE.claimFailedDepositLegacyErc20Bridge({ + L1_NULLIFIER.claimFailedDepositLegacyErc20Bridge({ _depositSender: _depositSender, _l1Token: _l1Token, _amount: amount, @@ -228,31 +269,14 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { emit ClaimedFailedDeposit(_depositSender, _l1Token, amount); } - /// @notice Finalize the withdrawal and release funds - /// @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 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes calldata _message, - bytes32[] calldata _merkleProof - ) external nonReentrant { - if (isWithdrawalFinalized[_l2BatchNumber][_l2MessageIndex]) { - revert WithdrawalAlreadyFinalized(); - } - // We don't need to set finalizeWithdrawal here, as we set it in the shared bridge + /*////////////////////////////////////////////////////////////// + ERA LEGACY GETTERS + //////////////////////////////////////////////////////////////*/ - (address l1Receiver, address l1Token, uint256 amount) = SHARED_BRIDGE.finalizeWithdrawalLegacyErc20Bridge({ - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _message: _message, - _merkleProof: _merkleProof - }); - emit WithdrawalFinalized(l1Receiver, l1Token, amount); + /// @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))); + return L2ContractHelper.computeCreate2Address(l2Bridge, salt, l2TokenProxyBytecodeHash, constructorInputHash); } } diff --git a/l1-contracts/contracts/bridge/L1Nullifier.sol b/l1-contracts/contracts/bridge/L1Nullifier.sol new file mode 100644 index 000000000..3eb7d9c5e --- /dev/null +++ b/l1-contracts/contracts/bridge/L1Nullifier.sol @@ -0,0 +1,779 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; +import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/security/PausableUpgradeable.sol"; + +import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +import {NEW_ENCODING_VERSION, LEGACY_ENCODING_VERSION} from "./asset-router/IAssetRouterBase.sol"; +import {IL1NativeTokenVault} from "./ntv/IL1NativeTokenVault.sol"; + +import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; +import {IL1AssetRouter} from "./asset-router/IL1AssetRouter.sol"; +import {IAssetRouterBase} from "./asset-router/IAssetRouterBase.sol"; + +import {IL1Nullifier, FinalizeL1DepositParams} from "./interfaces/IL1Nullifier.sol"; + +import {IGetters} from "../state-transition/chain-interfaces/IGetters.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 {ETH_TOKEN_ADDRESS} from "../common/Config.sol"; +import {DataEncoding} from "../common/libraries/DataEncoding.sol"; + +import {IBridgehub} from "../bridgehub/IBridgehub.sol"; +import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, L2_ASSET_ROUTER_ADDR} from "../common/L2ContractAddresses.sol"; +import {DataEncoding} from "../common/libraries/DataEncoding.sol"; +import {LegacyMethodForNonL1Token, LegacyBridgeNotSet, Unauthorized, SharedBridgeKey, DepositExists, AddressAlreadySet, InvalidProof, DepositDoesNotExist, SharedBridgeValueNotSet, WithdrawalAlreadyFinalized, L2WithdrawalMessageWrongLength, InvalidSelector, SharedBridgeValueNotSet, ZeroAddress} from "../common/L1ContractErrors.sol"; +import {WrongL2Sender, NativeTokenVaultAlreadySet, EthTransferFailed, WrongMsgLength} from "./L1BridgeContractErrors.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @dev Bridges assets between L1 and ZK chain, supporting both ETH and ERC20 tokens. +/// @dev Designed for use with a proxy for upgradability. +contract L1Nullifier is IL1Nullifier, ReentrancyGuard, Ownable2StepUpgradeable, PausableUpgradeable { + using SafeERC20 for IERC20; + + /// @dev Bridgehub smart contract that is used to operate with L2 via asynchronous L2 <-> L1 communication. + IBridgehub public immutable override BRIDGE_HUB; + + /// @dev Era's chainID + uint256 internal immutable ERA_CHAIN_ID; + + /// @dev The address of ZKsync Era diamond proxy contract. + address internal immutable ERA_DIAMOND_PROXY; + + /// @dev Stores the first batch number on the ZKsync Era Diamond Proxy that was settled after Diamond proxy upgrade. + /// This variable is used to differentiate between pre-upgrade and post-upgrade Eth withdrawals. Withdrawals from batches older + /// than this value are considered to have been finalized prior to the upgrade and handled separately. + uint256 internal eraPostDiamondUpgradeFirstBatch; + + /// @dev Stores the first batch number on the ZKsync Era Diamond Proxy that was settled after L1ERC20 Bridge upgrade. + /// This variable is used to differentiate between pre-upgrade and post-upgrade ERC20 withdrawals. Withdrawals from batches older + /// than this value are considered to have been finalized prior to the upgrade and handled separately. + uint256 internal eraPostLegacyBridgeUpgradeFirstBatch; + + /// @dev Stores the ZKsync Era batch number that processes the last deposit tx initiated by the legacy bridge + /// This variable (together with eraLegacyBridgeLastDepositTxNumber) is used to differentiate between pre-upgrade and post-upgrade deposits. Deposits processed in older batches + /// than this value are considered to have been processed prior to the upgrade and handled separately. + /// We use this both for Eth and erc20 token deposits, so we need to update the diamond and bridge simultaneously. + uint256 internal eraLegacyBridgeLastDepositBatch; + + /// @dev The tx number in the _eraLegacyBridgeLastDepositBatch that comes *right after* the last deposit tx initiated by the legacy bridge. + /// This variable (together with eraLegacyBridgeLastDepositBatch) is used to differentiate between pre-upgrade and post-upgrade deposits. Deposits processed in older txs + /// than this value are considered to have been processed prior to the upgrade and handled separately. + /// We use this both for Eth and erc20 token deposits, so we need to update the diamond and bridge simultaneously. + uint256 internal eraLegacyBridgeLastDepositTxNumber; + + /// @dev Legacy bridge smart contract that used to hold ERC20 tokens. + IL1ERC20Bridge public override legacyBridge; + + /// @dev A mapping chainId => bridgeProxy. Used to store the bridge proxy's address, and to see if it has been deployed yet. + // slither-disable-next-line uninitialized-state + mapping(uint256 chainId => address l2Bridge) public __DEPRECATED_l2BridgeAddress; + + /// @dev A mapping chainId => L2 deposit transaction hash => dataHash + // keccak256(abi.encode(account, tokenAddress, amount)) for legacy transfers + // keccak256(abi.encode(_originalCaller, assetId, transferData)) for new transfers + /// @dev Tracks deposit transactions to 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; + + /// @notice Deprecated. Kept for backwards compatibility. + /// @dev Indicates whether the hyperbridging is enabled for a given chain. + // slither-disable-next-line uninitialized-state + mapping(uint256 chainId => bool enabled) private __DEPRECATED_hyperbridgingEnabled; + + /// @dev Maps token balances for each chain to prevent unauthorized spending across ZK chain. + /// This serves as a security measure until hyperbridging is implemented. + /// NOTE: this function may be removed in the future, don't rely on it! + mapping(uint256 chainId => mapping(address l1Token => uint256 balance)) public __DEPRECATED_chainBalance; + + /// @dev Admin has the ability to register new chains within the shared bridge. + address public __DEPRECATED_admin; + + /// @dev The pending admin, i.e. the candidate to the admin role. + address public __DEPRECATED_pendingAdmin; + + /// @dev Address of L1 asset router. + IL1AssetRouter public l1AssetRouter; + + /// @dev Address of native token vault. + IL1NativeTokenVault public l1NativeTokenVault; + + /// @notice Checks that the message sender is the asset router.. + modifier onlyAssetRouter() { + if (msg.sender != address(l1AssetRouter)) { + revert Unauthorized(msg.sender); + } + _; + } + + /// @notice Checks that the message sender is the native token vault. + modifier onlyL1NTV() { + if (msg.sender != address(l1NativeTokenVault)) { + revert Unauthorized(msg.sender); + } + _; + } + + /// @notice Checks that the message sender is the legacy bridge. + modifier onlyLegacyBridge() { + if (msg.sender != address(legacyBridge)) { + revert Unauthorized(msg.sender); + } + _; + } + + /// @dev Contract is expected to be used as proxy implementation. + /// @dev Initialize the implementation to prevent Parity hack. + constructor(IBridgehub _bridgehub, uint256 _eraChainId, address _eraDiamondProxy) reentrancyGuardInitializer { + _disableInitializers(); + BRIDGE_HUB = _bridgehub; + ERA_CHAIN_ID = _eraChainId; + ERA_DIAMOND_PROXY = _eraDiamondProxy; + } + + /// @dev Initializes a contract bridge for later use. Expected to be used in the proxy. + /// @dev Used for testing purposes only, as the contract has been initialized on mainnet. + /// @param _owner The 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. + /// @param _eraPostDiamondUpgradeFirstBatch The first batch number on the ZKsync Era Diamond Proxy that was settled after diamond proxy upgrade. + /// @param _eraPostLegacyBridgeUpgradeFirstBatch The first batch number on the ZKsync Era Diamond Proxy that was settled after legacy bridge upgrade. + /// @param _eraLegacyBridgeLastDepositBatch The the ZKsync Era batch number that processes the last deposit tx initiated by the legacy bridge. + /// @param _eraLegacyBridgeLastDepositTxNumber The tx number in the _eraLegacyBridgeLastDepositBatch of the last deposit tx initiated by the legacy bridge. + function initialize( + address _owner, + uint256 _eraPostDiamondUpgradeFirstBatch, + uint256 _eraPostLegacyBridgeUpgradeFirstBatch, + uint256 _eraLegacyBridgeLastDepositBatch, + uint256 _eraLegacyBridgeLastDepositTxNumber + ) external reentrancyGuardInitializer initializer { + if (_owner == address(0)) { + revert ZeroAddress(); + } + _transferOwnership(_owner); + if (eraPostDiamondUpgradeFirstBatch == 0) { + eraPostDiamondUpgradeFirstBatch = _eraPostDiamondUpgradeFirstBatch; + eraPostLegacyBridgeUpgradeFirstBatch = _eraPostLegacyBridgeUpgradeFirstBatch; + eraLegacyBridgeLastDepositBatch = _eraLegacyBridgeLastDepositBatch; + eraLegacyBridgeLastDepositTxNumber = _eraLegacyBridgeLastDepositTxNumber; + } + } + + /// @notice Transfers tokens from shared bridge to native token vault. + /// @dev This function is part of the upgrade process used to transfer liquidity. + /// @param _token The address of the token to be transferred to NTV. + function transferTokenToNTV(address _token) external onlyL1NTV { + address ntvAddress = address(l1NativeTokenVault); + if (ETH_TOKEN_ADDRESS == _token) { + uint256 amount = address(this).balance; + bool callSuccess; + // Low-level assembly call, to avoid any memory copying (save gas) + assembly { + callSuccess := call(gas(), ntvAddress, amount, 0, 0, 0, 0) + } + if (!callSuccess) { + revert EthTransferFailed(); + } + } else { + IERC20(_token).safeTransfer(ntvAddress, IERC20(_token).balanceOf(address(this))); + } + } + + /// @notice Clears chain balance for specific token. + /// @dev This function is part of the upgrade process used to nullify chain balances once they are credited to NTV. + /// @param _chainId The ID of the ZK chain. + /// @param _token The address of the token which was previously deposit to shared bridge. + function nullifyChainBalanceByNTV(uint256 _chainId, address _token) external onlyL1NTV { + __DEPRECATED_chainBalance[_chainId][_token] = 0; + } + + /// @notice Legacy function used for migration, do not use! + /// @param _chainId The chain id on which the bridge is deployed. + // slither-disable-next-line uninitialized-state-variables + function l2BridgeAddress(uint256 _chainId) external view returns (address) { + // slither-disable-next-line uninitialized-state-variables + return __DEPRECATED_l2BridgeAddress[_chainId]; + } + + /// @notice Legacy function used for migration, do not use! + /// @param _chainId The chain id we want to get the balance for. + /// @param _token The address of the token. + // slither-disable-next-line uninitialized-state-variables + function chainBalance(uint256 _chainId, address _token) external view returns (uint256) { + // slither-disable-next-line uninitialized-state-variables + return __DEPRECATED_chainBalance[_chainId][_token]; + } + + /// @notice Sets the L1ERC20Bridge contract address. + /// @dev Should be called only once by the owner. + /// @param _legacyBridge The address of the legacy bridge. + function setL1Erc20Bridge(IL1ERC20Bridge _legacyBridge) external onlyOwner { + if (address(legacyBridge) != address(0)) { + revert AddressAlreadySet(address(legacyBridge)); + } + if (address(_legacyBridge) == address(0)) { + revert ZeroAddress(); + } + legacyBridge = _legacyBridge; + } + + /// @notice Sets the nativeTokenVault contract address. + /// @dev Should be called only once by the owner. + /// @param _l1NativeTokenVault The address of the native token vault. + function setL1NativeTokenVault(IL1NativeTokenVault _l1NativeTokenVault) external onlyOwner { + if (address(l1NativeTokenVault) != address(0)) { + revert NativeTokenVaultAlreadySet(); + } + if (address(_l1NativeTokenVault) == address(0)) { + revert ZeroAddress(); + } + l1NativeTokenVault = _l1NativeTokenVault; + } + + /// @notice Sets the L1 asset router contract address. + /// @dev Should be called only once by the owner. + /// @param _l1AssetRouter The address of the asset router. + function setL1AssetRouter(address _l1AssetRouter) external onlyOwner { + if (address(l1AssetRouter) != address(0)) { + revert AddressAlreadySet(address(l1AssetRouter)); + } + if (_l1AssetRouter == address(0)) { + revert ZeroAddress(); + } + l1AssetRouter = IL1AssetRouter(_l1AssetRouter); + } + + /// @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. + /// @param _chainId The chain ID of the ZK chain to which confirm the deposit. + /// @param _txDataHash The keccak256 hash of 0x01 || abi.encode(bytes32, bytes) to identify deposits. + /// @param _txHash The hash of the L1->L2 transaction to confirm the deposit. + function bridgehubConfirmL2TransactionForwarded( + uint256 _chainId, + bytes32 _txDataHash, + bytes32 _txHash + ) external override onlyAssetRouter whenNotPaused { + if (depositHappened[_chainId][_txHash] != 0x00) { + revert DepositExists(); + } + depositHappened[_chainId][_txHash] = _txDataHash; + emit BridgehubDepositFinalized(_chainId, _txDataHash, _txHash); + } + + /// @dev Calls the library `encodeTxDataHash`. Used as a wrapped for try / catch case. + /// @dev Encodes the transaction data hash using either the latest encoding standard or the legacy standard. + /// @param _encodingVersion EncodingVersion. + /// @param _originalCaller The address of the entity that initiated the deposit. + /// @param _assetId The unique identifier of the deposited L1 token. + /// @param _transferData The encoded transfer data, which includes both the deposit amount and the address of the L2 receiver. + /// @return txDataHash The resulting encoded transaction data hash. + function encodeTxDataHash( + bytes1 _encodingVersion, + address _originalCaller, + bytes32 _assetId, + bytes calldata _transferData + ) external view returns (bytes32 txDataHash) { + txDataHash = DataEncoding.encodeTxDataHash({ + _encodingVersion: _encodingVersion, + _originalCaller: _originalCaller, + _assetId: _assetId, + _nativeTokenVault: address(l1NativeTokenVault), + _transferData: _transferData + }); + } + + /// @inheritdoc IL1Nullifier + function bridgeRecoverFailedTransfer( + uint256 _chainId, + address _depositSender, + bytes32 _assetId, + bytes memory _assetData, + bytes32 _l2TxHash, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes32[] calldata _merkleProof + ) public nonReentrant { + _verifyAndClearFailedTransfer({ + _checkedInLegacyBridge: false, + _chainId: _chainId, + _depositSender: _depositSender, + _assetId: _assetId, + _assetData: _assetData, + _l2TxHash: _l2TxHash, + _l2BatchNumber: _l2BatchNumber, + _l2MessageIndex: _l2MessageIndex, + _l2TxNumberInBatch: _l2TxNumberInBatch, + _merkleProof: _merkleProof + }); + + l1AssetRouter.bridgeRecoverFailedTransfer(_chainId, _depositSender, _assetId, _assetData); + } + + /// @dev Withdraw funds from the initiated deposit, that failed when finalizing on L2. + /// @param _chainId The ZK chain id to which deposit was initiated. + /// @param _depositSender The address of the entity that initiated the deposit. + /// @param _assetId The unique identifier of the deposited L1 token. + /// @param _assetData The encoded data, which is used by the asset handler to determine L2 recipient and amount. Might include extra information. + /// @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. + /// @dev Processes claims of failed deposit, whether they originated from the legacy bridge or the current system. + function _verifyAndClearFailedTransfer( + bool _checkedInLegacyBridge, + uint256 _chainId, + address _depositSender, + bytes32 _assetId, + bytes memory _assetData, + bytes32 _l2TxHash, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes32[] calldata _merkleProof + ) internal whenNotPaused { + { + bool proofValid = BRIDGE_HUB.proveL1ToL2TransactionStatus({ + _chainId: _chainId, + _l2TxHash: _l2TxHash, + _l2BatchNumber: _l2BatchNumber, + _l2MessageIndex: _l2MessageIndex, + _l2TxNumberInBatch: _l2TxNumberInBatch, + _merkleProof: _merkleProof, + _status: TxStatus.Failure + }); + if (!proofValid) { + revert InvalidProof(); + } + } + + bool notCheckedInLegacyBridgeOrWeCanCheckDeposit; + { + // Deposits that happened before the upgrade cannot be checked here, they have to be claimed and checked in the legacyBridge + bool weCanCheckDepositHere = !_isPreSharedBridgeDepositOnEra(_chainId, _l2BatchNumber, _l2TxNumberInBatch); + // Double claims are not possible, as depositHappened is checked here for all except legacy deposits (which have to happen through the legacy bridge) + // 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]; + // Determine if the given dataHash matches the calculated legacy transaction hash. + bool isLegacyTxDataHash = _isLegacyTxDataHash(_depositSender, _assetId, _assetData, dataHash); + // If the dataHash matches the legacy transaction hash, skip the next step. + // Otherwise, perform the check using the new transaction data hash encoding. + if (!isLegacyTxDataHash) { + bytes32 txDataHash = DataEncoding.encodeTxDataHash({ + _encodingVersion: NEW_ENCODING_VERSION, + _originalCaller: _depositSender, + _assetId: _assetId, + _nativeTokenVault: address(l1NativeTokenVault), + _transferData: _assetData + }); + if (dataHash != txDataHash) { + revert DepositDoesNotExist(); + } + } + } + delete depositHappened[_chainId][_l2TxHash]; + } + + /// @notice Finalize the withdrawal and release funds. + /// @param _finalizeWithdrawalParams The structure that holds all necessary data to finalize withdrawal + /// @dev We have both the legacy finalizeWithdrawal and the new finalizeDeposit functions, + /// finalizeDeposit uses the new format. On the L2 we have finalizeDeposit with new and old formats both. + function finalizeDeposit(FinalizeL1DepositParams memory _finalizeWithdrawalParams) public { + _finalizeDeposit(_finalizeWithdrawalParams); + } + + /// @notice Internal function that handles the logic for finalizing withdrawals, supporting both the current bridge system and the legacy ERC20 bridge. + /// @param _finalizeWithdrawalParams The structure that holds all necessary data to finalize withdrawal + function _finalizeDeposit( + FinalizeL1DepositParams memory _finalizeWithdrawalParams + ) internal nonReentrant whenNotPaused { + uint256 chainId = _finalizeWithdrawalParams.chainId; + uint256 l2BatchNumber = _finalizeWithdrawalParams.l2BatchNumber; + uint256 l2MessageIndex = _finalizeWithdrawalParams.l2MessageIndex; + if (isWithdrawalFinalized[chainId][l2BatchNumber][l2MessageIndex]) { + revert WithdrawalAlreadyFinalized(); + } + isWithdrawalFinalized[chainId][l2BatchNumber][l2MessageIndex] = true; + + (bytes32 assetId, bytes memory transferData) = _verifyWithdrawal(_finalizeWithdrawalParams); + + // Handling special case for withdrawal from ZKsync Era initiated before Shared Bridge. + if (_isPreSharedBridgeEraEthWithdrawal(chainId, l2BatchNumber)) { + // Checks that the withdrawal wasn't finalized already. + bool alreadyFinalized = IGetters(ERA_DIAMOND_PROXY).isEthWithdrawalFinalized(l2BatchNumber, l2MessageIndex); + if (alreadyFinalized) { + revert WithdrawalAlreadyFinalized(); + } + } + if (_isPreSharedBridgeEraTokenWithdrawal(chainId, l2BatchNumber)) { + if (legacyBridge.isWithdrawalFinalized(l2BatchNumber, l2MessageIndex)) { + revert WithdrawalAlreadyFinalized(); + } + } + + l1AssetRouter.finalizeDeposit(chainId, assetId, transferData); + } + + /// @dev Determines if an eth 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 diamond proxy upgrade. + function _isPreSharedBridgeEraEthWithdrawal(uint256 _chainId, uint256 _l2BatchNumber) internal view returns (bool) { + if ((_chainId == ERA_CHAIN_ID) && eraPostDiamondUpgradeFirstBatch == 0) { + revert SharedBridgeValueNotSet(SharedBridgeKey.PostUpgradeFirstBatch); + } + return (_chainId == ERA_CHAIN_ID) && (_l2BatchNumber < eraPostDiamondUpgradeFirstBatch); + } + + /// @dev Determines if a token 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 Legacy Bridge upgrade. + function _isPreSharedBridgeEraTokenWithdrawal( + uint256 _chainId, + uint256 _l2BatchNumber + ) internal view returns (bool) { + if ((_chainId == ERA_CHAIN_ID) && eraPostLegacyBridgeUpgradeFirstBatch == 0) { + revert SharedBridgeValueNotSet(SharedBridgeKey.LegacyBridgeFirstBatch); + } + return (_chainId == ERA_CHAIN_ID) && (_l2BatchNumber < eraPostLegacyBridgeUpgradeFirstBatch); + } + + /// @dev Determines if the provided data for a failed deposit corresponds to a legacy failed deposit. + /// @param _depositSender The address of the entity that initiated the deposit. + /// @param _assetId The unique identifier of the deposited L1 token. + /// @param _transferData The encoded transfer data, which includes both the deposit amount and the address of the L2 receiver. + /// @param _expectedTxDataHash The nullifier data hash stored for the failed deposit. + /// @return isLegacyTxDataHash True if the transaction is legacy, false otherwise. + function _isLegacyTxDataHash( + address _depositSender, + bytes32 _assetId, + bytes memory _transferData, + bytes32 _expectedTxDataHash + ) internal view returns (bool isLegacyTxDataHash) { + try this.encodeTxDataHash(LEGACY_ENCODING_VERSION, _depositSender, _assetId, _transferData) returns ( + bytes32 txDataHash + ) { + return txDataHash == _expectedTxDataHash; + } catch { + return false; + } + } + + /// @dev Determines if a deposit 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 deposit where it was processed. + /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the deposit was processed. + /// @return Whether deposit was initiated on ZKsync Era before Shared Bridge upgrade. + function _isPreSharedBridgeDepositOnEra( + uint256 _chainId, + uint256 _l2BatchNumber, + uint256 _l2TxNumberInBatch + ) internal view returns (bool) { + if ((_chainId == ERA_CHAIN_ID) && (eraLegacyBridgeLastDepositBatch == 0)) { + revert SharedBridgeValueNotSet(SharedBridgeKey.LegacyBridgeLastDepositBatch); + } + return + (_chainId == ERA_CHAIN_ID) && + (_l2BatchNumber < eraLegacyBridgeLastDepositBatch || + (_l2TxNumberInBatch < eraLegacyBridgeLastDepositTxNumber && + _l2BatchNumber == eraLegacyBridgeLastDepositBatch)); + } + + /// @notice Verifies the validity of a withdrawal message from L2 and returns withdrawal details. + /// @param _finalizeWithdrawalParams The structure that holds all necessary data to finalize withdrawal + /// @return assetId The ID of the bridged asset. + /// @return transferData The transfer data used to finalize withdawal. + function _verifyWithdrawal( + FinalizeL1DepositParams memory _finalizeWithdrawalParams + ) internal returns (bytes32 assetId, bytes memory transferData) { + (assetId, transferData) = _parseL2WithdrawalMessage( + _finalizeWithdrawalParams.chainId, + _finalizeWithdrawalParams.message + ); + L2Message memory l2ToL1Message; + { + address l2Sender = _finalizeWithdrawalParams.l2Sender; + bool baseTokenWithdrawal = (assetId == BRIDGE_HUB.baseTokenAssetId(_finalizeWithdrawalParams.chainId)); + + bool isL2SenderCorrect = l2Sender == L2_ASSET_ROUTER_ADDR || + l2Sender == L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR || + l2Sender == __DEPRECATED_l2BridgeAddress[_finalizeWithdrawalParams.chainId]; + if (!isL2SenderCorrect) { + revert WrongL2Sender(l2Sender); + } + + l2ToL1Message = L2Message({ + txNumberInBatch: _finalizeWithdrawalParams.l2TxNumberInBatch, + sender: baseTokenWithdrawal ? L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR : l2Sender, + data: _finalizeWithdrawalParams.message + }); + } + + bool success = BRIDGE_HUB.proveL2MessageInclusion({ + _chainId: _finalizeWithdrawalParams.chainId, + _batchNumber: _finalizeWithdrawalParams.l2BatchNumber, + _index: _finalizeWithdrawalParams.l2MessageIndex, + _message: l2ToL1Message, + _proof: _finalizeWithdrawalParams.merkleProof + }); + // withdrawal wrong proof + if (!success) { + revert InvalidProof(); + } + } + + /// @notice Parses the withdrawal message and returns withdrawal details. + /// @dev Currently, 3 different encoding versions are supported: legacy mailbox withdrawal, ERC20 bridge withdrawal, + /// @dev and the latest version supported by shared bridge. Selectors are used for versioning. + /// @param _chainId The ZK chain ID. + /// @param _l2ToL1message The encoded L2 -> L1 message. + /// @return assetId The ID of the bridged asset. + /// @return transferData The transfer data used to finalize withdawal. + function _parseL2WithdrawalMessage( + uint256 _chainId, + bytes memory _l2ToL1message + ) internal returns (bytes32 assetId, bytes memory transferData) { + // Please note that there are three versions of the message: + // 1. The message that is sent from `L2BaseToken` to withdraw base token. + // 2. The message that is sent from L2 Legacy Shared Bridge to withdraw ERC20 tokens or base token. + // 3. The message that is sent from L2 Asset Router to withdraw ERC20 tokens or base token. + + uint256 amount; + address l1Receiver; + + (uint32 functionSignature, uint256 offset) = UnsafeBytes.readUint32(_l2ToL1message, 0); + if (bytes4(functionSignature) == IMailbox.finalizeEthWithdrawal.selector) { + // The data is expected to be at least 56 bytes long. + if (_l2ToL1message.length < 56) { + revert L2WithdrawalMessageWrongLength(_l2ToL1message.length); + } + // this message is a base token withdrawal + (l1Receiver, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); + // slither-disable-next-line unused-return + (amount, ) = UnsafeBytes.readUint256(_l2ToL1message, offset); + assetId = BRIDGE_HUB.baseTokenAssetId(_chainId); + transferData = DataEncoding.encodeBridgeMintData({ + _originalCaller: address(0), + _remoteReceiver: l1Receiver, + // Note, that `assetId` could belong to a token native to an L2, and so + // the logic for determining the correct origin token address will be complex. + // It is expected that this value won't be used in the NativeTokenVault and so providing + // any value is acceptable here. + _originToken: address(0), + _amount: amount, + _erc20Metadata: new bytes(0) + }); + } else if (bytes4(functionSignature) == IL1ERC20Bridge.finalizeWithdrawal.selector) { + // 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). + if (_l2ToL1message.length != 76) { + revert L2WithdrawalMessageWrongLength(_l2ToL1message.length); + } + (l1Receiver, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); + // We use the IL1ERC20Bridge for backward compatibility with old withdrawals. + address l1Token; + (l1Token, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); + // slither-disable-next-line unused-return + (amount, ) = UnsafeBytes.readUint256(_l2ToL1message, offset); + + l1NativeTokenVault.ensureTokenIsRegistered(l1Token); + assetId = DataEncoding.encodeNTVAssetId(block.chainid, l1Token); + transferData = DataEncoding.encodeBridgeMintData({ + _originalCaller: address(0), + _remoteReceiver: l1Receiver, + _originToken: l1Token, + _amount: amount, + _erc20Metadata: new bytes(0) + }); + } else if (bytes4(functionSignature) == IAssetRouterBase.finalizeDeposit.selector) { + // The data is expected to be at least 68 bytes long to contain assetId. + if (_l2ToL1message.length < 68) { + revert WrongMsgLength(68, _l2ToL1message.length); + } + // slither-disable-next-line unused-return + (, offset) = UnsafeBytes.readUint256(_l2ToL1message, offset); // originChainId, not used for L2->L1 txs + (assetId, offset) = UnsafeBytes.readBytes32(_l2ToL1message, offset); + transferData = UnsafeBytes.readRemainingBytes(_l2ToL1message, offset); + } else { + revert InvalidSelector(bytes4(functionSignature)); + } + } + + /*////////////////////////////////////////////////////////////// + SHARED BRIDGE TOKEN BRIDGING LEGACY FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @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 { + bytes32 assetId = l1NativeTokenVault.assetId(_l1Token); + bytes32 ntvAssetId = DataEncoding.encodeNTVAssetId(block.chainid, _l1Token); + if (assetId == bytes32(0)) { + assetId = ntvAssetId; + } else if (assetId != ntvAssetId) { + revert LegacyMethodForNonL1Token(); + } + + // For legacy deposits, the l2 receiver is not required to check tx data hash + // The token address does not have to be provided for this functionality either. + bytes memory assetData = DataEncoding.encodeBridgeBurnData(_amount, address(0), address(0)); + + _verifyAndClearFailedTransfer({ + _checkedInLegacyBridge: false, + _depositSender: _depositSender, + _chainId: _chainId, + _assetId: assetId, + _assetData: assetData, + _l2TxHash: _l2TxHash, + _l2BatchNumber: _l2BatchNumber, + _l2MessageIndex: _l2MessageIndex, + _l2TxNumberInBatch: _l2TxNumberInBatch, + _merkleProof: _merkleProof + }); + + l1AssetRouter.bridgeRecoverFailedTransfer({ + _chainId: _chainId, + _depositSender: _depositSender, + _assetId: assetId, + _assetData: assetData + }); + } + + /*////////////////////////////////////////////////////////////// + ERA ERC20 LEGACY FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @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 { + // For legacy deposits, the l2 receiver is not required to check tx data hash + // The token address does not have to be provided for this functionality either. + bytes memory assetData = DataEncoding.encodeBridgeBurnData(_amount, address(0), address(0)); + + /// the legacy bridge can only be used with L1 native tokens. + bytes32 assetId = DataEncoding.encodeNTVAssetId(block.chainid, _l1Token); + + _verifyAndClearFailedTransfer({ + _checkedInLegacyBridge: true, + _depositSender: _depositSender, + _chainId: ERA_CHAIN_ID, + _assetId: assetId, + _assetData: assetData, + _l2TxHash: _l2TxHash, + _l2BatchNumber: _l2BatchNumber, + _l2MessageIndex: _l2MessageIndex, + _l2TxNumberInBatch: _l2TxNumberInBatch, + _merkleProof: _merkleProof + }); + + l1AssetRouter.bridgeRecoverFailedTransfer({ + _chainId: ERA_CHAIN_ID, + _depositSender: _depositSender, + _assetId: assetId, + _assetData: assetData + }); + } + + /*////////////////////////////////////////////////////////////// + PAUSE + //////////////////////////////////////////////////////////////*/ + + /// @notice Pauses all functions marked with the `whenNotPaused` modifier. + function pause() external onlyOwner { + _pause(); + } + + /// @notice Unpauses the contract, allowing all functions marked with the `whenNotPaused` modifier to be called again. + function unpause() external onlyOwner { + _unpause(); + } + + /*////////////////////////////////////////////////////////////// + LEGACY INTERFACE + //////////////////////////////////////////////////////////////*/ + + /// @inheritdoc IL1Nullifier + function finalizeWithdrawal( + uint256 _chainId, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes calldata _message, + bytes32[] calldata _merkleProof + ) external override { + /// @dev We use a deprecated field to support L2->L1 legacy withdrawals, which were started + /// by the legacy bridge. + address legacyL2Bridge = __DEPRECATED_l2BridgeAddress[_chainId]; + if (legacyL2Bridge == address(0)) { + revert LegacyBridgeNotSet(); + } + + FinalizeL1DepositParams memory finalizeWithdrawalParams = FinalizeL1DepositParams({ + chainId: _chainId, + l2BatchNumber: _l2BatchNumber, + l2MessageIndex: _l2MessageIndex, + l2Sender: legacyL2Bridge, + l2TxNumberInBatch: _l2TxNumberInBatch, + message: _message, + merkleProof: _merkleProof + }); + finalizeDeposit(finalizeWithdrawalParams); + } +} diff --git a/l1-contracts/contracts/bridge/L1SharedBridge.sol b/l1-contracts/contracts/bridge/L1SharedBridge.sol deleted file mode 100644 index eaddcccc7..000000000 --- a/l1-contracts/contracts/bridge/L1SharedBridge.sol +++ /dev/null @@ -1,969 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; -import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/security/PausableUpgradeable.sol"; - -import {IERC20Metadata} from "@openzeppelin/contracts-v4/token/ERC20/extensions/IERC20Metadata.sol"; -import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts-v4/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 {ETH_TOKEN_ADDRESS, 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"; -import {Unauthorized, ZeroAddress, SharedBridgeValueAlreadySet, SharedBridgeKey, NoFundsTransferred, ZeroBalance, ValueMismatch, TokensWithFeesNotSupported, NonEmptyMsgValue, L2BridgeNotSet, TokenNotSupported, DepositIncorrectAmount, EmptyDeposit, DepositExists, AddressAlreadyUsed, InvalidProof, DepositDoesNotExist, InsufficientChainBalance, SharedBridgeValueNotSet, WithdrawalAlreadyFinalized, WithdrawFailed, L2WithdrawalMessageWrongLength, InvalidSelector, SharedBridgeBalanceMismatch, SharedBridgeValueNotSet} from "../common/L1ContractErrors.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, Ownable2StepUpgradeable, PausableUpgradeable { - using SafeERC20 for IERC20; - - /// @dev The address of the WETH token on L1. - address public immutable override L1_WETH_TOKEN; - - /// @dev Bridgehub smart contract that is used to operate with L2 via asynchronous L2 <-> L1 communication. - IBridgehub public immutable override BRIDGE_HUB; - - /// @dev Era's chainID - uint256 internal immutable ERA_CHAIN_ID; - - /// @dev The address of ZKsync Era diamond proxy contract. - address internal immutable ERA_DIAMOND_PROXY; - - /// @dev Stores the first batch number on the ZKsync Era Diamond Proxy that was settled after Diamond proxy upgrade. - /// This variable is used to differentiate between pre-upgrade and post-upgrade Eth withdrawals. Withdrawals from batches older - /// than this value are considered to have been finalized prior to the upgrade and handled separately. - uint256 internal eraPostDiamondUpgradeFirstBatch; - - /// @dev Stores the first batch number on the ZKsync Era Diamond Proxy that was settled after L1ERC20 Bridge upgrade. - /// This variable is used to differentiate between pre-upgrade and post-upgrade ERC20 withdrawals. Withdrawals from batches older - /// than this value are considered to have been finalized prior to the upgrade and handled separately. - uint256 internal eraPostLegacyBridgeUpgradeFirstBatch; - - /// @dev Stores the ZKsync Era batch number that processes the last deposit tx initiated by the legacy bridge - /// This variable (together with eraLegacyBridgeLastDepositTxNumber) is used to differentiate between pre-upgrade and post-upgrade deposits. Deposits processed in older batches - /// than this value are considered to have been processed prior to the upgrade and handled separately. - /// We use this both for Eth and erc20 token deposits, so we need to update the diamond and bridge simultaneously. - uint256 internal eraLegacyBridgeLastDepositBatch; - - /// @dev The tx number in the _eraLegacyBridgeLastDepositBatch of the last deposit tx initiated by the legacy bridge - /// This variable (together with eraLegacyBridgeLastDepositBatch) is used to differentiate between pre-upgrade and post-upgrade deposits. Deposits processed in older txs - /// than this value are considered to have been processed prior to the upgrade and handled separately. - /// We use this both for Eth and erc20 token deposits, so we need to update the diamond and bridge simultaneously. - uint256 internal eraLegacyBridgeLastDepositTxNumber; - - /// @dev Legacy bridge smart contract that used to hold ERC20 tokens. - IL1ERC20Bridge public override legacyBridge; - - /// @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. - // slither-disable-next-line uninitialized-state - 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. - /// NOTE: this function may be removed in the future, don't rely on it! - mapping(uint256 chainId => mapping(address l1Token => uint256 balance)) public chainBalance; - - /// @dev Admin has the ability to register new chains within the shared bridge. - address public admin; - - /// @dev The pending admin, i.e. the candidate to the admin role. - address public pendingAdmin; - - /// @notice Checks that the message sender is the bridgehub. - modifier onlyBridgehub() { - if (msg.sender != address(BRIDGE_HUB)) { - revert Unauthorized(msg.sender); - } - _; - } - - /// @notice Checks that the message sender is the bridgehub or ZKsync Era Diamond Proxy. - modifier onlyBridgehubOrEra(uint256 _chainId) { - if (msg.sender != address(BRIDGE_HUB) && (_chainId != ERA_CHAIN_ID || msg.sender != ERA_DIAMOND_PROXY)) { - revert Unauthorized(msg.sender); - } - _; - } - - /// @notice Checks that the message sender is the legacy bridge. - modifier onlyLegacyBridge() { - if (msg.sender != address(legacyBridge)) { - revert Unauthorized(msg.sender); - } - _; - } - - /// @notice Checks that the message sender is the shared bridge itself. - modifier onlySelf() { - if (msg.sender != address(this)) { - revert Unauthorized(msg.sender); - } - _; - } - - /// @notice Checks that the message sender is either the owner or admin. - modifier onlyOwnerOrAdmin() { - require(msg.sender == owner() || msg.sender == admin, "ShB not owner or admin"); - _; - } - - /// @dev Contract is expected to be used as proxy implementation. - /// @dev Initialize the implementation to prevent Parity hack. - constructor( - address _l1WethAddress, - IBridgehub _bridgehub, - uint256 _eraChainId, - address _eraDiamondProxy - ) reentrancyGuardInitializer { - _disableInitializers(); - L1_WETH_TOKEN = _l1WethAddress; - BRIDGE_HUB = _bridgehub; - ERA_CHAIN_ID = _eraChainId; - ERA_DIAMOND_PROXY = _eraDiamondProxy; - } - - /// @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) external reentrancyGuardInitializer initializer { - if (_owner == address(0)) { - revert ZeroAddress(); - } - _transferOwnership(_owner); - } - - /// @inheritdoc IL1SharedBridge - /// @dev Please note, if the owner wants to enforce the admin change it must execute both `setPendingAdmin` and - /// `acceptAdmin` atomically. Otherwise `admin` can set different pending admin and so fail to accept the admin rights. - 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 IL1SharedBridge - /// @notice Accepts transfer of admin rights. Only pending admin can accept the role. - function acceptAdmin() external { - address currentPendingAdmin = pendingAdmin; - require(msg.sender == currentPendingAdmin, "ShB not pending admin"); // 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, currentPendingAdmin); - } - - /// @dev This sets the first post diamond upgrade batch for era, used to check old eth withdrawals - /// @param _eraPostDiamondUpgradeFirstBatch The first batch number on the ZKsync Era Diamond Proxy that was settled after diamond proxy upgrade. - function setEraPostDiamondUpgradeFirstBatch(uint256 _eraPostDiamondUpgradeFirstBatch) external onlyOwner { - if (eraPostDiamondUpgradeFirstBatch != 0) { - revert SharedBridgeValueAlreadySet(SharedBridgeKey.PostUpgradeFirstBatch); - } - eraPostDiamondUpgradeFirstBatch = _eraPostDiamondUpgradeFirstBatch; - } - - /// @dev This sets the first post upgrade batch for era, used to check old token withdrawals - /// @param _eraPostLegacyBridgeUpgradeFirstBatch The first batch number on the ZKsync Era Diamond Proxy that was settled after legacy bridge upgrade. - function setEraPostLegacyBridgeUpgradeFirstBatch(uint256 _eraPostLegacyBridgeUpgradeFirstBatch) external onlyOwner { - if (eraPostLegacyBridgeUpgradeFirstBatch != 0) { - revert SharedBridgeValueAlreadySet(SharedBridgeKey.LegacyBridgeFirstBatch); - } - eraPostLegacyBridgeUpgradeFirstBatch = _eraPostLegacyBridgeUpgradeFirstBatch; - } - - /// @dev This sets the first post upgrade batch for era, used to check old withdrawals - /// @param _eraLegacyBridgeLastDepositBatch The the ZKsync Era batch number that processes the last deposit tx initiated by the legacy bridge - /// @param _eraLegacyBridgeLastDepositTxNumber The tx number in the _eraLegacyBridgeLastDepositBatch of the last deposit tx initiated by the legacy bridge - function setEraLegacyBridgeLastDepositTime( - uint256 _eraLegacyBridgeLastDepositBatch, - uint256 _eraLegacyBridgeLastDepositTxNumber - ) external onlyOwner { - if (eraLegacyBridgeLastDepositBatch != 0) { - revert SharedBridgeValueAlreadySet(SharedBridgeKey.LegacyBridgeLastDepositBatch); - } - if (eraLegacyBridgeLastDepositTxNumber != 0) { - revert SharedBridgeValueAlreadySet(SharedBridgeKey.LegacyBridgeLastDepositTxn); - } - eraLegacyBridgeLastDepositBatch = _eraLegacyBridgeLastDepositBatch; - eraLegacyBridgeLastDepositTxNumber = _eraLegacyBridgeLastDepositTxNumber; - } - - /// @dev Transfer tokens from legacy erc20 bridge or mailbox and set chainBalance as part of migration process. - /// @param _token The address of token to be transferred (address(1) for ether and contract address for ERC20). - /// @param _target The hyperchain or bridge contract address from where to transfer funds. - /// @param _targetChainId The chain ID of the corresponding hyperchain. - function transferFundsFromLegacy(address _token, address _target, uint256 _targetChainId) external onlySelf { - if (_token == ETH_TOKEN_ADDRESS) { - uint256 balanceBefore = address(this).balance; - IMailbox(_target).transferEthToSharedBridge(); - uint256 balanceAfter = address(this).balance; - if (balanceAfter <= balanceBefore) { - revert NoFundsTransferred(); - } - chainBalance[_targetChainId][ETH_TOKEN_ADDRESS] = - chainBalance[_targetChainId][ETH_TOKEN_ADDRESS] + - balanceAfter - - balanceBefore; - } else { - uint256 balanceBefore = IERC20(_token).balanceOf(address(this)); - uint256 legacyBridgeBalance = IERC20(_token).balanceOf(address(legacyBridge)); - if (legacyBridgeBalance == 0) { - revert ZeroBalance(); - } - IL1ERC20Bridge(_target).transferTokenToSharedBridge(_token); - uint256 balanceAfter = IERC20(_token).balanceOf(address(this)); - if (balanceAfter - balanceBefore < legacyBridgeBalance) { - revert SharedBridgeBalanceMismatch(); - } - chainBalance[_targetChainId][_token] = chainBalance[_targetChainId][_token] + legacyBridgeBalance; - } - } - - /// @dev transfer tokens from legacy erc20 bridge or mailbox and set chainBalance as part of migration process. - /// @dev Unlike `transferFundsFromLegacy` is provides a concrete limit on the gas used for the transfer and even if it will fail, it will not revert the whole transaction. - function safeTransferFundsFromLegacy( - address _token, - address _target, - uint256 _targetChainId, - uint256 _gasPerToken - ) external onlyOwner { - try this.transferFundsFromLegacy{gas: _gasPerToken}(_token, _target, _targetChainId) {} catch { - // A reasonable amount of gas will be provided to transfer the token. - // If the transfer fails, we don't want to revert the whole transaction. - } - } - - /// @dev Accepts ether only from the hyperchain associated with the specified chain ID. - /// @param _chainId The chain ID corresponding to the hyperchain allowed to send ether. - function receiveEth(uint256 _chainId) external payable { - if (BRIDGE_HUB.getHyperchain(_chainId) != msg.sender) { - revert Unauthorized(msg.sender); - } - } - - /// @dev Initializes the l2Bridge address by governance for a specific chain. - /// @param _chainId The chain ID for which the l2Bridge address is being initialized. - /// @param _l2BridgeAddress The address of the L2 bridge contract. - function initializeChainGovernance(uint256 _chainId, address _l2BridgeAddress) external onlyOwnerOrAdmin { - require(l2BridgeAddress[_chainId] == address(0), "ShB: l2 bridge already set"); - require(_l2BridgeAddress != address(0), "ShB: l2 bridge 0"); - l2BridgeAddress[_chainId] = _l2BridgeAddress; - } - - /// @dev Reinitializes the l2Bridge address by governance for a specific chain. - /// @dev Only accessible to the owner of the bridge to prevent malicious admin from changing the bridge address for - /// an existing chain. - /// @param _chainId The chain ID for which the l2Bridge address is being initialized. - /// @param _l2BridgeAddress The address of the L2 bridge contract. - function reinitializeChainGovernance(uint256 _chainId, address _l2BridgeAddress) external onlyOwner { - require(l2BridgeAddress[_chainId] != address(0), "ShB: l2 bridge not yet set"); - 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. - /// @param _chainId The chain ID of the hyperchain to which deposit. - /// @param _prevMsgSender The `msg.sender` address from the external call that initiated current one. - /// @param _l1Token The L1 token address which is deposited. - /// @param _amount The total amount of tokens to be bridged. - function bridgehubDepositBaseToken( - uint256 _chainId, - address _prevMsgSender, - address _l1Token, - uint256 _amount - ) external payable virtual onlyBridgehubOrEra(_chainId) whenNotPaused { - if (_l1Token == ETH_TOKEN_ADDRESS) { - if (msg.value != _amount) { - revert ValueMismatch(_amount, msg.value); - } - } else { - // The Bridgehub also checks this, but we want to be sure - if (msg.value != 0) { - revert NonEmptyMsgValue(); - } - - uint256 amount = _depositFunds(_prevMsgSender, IERC20(_l1Token), _amount); // note if _prevMsgSender is this contract, this will return 0. This does not happen. - // The token has non-standard transfer logic - if (amount != _amount) { - revert TokensWithFeesNotSupported(); - } - } - - 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)); - // slither-disable-next-line arbitrary-send-erc20 - _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`. - /// @param _chainId The chain ID of the hyperchain to which deposit. - /// @param _prevMsgSender The `msg.sender` address from the external call that initiated current one. - /// @param _l2Value The L2 `msg.value` from the L1 -> L2 deposit transaction. - /// @param _data The calldata for the second bridge deposit. - function bridgehubDeposit( - uint256 _chainId, - address _prevMsgSender, - // solhint-disable-next-line no-unused-vars - uint256 _l2Value, - bytes calldata _data - ) - external - payable - override - onlyBridgehub - whenNotPaused - returns (L2TransactionRequestTwoBridgesInner memory request) - { - if (l2BridgeAddress[_chainId] == address(0)) { - revert L2BridgeNotSet(_chainId); - } - - (address _l1Token, uint256 _depositAmount, address _l2Receiver) = abi.decode( - _data, - (address, uint256, address) - ); - if (_l1Token == L1_WETH_TOKEN) { - revert TokenNotSupported(L1_WETH_TOKEN); - } - if (BRIDGE_HUB.baseToken(_chainId) == _l1Token) { - revert TokenNotSupported(_l1Token); - } - - uint256 amount; - if (_l1Token == ETH_TOKEN_ADDRESS) { - amount = msg.value; - if (_depositAmount != 0) { - revert DepositIncorrectAmount(0, _depositAmount); - } - } else { - if (msg.value != 0) { - revert NonEmptyMsgValue(); - } - amount = _depositAmount; - - uint256 depAmount = _depositFunds(_prevMsgSender, IERC20(_l1Token), _depositAmount); - // The token has non-standard transfer logic - if (depAmount != _depositAmount) { - revert DepositIncorrectAmount(depAmount, _depositAmount); - } - } - // empty deposit amount - if (amount == 0) { - revert EmptyDeposit(); - } - - 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: _chainId, - txDataHash: txDataHash, - from: _prevMsgSender, - to: _l2Receiver, - l1Token: _l1Token, - amount: 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. - /// @param _chainId The chain ID of the hyperchain to which confirm the deposit. - /// @param _txDataHash The keccak256 hash of abi.encode(msgSender, l1Token, amount) - /// @param _txHash The hash of the L1->L2 transaction to confirm the deposit. - function bridgehubConfirmL2Transaction( - uint256 _chainId, - bytes32 _txDataHash, - bytes32 _txHash - ) external override onlyBridgehub whenNotPaused { - if (depositHappened[_chainId][_txHash] != 0x00) { - revert DepositExists(); - } - depositHappened[_chainId][_txHash] = _txDataHash; - emit BridgehubDepositFinalized(_chainId, _txDataHash, _txHash); - } - - /// @dev Sets the L1ERC20Bridge contract address. Should be called only once. - function setL1Erc20Bridge(address _legacyBridge) external onlyOwner { - if (address(legacyBridge) != address(0)) { - revert AddressAlreadyUsed(address(legacyBridge)); - } - if (_legacyBridge == address(0)) { - revert ZeroAddress(); - } - legacyBridge = IL1ERC20Bridge(_legacyBridge); - } - - /// @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 = abi.encode("Ether"); - bytes memory symbol = abi.encode("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({ - _checkedInLegacyBridge: false, - _chainId: _chainId, - _depositSender: _depositSender, - _l1Token: _l1Token, - _amount: _amount, - _l2TxHash: _l2TxHash, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _merkleProof: _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 whenNotPaused { - { - bool proofValid = BRIDGE_HUB.proveL1ToL2TransactionStatus({ - _chainId: _chainId, - _l2TxHash: _l2TxHash, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _merkleProof: _merkleProof, - _status: TxStatus.Failure - }); - if (!proofValid) { - revert InvalidProof(); - } - } - if (_amount == 0) { - revert NoFundsTransferred(); - } - - { - bool notCheckedInLegacyBridgeOrWeCanCheckDeposit; - { - // Deposits that happened before the upgrade cannot be checked here, they have to be claimed and checked in the legacyBridge - bool weCanCheckDepositHere = !_isEraLegacyDeposit(_chainId, _l2BatchNumber, _l2TxNumberInBatch); - // Double claims are not possible, as depositHappened is checked here for all except legacy deposits (which have to happen through the legacy bridge) - // 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)); - if (dataHash != txDataHash) { - revert DepositDoesNotExist(); - } - delete depositHappened[_chainId][_l2TxHash]; - } - } - - if (!hyperbridgingEnabled[_chainId]) { - // check that the chain has sufficient balance - if (chainBalance[_chainId][_l1Token] < _amount) { - revert InsufficientChainBalance(); - } - 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) - } - if (!callSuccess) { - revert WithdrawFailed(); - } - } 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 an eth 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 diamond proxy upgrade. - function _isEraLegacyEthWithdrawal(uint256 _chainId, uint256 _l2BatchNumber) internal view returns (bool) { - if ((_chainId == ERA_CHAIN_ID) && eraPostDiamondUpgradeFirstBatch == 0) { - revert SharedBridgeValueNotSet(SharedBridgeKey.PostUpgradeFirstBatch); - } - return (_chainId == ERA_CHAIN_ID) && (_l2BatchNumber < eraPostDiamondUpgradeFirstBatch); - } - - /// @dev Determines if a token 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 Legacy Bridge upgrade. - function _isEraLegacyTokenWithdrawal(uint256 _chainId, uint256 _l2BatchNumber) internal view returns (bool) { - if ((_chainId == ERA_CHAIN_ID) && eraPostLegacyBridgeUpgradeFirstBatch == 0) { - revert SharedBridgeValueNotSet(SharedBridgeKey.LegacyBridgeFirstBatch); - } - return (_chainId == ERA_CHAIN_ID) && (_l2BatchNumber < eraPostLegacyBridgeUpgradeFirstBatch); - } - - /// @dev Determines if a deposit 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 deposit where it was processed. - /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the deposit was processed. - /// @return Whether deposit was initiated on ZKsync Era before Shared Bridge upgrade. - function _isEraLegacyDeposit( - uint256 _chainId, - uint256 _l2BatchNumber, - uint256 _l2TxNumberInBatch - ) internal view returns (bool) { - if ((_chainId == ERA_CHAIN_ID) && (eraLegacyBridgeLastDepositBatch == 0)) { - revert SharedBridgeValueNotSet(SharedBridgeKey.LegacyBridgeLastDepositBatch); - } - return - (_chainId == ERA_CHAIN_ID) && - (_l2BatchNumber < eraLegacyBridgeLastDepositBatch || - (_l2TxNumberInBatch < eraLegacyBridgeLastDepositTxNumber && - _l2BatchNumber == eraLegacyBridgeLastDepositBatch)); - } - - /// @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 (_isEraLegacyTokenWithdrawal(_chainId, _l2BatchNumber)) { - if (legacyBridge.isWithdrawalFinalized(_l2BatchNumber, _l2MessageIndex)) { - revert WithdrawalAlreadyFinalized(); - } - } - _finalizeWithdrawal({ - _chainId: _chainId, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _message: _message, - _merkleProof: _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 whenNotPaused returns (address l1Receiver, address l1Token, uint256 amount) { - if (isWithdrawalFinalized[_chainId][_l2BatchNumber][_l2MessageIndex]) { - revert WithdrawalAlreadyFinalized(); - } - isWithdrawalFinalized[_chainId][_l2BatchNumber][_l2MessageIndex] = true; - - // Handling special case for withdrawal from ZKsync Era initiated before Shared Bridge. - if (_isEraLegacyEthWithdrawal(_chainId, _l2BatchNumber)) { - // Checks that the withdrawal wasn't finalized already. - bool alreadyFinalized = IGetters(ERA_DIAMOND_PROXY).isEthWithdrawalFinalized( - _l2BatchNumber, - _l2MessageIndex - ); - if (alreadyFinalized) { - revert WithdrawalAlreadyFinalized(); - } - } - - 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 - if (chainBalance[_chainId][l1Token] < amount) { - // not enough funds - revert InsufficientChainBalance(); - } - 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) - } - if (!callSuccess) { - revert WithdrawFailed(); - } - } 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 == BRIDGE_HUB.baseToken(_chainId)); - address l2Sender = baseTokenWithdrawal ? L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR : l2BridgeAddress[_chainId]; - - l2ToL1Message = L2Message({ - txNumberInBatch: _messageParams.l2TxNumberInBatch, - sender: l2Sender, - data: _message - }); - } - - bool success = BRIDGE_HUB.proveL2MessageInclusion({ - _chainId: _chainId, - _batchNumber: _messageParams.l2BatchNumber, - _index: _messageParams.l2MessageIndex, - _message: l2ToL1Message, - _proof: _merkleProof - }); - // withdrawal wrong proof - if (!success) { - revert InvalidProof(); - } - } - - 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. - // wrong message length - if (_l2ToL1message.length < 56) { - revert L2WithdrawalMessageWrongLength(_l2ToL1message.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 = BRIDGE_HUB.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). - if (_l2ToL1message.length != 76) { - revert L2WithdrawalMessageWrongLength(_l2ToL1message.length); - } - (l1Receiver, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); - (l1Token, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); - (amount, offset) = UnsafeBytes.readUint256(_l2ToL1message, offset); - } else { - revert InvalidSelector(bytes4(functionSignature)); - } - } - - /*////////////////////////////////////////////////////////////// - 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 _prevMsgSender The `msg.sender` address from the external call that initiated current one. - /// @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 whenNotPaused returns (bytes32 l2TxHash) { - if (l2BridgeAddress[ERA_CHAIN_ID] == address(0)) { - revert L2BridgeNotSet(ERA_CHAIN_ID); - } - if (_l1Token == L1_WETH_TOKEN) { - revert TokenNotSupported(L1_WETH_TOKEN); - } - - // 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 = AddressAliasHelper.actualRefundRecipient(_refundRecipient, _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 = BRIDGE_HUB.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({ - chainId: ERA_CHAIN_ID, - l2DepositTxHash: l2TxHash, - from: _prevMsgSender, - to: _l2Receiver, - l1Token: _l1Token, - amount: _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 withdrawn - 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({ - _chainId: ERA_CHAIN_ID, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _message: _message, - _merkleProof: _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({ - _checkedInLegacyBridge: true, - _chainId: ERA_CHAIN_ID, - _depositSender: _depositSender, - _l1Token: _l1Token, - _amount: _amount, - _l2TxHash: _l2TxHash, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _merkleProof: _merkleProof - }); - } - - /*////////////////////////////////////////////////////////////// - PAUSE - //////////////////////////////////////////////////////////////*/ - - /// @notice Pauses all functions marked with the `whenNotPaused` modifier. - function pause() external onlyOwner { - _pause(); - } - - /// @notice Unpauses the contract, allowing all functions marked with the `whenNotPaused` modifier to be called again. - function unpause() external onlyOwner { - _unpause(); - } -} diff --git a/l2-contracts/contracts/bridge/L2SharedBridge.sol b/l1-contracts/contracts/bridge/L2SharedBridgeLegacy.sol similarity index 58% rename from l2-contracts/contracts/bridge/L2SharedBridge.sol rename to l1-contracts/contracts/bridge/L2SharedBridgeLegacy.sol index d4e7b7900..b7c762b71 100644 --- a/l2-contracts/contracts/bridge/L2SharedBridge.sol +++ b/l1-contracts/contracts/bridge/L2SharedBridgeLegacy.sol @@ -3,25 +3,28 @@ pragma solidity 0.8.24; import {Initializable} from "@openzeppelin/contracts-v4/proxy/utils/Initializable.sol"; -import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; -import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; -import {IL2SharedBridge} from "./interfaces/IL2SharedBridge.sol"; -import {IL2StandardToken} from "./interfaces/IL2StandardToken.sol"; +import {BridgedStandardERC20} from "./BridgedStandardERC20.sol"; -import {L2StandardERC20} from "./L2StandardERC20.sol"; +import {L2_DEPLOYER_SYSTEM_CONTRACT_ADDR, L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR} from "../common/L2ContractAddresses.sol"; +import {SystemContractsCaller} from "../common/libraries/SystemContractsCaller.sol"; +import {L2ContractHelper, IContractDeployer} from "../common/libraries/L2ContractHelper.sol"; import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; -import {L2ContractHelper, DEPLOYER_SYSTEM_CONTRACT, IContractDeployer} from "../L2ContractHelper.sol"; -import {SystemContractsCaller} from "../SystemContractsCaller.sol"; -import {ZeroAddress, EmptyBytes32, Unauthorized, AddressMismatch, AmountMustBeGreaterThanZero, DeployFailed} from "../errors/L2ContractErrors.sol"; +import {IL2AssetRouter} from "./asset-router/IL2AssetRouter.sol"; +import {IL2NativeTokenVault} from "./ntv/IL2NativeTokenVault.sol"; + +import {IL2SharedBridgeLegacy} from "./interfaces/IL2SharedBridgeLegacy.sol"; +import {InvalidCaller, ZeroAddress, EmptyBytes32, Unauthorized, AmountMustBeGreaterThanZero, DeployFailed} from "../common/L1ContractErrors.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 L2SharedBridge is IL2SharedBridge, Initializable { +/// @dev Note, that this contract should be compatible with its previous version as it will be +/// the primary bridge to be used during migration. +contract L2SharedBridgeLegacy is IL2SharedBridgeLegacy, Initializable { /// @dev The address of the L1 shared bridge counterpart. address public override l1SharedBridge; @@ -37,25 +40,33 @@ contract L2SharedBridge is IL2SharedBridge, Initializable { /// @dev The address of the legacy L1 erc20 bridge counterpart. /// This is non-zero only on Era, and should not be renamed for backward compatibility with the SDKs. + // slither-disable-next-line uninitialized-state address public override l1Bridge; - /// @dev Contract is expected to be used as proxy implementation. - /// @dev Disable the initialization to prevent Parity hack. - uint256 public immutable ERA_CHAIN_ID; + modifier onlyNTV() { + if (msg.sender != L2_NATIVE_TOKEN_VAULT_ADDR) { + revert Unauthorized(msg.sender); + } + _; + } + + modifier onlyAssetRouter() { + if (msg.sender != L2_ASSET_ROUTER_ADDR) { + revert Unauthorized(msg.sender); + } + _; + } - constructor(uint256 _eraChainId) { - ERA_CHAIN_ID = _eraChainId; + constructor() { _disableInitializers(); } /// @notice Initializes the bridge contract for later use. Expected to be used in the proxy. /// @param _l1SharedBridge The address of the L1 Bridge contract. - /// @param _l1Bridge The address of the legacy L1 Bridge contract. /// @param _l2TokenProxyBytecodeHash The bytecode hash of the proxy for tokens deployed by the bridge. /// @param _aliasedOwner The address of the governor contract. function initialize( address _l1SharedBridge, - address _l1Bridge, bytes32 _l2TokenProxyBytecodeHash, address _aliasedOwner ) external reinitializer(2) { @@ -73,20 +84,29 @@ contract L2SharedBridge is IL2SharedBridge, Initializable { l1SharedBridge = _l1SharedBridge; - if (block.chainid != ERA_CHAIN_ID) { - address l2StandardToken = address(new L2StandardERC20{salt: bytes32(0)}()); + // The following statement is true only in freshly deployed environments. However, + // for those environments we do not need to deploy this contract at all. + // This check is primarily for local testing purposes. + if (l2TokenProxyBytecodeHash == bytes32(0) && address(l2TokenBeacon) == address(0)) { + address l2StandardToken = address(new BridgedStandardERC20{salt: bytes32(0)}()); l2TokenBeacon = new UpgradeableBeacon{salt: bytes32(0)}(l2StandardToken); l2TokenProxyBytecodeHash = _l2TokenProxyBytecodeHash; l2TokenBeacon.transferOwnership(_aliasedOwner); - } else { - if (_l1Bridge == address(0)) { - revert ZeroAddress(); - } - l1Bridge = _l1Bridge; - // l2StandardToken and l2TokenBeacon are already deployed on ERA, and stored in the proxy } } + /// @notice Initiates a withdrawal by burning funds on the contract and sending the message to L1 + /// where tokens would be unlocked + /// @param _l1Receiver The account address that should receive funds on L1 + /// @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 { + if (_amount == 0) { + revert AmountMustBeGreaterThanZero(); + } + IL2AssetRouter(L2_ASSET_ROUTER_ADDR).withdrawLegacyBridge(_l1Receiver, _l2Token, _amount, msg.sender); + } + /// @notice Finalize the deposit and mint funds /// @param _l1Sender The account address that initiated the deposit on L1 /// @param _l2Receiver The account address that would receive minted ether @@ -99,80 +119,45 @@ contract L2SharedBridge is IL2SharedBridge, Initializable { address _l1Token, uint256 _amount, bytes calldata _data - ) external override { + ) external { // Only the L1 bridge counterpart can initiate and finalize the deposit. if ( AddressAliasHelper.undoL1ToL2Alias(msg.sender) != l1Bridge && AddressAliasHelper.undoL1ToL2Alias(msg.sender) != l1SharedBridge ) { - revert Unauthorized(msg.sender); - } - - address expectedL2Token = l2TokenAddress(_l1Token); - address currentL1Token = l1TokenAddress[expectedL2Token]; - if (currentL1Token == address(0)) { - address deployedToken = _deployL2Token(_l1Token, _data); - if (deployedToken != expectedL2Token) { - revert AddressMismatch(expectedL2Token, deployedToken); - } - - l1TokenAddress[expectedL2Token] = _l1Token; - } else { - if (currentL1Token != _l1Token) { - revert AddressMismatch(_l1Token, currentL1Token); - } + revert InvalidCaller(msg.sender); } - IL2StandardToken(expectedL2Token).bridgeMint(_l2Receiver, _amount); - emit FinalizeDeposit(_l1Sender, _l2Receiver, expectedL2Token, _amount); - } - - /// @dev Deploy and initialize the L2 token for the L1 counterpart - function _deployL2Token(address _l1Token, bytes calldata _data) internal returns (address) { - bytes32 salt = _getCreate2Salt(_l1Token); - - BeaconProxy l2Token = _deployBeaconProxy(salt); - L2StandardERC20(address(l2Token)).bridgeInitialize(_l1Token, _data); - - return address(l2Token); - } - - /// @notice Initiates a withdrawal by burning funds on the contract and sending the message to L1 - /// where tokens would be unlocked - /// @param _l1Receiver The account address that should receive funds on L1 - /// @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 { - if (_amount == 0) { - revert AmountMustBeGreaterThanZero(); - } + IL2AssetRouter(L2_ASSET_ROUTER_ADDR).finalizeDepositLegacyBridge({ + _l1Sender: _l1Sender, + _l2Receiver: _l2Receiver, + _l1Token: _l1Token, + _amount: _amount, + _data: _data + }); - IL2StandardToken(_l2Token).bridgeBurn(msg.sender, _amount); + address l2Token = IL2NativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR).l2TokenAddress(_l1Token); - address l1Token = l1TokenAddress[_l2Token]; - if (l1Token == address(0)) { - revert ZeroAddress(); + if (l1TokenAddress[l2Token] == address(0)) { + l1TokenAddress[l2Token] = _l1Token; } - bytes memory message = _getL1WithdrawMessage(_l1Receiver, l1Token, _amount); - L2ContractHelper.sendMessageToL1(message); - - emit WithdrawalInitiated(msg.sender, _l1Receiver, _l2Token, _amount); - } - - /// @dev Encode the message for l2ToL1log sent with withdraw initialization - function _getL1WithdrawMessage( - address _to, - address _l1Token, - uint256 _amount - ) internal pure returns (bytes memory) { - // 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); + emit FinalizeDeposit(_l1Sender, _l2Receiver, l2Token, _amount); } /// @return Address of an L2 token counterpart function l2TokenAddress(address _l1Token) public view override returns (address) { + address token = IL2NativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR).l2TokenAddress(_l1Token); + if (token != address(0)) { + return token; + } + return _calculateCreate2TokenAddress(_l1Token); + } + + /// @notice Calculates L2 wrapped token address given the currently stored beacon proxy bytecode hash and beacon address. + /// @param _l1Token The address of token on L1. + /// @return Address of an L2 token counterpart. + function _calculateCreate2TokenAddress(address _l1Token) internal view returns (address) { bytes32 constructorInputHash = keccak256(abi.encode(address(l2TokenBeacon), "")); bytes32 salt = _getCreate2Salt(_l1Token); return @@ -187,10 +172,10 @@ contract L2SharedBridge is IL2SharedBridge, Initializable { /// @dev Deploy the beacon proxy for the L2 token, while using ContractDeployer system contract. /// @dev This function uses raw call to ContractDeployer to make sure that exactly `l2TokenProxyBytecodeHash` is used /// for the code of the proxy. - function _deployBeaconProxy(bytes32 salt) internal returns (BeaconProxy proxy) { + function deployBeaconProxy(bytes32 salt) external onlyNTV returns (address proxy) { (bool success, bytes memory returndata) = SystemContractsCaller.systemCallWithReturndata( uint32(gasleft()), - DEPLOYER_SYSTEM_CONTRACT, + L2_DEPLOYER_SYSTEM_CONTRACT_ADDR, 0, abi.encodeCall( IContractDeployer.create2, @@ -202,6 +187,11 @@ contract L2SharedBridge is IL2SharedBridge, Initializable { if (!success) { revert DeployFailed(); } - proxy = BeaconProxy(abi.decode(returndata, (address))); + proxy = abi.decode(returndata, (address)); + } + + function sendMessageToL1(bytes calldata _message) external override onlyAssetRouter returns (bytes32) { + // slither-disable-next-line unused-return + return L2ContractHelper.sendMessageToL1(_message); } } diff --git a/l2-contracts/contracts/bridge/L2WrappedBaseToken.sol b/l1-contracts/contracts/bridge/L2WrappedBaseToken.sol similarity index 81% rename from l2-contracts/contracts/bridge/L2WrappedBaseToken.sol rename to l1-contracts/contracts/bridge/L2WrappedBaseToken.sol index 8ff9f814e..7af311edf 100644 --- a/l2-contracts/contracts/bridge/L2WrappedBaseToken.sol +++ b/l1-contracts/contracts/bridge/L2WrappedBaseToken.sol @@ -5,9 +5,10 @@ pragma solidity 0.8.24; import {ERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol"; import {IL2WrappedBaseToken} from "./interfaces/IL2WrappedBaseToken.sol"; -import {IL2StandardToken} from "./interfaces/IL2StandardToken.sol"; +import {IBridgedStandardToken} from "./interfaces/IBridgedStandardToken.sol"; +import {L2_NATIVE_TOKEN_VAULT_ADDR} from "../common/L2ContractAddresses.sol"; -import {ZeroAddress, Unauthorized, UnimplementedMessage, BRIDGE_MINT_NOT_IMPLEMENTED, WithdrawFailed} from "../errors/L2ContractErrors.sol"; +import {ZeroAddress, Unauthorized, BridgeMintNotImplemented, WithdrawFailed} from "../common/L1ContractErrors.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev @@ -22,19 +23,37 @@ import {ZeroAddress, Unauthorized, UnimplementedMessage, BRIDGE_MINT_NOT_IMPLEME /// /// 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 L2WrappedBaseToken is ERC20PermitUpgradeable, IL2WrappedBaseToken, IL2StandardToken { +contract L2WrappedBaseToken is ERC20PermitUpgradeable, IL2WrappedBaseToken, IBridgedStandardToken { /// @dev Address of the L2 WETH Bridge. address public override l2Bridge; /// @dev Address of the L1 base token. It can be deposited to mint this L2 token. address public override l1Address; + /// @dev Address of the native token vault. + address public override nativeTokenVault; + + /// @dev The assetId of the base token. The wrapped token does not have its own assetId. + bytes32 public baseTokenAssetId; + + modifier onlyBridge() { + if (msg.sender != l2Bridge) { + revert Unauthorized(msg.sender); + } + _; + } + /// @dev Contract is expected to be used as proxy implementation. constructor() { // Disable initialization to prevent Parity hack. _disableInitializers(); } + /// @dev Fallback function to allow receiving Ether. + receive() external payable { + depositTo(msg.sender); + } + /// @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. @@ -43,12 +62,13 @@ contract L2WrappedBaseToken is ERC20PermitUpgradeable, IL2WrappedBaseToken, IL2S /// @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 initializeV2( + function initializeV3( string calldata name_, string calldata symbol_, address _l2Bridge, - address _l1Address - ) external reinitializer(2) { + address _l1Address, + bytes32 _baseTokenAssetId + ) external reinitializer(3) { if (_l2Bridge == address(0)) { revert ZeroAddress(); } @@ -56,8 +76,13 @@ contract L2WrappedBaseToken is ERC20PermitUpgradeable, IL2WrappedBaseToken, IL2S if (_l1Address == address(0)) { revert ZeroAddress(); } + if (_baseTokenAssetId == bytes32(0)) { + revert ZeroAddress(); + } l2Bridge = _l2Bridge; l1Address = _l1Address; + nativeTokenVault = L2_NATIVE_TOKEN_VAULT_ADDR; + baseTokenAssetId = _baseTokenAssetId; // Set decoded values for name and symbol. __ERC20_init_unchained(name_, symbol_); @@ -68,19 +93,12 @@ contract L2WrappedBaseToken is ERC20PermitUpgradeable, IL2WrappedBaseToken, IL2S emit Initialize(name_, symbol_, 18); } - modifier onlyBridge() { - if (msg.sender != l2Bridge) { - revert Unauthorized(msg.sender); - } - _; - } - /// @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. // solhint-disable-next-line no-unused-vars function bridgeMint(address _to, uint256 _amount) external override onlyBridge { - revert UnimplementedMessage(BRIDGE_MINT_NOT_IMPLEMENTED); + revert BridgeMintNotImplemented(); } /// @dev Burn tokens from a given account and send the same amount of Ether to the bridge. @@ -123,8 +141,11 @@ contract L2WrappedBaseToken is ERC20PermitUpgradeable, IL2WrappedBaseToken, IL2S } } - /// @dev Fallback function to allow receiving Ether. - receive() external payable { - depositTo(msg.sender); + function originToken() external view override returns (address) { + return l1Address; + } + + function assetId() external view override returns (bytes32) { + return baseTokenAssetId; } } diff --git a/l1-contracts/contracts/bridge/L2WrappedBaseTokenStore.sol b/l1-contracts/contracts/bridge/L2WrappedBaseTokenStore.sol new file mode 100644 index 000000000..d52a7599c --- /dev/null +++ b/l1-contracts/contracts/bridge/L2WrappedBaseTokenStore.sol @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Ownable2Step} from "@openzeppelin/contracts-v4/access/Ownable2Step.sol"; + +import {ZeroAddress, Unauthorized, WrappedBaseTokenAlreadyRegistered} from "../common/L1ContractErrors.sol"; + +/// @title L2WrappedBaseTokenStore +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice This contract is used as a store for L2 deployments of L2WrappedBaseToken for chains that have it. +/// These values will be stored in the corresponding chain's L2NativeTokenVault upon migration to the new version, +/// so these values being correct is crucial. The following upgrade process is expected for this contract: +/// - It will be populated for the existing chains before the governance reviews the values. +/// - Each new chain (before the new protocol version is available) will have to double check that the admin +/// has set the correct value in this contract. If the admin did not set a correct value, the chain should be discarded. +/// - Once the upgrade is done, this contract will no longer be needed. Even though it is unlikely for a chain to be corrupted, +/// the governance can fix any corrupted chains in the next upgrade. +/// @dev This contract is not expected to be deployed as a proxy, but rather a standalone contract. +/// @dev The `admin` of this contract is expected to be some cold wallet, trusted to provide correct values. However, +/// due to process above, even its malicious behavior should not impact security of the ecosystem. +/// @dev The `owner` of this contract is trusted decentralized governance. +contract L2WrappedBaseTokenStore is Ownable2Step { + /// @notice Mapping from chain ID to L2 wrapped base token address. + mapping(uint256 chainId => address l2WBaseTokenAddress) public l2WBaseTokenAddress; + + /// @notice Admin address who has the right to register weth token deployment for a chain. + address public admin; + + /// @notice used to accept the admin role + address public pendingAdmin; + + /// @notice Admin changed + event NewAdmin(address indexed oldAdmin, address indexed newAdmin); + + /// @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 Emitted when the L2 wrapped base token address is set for a chain + /// @param chainId The id of the chain. + /// @param l2WBaseTokenAddress The L2 wrapped base token address. + event NewWBaseTokenAddress(uint256 indexed chainId, address indexed l2WBaseTokenAddress); + + /// @notice Sets the initial owner and admin. + /// @param _initialOwner The initial owner. + /// @param _admin The address of the admin. + constructor(address _initialOwner, address _admin) { + if (_admin == address(0) || _initialOwner == address(0)) { + revert ZeroAddress(); + } + admin = _admin; + _transferOwnership(_initialOwner); + } + + /// @notice Throws if called by any account other than the owner or admin. + modifier onlyOwnerOrAdmin() { + if (msg.sender != owner() && msg.sender != admin) { + revert Unauthorized(msg.sender); + } + _; + } + + /// @notice Initializes the L2 WBaseToken address for a specific chain ID. + /// @dev Can be called by the owner or the admin. + /// @param _chainId The ID of the blockchain network. + /// @param _l2WBaseToken The address of the L2 WBaseToken token. + function initializeChain(uint256 _chainId, address _l2WBaseToken) external onlyOwnerOrAdmin { + if (_l2WBaseToken == address(0)) { + revert ZeroAddress(); + } + if (l2WBaseTokenAddress[_chainId] != address(0)) { + revert WrappedBaseTokenAlreadyRegistered(); + } + _setWBaseTokenAddress(_chainId, _l2WBaseToken); + } + + /// @notice Reinitializes the L2 WBaseToken address for a specific chain ID. + /// @dev Can only be called by the owner. It can not be called by the admin second time + /// to prevent retroactively damaging existing chains. + /// @param _chainId The ID of the blockchain network. + /// @param _l2WBaseToken The new address of the L2 WBaseToken token. + function reinitializeChain(uint256 _chainId, address _l2WBaseToken) external onlyOwner { + if (_l2WBaseToken == address(0)) { + revert ZeroAddress(); + } + _setWBaseTokenAddress(_chainId, _l2WBaseToken); + } + + /// @notice Sets the address of the L2 wrapped base token deployment for a chain. + /// @param _chainId The ID of the blockchain network. + /// @param _l2WBaseToken The new address of the L2 WBaseToken token. + function _setWBaseTokenAddress(uint256 _chainId, address _l2WBaseToken) internal { + l2WBaseTokenAddress[_chainId] = _l2WBaseToken; + emit NewWBaseTokenAddress(_chainId, _l2WBaseToken); + } + + /// @notice Starts the transfer of admin rights. Only the current admin or owner can propose a new pending one. + /// @notice New admin can accept admin rights by calling `acceptAdmin` function. + /// @param _newPendingAdmin Address of the new admin + /// @dev Please note, if the owner wants to enforce the admin change it must execute both `setPendingAdmin` and + /// `acceptAdmin` atomically. Otherwise `admin` can set different pending admin and so fail to accept the admin rights. + function setPendingAdmin(address _newPendingAdmin) external onlyOwnerOrAdmin { + if (_newPendingAdmin == address(0)) { + revert ZeroAddress(); + } + // 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); + } + + /// @notice Accepts transfer of admin rights. Only pending admin can accept the role. + function acceptAdmin() external { + address currentPendingAdmin = pendingAdmin; + // Only proposed by current admin address can claim the admin rights + if (msg.sender != currentPendingAdmin) { + revert Unauthorized(msg.sender); + } + + address previousAdmin = admin; + admin = currentPendingAdmin; + delete pendingAdmin; + + emit NewPendingAdmin(currentPendingAdmin, address(0)); + emit NewAdmin(previousAdmin, currentPendingAdmin); + } +} diff --git a/l1-contracts/contracts/bridge/asset-router/AssetRouterBase.sol b/l1-contracts/contracts/bridge/asset-router/AssetRouterBase.sol new file mode 100644 index 000000000..ba30261f1 --- /dev/null +++ b/l1-contracts/contracts/bridge/asset-router/AssetRouterBase.sol @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; +import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/security/PausableUpgradeable.sol"; + +import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +import {IAssetRouterBase} from "./IAssetRouterBase.sol"; +import {IAssetHandler} from "../interfaces/IAssetHandler.sol"; +import {DataEncoding} from "../../common/libraries/DataEncoding.sol"; + +import {L2_NATIVE_TOKEN_VAULT_ADDR} from "../../common/L2ContractAddresses.sol"; + +import {IBridgehub} from "../../bridgehub/IBridgehub.sol"; +import {Unauthorized} from "../../common/L1ContractErrors.sol"; +import {INativeTokenVault} from "../ntv/INativeTokenVault.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @dev Bridges assets between L1 and ZK chain, supporting both ETH and ERC20 tokens. +/// @dev Designed for use with a proxy for upgradability. +abstract contract AssetRouterBase is IAssetRouterBase, Ownable2StepUpgradeable, PausableUpgradeable { + using SafeERC20 for IERC20; + + /// @dev Bridgehub smart contract that is used to operate with L2 via asynchronous L2 <-> L1 communication. + IBridgehub public immutable override BRIDGE_HUB; + + /// @dev Chain ID of L1 for bridging reasons + uint256 public immutable L1_CHAIN_ID; + + /// @dev Chain ID of Era for legacy reasons + uint256 public immutable ERA_CHAIN_ID; + + /// @dev Maps asset ID to address of corresponding asset handler. + /// @dev Tracks the address of Asset Handler contracts, where bridged funds are locked for each asset. + /// @dev P.S. this liquidity was locked directly in SharedBridge before. + /// @dev Current AssetHandlers: NTV for tokens, Bridgehub for chains. + mapping(bytes32 assetId => address assetHandlerAddress) public assetHandlerAddress; + + /// @dev Maps asset ID to the asset deployment tracker address. + /// @dev Tracks the address of Deployment Tracker contract on L1, which sets Asset Handlers on L2s (ZK chain). + /// @dev For the asset and stores respective addresses. + /// @dev Current AssetDeploymentTrackers: NTV for tokens, CTMDeploymentTracker for chains. + mapping(bytes32 assetId => address assetDeploymentTracker) public assetDeploymentTracker; + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[48] private __gap; + + /// @notice Checks that the message sender is the bridgehub. + modifier onlyBridgehub() { + if (msg.sender != address(BRIDGE_HUB)) { + revert Unauthorized(msg.sender); + } + _; + } + + /// @dev Contract is expected to be used as proxy implementation. + /// @dev Initialize the implementation to prevent Parity hack. + constructor(uint256 _l1ChainId, uint256 _eraChainId, IBridgehub _bridgehub) { + L1_CHAIN_ID = _l1ChainId; + ERA_CHAIN_ID = _eraChainId; + BRIDGE_HUB = _bridgehub; + } + + /// @inheritdoc IAssetRouterBase + function setAssetHandlerAddressThisChain( + bytes32 _assetRegistrationData, + address _assetHandlerAddress + ) external virtual override; + + function _setAssetHandlerAddressThisChain( + address _nativeTokenVault, + bytes32 _assetRegistrationData, + address _assetHandlerAddress + ) internal { + bool senderIsNTV = msg.sender == address(_nativeTokenVault); + address sender = senderIsNTV ? L2_NATIVE_TOKEN_VAULT_ADDR : msg.sender; + bytes32 assetId = DataEncoding.encodeAssetId(block.chainid, _assetRegistrationData, sender); + if (!senderIsNTV && msg.sender != assetDeploymentTracker[assetId]) { + revert Unauthorized(msg.sender); + } + _setAssetHandler(assetId, _assetHandlerAddress); + assetDeploymentTracker[assetId] = msg.sender; + emit AssetDeploymentTrackerRegistered(assetId, _assetRegistrationData, msg.sender); + } + + /*////////////////////////////////////////////////////////////// + Receive transaction Functions + //////////////////////////////////////////////////////////////*/ + + /// @inheritdoc IAssetRouterBase + function finalizeDeposit(uint256 _chainId, bytes32 _assetId, bytes calldata _transferData) public payable virtual; + + function _finalizeDeposit( + uint256 _chainId, + bytes32 _assetId, + bytes calldata _transferData, + address _nativeTokenVault + ) internal { + address assetHandler = assetHandlerAddress[_assetId]; + + if (assetHandler != address(0)) { + IAssetHandler(assetHandler).bridgeMint{value: msg.value}(_chainId, _assetId, _transferData); + } else { + _setAssetHandler(_assetId, _nativeTokenVault); + // Native token vault may not support non-zero `msg.value`, but we still provide it here to + // prevent the passed ETH from being stuck in the asset router and also for consistency. + // So the decision on whether to support non-zero `msg.value` is done at the asset handler layer. + IAssetHandler(_nativeTokenVault).bridgeMint{value: msg.value}(_chainId, _assetId, _transferData); // ToDo: Maybe it's better to receive amount and receiver here? transferData may have different encoding + } + } + + /*////////////////////////////////////////////////////////////// + Internal Functions + //////////////////////////////////////////////////////////////*/ + + function _setAssetHandler(bytes32 _assetId, address _assetHandlerAddress) internal { + assetHandlerAddress[_assetId] = _assetHandlerAddress; + emit AssetHandlerRegistered(_assetId, _assetHandlerAddress); + } + + /// @dev send the burn message to the asset + /// @notice Forwards the burn request for specific asset to respective asset handler. + /// @param _chainId The chain ID of the ZK chain to which to deposit. + /// @param _nextMsgValue The L2 `msg.value` from the L1 -> L2 deposit transaction. + /// @param _assetId The deposited asset ID. + /// @param _originalCaller The `msg.sender` address from the external call that initiated current one. + /// @param _transferData The encoded data, which is used by the asset handler to determine L2 recipient and amount. Might include extra information. + /// @param _passValue Boolean indicating whether to pass msg.value in the call. + /// @param _nativeTokenVault The address of the native token vault. + /// @return bridgeMintCalldata The calldata used by remote asset handler to mint tokens for recipient. + function _burn( + uint256 _chainId, + uint256 _nextMsgValue, + bytes32 _assetId, + address _originalCaller, + bytes memory _transferData, + bool _passValue, + address _nativeTokenVault + ) internal returns (bytes memory bridgeMintCalldata) { + address l1AssetHandler = assetHandlerAddress[_assetId]; + if (l1AssetHandler == address(0)) { + // As a UX feature, whenever an asset handler is not present, we always try to register asset within native token vault. + // The Native Token Vault is trusted to revert in an asset does not belong to it. + // + // Note, that it may "pollute" error handling a bit: instead of getting error for asset handler not being + // present, the user will get whatever error the native token vault will return, however, providing + // more advanced error handling requires more extensive code and will be added in the future releases. + INativeTokenVault(_nativeTokenVault).tryRegisterTokenFromBurnData(_transferData, _assetId); + + // We do not do any additional transformations here (like setting `assetHandler` in the mapping), + // because we expect that all those happened inside `tryRegisterTokenFromBurnData` + + l1AssetHandler = _nativeTokenVault; + } + + uint256 msgValue = _passValue ? msg.value : 0; + bridgeMintCalldata = IAssetHandler(l1AssetHandler).bridgeBurn{value: msgValue}({ + _chainId: _chainId, + _msgValue: _nextMsgValue, + _assetId: _assetId, + _originalCaller: _originalCaller, + _data: _transferData + }); + } + + /// @notice Ensures that token is registered with native token vault. + /// @dev Only used when deposit is made with legacy data encoding format. + /// @param _token The native token address which should be registered with native token vault. + /// @return assetId The asset ID of the token provided. + function _ensureTokenRegisteredWithNTV(address _token) internal virtual returns (bytes32 assetId); + + /*////////////////////////////////////////////////////////////// + PAUSE + //////////////////////////////////////////////////////////////*/ + + /// @notice Pauses all functions marked with the `whenNotPaused` modifier. + function pause() external onlyOwner { + _pause(); + } + + /// @notice Unpauses the contract, allowing all functions marked with the `whenNotPaused` modifier to be called again. + function unpause() external onlyOwner { + _unpause(); + } +} diff --git a/l1-contracts/contracts/bridge/asset-router/IAssetRouterBase.sol b/l1-contracts/contracts/bridge/asset-router/IAssetRouterBase.sol new file mode 100644 index 000000000..2be571ddc --- /dev/null +++ b/l1-contracts/contracts/bridge/asset-router/IAssetRouterBase.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IBridgehub} from "../../bridgehub/IBridgehub.sol"; + +/// @dev The encoding version used for legacy txs. +bytes1 constant LEGACY_ENCODING_VERSION = 0x00; + +/// @dev The encoding version used for new txs. +bytes1 constant NEW_ENCODING_VERSION = 0x01; + +/// @dev The encoding version used for txs that set the asset handler on the counterpart contract. +bytes1 constant SET_ASSET_HANDLER_COUNTERPART_ENCODING_VERSION = 0x02; + +/// @title L1 Bridge contract interface +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +interface IAssetRouterBase { + event BridgehubDepositBaseTokenInitiated( + uint256 indexed chainId, + address indexed from, + bytes32 assetId, + uint256 amount + ); + + event BridgehubDepositInitiated( + uint256 indexed chainId, + bytes32 indexed txDataHash, + address indexed from, + bytes32 assetId, + bytes bridgeMintCalldata + ); + + event BridgehubWithdrawalInitiated( + uint256 chainId, + address indexed sender, + bytes32 indexed assetId, + bytes32 assetDataHash // Todo: What's the point of emitting hash? + ); + + event AssetDeploymentTrackerRegistered( + bytes32 indexed assetId, + bytes32 indexed additionalData, + address assetDeploymentTracker + ); + + event AssetHandlerRegistered(bytes32 indexed assetId, address indexed _assetHandlerAddress); + + event DepositFinalizedAssetRouter(uint256 indexed chainId, bytes32 indexed assetId, bytes assetData); + + function BRIDGE_HUB() external view returns (IBridgehub); + + /// @notice Sets the asset handler address for a specified asset ID on the chain of the asset deployment tracker. + /// @dev The caller of this function is encoded within the `assetId`, therefore, it should be invoked by the asset deployment tracker contract. + /// @dev No access control on the caller, as msg.sender is encoded in the assetId. + /// @dev Typically, for most tokens, ADT is the native token vault. However, custom tokens may have their own specific asset deployment trackers. + /// @dev `setAssetHandlerAddressOnCounterpart` should be called on L1 to set asset handlers on L2 chains for a specific asset ID. + /// @param _assetRegistrationData The asset data which may include the asset address and any additional required data or encodings. + /// @param _assetHandlerAddress The address of the asset handler to be set for the provided asset. + function setAssetHandlerAddressThisChain(bytes32 _assetRegistrationData, address _assetHandlerAddress) external; + + function assetHandlerAddress(bytes32 _assetId) external view returns (address); + + /// @notice Finalize the withdrawal and release funds. + /// @param _chainId The chain ID of the transaction to check. + /// @param _assetId The bridged asset ID. + /// @param _transferData The position in the L2 logs Merkle tree of the l2Log that was sent with the message. + /// @dev We have both the legacy finalizeWithdrawal and the new finalizeDeposit functions, + /// finalizeDeposit uses the new format. On the L2 we have finalizeDeposit with new and old formats both. + function finalizeDeposit(uint256 _chainId, bytes32 _assetId, bytes memory _transferData) external payable; +} diff --git a/l1-contracts/contracts/bridge/asset-router/IL1AssetRouter.sol b/l1-contracts/contracts/bridge/asset-router/IL1AssetRouter.sol new file mode 100644 index 000000000..a9ee26525 --- /dev/null +++ b/l1-contracts/contracts/bridge/asset-router/IL1AssetRouter.sol @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: MIT +// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. +pragma solidity ^0.8.21; + +import {IL1Nullifier} from "../interfaces/IL1Nullifier.sol"; +import {INativeTokenVault} from "../ntv/INativeTokenVault.sol"; +import {IAssetRouterBase} from "./IAssetRouterBase.sol"; +import {L2TransactionRequestTwoBridgesInner} from "../../bridgehub/IBridgehub.sol"; +import {IL1SharedBridgeLegacy} from "../interfaces/IL1SharedBridgeLegacy.sol"; + +/// @title L1 Bridge contract interface +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +interface IL1AssetRouter is IAssetRouterBase, IL1SharedBridgeLegacy { + event BridgehubMintData(bytes bridgeMintData); + + event BridgehubDepositFinalized( + uint256 indexed chainId, + bytes32 indexed txDataHash, + bytes32 indexed l2DepositTxHash + ); + + event ClaimedFailedDepositAssetRouter(uint256 indexed chainId, bytes32 indexed assetId, bytes assetData); + + event AssetDeploymentTrackerSet( + bytes32 indexed assetId, + address indexed assetDeploymentTracker, + bytes32 indexed additionalData + ); + + event LegacyDepositInitiated( + uint256 indexed chainId, + bytes32 indexed l2DepositTxHash, + address indexed from, + address to, + address l1Token, + uint256 amount + ); + + /// @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 _originalCaller The `msg.sender` address from the external call that initiated current one. + /// @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 txHash The L2 transaction hash of deposit finalization. + function depositLegacyErc20Bridge( + address _originalCaller, + address _l2Receiver, + address _l1Token, + uint256 _amount, + uint256 _l2TxGasLimit, + uint256 _l2TxGasPerPubdataByte, + address _refundRecipient + ) external payable returns (bytes32 txHash); + + function L1_NULLIFIER() external view returns (IL1Nullifier); + + function L1_WETH_TOKEN() external view returns (address); + + function nativeTokenVault() external view returns (INativeTokenVault); + + function setAssetDeploymentTracker(bytes32 _assetRegistrationData, address _assetDeploymentTracker) external; + + function setNativeTokenVault(INativeTokenVault _nativeTokenVault) external; + + /// @notice Withdraw funds from the initiated deposit, that failed when finalizing on L2. + /// @param _chainId The ZK chain id to which the deposit was initiated. + /// @param _depositSender The address of the entity that initiated the deposit. + /// @param _assetId The unique identifier of the deposited L1 token. + /// @param _assetData The encoded transfer data, which includes both the deposit amount and the address of the L2 receiver. Might include extra information. + /// @dev Processes claims of failed deposit, whether they originated from the legacy bridge or the current system. + function bridgeRecoverFailedTransfer( + uint256 _chainId, + address _depositSender, + bytes32 _assetId, + bytes calldata _assetData + ) external; + + /// @dev Withdraw funds from the initiated deposit, that failed when finalizing on L2. + /// @param _chainId The ZK chain id to which deposit was initiated. + /// @param _depositSender The address of the entity that initiated the deposit. + /// @param _assetId The unique identifier of the deposited L1 token. + /// @param _assetData The encoded transfer data, which includes both the deposit amount and the address of the L2 receiver. Might include extra information. + /// @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. + /// @dev Processes claims of failed deposit, whether they originated from the legacy bridge or the current system. + function bridgeRecoverFailedTransfer( + uint256 _chainId, + address _depositSender, + bytes32 _assetId, + bytes memory _assetData, + bytes32 _l2TxHash, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes32[] calldata _merkleProof + ) external; + + /// @notice Transfers funds to Native Token Vault, if the asset is registered with it. Does nothing for ETH or non-registered tokens. + /// @dev assetId is not the padded address, but the correct encoded id (NTV stores respective format for IDs) + /// @param _amount The asset amount to be transferred to native token vault. + /// @param _originalCaller The `msg.sender` address from the external call that initiated current one. + function transferFundsToNTV(bytes32 _assetId, uint256 _amount, address _originalCaller) external returns (bool); + + /// @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; + + /// @notice Initiates a transfer transaction within Bridgehub, used by `requestL2TransactionTwoBridges`. + /// @param _chainId The chain ID of the ZK chain to which deposit. + /// @param _originalCaller The `msg.sender` address from the external call that initiated current one. + /// @param _value The `msg.value` on the target chain tx. + /// @param _data The calldata for the second bridge deposit. + /// @return request The data used by the bridgehub to create L2 transaction request to specific ZK chain. + /// @dev Data has the following abi encoding for legacy deposits: + /// address _l1Token, + /// uint256 _amount, + /// address _l2Receiver + /// for new deposits: + /// bytes32 _assetId, + /// bytes _transferData + function bridgehubDeposit( + uint256 _chainId, + address _originalCaller, + uint256 _value, + bytes calldata _data + ) external payable returns (L2TransactionRequestTwoBridgesInner memory request); + + /// @notice Generates a calldata for calling the deposit finalization on the L2 native token contract. + // / @param _chainId The chain ID of the ZK chain to which deposit. + /// @param _sender The address of the deposit initiator. + /// @param _assetId The deposited asset ID. + /// @param _assetData The encoded data, which is used by the asset handler to determine L2 recipient and amount. Might include extra information. + /// @return Returns calldata used on ZK chain. + function getDepositCalldata( + address _sender, + bytes32 _assetId, + bytes memory _assetData + ) external view returns (bytes memory); + + /// @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. + /// @param _chainId The chain ID of the ZK chain to which deposit. + /// @param _assetId The deposited asset ID. + /// @param _originalCaller The `msg.sender` address from the external call that initiated current one. + /// @param _amount The total amount of tokens to be bridged. + function bridgehubDepositBaseToken( + uint256 _chainId, + bytes32 _assetId, + address _originalCaller, + uint256 _amount + ) external payable; + + /// @notice Routes the confirmation to nullifier for backward compatibility. + /// @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. + /// @param _chainId The chain ID of the ZK chain to which confirm the deposit. + /// @param _txDataHash The keccak256 hash of 0x01 || abi.encode(bytes32, bytes) to identify deposits. + /// @param _txHash The hash of the L1->L2 transaction to confirm the deposit. + function bridgehubConfirmL2Transaction(uint256 _chainId, bytes32 _txDataHash, bytes32 _txHash) external; + + function isWithdrawalFinalized( + uint256 _chainId, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex + ) external view returns (bool); +} diff --git a/l1-contracts/contracts/bridge/asset-router/IL2AssetRouter.sol b/l1-contracts/contracts/bridge/asset-router/IL2AssetRouter.sol new file mode 100644 index 000000000..1c9b0d1ea --- /dev/null +++ b/l1-contracts/contracts/bridge/asset-router/IL2AssetRouter.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IAssetRouterBase} from "./IAssetRouterBase.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +interface IL2AssetRouter is IAssetRouterBase { + event WithdrawalInitiatedAssetRouter( + uint256 chainId, + address indexed l2Sender, + bytes32 indexed assetId, + bytes assetData + ); + + function withdraw(bytes32 _assetId, bytes calldata _transferData) external returns (bytes32); + + function L1_ASSET_ROUTER() external view returns (address); + + function withdrawLegacyBridge(address _l1Receiver, address _l2Token, uint256 _amount, address _sender) external; + + function finalizeDepositLegacyBridge( + address _l1Sender, + address _l2Receiver, + address _l1Token, + uint256 _amount, + bytes calldata _data + ) external; + + /// @dev Used to set the assetHandlerAddress for a given assetId. + /// @dev Will be used by ZK Gateway + function setAssetHandlerAddress(uint256 _originChainId, bytes32 _assetId, address _assetHandlerAddress) external; + + /// @notice Function that allows native token vault to register itself as the asset handler for + /// a legacy asset. + /// @param _assetId The assetId of the legacy token. + function setLegacyTokenAssetHandler(bytes32 _assetId) external; +} diff --git a/l1-contracts/contracts/bridge/asset-router/L1AssetRouter.sol b/l1-contracts/contracts/bridge/asset-router/L1AssetRouter.sol new file mode 100644 index 000000000..3ac8349a7 --- /dev/null +++ b/l1-contracts/contracts/bridge/asset-router/L1AssetRouter.sol @@ -0,0 +1,682 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +import {IL1AssetRouter} from "./IL1AssetRouter.sol"; +import {IL2AssetRouter} from "./IL2AssetRouter.sol"; +import {IAssetRouterBase, LEGACY_ENCODING_VERSION, NEW_ENCODING_VERSION, SET_ASSET_HANDLER_COUNTERPART_ENCODING_VERSION} from "./IAssetRouterBase.sol"; +import {AssetRouterBase} from "./AssetRouterBase.sol"; + +import {IL1AssetHandler} from "../interfaces/IL1AssetHandler.sol"; +import {IL1ERC20Bridge} from "../interfaces/IL1ERC20Bridge.sol"; +import {IAssetHandler} from "../interfaces/IAssetHandler.sol"; +import {IL1Nullifier} from "../interfaces/IL1Nullifier.sol"; +import {INativeTokenVault} from "../ntv/INativeTokenVault.sol"; +import {IL2SharedBridgeLegacyFunctions} from "../interfaces/IL2SharedBridgeLegacyFunctions.sol"; + +import {ReentrancyGuard} from "../../common/ReentrancyGuard.sol"; +import {DataEncoding} from "../../common/libraries/DataEncoding.sol"; +import {AddressAliasHelper} from "../../vendor/AddressAliasHelper.sol"; +import {TWO_BRIDGES_MAGIC_VALUE, ETH_TOKEN_ADDRESS} from "../../common/Config.sol"; +import {NativeTokenVaultAlreadySet} from "../L1BridgeContractErrors.sol"; +import {LegacyEncodingUsedForNonL1Token, LegacyBridgeUsesNonNativeToken, NonEmptyMsgValue, UnsupportedEncodingVersion, AssetIdNotSupported, AssetHandlerDoesNotExist, Unauthorized, ZeroAddress, TokenNotSupported, AddressAlreadyUsed, TokensWithFeesNotSupported} from "../../common/L1ContractErrors.sol"; +import {L2_ASSET_ROUTER_ADDR} from "../../common/L2ContractAddresses.sol"; + +import {IBridgehub, L2TransactionRequestTwoBridgesInner, L2TransactionRequestDirect} from "../../bridgehub/IBridgehub.sol"; + +import {IL1AssetDeploymentTracker} from "../interfaces/IL1AssetDeploymentTracker.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @dev Bridges assets between L1 and ZK chain, supporting both ETH and ERC20 tokens. +/// @dev Designed for use with a proxy for upgradability. +contract L1AssetRouter is AssetRouterBase, IL1AssetRouter, ReentrancyGuard { + using SafeERC20 for IERC20; + + /// @dev The address of the WETH token on L1. + address public immutable override L1_WETH_TOKEN; + + /// @dev The assetId of the base token. + bytes32 public immutable ETH_TOKEN_ASSET_ID; + + /// @dev The address of ZKsync Era diamond proxy contract. + address internal immutable ERA_DIAMOND_PROXY; + + /// @dev Address of nullifier. + IL1Nullifier public immutable L1_NULLIFIER; + + /// @dev Address of native token vault. + INativeTokenVault public nativeTokenVault; + + /// @dev Address of legacy bridge. + IL1ERC20Bridge public legacyBridge; + + /// @notice Checks that the message sender is the nullifier. + modifier onlyNullifier() { + if (msg.sender != address(L1_NULLIFIER)) { + revert Unauthorized(msg.sender); + } + _; + } + + /// @notice Checks that the message sender is the bridgehub or ZKsync Era Diamond Proxy. + modifier onlyBridgehubOrEra(uint256 _chainId) { + if (msg.sender != address(BRIDGE_HUB) && (_chainId != ERA_CHAIN_ID || msg.sender != ERA_DIAMOND_PROXY)) { + revert Unauthorized(msg.sender); + } + _; + } + + /// @notice Checks that the message sender is the legacy bridge. + modifier onlyLegacyBridge() { + if (msg.sender != address(legacyBridge)) { + revert Unauthorized(msg.sender); + } + _; + } + + /// @notice Checks that the message sender is the native token vault. + modifier onlyNativeTokenVault() { + if (msg.sender != address(nativeTokenVault)) { + revert Unauthorized(msg.sender); + } + _; + } + + /// @dev Contract is expected to be used as proxy implementation. + /// @dev Initialize the implementation to prevent Parity hack. + constructor( + address _l1WethAddress, + address _bridgehub, + address _l1Nullifier, + uint256 _eraChainId, + address _eraDiamondProxy + ) reentrancyGuardInitializer AssetRouterBase(block.chainid, _eraChainId, IBridgehub(_bridgehub)) { + _disableInitializers(); + L1_WETH_TOKEN = _l1WethAddress; + ERA_DIAMOND_PROXY = _eraDiamondProxy; + L1_NULLIFIER = IL1Nullifier(_l1Nullifier); + ETH_TOKEN_ASSET_ID = DataEncoding.encodeNTVAssetId(block.chainid, ETH_TOKEN_ADDRESS); + } + + /// @dev Initializes a contract bridge for later use. Expected to be used in the proxy. + /// @dev Used for testing purposes only, as the contract has been initialized on mainnet. + /// @param _owner The 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) external reentrancyGuardInitializer initializer { + if (_owner == address(0)) { + revert ZeroAddress(); + } + _transferOwnership(_owner); + } + + /// @notice Sets the NativeTokenVault contract address. + /// @dev Should be called only once by the owner. + /// @param _nativeTokenVault The address of the native token vault. + function setNativeTokenVault(INativeTokenVault _nativeTokenVault) external onlyOwner { + if (address(nativeTokenVault) != address(0)) { + revert NativeTokenVaultAlreadySet(); + } + if (address(_nativeTokenVault) == address(0)) { + revert ZeroAddress(); + } + nativeTokenVault = _nativeTokenVault; + bytes32 ethAssetId = DataEncoding.encodeNTVAssetId(block.chainid, ETH_TOKEN_ADDRESS); + _setAssetHandler(ethAssetId, address(_nativeTokenVault)); + } + + /// @notice Sets the L1ERC20Bridge contract address. + /// @dev Should be called only once by the owner. + /// @param _legacyBridge The address of the legacy bridge. + function setL1Erc20Bridge(IL1ERC20Bridge _legacyBridge) external onlyOwner { + if (address(legacyBridge) != address(0)) { + revert AddressAlreadyUsed(address(legacyBridge)); + } + if (address(_legacyBridge) == address(0)) { + revert ZeroAddress(); + } + legacyBridge = _legacyBridge; + } + + /// @notice Used to set the assed deployment tracker address for given asset data. + /// @param _assetRegistrationData The asset data which may include the asset address and any additional required data or encodings. + /// @param _assetDeploymentTracker The whitelisted address of asset deployment tracker for provided asset. + function setAssetDeploymentTracker( + bytes32 _assetRegistrationData, + address _assetDeploymentTracker + ) external onlyOwner { + bytes32 assetId = DataEncoding.encodeAssetId(block.chainid, _assetRegistrationData, _assetDeploymentTracker); + assetDeploymentTracker[assetId] = _assetDeploymentTracker; + emit AssetDeploymentTrackerSet(assetId, _assetDeploymentTracker, _assetRegistrationData); + } + + /// @inheritdoc IAssetRouterBase + function setAssetHandlerAddressThisChain( + bytes32 _assetRegistrationData, + address _assetHandlerAddress + ) external override(AssetRouterBase, IAssetRouterBase) { + _setAssetHandlerAddressThisChain(address(nativeTokenVault), _assetRegistrationData, _assetHandlerAddress); + } + + /// @notice Used to set the asset handler address for a given asset ID on a remote ZK chain + /// @param _chainId The ZK chain ID. + /// @param _originalCaller The `msg.sender` address from the external call that initiated current one. + /// @param _assetId The encoding of asset ID. + /// @param _assetHandlerAddressOnCounterpart The address of the asset handler, which will hold the token of interest. + /// @return request The tx request sent to the Bridgehub + function _setAssetHandlerAddressOnCounterpart( + uint256 _chainId, + address _originalCaller, + bytes32 _assetId, + address _assetHandlerAddressOnCounterpart + ) internal view returns (L2TransactionRequestTwoBridgesInner memory request) { + IL1AssetDeploymentTracker(assetDeploymentTracker[_assetId]).bridgeCheckCounterpartAddress( + _chainId, + _assetId, + _originalCaller, + _assetHandlerAddressOnCounterpart + ); + + bytes memory l2Calldata = abi.encodeCall( + IL2AssetRouter.setAssetHandlerAddress, + (block.chainid, _assetId, _assetHandlerAddressOnCounterpart) + ); + request = L2TransactionRequestTwoBridgesInner({ + magicValue: TWO_BRIDGES_MAGIC_VALUE, + l2Contract: L2_ASSET_ROUTER_ADDR, + l2Calldata: l2Calldata, + factoryDeps: new bytes[](0), + txDataHash: bytes32(0x00) + }); + } + + /*////////////////////////////////////////////////////////////// + INITIATTE DEPOSIT Functions + //////////////////////////////////////////////////////////////*/ + + /// @inheritdoc IL1AssetRouter + function bridgehubDepositBaseToken( + uint256 _chainId, + bytes32 _assetId, + address _originalCaller, + uint256 _amount + ) public payable virtual override onlyBridgehubOrEra(_chainId) whenNotPaused { + address assetHandler = assetHandlerAddress[_assetId]; + if (assetHandler == address(0)) { + revert AssetHandlerDoesNotExist(_assetId); + } + + // slither-disable-next-line unused-return + IAssetHandler(assetHandler).bridgeBurn{value: msg.value}({ + _chainId: _chainId, + _msgValue: 0, + _assetId: _assetId, + _originalCaller: _originalCaller, + _data: DataEncoding.encodeBridgeBurnData(_amount, address(0), address(0)) + }); + + // 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, _originalCaller, _assetId, _amount); + } + + /// @inheritdoc IL1AssetRouter + function bridgehubDeposit( + uint256 _chainId, + address _originalCaller, + uint256 _value, + bytes calldata _data + ) + external + payable + virtual + override + onlyBridgehub + whenNotPaused + returns (L2TransactionRequestTwoBridgesInner memory request) + { + bytes32 assetId; + bytes memory transferData; + bytes1 encodingVersion = _data[0]; + // The new encoding ensures that the calldata is collision-resistant with respect to the legacy format. + // In the legacy calldata, the first input was the address, meaning the most significant byte was always `0x00`. + if (encodingVersion == SET_ASSET_HANDLER_COUNTERPART_ENCODING_VERSION) { + if (msg.value != 0 || _value != 0) { + revert NonEmptyMsgValue(); + } + + (bytes32 _assetId, address _assetHandlerAddressOnCounterpart) = abi.decode(_data[1:], (bytes32, address)); + return + _setAssetHandlerAddressOnCounterpart( + _chainId, + _originalCaller, + _assetId, + _assetHandlerAddressOnCounterpart + ); + } else if (encodingVersion == NEW_ENCODING_VERSION) { + (assetId, transferData) = abi.decode(_data[1:], (bytes32, bytes)); + } else if (encodingVersion == LEGACY_ENCODING_VERSION) { + (assetId, transferData) = _handleLegacyData(_data, _originalCaller); + } else { + revert UnsupportedEncodingVersion(); + } + + if (BRIDGE_HUB.baseTokenAssetId(_chainId) == assetId) { + revert AssetIdNotSupported(assetId); + } + + address ntvCached = address(nativeTokenVault); + + bytes memory bridgeMintCalldata = _burn({ + _chainId: _chainId, + _nextMsgValue: _value, + _assetId: assetId, + _originalCaller: _originalCaller, + _transferData: transferData, + _passValue: true, + _nativeTokenVault: ntvCached + }); + + bytes32 txDataHash = DataEncoding.encodeTxDataHash({ + _nativeTokenVault: ntvCached, + _encodingVersion: encodingVersion, + _originalCaller: _originalCaller, + _assetId: assetId, + _transferData: transferData + }); + + request = _requestToBridge({ + _originalCaller: _originalCaller, + _assetId: assetId, + _bridgeMintCalldata: bridgeMintCalldata, + _txDataHash: txDataHash + }); + + emit BridgehubDepositInitiated({ + chainId: _chainId, + txDataHash: txDataHash, + from: _originalCaller, + assetId: assetId, + bridgeMintCalldata: bridgeMintCalldata + }); + } + + /// @inheritdoc IL1AssetRouter + function bridgehubConfirmL2Transaction( + uint256 _chainId, + bytes32 _txDataHash, + bytes32 _txHash + ) external override onlyBridgehub whenNotPaused { + L1_NULLIFIER.bridgehubConfirmL2TransactionForwarded(_chainId, _txDataHash, _txHash); + } + + /*////////////////////////////////////////////////////////////// + Receive transaction Functions + //////////////////////////////////////////////////////////////*/ + + /// @inheritdoc IAssetRouterBase + function finalizeDeposit( + uint256 _chainId, + bytes32 _assetId, + bytes calldata _transferData + ) public payable override(AssetRouterBase, IAssetRouterBase) onlyNullifier { + _finalizeDeposit(_chainId, _assetId, _transferData, address(nativeTokenVault)); + emit DepositFinalizedAssetRouter(_chainId, _assetId, _transferData); + } + + /*////////////////////////////////////////////////////////////// + CLAIM FAILED DEPOSIT Functions + //////////////////////////////////////////////////////////////*/ + + /// @inheritdoc IL1AssetRouter + function bridgeRecoverFailedTransfer( + uint256 _chainId, + address _depositSender, + bytes32 _assetId, + bytes calldata _assetData + ) external override onlyNullifier nonReentrant whenNotPaused { + IL1AssetHandler(assetHandlerAddress[_assetId]).bridgeRecoverFailedTransfer( + _chainId, + _assetId, + _depositSender, + _assetData + ); + + emit ClaimedFailedDepositAssetRouter(_chainId, _assetId, _assetData); + } + + function bridgeRecoverFailedTransfer( + uint256 _chainId, + address _depositSender, + bytes32 _assetId, + bytes calldata _assetData, + bytes32 _l2TxHash, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes32[] calldata _merkleProof + ) external { + L1_NULLIFIER.bridgeRecoverFailedTransfer({ + _chainId: _chainId, + _depositSender: _depositSender, + _assetId: _assetId, + _assetData: _assetData, + _l2TxHash: _l2TxHash, + _l2BatchNumber: _l2BatchNumber, + _l2MessageIndex: _l2MessageIndex, + _l2TxNumberInBatch: _l2TxNumberInBatch, + _merkleProof: _merkleProof + }); + } + + /*////////////////////////////////////////////////////////////// + Internal & Helpers + //////////////////////////////////////////////////////////////*/ + + /// @notice Decodes the transfer input for legacy data and transfers allowance to NTV. + /// @dev Is not applicable for custom asset handlers. + /// @param _data The encoded transfer data (address _l1Token, uint256 _depositAmount, address _l2Receiver). + /// @return Tuple of asset ID and encoded transfer data to conform with new encoding standard. + function _handleLegacyData(bytes calldata _data, address) internal returns (bytes32, bytes memory) { + (address _l1Token, uint256 _depositAmount, address _l2Receiver) = abi.decode( + _data, + (address, uint256, address) + ); + bytes32 assetId = _ensureTokenRegisteredWithNTV(_l1Token); + + // We ensure that the legacy data format can not be used for tokens that did not originate from L1. + bytes32 expectedAssetId = DataEncoding.encodeNTVAssetId(block.chainid, _l1Token); + if (assetId != expectedAssetId) { + revert LegacyEncodingUsedForNonL1Token(); + } + + if (assetId == ETH_TOKEN_ASSET_ID) { + // In the old SDK/contracts the user had to always provide `0` as the deposit amount for ETH token, while + // ultimately the provided `msg.value` was used as the deposit amount. This check is needed for backwards compatibility. + + if (_depositAmount == 0) { + _depositAmount = msg.value; + } + } + + return (assetId, DataEncoding.encodeBridgeBurnData(_depositAmount, _l2Receiver, _l1Token)); + } + + /// @notice Ensures that token is registered with native token vault. + /// @dev Only used when deposit is made with legacy data encoding format. + /// @param _token The native token address which should be registered with native token vault. + /// @return assetId The asset ID of the token provided. + function _ensureTokenRegisteredWithNTV(address _token) internal override returns (bytes32 assetId) { + assetId = nativeTokenVault.assetId(_token); + if (assetId != bytes32(0)) { + return assetId; + } + nativeTokenVault.ensureTokenIsRegistered(_token); + assetId = nativeTokenVault.assetId(_token); + } + + /// @inheritdoc IL1AssetRouter + function transferFundsToNTV( + bytes32 _assetId, + uint256 _amount, + address _originalCaller + ) external onlyNativeTokenVault returns (bool) { + address l1TokenAddress = INativeTokenVault(address(nativeTokenVault)).tokenAddress(_assetId); + if (l1TokenAddress == address(0) || l1TokenAddress == ETH_TOKEN_ADDRESS) { + return false; + } + IERC20 l1Token = IERC20(l1TokenAddress); + + // Do the transfer if allowance to Shared bridge is bigger than amount + // And if there is not enough allowance for the NTV + bool weCanTransfer = false; + if (l1Token.allowance(address(legacyBridge), address(this)) >= _amount) { + _originalCaller = address(legacyBridge); + weCanTransfer = true; + } else if ( + l1Token.allowance(_originalCaller, address(this)) >= _amount && + l1Token.allowance(_originalCaller, address(nativeTokenVault)) < _amount + ) { + weCanTransfer = true; + } + if (weCanTransfer) { + uint256 balanceBefore = l1Token.balanceOf(address(nativeTokenVault)); + // slither-disable-next-line arbitrary-send-erc20 + l1Token.safeTransferFrom(_originalCaller, address(nativeTokenVault), _amount); + uint256 balanceAfter = l1Token.balanceOf(address(nativeTokenVault)); + + if (balanceAfter - balanceBefore != _amount) { + revert TokensWithFeesNotSupported(); + } + return true; + } + return false; + } + + /// @dev The request data that is passed to the bridgehub. + /// @param _originalCaller The `msg.sender` address from the external call that initiated current one. + /// @param _assetId The deposited asset ID. + /// @param _bridgeMintCalldata The calldata used by remote asset handler to mint tokens for recipient. + /// @param _txDataHash The keccak256 hash of 0x01 || abi.encode(bytes32, bytes) to identify deposits. + /// @return request The data used by the bridgehub to create L2 transaction request to specific ZK chain. + function _requestToBridge( + address _originalCaller, + bytes32 _assetId, + bytes memory _bridgeMintCalldata, + bytes32 _txDataHash + ) internal view virtual returns (L2TransactionRequestTwoBridgesInner memory request) { + bytes memory l2TxCalldata = getDepositCalldata(_originalCaller, _assetId, _bridgeMintCalldata); + + request = L2TransactionRequestTwoBridgesInner({ + magicValue: TWO_BRIDGES_MAGIC_VALUE, + l2Contract: L2_ASSET_ROUTER_ADDR, + l2Calldata: l2TxCalldata, + factoryDeps: new bytes[](0), + txDataHash: _txDataHash + }); + } + + /// @inheritdoc IL1AssetRouter + function getDepositCalldata( + address _sender, + bytes32 _assetId, + bytes memory _assetData + ) public view override returns (bytes memory) { + // First branch covers the case when asset is not registered with NTV (custom asset handler) + // Second branch handles tokens registered with NTV and uses legacy calldata encoding + // We need to use the legacy encoding to support the old SDK, which relies on a specific encoding of the data. + if ( + (nativeTokenVault.tokenAddress(_assetId) == address(0)) || + (nativeTokenVault.originChainId(_assetId) != block.chainid) + ) { + return abi.encodeCall(IAssetRouterBase.finalizeDeposit, (block.chainid, _assetId, _assetData)); + } else { + // slither-disable-next-line unused-return + (, address _receiver, address _parsedNativeToken, uint256 _amount, bytes memory _gettersData) = DataEncoding + .decodeBridgeMintData(_assetData); + return + _getLegacyNTVCalldata({ + _sender: _sender, + _receiver: _receiver, + _parsedNativeToken: _parsedNativeToken, + _amount: _amount, + _gettersData: _gettersData + }); + } + } + + function _getLegacyNTVCalldata( + address _sender, + address _receiver, + address _parsedNativeToken, + uint256 _amount, + bytes memory _gettersData + ) internal pure returns (bytes memory) { + return + abi.encodeCall( + IL2SharedBridgeLegacyFunctions.finalizeDeposit, + (_sender, _receiver, _parsedNativeToken, _amount, _gettersData) + ); + } + + /*////////////////////////////////////////////////////////////// + Legacy Functions + //////////////////////////////////////////////////////////////*/ + + /// @inheritdoc IL1AssetRouter + function depositLegacyErc20Bridge( + address _originalCaller, + address _l2Receiver, + address _l1Token, + uint256 _amount, + uint256 _l2TxGasLimit, + uint256 _l2TxGasPerPubdataByte, + address _refundRecipient + ) external payable override onlyLegacyBridge nonReentrant whenNotPaused returns (bytes32 txHash) { + if (_l1Token == L1_WETH_TOKEN) { + revert TokenNotSupported(L1_WETH_TOKEN); + } + + bytes32 _assetId; + { + // Note, that to keep the code simple, while avoiding "stack too deep" error, + // this `bridgeData` variable is reused in two places with different meanings: + // - Firstly, it denotes the bridgeBurn data to be used for the NativeTokenVault + // - Secondly, after the call to `_burn` function, it denotes the `bridgeMint` data that + // will be sent to the L2 counterpart of the L1NTV. + bytes memory bridgeData = DataEncoding.encodeBridgeBurnData(_amount, _l2Receiver, _l1Token); + // Inner call to encode data to decrease local var numbers + _assetId = _ensureTokenRegisteredWithNTV(_l1Token); + // Legacy bridge is only expected to use native tokens for L1. + if (_assetId != DataEncoding.encodeNTVAssetId(block.chainid, _l1Token)) { + revert LegacyBridgeUsesNonNativeToken(); + } + + // Note, that starting from here `bridgeData` starts denoting bridgeMintData. + bridgeData = _burn({ + _chainId: ERA_CHAIN_ID, + _nextMsgValue: 0, + _assetId: _assetId, + _originalCaller: _originalCaller, + _transferData: bridgeData, + _passValue: false, + _nativeTokenVault: address(nativeTokenVault) + }); + + bytes memory l2TxCalldata = getDepositCalldata(_originalCaller, _assetId, bridgeData); + + // 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 = AddressAliasHelper.actualRefundRecipient(_refundRecipient, _originalCaller); + + L2TransactionRequestDirect memory request = L2TransactionRequestDirect({ + chainId: ERA_CHAIN_ID, + l2Contract: L2_ASSET_ROUTER_ADDR, + 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 + }); + txHash = BRIDGE_HUB.requestL2TransactionDirect{value: msg.value}(request); + } + + { + bytes memory transferData = DataEncoding.encodeBridgeBurnData(_amount, _l2Receiver, _l1Token); + // Save the deposited amount to claim funds on L1 if the deposit failed on L2 + L1_NULLIFIER.bridgehubConfirmL2TransactionForwarded( + ERA_CHAIN_ID, + DataEncoding.encodeTxDataHash({ + _encodingVersion: LEGACY_ENCODING_VERSION, + _originalCaller: _originalCaller, + _assetId: _assetId, + _nativeTokenVault: address(nativeTokenVault), + _transferData: transferData + }), + txHash + ); + } + + emit LegacyDepositInitiated({ + chainId: ERA_CHAIN_ID, + l2DepositTxHash: txHash, + from: _originalCaller, + to: _l2Receiver, + l1Token: _l1Token, + amount: _amount + }); + } + + /// @inheritdoc IL1AssetRouter + function finalizeWithdrawal( + uint256 _chainId, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes calldata _message, + bytes32[] calldata _merkleProof + ) external override { + L1_NULLIFIER.finalizeWithdrawal({ + _chainId: _chainId, + _l2BatchNumber: _l2BatchNumber, + _l2MessageIndex: _l2MessageIndex, + _l2TxNumberInBatch: _l2TxNumberInBatch, + _message: _message, + _merkleProof: _merkleProof + }); + } + + /// @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 { + L1_NULLIFIER.claimFailedDeposit({ + _chainId: _chainId, + _depositSender: _depositSender, + _l1Token: _l1Token, + _amount: _amount, + _l2TxHash: _l2TxHash, + _l2BatchNumber: _l2BatchNumber, + _l2MessageIndex: _l2MessageIndex, + _l2TxNumberInBatch: _l2TxNumberInBatch, + _merkleProof: _merkleProof + }); + } + + /// @notice Legacy read method, which forwards the call to L1Nullifier to check if withdrawal was finalized + function isWithdrawalFinalized( + uint256 _chainId, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex + ) external view returns (bool) { + return L1_NULLIFIER.isWithdrawalFinalized(_chainId, _l2BatchNumber, _l2MessageIndex); + } + + /// @notice Legacy function to get the L2 shared bridge address for a chain. + /// @dev In case the chain has been deployed after the gateway release, + /// the returned value is 0. + function l2BridgeAddress(uint256 _chainId) external view override returns (address) { + return L1_NULLIFIER.l2BridgeAddress(_chainId); + } +} diff --git a/l1-contracts/contracts/bridge/asset-router/L2AssetRouter.sol b/l1-contracts/contracts/bridge/asset-router/L2AssetRouter.sol new file mode 100644 index 000000000..d415618e7 --- /dev/null +++ b/l1-contracts/contracts/bridge/asset-router/L2AssetRouter.sol @@ -0,0 +1,367 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IL2AssetRouter} from "./IL2AssetRouter.sol"; +import {IAssetRouterBase} from "./IAssetRouterBase.sol"; +import {AssetRouterBase} from "./AssetRouterBase.sol"; + +import {IL2NativeTokenVault} from "../ntv/IL2NativeTokenVault.sol"; +import {IL2SharedBridgeLegacy} from "../interfaces/IL2SharedBridgeLegacy.sol"; +import {IBridgedStandardToken} from "../interfaces/IBridgedStandardToken.sol"; +import {IL1ERC20Bridge} from "../interfaces/IL1ERC20Bridge.sol"; + +import {IBridgehub} from "../../bridgehub/IBridgehub.sol"; +import {AddressAliasHelper} from "../../vendor/AddressAliasHelper.sol"; +import {ReentrancyGuard} from "../../common/ReentrancyGuard.sol"; + +import {L2_NATIVE_TOKEN_VAULT_ADDR, L2_BRIDGEHUB_ADDR} from "../../common/L2ContractAddresses.sol"; +import {L2ContractHelper} from "../../common/libraries/L2ContractHelper.sol"; +import {DataEncoding} from "../../common/libraries/DataEncoding.sol"; +import {TokenNotLegacy, EmptyAddress, InvalidCaller, AmountMustBeGreaterThanZero, AssetIdNotSupported} from "../../common/L1ContractErrors.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 L2AssetRouter is AssetRouterBase, IL2AssetRouter, ReentrancyGuard { + /// @dev The address of the L2 legacy shared bridge. + address public immutable L2_LEGACY_SHARED_BRIDGE; + + /// @dev The asset id of the base token. + bytes32 public immutable BASE_TOKEN_ASSET_ID; + + /// @dev The address of the L1 asset router counterpart. + address public immutable override L1_ASSET_ROUTER; + + /// @notice Checks that the message sender is the L1 Asset Router. + modifier onlyAssetRouterCounterpart(uint256 _originChainId) { + if (_originChainId == L1_CHAIN_ID) { + // Only the L1 Asset Router counterpart can initiate and finalize the deposit. + if (AddressAliasHelper.undoL1ToL2Alias(msg.sender) != L1_ASSET_ROUTER) { + revert InvalidCaller(msg.sender); + } + } else { + revert InvalidCaller(msg.sender); // xL2 messaging not supported for now + } + _; + } + + /// @notice Checks that the message sender is the L1 Asset Router. + modifier onlyAssetRouterCounterpartOrSelf(uint256 _chainId) { + if (_chainId == L1_CHAIN_ID) { + // Only the L1 Asset Router counterpart can initiate and finalize the deposit. + if ((AddressAliasHelper.undoL1ToL2Alias(msg.sender) != L1_ASSET_ROUTER) && (msg.sender != address(this))) { + revert InvalidCaller(msg.sender); + } + } else { + revert InvalidCaller(msg.sender); // xL2 messaging not supported for now + } + _; + } + + /// @notice Checks that the message sender is the legacy L2 bridge. + modifier onlyLegacyBridge() { + if (msg.sender != L2_LEGACY_SHARED_BRIDGE) { + revert InvalidCaller(msg.sender); + } + _; + } + + modifier onlyNTV() { + if (msg.sender != L2_NATIVE_TOKEN_VAULT_ADDR) { + revert InvalidCaller(msg.sender); + } + _; + } + + /// @dev Disable the initialization to prevent Parity hack. + /// @dev this contract is deployed in the L2GenesisUpgrade, and is meant as direct deployment without a proxy. + /// @param _l1AssetRouter The address of the L1 Bridge contract. + constructor( + uint256 _l1ChainId, + uint256 _eraChainId, + address _l1AssetRouter, + address _legacySharedBridge, + bytes32 _baseTokenAssetId, + address _aliasedOwner + ) AssetRouterBase(_l1ChainId, _eraChainId, IBridgehub(L2_BRIDGEHUB_ADDR)) reentrancyGuardInitializer { + L2_LEGACY_SHARED_BRIDGE = _legacySharedBridge; + if (_l1AssetRouter == address(0)) { + revert EmptyAddress(); + } + L1_ASSET_ROUTER = _l1AssetRouter; + _setAssetHandler(_baseTokenAssetId, L2_NATIVE_TOKEN_VAULT_ADDR); + BASE_TOKEN_ASSET_ID = _baseTokenAssetId; + _disableInitializers(); + _transferOwnership(_aliasedOwner); + } + + /// @inheritdoc IL2AssetRouter + function setAssetHandlerAddress( + uint256 _originChainId, + bytes32 _assetId, + address _assetHandlerAddress + ) external override onlyAssetRouterCounterpart(_originChainId) { + _setAssetHandler(_assetId, _assetHandlerAddress); + } + + /// @inheritdoc IAssetRouterBase + function setAssetHandlerAddressThisChain( + bytes32 _assetRegistrationData, + address _assetHandlerAddress + ) external override(AssetRouterBase, IAssetRouterBase) { + _setAssetHandlerAddressThisChain(L2_NATIVE_TOKEN_VAULT_ADDR, _assetRegistrationData, _assetHandlerAddress); + } + + function setLegacyTokenAssetHandler(bytes32 _assetId) external override onlyNTV { + // Note, that it is an asset handler, but not asset deployment tracker, + // which is located on L1. + _setAssetHandler(_assetId, L2_NATIVE_TOKEN_VAULT_ADDR); + } + + /*////////////////////////////////////////////////////////////// + Receive transaction Functions + //////////////////////////////////////////////////////////////*/ + + /// @notice Finalize the deposit and mint funds + /// @param _assetId The encoding of the asset on L2 + /// @param _transferData The encoded data required for deposit (address _l1Sender, uint256 _amount, address _l2Receiver, bytes memory erc20Data, address originToken) + function finalizeDeposit( + // solhint-disable-next-line no-unused-vars + uint256, + bytes32 _assetId, + bytes calldata _transferData + ) + public + payable + override(AssetRouterBase, IAssetRouterBase) + onlyAssetRouterCounterpartOrSelf(L1_CHAIN_ID) + nonReentrant + { + if (_assetId == BASE_TOKEN_ASSET_ID) { + revert AssetIdNotSupported(BASE_TOKEN_ASSET_ID); + } + _finalizeDeposit(L1_CHAIN_ID, _assetId, _transferData, L2_NATIVE_TOKEN_VAULT_ADDR); + + emit DepositFinalizedAssetRouter(L1_CHAIN_ID, _assetId, _transferData); + } + + /// @notice Initiates a withdrawal by burning funds on the contract and sending the message to L1 + /// where tokens would be unlocked + /// @dev IMPORTANT: this method will be deprecated in one of the future releases, so contracts + /// that rely on it must be upgradeable. + /// @param _assetId The asset id of the withdrawn asset + /// @param _assetData The data that is passed to the asset handler contract + function withdraw(bytes32 _assetId, bytes memory _assetData) public override nonReentrant returns (bytes32) { + return _withdrawSender(_assetId, _assetData, msg.sender, true); + } + + /*////////////////////////////////////////////////////////////// + Internal & Helpers + //////////////////////////////////////////////////////////////*/ + + /// @inheritdoc AssetRouterBase + function _ensureTokenRegisteredWithNTV(address _token) internal override returns (bytes32 assetId) { + IL2NativeTokenVault nativeTokenVault = IL2NativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR); + nativeTokenVault.ensureTokenIsRegistered(_token); + assetId = nativeTokenVault.assetId(_token); + } + + /// @param _assetId The asset id of the withdrawn asset + /// @param _assetData The data that is passed to the asset handler contract + /// @param _sender The address of the sender of the message + /// @param _alwaysNewMessageFormat Whether to use the new message format compatible with Custom Asset Handlers + function _withdrawSender( + bytes32 _assetId, + bytes memory _assetData, + address _sender, + bool _alwaysNewMessageFormat + ) internal returns (bytes32 txHash) { + bytes memory l1bridgeMintData = _burn({ + _chainId: L1_CHAIN_ID, + _nextMsgValue: 0, + _assetId: _assetId, + _originalCaller: _sender, + _transferData: _assetData, + _passValue: false, + _nativeTokenVault: L2_NATIVE_TOKEN_VAULT_ADDR + }); + + bytes memory message; + if (_alwaysNewMessageFormat || L2_LEGACY_SHARED_BRIDGE == address(0)) { + message = _getAssetRouterWithdrawMessage(_assetId, l1bridgeMintData); + // slither-disable-next-line unused-return + txHash = L2ContractHelper.sendMessageToL1(message); + } else { + address l1Token = IBridgedStandardToken( + IL2NativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR).tokenAddress(_assetId) + ).originToken(); + if (l1Token == address(0)) { + revert AssetIdNotSupported(_assetId); + } + // slither-disable-next-line unused-return + (uint256 amount, address l1Receiver, ) = DataEncoding.decodeBridgeBurnData(_assetData); + message = _getSharedBridgeWithdrawMessage(l1Receiver, l1Token, amount); + txHash = IL2SharedBridgeLegacy(L2_LEGACY_SHARED_BRIDGE).sendMessageToL1(message); + } + + emit WithdrawalInitiatedAssetRouter(L1_CHAIN_ID, _sender, _assetId, _assetData); + } + + /// @notice Encodes the message for l2ToL1log sent during withdraw initialization. + /// @param _assetId The encoding of the asset on L2 which is withdrawn. + /// @param _l1bridgeMintData The calldata used by l1 asset handler to unlock tokens for recipient. + function _getAssetRouterWithdrawMessage( + bytes32 _assetId, + bytes memory _l1bridgeMintData + ) internal view returns (bytes memory) { + // solhint-disable-next-line func-named-parameters + return abi.encodePacked(IAssetRouterBase.finalizeDeposit.selector, block.chainid, _assetId, _l1bridgeMintData); + } + + /// @notice Encodes the message for l2ToL1log sent during withdraw initialization. + function _getSharedBridgeWithdrawMessage( + address _l1Receiver, + address _l1Token, + uint256 _amount + ) internal pure returns (bytes memory) { + // solhint-disable-next-line func-named-parameters + return abi.encodePacked(IL1ERC20Bridge.finalizeWithdrawal.selector, _l1Receiver, _l1Token, _amount); + } + + /*////////////////////////////////////////////////////////////// + LEGACY FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @notice Legacy finalizeDeposit. + /// @dev Finalizes the deposit and mint funds. + /// @param _l1Sender The address of token sender on L1. + /// @param _l2Receiver The address of token receiver on L2. + /// @param _l1Token The address of the token transferred. + /// @param _amount The amount of the token transferred. + /// @param _data The metadata of the token transferred. + function finalizeDeposit( + address _l1Sender, + address _l2Receiver, + address _l1Token, + uint256 _amount, + bytes calldata _data + ) external payable onlyAssetRouterCounterpart(L1_CHAIN_ID) { + _translateLegacyFinalizeDeposit({ + _l1Sender: _l1Sender, + _l2Receiver: _l2Receiver, + _l1Token: _l1Token, + _amount: _amount, + _data: _data + }); + } + + function finalizeDepositLegacyBridge( + address _l1Sender, + address _l2Receiver, + address _l1Token, + uint256 _amount, + bytes calldata _data + ) external onlyLegacyBridge { + _translateLegacyFinalizeDeposit({ + _l1Sender: _l1Sender, + _l2Receiver: _l2Receiver, + _l1Token: _l1Token, + _amount: _amount, + _data: _data + }); + } + + function _translateLegacyFinalizeDeposit( + address _l1Sender, + address _l2Receiver, + address _l1Token, + uint256 _amount, + bytes calldata _data + ) internal { + bytes32 assetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, _l1Token); + // solhint-disable-next-line func-named-parameters + bytes memory data = DataEncoding.encodeBridgeMintData(_l1Sender, _l2Receiver, _l1Token, _amount, _data); + this.finalizeDeposit{value: msg.value}(L1_CHAIN_ID, assetId, data); + } + + /// @notice Initiates a withdrawal by burning funds on the contract and sending the message to L1 + /// where tokens would be unlocked + /// @dev A compatibility method to support legacy functionality for the SDK. + /// @param _l1Receiver The account address that should receive funds on L1 + /// @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 nonReentrant { + if (_amount == 0) { + revert AmountMustBeGreaterThanZero(); + } + _withdrawLegacy(_l1Receiver, _l2Token, _amount, msg.sender); + } + + /// @notice Legacy withdraw. + /// @dev Finalizes the deposit and mint funds. + /// @param _l1Receiver The address of token receiver on L1. + /// @param _l2Token The address of token on L2. + /// @param _amount The amount of the token transferred. + /// @param _sender The original msg.sender. + function withdrawLegacyBridge( + address _l1Receiver, + address _l2Token, + uint256 _amount, + address _sender + ) external onlyLegacyBridge nonReentrant { + _withdrawLegacy(_l1Receiver, _l2Token, _amount, _sender); + } + + function _withdrawLegacy(address _l1Receiver, address _l2Token, uint256 _amount, address _sender) internal { + address l1Address = l1TokenAddress(_l2Token); + if (l1Address == address(0)) { + revert TokenNotLegacy(); + } + bytes32 assetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, l1Address); + bytes memory data = DataEncoding.encodeBridgeBurnData(_amount, _l1Receiver, _l2Token); + _withdrawSender(assetId, data, _sender, false); + } + + /// @notice Legacy getL1TokenAddress. + /// @param _l2Token The address of token on L2. + /// @return The address of token on L1. + function l1TokenAddress(address _l2Token) public view returns (address) { + bytes32 assetId = IL2NativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR).assetId(_l2Token); + if (assetId == bytes32(0)) { + return address(0); + } + uint256 originChainId = IL2NativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR).originChainId(assetId); + if (originChainId != L1_CHAIN_ID) { + return address(0); + } + + return IBridgedStandardToken(_l2Token).originToken(); + } + + /// @notice Legacy function used for backward compatibility to return L2 wrapped token + /// @notice address corresponding to provided L1 token address and deployed through NTV. + /// @dev However, the shared bridge can use custom asset handlers such that L2 addresses differ, + /// @dev or an L1 token may not have an L2 counterpart. + /// @param _l1Token The address of token on L1. + /// @return Address of an L2 token counterpart + function l2TokenAddress(address _l1Token) public view returns (address) { + IL2NativeTokenVault l2NativeTokenVault = IL2NativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR); + address currentlyDeployedAddress = l2NativeTokenVault.l2TokenAddress(_l1Token); + + if (currentlyDeployedAddress != address(0)) { + return currentlyDeployedAddress; + } + + // For backwards compatibility, the bridge smust return the address of the token even if it + // has not been deployed yet. + return l2NativeTokenVault.calculateCreate2TokenAddress(L1_CHAIN_ID, _l1Token); + } + + /// @notice Returns the address of the L1 asset router. + /// @dev The old name is kept for backward compatibility. + function l1Bridge() external view returns (address) { + return L1_ASSET_ROUTER; + } +} diff --git a/l1-contracts/contracts/bridge/interfaces/AssetHandlerModifiers.sol b/l1-contracts/contracts/bridge/interfaces/AssetHandlerModifiers.sol new file mode 100644 index 000000000..e08cb2dff --- /dev/null +++ b/l1-contracts/contracts/bridge/interfaces/AssetHandlerModifiers.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {NonEmptyMsgValue} from "../../common/L1ContractErrors.sol"; + +abstract contract AssetHandlerModifiers { + /// @notice Modifier that ensures that a certain value is zero. + /// @dev This should be used in bridgeBurn-like functions to ensure that users + /// do not accidentally provide value there. + modifier requireZeroValue(uint256 _value) { + if (_value != 0) { + revert NonEmptyMsgValue(); + } + _; + } +} diff --git a/l1-contracts/contracts/bridge/interfaces/IAssetHandler.sol b/l1-contracts/contracts/bridge/interfaces/IAssetHandler.sol new file mode 100644 index 000000000..dd32380eb --- /dev/null +++ b/l1-contracts/contracts/bridge/interfaces/IAssetHandler.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +/// @title Asset Handler contract interface +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice Used for any asset handler and called by the AssetRouter +interface IAssetHandler { + /// @dev Emitted when a token is minted + event BridgeMint(uint256 indexed chainId, bytes32 indexed assetId, address receiver, uint256 amount); + + /// @dev Emitted when a token is burned + event BridgeBurn( + uint256 indexed chainId, + bytes32 indexed assetId, + address indexed sender, + address receiver, + uint256 amount + ); + + /// @param _chainId the chainId that the message is from + /// @param _assetId the assetId of the asset being bridged + /// @param _data the actual data specified for the function + /// @dev Note, that while payable, this function will only receive base token on L2 chains, + /// while L1 the provided msg.value is always 0. However, this may change in the future, + /// so if your AssetHandler implementation relies on it, it is better to explicitly check it. + function bridgeMint(uint256 _chainId, bytes32 _assetId, bytes calldata _data) external payable; + + /// @notice Burns bridged tokens and returns the calldata for L2 <-> L1 message. + /// @dev In case of native token vault _data is the tuple of _depositAmount and _l2Receiver. + /// @param _chainId the chainId that the message will be sent to + /// @param _msgValue the msg.value of the L2 transaction. For now it is always 0. + /// @param _assetId the assetId of the asset being bridged + /// @param _originalCaller the original caller of the + /// @param _data the actual data specified for the function + /// @return _bridgeMintData The calldata used by counterpart asset handler to unlock tokens for recipient. + function bridgeBurn( + uint256 _chainId, + uint256 _msgValue, + bytes32 _assetId, + address _originalCaller, + bytes calldata _data + ) external payable returns (bytes memory _bridgeMintData); +} diff --git a/l1-contracts/contracts/bridge/interfaces/IBridgedStandardToken.sol b/l1-contracts/contracts/bridge/interfaces/IBridgedStandardToken.sol new file mode 100644 index 000000000..2ba2a081b --- /dev/null +++ b/l1-contracts/contracts/bridge/interfaces/IBridgedStandardToken.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. +pragma solidity ^0.8.20; + +interface IBridgedStandardToken { + event BridgeInitialize(address indexed l1Token, string name, string symbol, uint8 decimals); + + event BridgeMint(address indexed account, uint256 amount); + + event BridgeBurn(address indexed account, uint256 amount); + + function bridgeMint(address _account, uint256 _amount) external; + + function bridgeBurn(address _account, uint256 _amount) external; + + function l1Address() external view returns (address); + + function originToken() external view returns (address); + + function l2Bridge() external view returns (address); + + function assetId() external view returns (bytes32); + + function nativeTokenVault() external view returns (address); +} diff --git a/l1-contracts/contracts/bridge/interfaces/IL1AssetDeploymentTracker.sol b/l1-contracts/contracts/bridge/interfaces/IL1AssetDeploymentTracker.sol new file mode 100644 index 000000000..6fb6538b6 --- /dev/null +++ b/l1-contracts/contracts/bridge/interfaces/IL1AssetDeploymentTracker.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +interface IL1AssetDeploymentTracker { + function bridgeCheckCounterpartAddress( + uint256 _chainId, + bytes32 _assetId, + address _originalCaller, + address _assetHandlerAddressOnCounterpart + ) external view; +} diff --git a/l1-contracts/contracts/bridge/interfaces/IL1AssetHandler.sol b/l1-contracts/contracts/bridge/interfaces/IL1AssetHandler.sol new file mode 100644 index 000000000..c62dce3da --- /dev/null +++ b/l1-contracts/contracts/bridge/interfaces/IL1AssetHandler.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +/// @title L1 Asset Handler contract interface +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice Used for any asset handler and called by the L1AssetRouter +interface IL1AssetHandler { + /// @param _chainId the chainId that the message will be sent to + /// @param _assetId the assetId of the asset being bridged + /// @param _depositSender the address of the entity that initiated the deposit. + /// @param _data the actual data specified for the function + function bridgeRecoverFailedTransfer( + uint256 _chainId, + bytes32 _assetId, + address _depositSender, + bytes calldata _data + ) external payable; +} diff --git a/l1-contracts/contracts/bridge/interfaces/IL1BaseTokenAssetHandler.sol b/l1-contracts/contracts/bridge/interfaces/IL1BaseTokenAssetHandler.sol new file mode 100644 index 000000000..1e8d08bdd --- /dev/null +++ b/l1-contracts/contracts/bridge/interfaces/IL1BaseTokenAssetHandler.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +/// @title L1 Base Token Asset Handler contract interface +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice Used for any asset handler and called by the L1AssetRouter +interface IL1BaseTokenAssetHandler { + /// @notice Used to get the token address of an assetId + function tokenAddress(bytes32 _assetId) external view returns (address); +} diff --git a/l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol b/l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol index 722eeedc1..fcba5da5a 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol @@ -2,12 +2,14 @@ // We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. pragma solidity ^0.8.21; -import {IL1SharedBridge} from "./IL1SharedBridge.sol"; +import {IL1Nullifier} from "./IL1Nullifier.sol"; +import {IL1NativeTokenVault} from "../ntv/IL1NativeTokenVault.sol"; +import {IL1AssetRouter} from "../asset-router/IL1AssetRouter.sol"; /// @title L1 Bridge contract legacy interface /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -/// @notice Legacy Bridge interface before hyperchain migration, used for backward compatibility with ZKsync Era +/// @notice Legacy Bridge interface before ZK chain migration, used for backward compatibility with ZKsync Era interface IL1ERC20Bridge { event DepositInitiated( bytes32 indexed l2DepositTxHash, @@ -60,7 +62,11 @@ interface IL1ERC20Bridge { function l2TokenAddress(address _l1Token) external view returns (address); - function SHARED_BRIDGE() external view returns (IL1SharedBridge); + function L1_NULLIFIER() external view returns (IL1Nullifier); + + function L1_ASSET_ROUTER() external view returns (IL1AssetRouter); + + function L1_NATIVE_TOKEN_VAULT() external view returns (IL1NativeTokenVault); function l2TokenBeacon() external view returns (address); @@ -70,7 +76,5 @@ interface IL1ERC20Bridge { address _account, address _l1Token, bytes32 _depositL2TxHash - ) external returns (uint256 amount); - - function transferTokenToSharedBridge(address _token) external; + ) external view returns (uint256 amount); } diff --git a/l1-contracts/contracts/bridge/interfaces/IL1Nullifier.sol b/l1-contracts/contracts/bridge/interfaces/IL1Nullifier.sol new file mode 100644 index 000000000..c246454da --- /dev/null +++ b/l1-contracts/contracts/bridge/interfaces/IL1Nullifier.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IBridgehub} from "../../bridgehub/IBridgehub.sol"; +import {IL1NativeTokenVault} from "../ntv/IL1NativeTokenVault.sol"; +import {IL1ERC20Bridge} from "./IL1ERC20Bridge.sol"; + +/// @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 l2sender The address of the message sender on L2 (base token system contract address or asset handler) +/// @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. +struct FinalizeL1DepositParams { + uint256 chainId; + uint256 l2BatchNumber; + uint256 l2MessageIndex; + address l2Sender; + uint16 l2TxNumberInBatch; + bytes message; + bytes32[] merkleProof; +} + +/// @title L1 Bridge contract interface +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +interface IL1Nullifier { + event BridgehubDepositFinalized( + uint256 indexed chainId, + bytes32 indexed txDataHash, + bytes32 indexed l2DepositTxHash + ); + + function isWithdrawalFinalized( + uint256 _chainId, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex + ) external view returns (bool); + + 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 finalizeDeposit(FinalizeL1DepositParams calldata _finalizeWithdrawalParams) external; + + function BRIDGE_HUB() external view returns (IBridgehub); + + function legacyBridge() external view returns (IL1ERC20Bridge); + + function depositHappened(uint256 _chainId, bytes32 _l2TxHash) external view returns (bytes32); + + function bridgehubConfirmL2TransactionForwarded(uint256 _chainId, bytes32 _txDataHash, bytes32 _txHash) external; + + function l1NativeTokenVault() external view returns (IL1NativeTokenVault); + + function setL1NativeTokenVault(IL1NativeTokenVault _nativeTokenVault) external; + + function setL1AssetRouter(address _l1AssetRouter) external; + + function chainBalance(uint256 _chainId, address _token) external view returns (uint256); + + function l2BridgeAddress(uint256 _chainId) external view returns (address); + + function transferTokenToNTV(address _token) external; + + function nullifyChainBalanceByNTV(uint256 _chainId, address _token) external; + + /// @dev Withdraw funds from the initiated deposit, that failed when finalizing on L2. + /// @param _chainId The ZK chain id to which deposit was initiated. + /// @param _depositSender The address of the entity that initiated the deposit. + /// @param _assetId The unique identifier of the deposited L1 token. + /// @param _assetData The encoded transfer data, which includes both the deposit amount and the address of the L2 receiver. Might include extra information. + /// @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. + /// @dev Processes claims of failed deposit, whether they originated from the legacy bridge or the current system. + function bridgeRecoverFailedTransfer( + uint256 _chainId, + address _depositSender, + bytes32 _assetId, + bytes memory _assetData, + bytes32 _l2TxHash, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes32[] calldata _merkleProof + ) external; + + /// @notice Legacy function to finalize withdrawal via the same + /// interface as the old L1SharedBridge. + /// @dev Note, that we need to keep this interface, since the `L2AssetRouter` + /// will continue returning the previous address as the `l1SharedBridge`. The value + /// returned by it is used in the SDK for finalizing withdrawals. + /// @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; +} diff --git a/l1-contracts/contracts/bridge/interfaces/IL1SharedBridge.sol b/l1-contracts/contracts/bridge/interfaces/IL1SharedBridge.sol deleted file mode 100644 index cc58d160f..000000000 --- a/l1-contracts/contracts/bridge/interfaces/IL1SharedBridge.sol +++ /dev/null @@ -1,169 +0,0 @@ -// SPDX-License-Identifier: MIT -// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. -pragma solidity ^0.8.21; - -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 { - /// @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); - - 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 setEraPostDiamondUpgradeFirstBatch(uint256 _eraPostDiamondUpgradeFirstBatch) external; - - function setEraPostLegacyBridgeUpgradeFirstBatch(uint256 _eraPostLegacyBridgeUpgradeFirstBatch) external; - - function setEraLegacyBridgeLastDepositTime( - uint256 _eraLegacyBridgeLastDepositBatch, - uint256 _eraLegacyBridgeLastDepositTxNumber - ) external; - - function L1_WETH_TOKEN() external view returns (address); - - function BRIDGE_HUB() 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; - - /// @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; -} diff --git a/l1-contracts/contracts/bridge/interfaces/IL1SharedBridgeLegacy.sol b/l1-contracts/contracts/bridge/interfaces/IL1SharedBridgeLegacy.sol new file mode 100644 index 000000000..43fca83a3 --- /dev/null +++ b/l1-contracts/contracts/bridge/interfaces/IL1SharedBridgeLegacy.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +/// @title L1 Bridge contract interface +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +interface IL1SharedBridgeLegacy { + function l2BridgeAddress(uint256 _chainId) external view returns (address); +} diff --git a/l1-contracts/contracts/bridge/interfaces/IL2Bridge.sol b/l1-contracts/contracts/bridge/interfaces/IL2Bridge.sol deleted file mode 100644 index 39c6b423f..000000000 --- a/l1-contracts/contracts/bridge/interfaces/IL2Bridge.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. -pragma solidity ^0.8.21; - -/// @author Matter Labs -interface IL2Bridge { - function finalizeDeposit( - address _l1Sender, - address _l2Receiver, - address _l1Token, - uint256 _amount, - bytes calldata _data - ) external; - - function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external; - - function l1TokenAddress(address _l2Token) external view returns (address); - - function l2TokenAddress(address _l1Token) external view returns (address); - - function l1Bridge() external view returns (address); -} diff --git a/l1-contracts/contracts/bridge/interfaces/IL2SharedBridgeLegacy.sol b/l1-contracts/contracts/bridge/interfaces/IL2SharedBridgeLegacy.sol new file mode 100644 index 000000000..71c7a46c5 --- /dev/null +++ b/l1-contracts/contracts/bridge/interfaces/IL2SharedBridgeLegacy.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +interface IL2SharedBridgeLegacy { + event FinalizeDeposit( + address indexed l1Sender, + address indexed l2Receiver, + address indexed l2Token, + uint256 amount + ); + + function l2TokenBeacon() external returns (UpgradeableBeacon); + + function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external; + + function l1TokenAddress(address _l2Token) external view returns (address); + + function l2TokenAddress(address _l1Token) external view returns (address); + + function l1Bridge() external view returns (address); + + function l1SharedBridge() external view returns (address); + + function deployBeaconProxy(bytes32 _salt) external returns (address); + + function sendMessageToL1(bytes calldata _message) external returns (bytes32); +} diff --git a/l2-contracts/contracts/bridge/interfaces/IL2SharedBridge.sol b/l1-contracts/contracts/bridge/interfaces/IL2SharedBridgeLegacyFunctions.sol similarity index 65% rename from l2-contracts/contracts/bridge/interfaces/IL2SharedBridge.sol rename to l1-contracts/contracts/bridge/interfaces/IL2SharedBridgeLegacyFunctions.sol index ee31f6691..42c8f7759 100644 --- a/l2-contracts/contracts/bridge/interfaces/IL2SharedBridge.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL2SharedBridgeLegacyFunctions.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.20; /// @author Matter Labs -interface IL2SharedBridge { +interface IL2SharedBridgeLegacyFunctions { event FinalizeDeposit( address indexed l1Sender, address indexed l2Receiver, @@ -25,14 +25,4 @@ interface IL2SharedBridge { uint256 _amount, bytes calldata _data ) external; - - function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external; - - function l1TokenAddress(address _l2Token) external view returns (address); - - function l2TokenAddress(address _l1Token) external view returns (address); - - function l1Bridge() external view returns (address); - - function l1SharedBridge() external view returns (address); } diff --git a/l2-contracts/contracts/bridge/interfaces/IL2WrappedBaseToken.sol b/l1-contracts/contracts/bridge/interfaces/IL2WrappedBaseToken.sol similarity index 100% rename from l2-contracts/contracts/bridge/interfaces/IL2WrappedBaseToken.sol rename to l1-contracts/contracts/bridge/interfaces/IL2WrappedBaseToken.sol diff --git a/l1-contracts/contracts/bridge/ntv/IL1NativeTokenVault.sol b/l1-contracts/contracts/bridge/ntv/IL1NativeTokenVault.sol new file mode 100644 index 000000000..a3fcbe917 --- /dev/null +++ b/l1-contracts/contracts/bridge/ntv/IL1NativeTokenVault.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IL1Nullifier} from "../interfaces/IL1Nullifier.sol"; +import {INativeTokenVault} from "./INativeTokenVault.sol"; +import {IL1AssetDeploymentTracker} from "../interfaces/IL1AssetDeploymentTracker.sol"; + +/// @title L1 Native token vault contract interface +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice The NTV is an Asset Handler for the L1AssetRouter to handle native tokens +// is IL1AssetHandler, IL1BaseTokenAssetHandler { +interface IL1NativeTokenVault is INativeTokenVault, IL1AssetDeploymentTracker { + /// @notice The L1Nullifier contract + function L1_NULLIFIER() external view returns (IL1Nullifier); + + /// @notice Returns the total number of specific tokens locked for some chain + function chainBalance(uint256 _chainId, bytes32 _assetId) external view returns (uint256); + + /// @notice Registers ETH token + function registerEthToken() external; + + event TokenBeaconUpdated(address indexed l2TokenBeacon); +} diff --git a/l1-contracts/contracts/bridge/ntv/IL2NativeTokenVault.sol b/l1-contracts/contracts/bridge/ntv/IL2NativeTokenVault.sol new file mode 100644 index 000000000..8938a8c28 --- /dev/null +++ b/l1-contracts/contracts/bridge/ntv/IL2NativeTokenVault.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {INativeTokenVault} from "./INativeTokenVault.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +interface IL2NativeTokenVault is INativeTokenVault { + event FinalizeDeposit( + address indexed l1Sender, + address indexed l2Receiver, + address indexed l2Token, + uint256 amount + ); + + event WithdrawalInitiated( + address indexed l2Sender, + address indexed l1Receiver, + address indexed l2Token, + uint256 amount + ); + + event L2TokenBeaconUpdated(address indexed l2TokenBeacon, bytes32 indexed l2TokenProxyBytecodeHash); + + function l2TokenAddress(address _l1Token) external view returns (address); +} diff --git a/l1-contracts/contracts/bridge/ntv/INativeTokenVault.sol b/l1-contracts/contracts/bridge/ntv/INativeTokenVault.sol new file mode 100644 index 000000000..39ca0e20f --- /dev/null +++ b/l1-contracts/contracts/bridge/ntv/INativeTokenVault.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IAssetRouterBase} from "../asset-router/IAssetRouterBase.sol"; + +/// @title Base Native token vault contract interface +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice The NTV is an Asset Handler for the L1AssetRouter to handle native tokens +interface INativeTokenVault { + event BridgedTokenBeaconUpdated(address bridgedTokenBeacon, bytes32 bridgedTokenProxyBytecodeHash); + + /// @notice The Weth token address + function WETH_TOKEN() external view returns (address); + + /// @notice The AssetRouter contract + function ASSET_ROUTER() external view returns (IAssetRouterBase); + + /// @notice The chain ID of the L1 chain + function L1_CHAIN_ID() external view returns (uint256); + + /// @notice Returns the chain ID of the origin chain for a given asset ID + function originChainId(bytes32 assetId) external view returns (uint256); + + /// @notice Registers tokens within the NTV. + /// @dev The goal is to allow bridging native tokens automatically, by registering them on the fly. + /// @notice Allows the bridge to register a token address for the vault. + /// @notice No access control is ok, since the bridging of tokens should be permissionless. This requires permissionless registration. + function registerToken(address _l1Token) external; + + /// @notice Ensures that the native token is registered with the NTV. + /// @dev This function is used to ensure that the token is registered with the NTV. + function ensureTokenIsRegistered(address _nativeToken) external; + + /// @notice Used to get the the ERC20 data for a token + function getERC20Getters(address _token, uint256 _originChainId) external view returns (bytes memory); + + /// @notice Used to get the token address of an assetId + function tokenAddress(bytes32 assetId) external view returns (address); + + /// @notice Used to get the assetId of a token + function assetId(address token) external view returns (bytes32); + + /// @notice Used to get the expected bridged token address corresponding to its native counterpart + function calculateCreate2TokenAddress(uint256 _originChainId, address _originToken) external view returns (address); + + /// @notice Tries to register a token from the provided `_burnData` and reverts if it is not possible. + function tryRegisterTokenFromBurnData(bytes calldata _burnData, bytes32 _expectedAssetId) external; +} diff --git a/l1-contracts/contracts/bridge/ntv/L1NativeTokenVault.sol b/l1-contracts/contracts/bridge/ntv/L1NativeTokenVault.sol new file mode 100644 index 000000000..192d44e2d --- /dev/null +++ b/l1-contracts/contracts/bridge/ntv/L1NativeTokenVault.sol @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; +import {IBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/IBeacon.sol"; +import {Create2} from "@openzeppelin/contracts-v4/utils/Create2.sol"; + +import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +import {IL1NativeTokenVault} from "./IL1NativeTokenVault.sol"; +import {INativeTokenVault} from "./INativeTokenVault.sol"; +import {NativeTokenVault} from "./NativeTokenVault.sol"; + +import {IL1AssetHandler} from "../interfaces/IL1AssetHandler.sol"; +import {IL1Nullifier} from "../interfaces/IL1Nullifier.sol"; +import {IBridgedStandardToken} from "../interfaces/IBridgedStandardToken.sol"; +import {IL1AssetRouter} from "../asset-router/IL1AssetRouter.sol"; + +import {ETH_TOKEN_ADDRESS} from "../../common/Config.sol"; +import {L2_NATIVE_TOKEN_VAULT_ADDR} from "../../common/L2ContractAddresses.sol"; +import {DataEncoding} from "../../common/libraries/DataEncoding.sol"; + +import {OriginChainIdNotFound, Unauthorized, ZeroAddress, NoFundsTransferred, InsufficientChainBalance, WithdrawFailed} from "../../common/L1ContractErrors.sol"; +import {ClaimFailedDepositFailed, ZeroAmountToTransfer, WrongAmountTransferred, WrongCounterpart} from "../L1BridgeContractErrors.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @dev Vault holding L1 native ETH and ERC20 tokens bridged into the ZK chains. +/// @dev Designed for use with a proxy for upgradability. +contract L1NativeTokenVault is IL1NativeTokenVault, IL1AssetHandler, NativeTokenVault { + using SafeERC20 for IERC20; + + /// @dev L1 nullifier contract that handles legacy functions & finalize withdrawal, confirm l2 tx mappings + IL1Nullifier public immutable override L1_NULLIFIER; + + /// @dev Maps token balances for each chain to prevent unauthorized spending across ZK chains. + /// This serves as a security measure until hyperbridging is implemented. + /// NOTE: this function may be removed in the future, don't rely on it! + mapping(uint256 chainId => mapping(bytes32 assetId => uint256 balance)) public chainBalance; + + /// @dev Contract is expected to be used as proxy implementation. + /// @dev Initialize the implementation to prevent Parity hack. + /// @param _l1WethAddress Address of WETH on deployed chain + /// @param _l1AssetRouter Address of Asset Router on L1. + /// @param _l1Nullifier Address of the nullifier contract, which handles transaction progress between L1 and ZK chains. + constructor( + address _l1WethAddress, + address _l1AssetRouter, + IL1Nullifier _l1Nullifier + ) + NativeTokenVault( + _l1WethAddress, + _l1AssetRouter, + DataEncoding.encodeNTVAssetId(block.chainid, ETH_TOKEN_ADDRESS), + block.chainid + ) + { + L1_NULLIFIER = _l1Nullifier; + } + + /// @dev Accepts ether only from the contract that was the shared Bridge. + receive() external payable { + if (address(L1_NULLIFIER) != msg.sender) { + revert Unauthorized(msg.sender); + } + } + + /// @dev Initializes a contract for later use. Expected to be used in the proxy + /// @param _owner Address which can change pause / unpause the NTV + /// 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, address _bridgedTokenBeacon) external initializer { + if (_owner == address(0)) { + revert ZeroAddress(); + } + bridgedTokenBeacon = IBeacon(_bridgedTokenBeacon); + _transferOwnership(_owner); + } + + /// @inheritdoc IL1NativeTokenVault + function registerEthToken() external { + _unsafeRegisterNativeToken(ETH_TOKEN_ADDRESS); + } + + /// @notice Transfers tokens from shared bridge as part of the migration process. + /// The shared bridge becomes the L1Nullifier contract. + /// @dev Both ETH and ERC20 tokens can be transferred. Exhausts balance of shared bridge after the first call. + /// @dev Calling second time for the same token will revert. + /// @param _token The address of token to be transferred (address(1) for ether and contract address for ERC20). + function transferFundsFromSharedBridge(address _token) external { + ensureTokenIsRegistered(_token); + if (_token == ETH_TOKEN_ADDRESS) { + uint256 balanceBefore = address(this).balance; + L1_NULLIFIER.transferTokenToNTV(_token); + uint256 balanceAfter = address(this).balance; + if (balanceAfter <= balanceBefore) { + revert NoFundsTransferred(); + } + } else { + uint256 balanceBefore = IERC20(_token).balanceOf(address(this)); + uint256 nullifierChainBalance = IERC20(_token).balanceOf(address(L1_NULLIFIER)); + if (nullifierChainBalance == 0) { + revert ZeroAmountToTransfer(); + } + L1_NULLIFIER.transferTokenToNTV(_token); + uint256 balanceAfter = IERC20(_token).balanceOf(address(this)); + if (balanceAfter - balanceBefore < nullifierChainBalance) { + revert WrongAmountTransferred(balanceAfter - balanceBefore, nullifierChainBalance); + } + } + } + + /// @notice Updates chain token balance within NTV to account for tokens transferred from the shared bridge (part of the migration process). + /// @dev Clears chain balance on the shared bridge after the first call. Subsequent calls will not affect the state. + /// @param _token The address of token to be transferred (address(1) for ether and contract address for ERC20). + /// @param _targetChainId The chain ID of the corresponding ZK chain. + function updateChainBalancesFromSharedBridge(address _token, uint256 _targetChainId) external { + uint256 nullifierChainBalance = L1_NULLIFIER.chainBalance(_targetChainId, _token); + bytes32 assetId = DataEncoding.encodeNTVAssetId(block.chainid, _token); + chainBalance[_targetChainId][assetId] = chainBalance[_targetChainId][assetId] + nullifierChainBalance; + originChainId[assetId] = block.chainid; + L1_NULLIFIER.nullifyChainBalanceByNTV(_targetChainId, _token); + } + + /// @notice Used to register the Asset Handler asset in L2 AssetRouter. + /// @param _assetHandlerAddressOnCounterpart the address of the asset handler on the counterpart chain. + function bridgeCheckCounterpartAddress( + uint256, + bytes32, + address, + address _assetHandlerAddressOnCounterpart + ) external view override onlyAssetRouter { + if (_assetHandlerAddressOnCounterpart != L2_NATIVE_TOKEN_VAULT_ADDR) { + revert WrongCounterpart(); + } + } + + function _getOriginChainId(bytes32 _assetId) internal view returns (uint256) { + uint256 chainId = originChainId[_assetId]; + if (chainId != 0) { + return chainId; + } else { + address token = tokenAddress[_assetId]; + if (token == ETH_TOKEN_ADDRESS) { + return block.chainid; + } else if (IERC20(token).balanceOf(address(this)) > 0) { + return block.chainid; + } else if (IERC20(token).balanceOf(address(L1_NULLIFIER)) > 0) { + return block.chainid; + } else { + return 0; + } + } + } + + /*////////////////////////////////////////////////////////////// + Start transaction Functions + //////////////////////////////////////////////////////////////*/ + + function _bridgeBurnNativeToken( + uint256 _chainId, + bytes32 _assetId, + address _originalCaller, + // solhint-disable-next-line no-unused-vars + bool _depositChecked, + uint256 _depositAmount, + address _receiver, + address _nativeToken + ) internal override returns (bytes memory _bridgeMintData) { + bool depositChecked = IL1AssetRouter(address(ASSET_ROUTER)).transferFundsToNTV( + _assetId, + _depositAmount, + _originalCaller + ); + _bridgeMintData = super._bridgeBurnNativeToken({ + _chainId: _chainId, + _assetId: _assetId, + _originalCaller: _originalCaller, + _depositChecked: depositChecked, + _depositAmount: _depositAmount, + _receiver: _receiver, + _nativeToken: _nativeToken + }); + } + + /*////////////////////////////////////////////////////////////// + L1 SPECIFIC FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @inheritdoc IL1AssetHandler + function bridgeRecoverFailedTransfer( + uint256 _chainId, + bytes32 _assetId, + address _depositSender, + bytes calldata _data + ) external payable override requireZeroValue(msg.value) onlyAssetRouter whenNotPaused { + // slither-disable-next-line unused-return + (uint256 _amount, , ) = DataEncoding.decodeBridgeBurnData(_data); + address l1Token = tokenAddress[_assetId]; + if (_amount == 0) { + revert NoFundsTransferred(); + } + + _handleChainBalanceDecrease(_chainId, _assetId, _amount, false); + + 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) + } + if (!callSuccess) { + revert ClaimFailedDepositFailed(); + } + } else { + uint256 originChainId = _getOriginChainId(_assetId); + if (originChainId == block.chainid) { + IERC20(l1Token).safeTransfer(_depositSender, _amount); + } else if (originChainId != 0) { + IBridgedStandardToken(l1Token).bridgeMint(_depositSender, _amount); + } else { + revert OriginChainIdNotFound(); + } + // 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. + } + } + + /*////////////////////////////////////////////////////////////// + INTERNAL & HELPER FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + function _registerTokenIfBridgedLegacy(address) internal override returns (bytes32) { + // There are no legacy tokens present on L1. + return bytes32(0); + } + + // get the computed address before the contract DeployWithCreate2 deployed using Bytecode of contract DeployWithCreate2 and salt specified by the sender + function calculateCreate2TokenAddress( + uint256 _originChainId, + address _nonNativeToken + ) public view override(INativeTokenVault, NativeTokenVault) returns (address) { + bytes32 salt = _getCreate2Salt(_originChainId, _nonNativeToken); + return + Create2.computeAddress( + salt, + keccak256(abi.encodePacked(type(BeaconProxy).creationCode, abi.encode(bridgedTokenBeacon, ""))) + ); + } + + function _withdrawFunds(bytes32 _assetId, address _to, address _token, uint256 _amount) internal override { + if (_assetId == BASE_TOKEN_ASSET_ID) { + bool callSuccess; + // Low-level assembly call, to avoid any memory copying (save gas) + assembly { + callSuccess := call(gas(), _to, _amount, 0, 0, 0, 0) + } + if (!callSuccess) { + revert WithdrawFailed(); + } + } else { + // Withdraw funds + IERC20(_token).safeTransfer(_to, _amount); + } + } + + function _deployBeaconProxy(bytes32 _salt, uint256) internal override returns (BeaconProxy proxy) { + // Use CREATE2 to deploy the BeaconProxy + address proxyAddress = Create2.deploy( + 0, + _salt, + abi.encodePacked(type(BeaconProxy).creationCode, abi.encode(bridgedTokenBeacon, "")) + ); + return BeaconProxy(payable(proxyAddress)); + } + + function _handleChainBalanceIncrease( + uint256 _chainId, + bytes32 _assetId, + uint256 _amount, + bool _isNative + ) internal override { + // Note, that we do not update balances for chains where the assetId comes from, + // since these chains can mint new instances of the token. + if (!_hasInfiniteBalance(_isNative, _assetId, _chainId)) { + chainBalance[_chainId][_assetId] += _amount; + } + } + + function _handleChainBalanceDecrease( + uint256 _chainId, + bytes32 _assetId, + uint256 _amount, + bool _isNative + ) internal override { + // Note, that we do not update balances for chains where the assetId comes from, + // since these chains can mint new instances of the token. + if (!_hasInfiniteBalance(_isNative, _assetId, _chainId)) { + // Check that the chain has sufficient balance + if (chainBalance[_chainId][_assetId] < _amount) { + revert InsufficientChainBalance(); + } + chainBalance[_chainId][_assetId] -= _amount; + } + } + + /// @dev Returns whether a chain `_chainId` has infinite balance for an asset `_assetId`, i.e. + /// it can be minted by it. + /// @param _isNative Whether the asset is native to the L1 chain. + /// @param _assetId The asset id + /// @param _chainId An id of a chain which we test against. + /// @return Whether The chain `_chainId` has infinite balance of the token + function _hasInfiniteBalance(bool _isNative, bytes32 _assetId, uint256 _chainId) private view returns (bool) { + return !_isNative && originChainId[_assetId] == _chainId; + } +} diff --git a/l1-contracts/contracts/bridge/ntv/L2NativeTokenVault.sol b/l1-contracts/contracts/bridge/ntv/L2NativeTokenVault.sol new file mode 100644 index 000000000..135e9fb64 --- /dev/null +++ b/l1-contracts/contracts/bridge/ntv/L2NativeTokenVault.sol @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; +import {IBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/IBeacon.sol"; +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; + +import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +import {INativeTokenVault} from "./INativeTokenVault.sol"; +import {IL2NativeTokenVault} from "./IL2NativeTokenVault.sol"; +import {NativeTokenVault} from "./NativeTokenVault.sol"; + +import {IL2SharedBridgeLegacy} from "../interfaces/IL2SharedBridgeLegacy.sol"; +import {BridgedStandardERC20} from "../BridgedStandardERC20.sol"; +import {IL2AssetRouter} from "../asset-router/IL2AssetRouter.sol"; + +import {L2_DEPLOYER_SYSTEM_CONTRACT_ADDR, L2_ASSET_ROUTER_ADDR} from "../../common/L2ContractAddresses.sol"; +import {L2ContractHelper, IContractDeployer} from "../../common/libraries/L2ContractHelper.sol"; + +import {SystemContractsCaller} from "../../common/libraries/SystemContractsCaller.sol"; +import {DataEncoding} from "../../common/libraries/DataEncoding.sol"; + +import {AssetIdAlreadyRegistered, NoLegacySharedBridge, TokenIsLegacy, TokenIsNotLegacy, EmptyAddress, EmptyBytes32, AddressMismatch, DeployFailed, AssetIdNotSupported} from "../../common/L1ContractErrors.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 L2NativeTokenVault is IL2NativeTokenVault, NativeTokenVault { + using SafeERC20 for IERC20; + + IL2SharedBridgeLegacy public immutable L2_LEGACY_SHARED_BRIDGE; + + /// @dev Bytecode hash of the proxy for tokens deployed by the bridge. + bytes32 internal immutable L2_TOKEN_PROXY_BYTECODE_HASH; + + /// @notice Initializes the bridge contract for later use. + /// @dev this contract is deployed in the L2GenesisUpgrade, and is meant as direct deployment without a proxy. + /// @param _l1ChainId The L1 chain id differs between mainnet and testnets. + /// @param _l2TokenProxyBytecodeHash The bytecode hash of the proxy for tokens deployed by the bridge. + /// @param _aliasedOwner The address of the governor contract. + /// @param _legacySharedBridge The address of the L2 legacy shared bridge. + /// @param _bridgedTokenBeacon The address of the L2 token beacon for legacy chains. + /// @param _contractsDeployedAlready Ensures beacon proxy for standard ERC20 has not been deployed. + /// @param _wethToken Address of WETH on deployed chain + constructor( + uint256 _l1ChainId, + address _aliasedOwner, + bytes32 _l2TokenProxyBytecodeHash, + address _legacySharedBridge, + address _bridgedTokenBeacon, + bool _contractsDeployedAlready, + address _wethToken, + bytes32 _baseTokenAssetId + ) NativeTokenVault(_wethToken, L2_ASSET_ROUTER_ADDR, _baseTokenAssetId, _l1ChainId) { + L2_LEGACY_SHARED_BRIDGE = IL2SharedBridgeLegacy(_legacySharedBridge); + + if (_l2TokenProxyBytecodeHash == bytes32(0)) { + revert EmptyBytes32(); + } + if (_aliasedOwner == address(0)) { + revert EmptyAddress(); + } + + L2_TOKEN_PROXY_BYTECODE_HASH = _l2TokenProxyBytecodeHash; + _transferOwnership(_aliasedOwner); + + if (_contractsDeployedAlready) { + if (_bridgedTokenBeacon == address(0)) { + revert EmptyAddress(); + } + bridgedTokenBeacon = IBeacon(_bridgedTokenBeacon); + } else { + address l2StandardToken = address(new BridgedStandardERC20{salt: bytes32(0)}()); + + UpgradeableBeacon tokenBeacon = new UpgradeableBeacon{salt: bytes32(0)}(l2StandardToken); + + tokenBeacon.transferOwnership(owner()); + bridgedTokenBeacon = IBeacon(address(tokenBeacon)); + emit L2TokenBeaconUpdated(address(bridgedTokenBeacon), _l2TokenProxyBytecodeHash); + } + } + + function _registerTokenIfBridgedLegacy(address _tokenAddress) internal override returns (bytes32) { + // In zkEVM immutables are stored in a storage of a system contract, + // so it makes sense to cache them for efficiency. + IL2SharedBridgeLegacy legacyBridge = L2_LEGACY_SHARED_BRIDGE; + if (address(legacyBridge) == address(0)) { + // No legacy bridge, the token must be native + return bytes32(0); + } + + address l1TokenAddress = legacyBridge.l1TokenAddress(_tokenAddress); + if (l1TokenAddress == address(0)) { + // The token is not legacy + return bytes32(0); + } + + return _registerLegacyTokenAssetId(_tokenAddress, l1TokenAddress); + } + + /// @notice Sets the legacy token asset ID for the given L2 token address. + function setLegacyTokenAssetId(address _l2TokenAddress) public { + if (assetId[_l2TokenAddress] != bytes32(0)) { + revert AssetIdAlreadyRegistered(); + } + if (address(L2_LEGACY_SHARED_BRIDGE) == address(0)) { + revert NoLegacySharedBridge(); + } + address l1TokenAddress = L2_LEGACY_SHARED_BRIDGE.l1TokenAddress(_l2TokenAddress); + if (l1TokenAddress == address(0)) { + revert TokenIsNotLegacy(); + } + + _registerLegacyTokenAssetId(_l2TokenAddress, l1TokenAddress); + } + + function _registerLegacyTokenAssetId( + address _l2TokenAddress, + address _l1TokenAddress + ) internal returns (bytes32 newAssetId) { + newAssetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, _l1TokenAddress); + IL2AssetRouter(L2_ASSET_ROUTER_ADDR).setLegacyTokenAssetHandler(newAssetId); + tokenAddress[newAssetId] = _l2TokenAddress; + assetId[_l2TokenAddress] = newAssetId; + originChainId[newAssetId] = L1_CHAIN_ID; + } + + /// @notice Ensures that the token is deployed. + /// @param _assetId The asset ID. + /// @param _originToken The origin token address. + /// @param _erc20Data The ERC20 data. + /// @return expectedToken The token address. + function _ensureAndSaveTokenDeployed( + bytes32 _assetId, + address _originToken, + bytes memory _erc20Data + ) internal override returns (address expectedToken) { + uint256 tokenOriginChainId; + (expectedToken, tokenOriginChainId) = _calculateExpectedTokenAddress(_originToken, _erc20Data); + address l1LegacyToken; + if (address(L2_LEGACY_SHARED_BRIDGE) != address(0)) { + l1LegacyToken = L2_LEGACY_SHARED_BRIDGE.l1TokenAddress(expectedToken); + } + + if (l1LegacyToken != address(0)) { + _ensureAndSaveTokenDeployedInnerLegacyToken({ + _assetId: _assetId, + _originToken: _originToken, + _expectedToken: expectedToken, + _l1LegacyToken: l1LegacyToken + }); + } else { + super._ensureAndSaveTokenDeployedInner({ + _tokenOriginChainId: tokenOriginChainId, + _assetId: _assetId, + _originToken: _originToken, + _erc20Data: _erc20Data, + _expectedToken: expectedToken + }); + } + } + + /// @notice Ensures that the token is deployed inner for legacy tokens. + function _ensureAndSaveTokenDeployedInnerLegacyToken( + bytes32 _assetId, + address _originToken, + address _expectedToken, + address _l1LegacyToken + ) internal { + _assetIdCheck(L1_CHAIN_ID, _assetId, _originToken); + + /// token is a legacy token, no need to deploy + if (_l1LegacyToken != _originToken) { + revert AddressMismatch(_originToken, _l1LegacyToken); + } + + tokenAddress[_assetId] = _expectedToken; + assetId[_expectedToken] = _assetId; + } + + /// @notice Deploys the beacon proxy for the L2 token, while using ContractDeployer system contract. + /// @dev This function uses raw call to ContractDeployer to make sure that exactly `L2_TOKEN_PROXY_BYTECODE_HASH` is used + /// for the code of the proxy. + /// @param _salt The salt used for beacon proxy deployment of L2 bridged token. + /// @param _tokenOriginChainId The origin chain id of the token. + /// @return proxy The beacon proxy, i.e. L2 bridged token. + function _deployBeaconProxy( + bytes32 _salt, + uint256 _tokenOriginChainId + ) internal virtual override returns (BeaconProxy proxy) { + if (address(L2_LEGACY_SHARED_BRIDGE) == address(0) || _tokenOriginChainId != L1_CHAIN_ID) { + // Deploy the beacon proxy for the L2 token + + (bool success, bytes memory returndata) = SystemContractsCaller.systemCallWithReturndata( + uint32(gasleft()), + L2_DEPLOYER_SYSTEM_CONTRACT_ADDR, + 0, + abi.encodeCall( + IContractDeployer.create2, + (_salt, L2_TOKEN_PROXY_BYTECODE_HASH, abi.encode(address(bridgedTokenBeacon), "")) + ) + ); + + // The deployment should be successful and return the address of the proxy + if (!success) { + revert DeployFailed(); + } + proxy = BeaconProxy(abi.decode(returndata, (address))); + } else { + // Deploy the beacon proxy for the L2 token + address l2TokenAddr = L2_LEGACY_SHARED_BRIDGE.deployBeaconProxy(_salt); + proxy = BeaconProxy(payable(l2TokenAddr)); + } + } + + function _withdrawFunds(bytes32 _assetId, address _to, address _token, uint256 _amount) internal override { + if (_assetId == BASE_TOKEN_ASSET_ID) { + revert AssetIdNotSupported(BASE_TOKEN_ASSET_ID); + } else { + // Withdraw funds + IERC20(_token).safeTransfer(_to, _amount); + } + } + + /*////////////////////////////////////////////////////////////// + INTERNAL & HELPER FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @notice Calculates L2 wrapped token address given the currently stored beacon proxy bytecode hash and beacon address. + /// @param _tokenOriginChainId The chain id of the origin token. + /// @param _nonNativeToken The address of token on its origin chain.. + /// @return Address of an L2 token counterpart. + function calculateCreate2TokenAddress( + uint256 _tokenOriginChainId, + address _nonNativeToken + ) public view virtual override(INativeTokenVault, NativeTokenVault) returns (address) { + if (address(L2_LEGACY_SHARED_BRIDGE) != address(0) && _tokenOriginChainId == L1_CHAIN_ID) { + return L2_LEGACY_SHARED_BRIDGE.l2TokenAddress(_nonNativeToken); + } else { + bytes32 constructorInputHash = keccak256(abi.encode(address(bridgedTokenBeacon), "")); + bytes32 salt = _getCreate2Salt(_tokenOriginChainId, _nonNativeToken); + return + L2ContractHelper.computeCreate2Address( + address(this), + salt, + L2_TOKEN_PROXY_BYTECODE_HASH, + constructorInputHash + ); + } + } + + /// @notice Calculates the salt for the Create2 deployment of the L2 token. + function _getCreate2Salt( + uint256 _tokenOriginChainId, + address _l1Token + ) internal view override returns (bytes32 salt) { + salt = _tokenOriginChainId == L1_CHAIN_ID + ? bytes32(uint256(uint160(_l1Token))) + : keccak256(abi.encode(_tokenOriginChainId, _l1Token)); + } + + function _handleChainBalanceIncrease( + uint256 _chainId, + bytes32 _assetId, + uint256 _amount, + bool _isNative + ) internal override { + // on L2s we don't track the balance + } + + function _handleChainBalanceDecrease( + uint256 _chainId, + bytes32 _assetId, + uint256 _amount, + bool _isNative + ) internal override { + // on L2s we don't track the balance + } + + function _registerToken(address _nativeToken) internal override returns (bytes32) { + if ( + address(L2_LEGACY_SHARED_BRIDGE) != address(0) && + L2_LEGACY_SHARED_BRIDGE.l1TokenAddress(_nativeToken) != address(0) + ) { + // Legacy tokens should be registered via `setLegacyTokenAssetId`. + revert TokenIsLegacy(); + } + return super._registerToken(_nativeToken); + } + + /*////////////////////////////////////////////////////////////// + LEGACY FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @notice Calculates L2 wrapped token address corresponding to L1 token counterpart. + /// @param _l1Token The address of token on L1. + /// @return expectedToken The address of token on L2. + function l2TokenAddress(address _l1Token) public view returns (address expectedToken) { + bytes32 expectedAssetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, _l1Token); + expectedToken = tokenAddress[expectedAssetId]; + } +} diff --git a/l1-contracts/contracts/bridge/ntv/NativeTokenVault.sol b/l1-contracts/contracts/bridge/ntv/NativeTokenVault.sol new file mode 100644 index 000000000..021cab58a --- /dev/null +++ b/l1-contracts/contracts/bridge/ntv/NativeTokenVault.sol @@ -0,0 +1,569 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; +import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/security/PausableUpgradeable.sol"; +import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; +import {IBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/IBeacon.sol"; + +import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; + +import {IBridgedStandardToken} from "../interfaces/IBridgedStandardToken.sol"; +import {INativeTokenVault} from "./INativeTokenVault.sol"; +import {IAssetHandler} from "../interfaces/IAssetHandler.sol"; +import {IAssetRouterBase} from "../asset-router/IAssetRouterBase.sol"; +import {DataEncoding} from "../../common/libraries/DataEncoding.sol"; + +import {BridgedStandardERC20} from "../BridgedStandardERC20.sol"; +import {BridgeHelper} from "../BridgeHelper.sol"; + +import {EmptyToken} from "../L1BridgeContractErrors.sol"; +import {BurningNativeWETHNotSupported, AssetIdAlreadyRegistered, EmptyDeposit, Unauthorized, TokensWithFeesNotSupported, TokenNotSupported, NonEmptyMsgValue, ValueMismatch, AddressMismatch, AssetIdMismatch, AmountMustBeGreaterThanZero, ZeroAddress, DeployingBridgedTokenForNativeToken} from "../../common/L1ContractErrors.sol"; +import {AssetHandlerModifiers} from "../interfaces/AssetHandlerModifiers.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @dev Vault holding L1 native ETH and ERC20 tokens bridged into the ZK chains. +/// @dev Designed for use with a proxy for upgradability. +abstract contract NativeTokenVault is + INativeTokenVault, + IAssetHandler, + Ownable2StepUpgradeable, + PausableUpgradeable, + AssetHandlerModifiers +{ + using SafeERC20 for IERC20; + + /// @dev The address of the WETH token. + address public immutable override WETH_TOKEN; + + /// @dev L1 Shared Bridge smart contract that handles communication with its counterparts on L2s + IAssetRouterBase public immutable override ASSET_ROUTER; + + /// @dev The assetId of the base token. + bytes32 public immutable BASE_TOKEN_ASSET_ID; + + /// @dev Chain ID of L1 for bridging reasons. + uint256 public immutable L1_CHAIN_ID; + + /// @dev Contract that stores the implementation address for token. + /// @dev For more details see https://docs.openzeppelin.com/contracts/3.x/api/proxy#UpgradeableBeacon. + IBeacon public bridgedTokenBeacon; + + /// @dev A mapping assetId => originChainId + mapping(bytes32 assetId => uint256 originChainId) public originChainId; + + /// @dev A mapping assetId => tokenAddress + mapping(bytes32 assetId => address tokenAddress) public tokenAddress; + + /// @dev A mapping tokenAddress => assetId + mapping(address tokenAddress => bytes32 assetId) public assetId; + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[46] private __gap; + + /// @notice Checks that the message sender is the bridgehub. + modifier onlyAssetRouter() { + if (msg.sender != address(ASSET_ROUTER)) { + revert Unauthorized(msg.sender); + } + _; + } + + /// @dev Contract is expected to be used as proxy implementation. + /// @dev Disable the initialization to prevent Parity hack. + /// @param _wethToken Address of WETH on deployed chain + /// @param _assetRouter Address of assetRouter + constructor(address _wethToken, address _assetRouter, bytes32 _baseTokenAssetId, uint256 _l1ChainId) { + _disableInitializers(); + L1_CHAIN_ID = _l1ChainId; + ASSET_ROUTER = IAssetRouterBase(_assetRouter); + WETH_TOKEN = _wethToken; + BASE_TOKEN_ASSET_ID = _baseTokenAssetId; + } + + /// @inheritdoc INativeTokenVault + function registerToken(address _nativeToken) external virtual { + _registerToken(_nativeToken); + } + + function _registerToken(address _nativeToken) internal virtual returns (bytes32 newAssetId) { + // We allow registering `WETH_TOKEN` inside `NativeTokenVault` only for L1 native token vault. + // It is needed to allow withdrawing such assets. We restrict all WETH-related + // operations to deposits from L1 only to be able to upgrade their logic more easily in the + // future. + if (_nativeToken == WETH_TOKEN && block.chainid != L1_CHAIN_ID) { + revert TokenNotSupported(WETH_TOKEN); + } + if (_nativeToken.code.length == 0) { + revert EmptyToken(); + } + if (assetId[_nativeToken] != bytes32(0)) { + revert AssetIdAlreadyRegistered(); + } + newAssetId = _unsafeRegisterNativeToken(_nativeToken); + } + + /// @inheritdoc INativeTokenVault + function ensureTokenIsRegistered(address _nativeToken) public { + if (assetId[_nativeToken] == bytes32(0)) { + _registerToken(_nativeToken); + } + } + + /*////////////////////////////////////////////////////////////// + FINISH TRANSACTION FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @inheritdoc IAssetHandler + /// @notice Used when the chain receives a transfer from L1 Shared Bridge and correspondingly mints the asset. + /// @param _chainId The chainId that the message is from. + /// @param _assetId The assetId of the asset being bridged. + /// @param _data The abi.encoded transfer data. + function bridgeMint( + uint256 _chainId, + bytes32 _assetId, + bytes calldata _data + ) external payable override requireZeroValue(msg.value) onlyAssetRouter whenNotPaused { + address receiver; + uint256 amount; + // we set all originChainId for all already bridged tokens with the setLegacyTokenAssetId and updateChainBalancesFromSharedBridge functions. + // for tokens that are bridged for the first time, the originChainId will be 0. + if (originChainId[_assetId] == block.chainid) { + (receiver, amount) = _bridgeMintNativeToken(_chainId, _assetId, _data); + } else { + (receiver, amount) = _bridgeMintBridgedToken(_chainId, _assetId, _data); + } + // solhint-disable-next-line func-named-parameters + emit BridgeMint(_chainId, _assetId, receiver, amount); + } + + function _bridgeMintBridgedToken( + uint256 _chainId, + bytes32 _assetId, + bytes calldata _data + ) internal virtual returns (address receiver, uint256 amount) { + // Either it was bridged before, therefore address is not zero, or it is first time bridging and standard erc20 will be deployed + address token = tokenAddress[_assetId]; + bytes memory erc20Data; + address originToken; + // slither-disable-next-line unused-return + (, receiver, originToken, amount, erc20Data) = DataEncoding.decodeBridgeMintData(_data); + + if (token == address(0)) { + token = _ensureAndSaveTokenDeployed(_assetId, originToken, erc20Data); + } + _handleChainBalanceDecrease(_chainId, _assetId, amount, false); + IBridgedStandardToken(token).bridgeMint(receiver, amount); + } + + function _bridgeMintNativeToken( + uint256 _chainId, + bytes32 _assetId, + bytes calldata _data + ) internal returns (address receiver, uint256 amount) { + address token = tokenAddress[_assetId]; + // slither-disable-next-line unused-return + (, receiver, , amount, ) = DataEncoding.decodeBridgeMintData(_data); + + _handleChainBalanceDecrease(_chainId, _assetId, amount, true); + _withdrawFunds(_assetId, receiver, token, amount); + } + + function _withdrawFunds(bytes32 _assetId, address _to, address _token, uint256 _amount) internal virtual; + + /*////////////////////////////////////////////////////////////// + Start transaction Functions + //////////////////////////////////////////////////////////////*/ + + /// @inheritdoc IAssetHandler + /// @notice Allows bridgehub to acquire mintValue for L1->L2 transactions. + /// @dev In case of native token vault _data is the tuple of _depositAmount and _receiver. + function bridgeBurn( + uint256 _chainId, + uint256 _l2MsgValue, + bytes32 _assetId, + address _originalCaller, + bytes calldata _data + ) + external + payable + override + requireZeroValue(_l2MsgValue) + onlyAssetRouter + whenNotPaused + returns (bytes memory _bridgeMintData) + { + (uint256 amount, address receiver, address tokenAddress) = _decodeBurnAndCheckAssetId(_data, _assetId); + if (originChainId[_assetId] != block.chainid) { + _bridgeMintData = _bridgeBurnBridgedToken({ + _chainId: _chainId, + _assetId: _assetId, + _originalCaller: _originalCaller, + _amount: amount, + _receiver: receiver, + _tokenAddress: tokenAddress + }); + } else { + _bridgeMintData = _bridgeBurnNativeToken({ + _chainId: _chainId, + _assetId: _assetId, + _originalCaller: _originalCaller, + _depositChecked: false, + _depositAmount: amount, + _receiver: receiver, + _nativeToken: tokenAddress + }); + } + } + + function tryRegisterTokenFromBurnData(bytes calldata _burnData, bytes32 _expectedAssetId) external { + // slither-disable-next-line unused-return + (, , address tokenAddress) = DataEncoding.decodeBridgeBurnData(_burnData); + + if (tokenAddress == address(0)) { + revert ZeroAddress(); + } + + bytes32 storedAssetId = assetId[tokenAddress]; + if (storedAssetId != bytes32(0)) { + revert AssetIdAlreadyRegistered(); + } + + // This token has not been registered within this NTV yet. Usually this means that the + // token is native to the chain and the user would prefer to get it registered as such. + // However, there are exceptions (e.g. bridged legacy ERC20 tokens on L2) when the + // assetId has not been stored yet. We will ask the implementor to double check that the token + // is not legacy. + + // We try to register it as legacy token. If it fails, we know + // it is a native one and so register it as a native token. + bytes32 newAssetId = _registerTokenIfBridgedLegacy(tokenAddress); + if (newAssetId == bytes32(0)) { + newAssetId = _registerToken(tokenAddress); + } + + if (newAssetId != _expectedAssetId) { + revert AssetIdMismatch(_expectedAssetId, newAssetId); + } + } + + function _decodeBurnAndCheckAssetId( + bytes calldata _data, + bytes32 _suppliedAssetId + ) internal returns (uint256 amount, address receiver, address parsedTokenAddress) { + (amount, receiver, parsedTokenAddress) = DataEncoding.decodeBridgeBurnData(_data); + + if (parsedTokenAddress == address(0)) { + // This means that the user wants the native token vault to resolve the + // address. In this case, it is assumed that the assetId is already registered. + parsedTokenAddress = tokenAddress[_suppliedAssetId]; + } + + // If it is still zero, it means that the token has not been registered. + if (parsedTokenAddress == address(0)) { + revert ZeroAddress(); + } + + bytes32 storedAssetId = assetId[parsedTokenAddress]; + if (_suppliedAssetId != storedAssetId) { + revert AssetIdMismatch(storedAssetId, _suppliedAssetId); + } + } + + function _registerTokenIfBridgedLegacy(address _token) internal virtual returns (bytes32); + + function _bridgeBurnBridgedToken( + uint256 _chainId, + bytes32 _assetId, + address _originalCaller, + uint256 _amount, + address _receiver, + address _tokenAddress + ) internal requireZeroValue(msg.value) returns (bytes memory _bridgeMintData) { + if (_amount == 0) { + // "Amount cannot be zero"); + revert AmountMustBeGreaterThanZero(); + } + + IBridgedStandardToken(_tokenAddress).bridgeBurn(_originalCaller, _amount); + _handleChainBalanceIncrease(_chainId, _assetId, _amount, false); + + emit BridgeBurn({ + chainId: _chainId, + assetId: _assetId, + sender: _originalCaller, + receiver: _receiver, + amount: _amount + }); + bytes memory erc20Metadata; + { + // we set all originChainId for all already bridged tokens with the setLegacyTokenAssetId and updateChainBalancesFromSharedBridge functions. + // for native tokens the originChainId is set when they register. + uint256 originChainId = originChainId[_assetId]; + if (originChainId == 0) { + revert ZeroAddress(); + } + erc20Metadata = getERC20Getters(_tokenAddress, originChainId); + } + address originToken; + { + originToken = IBridgedStandardToken(_tokenAddress).originToken(); + if (originToken == address(0)) { + revert ZeroAddress(); + } + } + + _bridgeMintData = DataEncoding.encodeBridgeMintData({ + _originalCaller: _originalCaller, + _remoteReceiver: _receiver, + _originToken: originToken, + _amount: _amount, + _erc20Metadata: erc20Metadata + }); + } + + function _bridgeBurnNativeToken( + uint256 _chainId, + bytes32 _assetId, + address _originalCaller, + bool _depositChecked, + uint256 _depositAmount, + address _receiver, + address _nativeToken + ) internal virtual returns (bytes memory _bridgeMintData) { + if (_nativeToken == WETH_TOKEN) { + // This ensures that WETH_TOKEN can never be bridged from chains it is native to. + // It can only be withdrawn from the chain where it has already gotten. + revert BurningNativeWETHNotSupported(); + } + + if (_assetId == BASE_TOKEN_ASSET_ID) { + if (_depositAmount != msg.value) { + revert ValueMismatch(_depositAmount, msg.value); + } + + _handleChainBalanceIncrease(_chainId, _assetId, _depositAmount, true); + } else { + if (msg.value != 0) { + revert NonEmptyMsgValue(); + } + _handleChainBalanceIncrease(_chainId, _assetId, _depositAmount, true); + if (!_depositChecked) { + uint256 expectedDepositAmount = _depositFunds(_originalCaller, IERC20(_nativeToken), _depositAmount); // note if _originalCaller is this contract, this will return 0. This does not happen. + // The token has non-standard transfer logic + if (_depositAmount != expectedDepositAmount) { + revert TokensWithFeesNotSupported(); + } + } + } + if (_depositAmount == 0) { + // empty deposit amount + revert EmptyDeposit(); + } + + bytes memory erc20Metadata; + { + erc20Metadata = getERC20Getters(_nativeToken, originChainId[_assetId]); + } + _bridgeMintData = DataEncoding.encodeBridgeMintData({ + _originalCaller: _originalCaller, + _remoteReceiver: _receiver, + _originToken: _nativeToken, + _amount: _depositAmount, + _erc20Metadata: erc20Metadata + }); + + emit BridgeBurn({ + chainId: _chainId, + assetId: _assetId, + sender: _originalCaller, + receiver: _receiver, + amount: _depositAmount + }); + } + + /*////////////////////////////////////////////////////////////// + INTERNAL & HELPER FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @notice Transfers tokens from the depositor address to the smart contract address. + /// @param _from The address of the depositor. + /// @param _token The ERC20 token to be transferred. + /// @param _amount The amount to be transferred. + /// @return The difference between the contract balance before and after the transferring of funds. + function _depositFunds(address _from, IERC20 _token, uint256 _amount) internal virtual returns (uint256) { + uint256 balanceBefore = _token.balanceOf(address(this)); + // slither-disable-next-line arbitrary-send-erc20 + _token.safeTransferFrom(_from, address(this), _amount); + uint256 balanceAfter = _token.balanceOf(address(this)); + + return balanceAfter - balanceBefore; + } + + /// @param _token The address of token of interest. + /// @dev Receives and parses (name, symbol, decimals) from the token contract + function getERC20Getters(address _token, uint256 _originChainId) public view override returns (bytes memory) { + return BridgeHelper.getERC20Getters(_token, _originChainId); + } + + /// @notice Registers a native token address for the vault. + /// @dev It does not perform any checks for the correctnesss of the token contract. + /// @param _nativeToken The address of the token to be registered. + function _unsafeRegisterNativeToken(address _nativeToken) internal returns (bytes32 newAssetId) { + newAssetId = DataEncoding.encodeNTVAssetId(block.chainid, _nativeToken); + tokenAddress[newAssetId] = _nativeToken; + assetId[_nativeToken] = newAssetId; + originChainId[newAssetId] = block.chainid; + ASSET_ROUTER.setAssetHandlerAddressThisChain(bytes32(uint256(uint160(_nativeToken))), address(this)); + } + + function _handleChainBalanceIncrease( + uint256 _chainId, + bytes32 _assetId, + uint256 _amount, + bool _isNative + ) internal virtual; + + function _handleChainBalanceDecrease( + uint256 _chainId, + bytes32 _assetId, + uint256 _amount, + bool _isNative + ) internal virtual; + + /*////////////////////////////////////////////////////////////// + TOKEN DEPLOYER FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + function _ensureAndSaveTokenDeployed( + bytes32 _assetId, + address _originToken, + bytes memory _erc20Data + ) internal virtual returns (address expectedToken) { + uint256 tokenOriginChainId; + (expectedToken, tokenOriginChainId) = _calculateExpectedTokenAddress(_originToken, _erc20Data); + _ensureAndSaveTokenDeployedInner({ + _tokenOriginChainId: tokenOriginChainId, + _assetId: _assetId, + _originToken: _originToken, + _erc20Data: _erc20Data, + _expectedToken: expectedToken + }); + } + + /// @notice Calculates the bridged token address corresponding to native token counterpart. + function _calculateExpectedTokenAddress( + address _originToken, + bytes memory _erc20Data + ) internal view returns (address expectedToken, uint256 tokenOriginChainId) { + /// @dev calling externally to convert from memory to calldata + tokenOriginChainId = this.tokenDataOriginChainId(_erc20Data); + expectedToken = calculateCreate2TokenAddress(tokenOriginChainId, _originToken); + } + + /// @notice Returns the origin chain id from the token data. + function tokenDataOriginChainId(bytes calldata _erc20Data) public view returns (uint256 tokenOriginChainId) { + // slither-disable-next-line unused-return + (tokenOriginChainId, , , ) = DataEncoding.decodeTokenData(_erc20Data); + if (tokenOriginChainId == 0) { + tokenOriginChainId = L1_CHAIN_ID; + } + } + + /// @notice Checks that the assetId is correct for the origin token and chain. + function _assetIdCheck(uint256 _tokenOriginChainId, bytes32 _assetId, address _originToken) internal view { + bytes32 expectedAssetId = DataEncoding.encodeNTVAssetId(_tokenOriginChainId, _originToken); + if (_assetId != expectedAssetId) { + // Make sure that a NativeTokenVault sent the message + revert AssetIdMismatch(_assetId, expectedAssetId); + } + } + + function _ensureAndSaveTokenDeployedInner( + uint256 _tokenOriginChainId, + bytes32 _assetId, + address _originToken, + bytes memory _erc20Data, + address _expectedToken + ) internal { + _assetIdCheck(_tokenOriginChainId, _assetId, _originToken); + + address deployedToken = _deployBridgedToken(_tokenOriginChainId, _assetId, _originToken, _erc20Data); + if (deployedToken != _expectedToken) { + revert AddressMismatch(_expectedToken, deployedToken); + } + + tokenAddress[_assetId] = _expectedToken; + assetId[_expectedToken] = _assetId; + } + + /// @notice Calculates the bridged token address corresponding to native token counterpart. + /// @param _tokenOriginChainId The chain id of the origin token. + /// @param _bridgeToken The address of native token. + /// @return The address of bridged token. + function calculateCreate2TokenAddress( + uint256 _tokenOriginChainId, + address _bridgeToken + ) public view virtual override returns (address); + + /// @notice Deploys and initializes the bridged token for the native counterpart. + /// @param _tokenOriginChainId The chain id of the origin token. + /// @param _originToken The address of origin token. + /// @param _erc20Data The ERC20 metadata of the token deployed. + /// @return The address of the beacon proxy (bridged token). + function _deployBridgedToken( + uint256 _tokenOriginChainId, + bytes32 _assetId, + address _originToken, + bytes memory _erc20Data + ) internal returns (address) { + if (_tokenOriginChainId == block.chainid) { + revert DeployingBridgedTokenForNativeToken(); + } + bytes32 salt = _getCreate2Salt(_tokenOriginChainId, _originToken); + + BeaconProxy l2Token = _deployBeaconProxy(salt, _tokenOriginChainId); + BridgedStandardERC20(address(l2Token)).bridgeInitialize(_assetId, _originToken, _erc20Data); + + originChainId[_assetId] = _tokenOriginChainId; + return address(l2Token); + } + + /// @notice Converts the L1 token address to the create2 salt of deployed L2 token. + /// @param _l1Token The address of token on L1. + /// @return salt The salt used to compute address of bridged token on L2 and for beacon proxy deployment. + function _getCreate2Salt(uint256 _originChainId, address _l1Token) internal view virtual returns (bytes32 salt) { + salt = keccak256(abi.encode(_originChainId, _l1Token)); + } + + /// @notice Deploys the beacon proxy for the bridged token. + /// @dev This function uses raw call to ContractDeployer to make sure that exactly `l2TokenProxyBytecodeHash` is used + /// for the code of the proxy. + /// @param _salt The salt used for beacon proxy deployment of the bridged token (we pass the native token address). + /// @return proxy The beacon proxy, i.e. bridged token. + function _deployBeaconProxy( + bytes32 _salt, + uint256 _tokenOriginChainId + ) internal virtual returns (BeaconProxy proxy); + + /*////////////////////////////////////////////////////////////// + PAUSE + //////////////////////////////////////////////////////////////*/ + + /// @notice Pauses all functions marked with the `whenNotPaused` modifier. + function pause() external onlyOwner { + _pause(); + } + + /// @notice Unpauses the contract, allowing all functions marked with the `whenNotPaused` modifier to be called again. + function unpause() external onlyOwner { + _unpause(); + } +} diff --git a/l1-contracts/contracts/bridgehub/Bridgehub.sol b/l1-contracts/contracts/bridgehub/Bridgehub.sol index 89001c2cd..69a5bc768 100644 --- a/l1-contracts/contracts/bridgehub/Bridgehub.sol +++ b/l1-contracts/contracts/bridgehub/Bridgehub.sol @@ -2,38 +2,66 @@ pragma solidity 0.8.24; +import {EnumerableMap} from "@openzeppelin/contracts-v4/utils/structs/EnumerableMap.sol"; + import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/security/PausableUpgradeable.sol"; -import {L2TransactionRequestDirect, L2TransactionRequestTwoBridgesOuter, L2TransactionRequestTwoBridgesInner} from "./IBridgehub.sol"; -import {IBridgehub, IL1SharedBridge} from "../bridge/interfaces/IL1SharedBridge.sol"; -import {IStateTransitionManager} from "../state-transition/IStateTransitionManager.sol"; +import {IBridgehub, L2TransactionRequestDirect, L2TransactionRequestTwoBridgesOuter, L2TransactionRequestTwoBridgesInner, BridgehubMintCTMAssetData, BridgehubBurnCTMAssetData} from "./IBridgehub.sol"; +import {IAssetRouterBase} from "../bridge/asset-router/IAssetRouterBase.sol"; +import {IL1AssetRouter} from "../bridge/asset-router/IL1AssetRouter.sol"; +import {IL1BaseTokenAssetHandler} from "../bridge/interfaces/IL1BaseTokenAssetHandler.sol"; +import {IChainTypeManager} from "../state-transition/IChainTypeManager.sol"; import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; -import {IZkSyncHyperchain} from "../state-transition/chain-interfaces/IZkSyncHyperchain.sol"; -import {ETH_TOKEN_ADDRESS, TWO_BRIDGES_MAGIC_VALUE, BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS} from "../common/Config.sol"; +import {DataEncoding} from "../common/libraries/DataEncoding.sol"; +import {IZKChain} from "../state-transition/chain-interfaces/IZKChain.sol"; + +import {ETH_TOKEN_ADDRESS, TWO_BRIDGES_MAGIC_VALUE, BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER, L1_SETTLEMENT_LAYER_VIRTUAL_ADDRESS} from "../common/Config.sol"; import {BridgehubL2TransactionRequest, L2Message, L2Log, TxStatus} from "../common/Messaging.sol"; import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; -import {Unauthorized, STMAlreadyRegistered, STMNotRegistered, TokenAlreadyRegistered, TokenNotRegistered, ZeroChainId, ChainIdTooBig, SharedBridgeNotSet, BridgeHubAlreadyRegistered, AddressTooLow, MsgValueMismatch, WrongMagicValue, ZeroAddress} from "../common/L1ContractErrors.sol"; +import {IMessageRoot} from "./IMessageRoot.sol"; +import {ICTMDeploymentTracker} from "./ICTMDeploymentTracker.sol"; +import {NotL1, NotRelayedSender, NotAssetRouter, ChainIdAlreadyPresent, ChainNotPresentInCTM, SecondBridgeAddressTooLow, NotInGatewayMode, SLNotWhitelisted, IncorrectChainAssetId, NotCurrentSL, HyperchainNotRegistered, IncorrectSender, AlreadyCurrentSL, ChainNotLegacy} from "./L1BridgehubErrors.sol"; +import {NoCTMForAssetId, SettlementLayersMustSettleOnL1, MigrationPaused, AssetIdAlreadyRegistered, CTMNotRegistered, ChainIdNotRegistered, AssetHandlerNotRegistered, ZKChainLimitReached, CTMAlreadyRegistered, CTMNotRegistered, ZeroChainId, ChainIdTooBig, BridgeHubAlreadyRegistered, MsgValueMismatch, ZeroAddress, Unauthorized, SharedBridgeNotSet, WrongMagicValue, ChainIdAlreadyExists, ChainIdMismatch, ChainIdCantBeCurrentChain, EmptyAssetId, AssetIdNotSupported, IncorrectBridgeHubAddress} from "../common/L1ContractErrors.sol"; + +import {AssetHandlerModifiers} from "../bridge/interfaces/AssetHandlerModifiers.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -/// @dev The Bridgehub contract serves as the primary entry point for L1<->L2 communication, +/// @dev The Bridgehub contract serves as the primary entry point for L1->L2 communication, /// facilitating interactions between end user and bridges. /// It also manages state transition managers, base tokens, and chain registrations. -contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, PausableUpgradeable { - /// @notice all the ether is held by the weth bridge - IL1SharedBridge public sharedBridge; +/// Bridgehub is also an IL1AssetHandler for the chains themselves, which is used to migrate the chains +/// between different settlement layers (for example from L1 to Gateway). +contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, PausableUpgradeable, AssetHandlerModifiers { + using EnumerableMap for EnumerableMap.UintToAddressMap; + + /// @notice the asset id of Eth. This is only used on L1. + bytes32 internal immutable ETH_TOKEN_ASSET_ID; + + /// @notice The chain id of L1. This contract can be deployed on multiple layers, but this value is still equal to the + /// L1 that is at the most base layer. + uint256 public immutable L1_CHAIN_ID; + + /// @notice The total number of ZK chains can be created/connected to this CTM. + /// This is the temporary security measure. + uint256 public immutable MAX_NUMBER_OF_ZK_CHAINS; + + /// @notice all the ether and ERC20 tokens are held by NativeVaultToken managed by the asset router. + address public assetRouter; + + /// @notice ChainTypeManagers that are registered, and ZKchains that use these CTMs can use this bridgehub as settlement layer. + mapping(address chainTypeManager => bool) public chainTypeManagerIsRegistered; - /// @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; + mapping(address baseToken => bool) public __DEPRECATED_tokenIsRegistered; - /// @notice chainID => StateTransitionManager contract address, storing StateTransitionManager - mapping(uint256 _chainId => address) public stateTransitionManager; + /// @notice chainID => ChainTypeManager contract address, CTM that is managing rules for a given ZKchain. + mapping(uint256 chainId => address) public chainTypeManager; - /// @notice chainID => baseToken contract address, storing baseToken - mapping(uint256 _chainId => address) public baseToken; + /// @notice chainID => baseToken contract address, token that is used as 'base token' by a given child chain. + // slither-disable-next-line uninitialized-state + mapping(uint256 chainId => address) public __DEPRECATED_baseToken; /// @dev used to manage non critical updates address public admin; @@ -41,13 +69,40 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @dev used to accept the admin role address private pendingAdmin; - /// @notice to avoid parity hack - constructor() reentrancyGuardInitializer {} + /// @notice The map from chainId => zkChain contract + EnumerableMap.UintToAddressMap internal zkChainMap; - /// @notice used to initialize the contract - function initialize(address _owner) external reentrancyGuardInitializer { - _transferOwnership(_owner); - } + /// @notice The contract that stores the cross-chain message root for each chain and the aggregated root. + /// @dev Note that the message root does not contain messages from the chain it is deployed on. It may + /// be added later on if needed. + IMessageRoot public override messageRoot; + + /// @notice Mapping from chain id to encoding of the base token used for deposits / withdrawals + mapping(uint256 chainId => bytes32) public baseTokenAssetId; + + /// @notice The deployment tracker for the state transition managers. + /// @dev The L1 address of the ctm deployer is provided. + ICTMDeploymentTracker public l1CtmDeployer; + + /// @dev asset info used to identify chains in the Shared Bridge + mapping(bytes32 ctmAssetId => address ctmAddress) public ctmAssetIdToAddress; + + /// @dev ctmAddress to ctmAssetId + mapping(address ctmAddress => bytes32 ctmAssetId) public ctmAssetIdFromAddress; + + /// @dev used to indicate the currently active settlement layer for a given chainId + mapping(uint256 chainId => uint256 activeSettlementLayerChainId) public settlementLayer; + + /// @notice shows whether the given chain can be used as a settlement layer. + /// @dev the Gateway will be one of the possible settlement layers. The L1 is also a settlement layer. + /// @dev Sync layer chain is expected to have .. as the base token. + mapping(uint256 chainId => bool isWhitelistedSettlementLayer) public whitelistedSettlementLayers; + + /// @notice we store registered assetIds (for arbitrary base token) + mapping(bytes32 baseTokenAssetId => bool) public assetIdIsRegistered; + + /// @notice used to pause the migrations of chains. Used for upgrades. + bool public migrationPaused; modifier onlyOwnerOrAdmin() { if (msg.sender != admin && msg.sender != owner()) { @@ -56,6 +111,70 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus _; } + modifier onlyL1() { + if (L1_CHAIN_ID != block.chainid) { + revert NotL1(L1_CHAIN_ID, block.chainid); + } + _; + } + + modifier onlySettlementLayerRelayedSender() { + /// There is no sender for the wrapping, we use a virtual address. + if (msg.sender != SETTLEMENT_LAYER_RELAY_SENDER) { + revert NotRelayedSender(msg.sender, SETTLEMENT_LAYER_RELAY_SENDER); + } + _; + } + + modifier onlyAssetRouter() { + if (msg.sender != assetRouter) { + revert NotAssetRouter(msg.sender, assetRouter); + } + _; + } + + modifier whenMigrationsNotPaused() { + if (migrationPaused) { + revert MigrationPaused(); + } + _; + } + + /// @notice to avoid parity hack + constructor(uint256 _l1ChainId, address _owner, uint256 _maxNumberOfZKChains) reentrancyGuardInitializer { + _disableInitializers(); + L1_CHAIN_ID = _l1ChainId; + MAX_NUMBER_OF_ZK_CHAINS = _maxNumberOfZKChains; + + // Note that this assumes that the bridgehub only accepts transactions on chains with ETH base token only. + // This is indeed true, since the only methods where this immutable is used are the ones with `onlyL1` modifier. + // We will change this with interop. + ETH_TOKEN_ASSET_ID = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, ETH_TOKEN_ADDRESS); + _transferOwnership(_owner); + _initializeInner(); + } + + /// @notice used to initialize the contract + /// @notice this contract is also deployed on L2 as a system contract there the owner and the related functions will not be used + /// @param _owner the owner of the contract + function initialize(address _owner) external reentrancyGuardInitializer onlyL1 { + _transferOwnership(_owner); + _initializeInner(); + } + + /// @notice Used to initialize the contract on L1 + function initializeV2() external initializer onlyL1 { + _initializeInner(); + } + + /// @notice Initializes the contract + function _initializeInner() internal { + assetIdIsRegistered[ETH_TOKEN_ASSET_ID] = true; + whitelistedSettlementLayers[L1_CHAIN_ID] = true; + } + + //// Initialization and registration + /// @inheritdoc IBridgehub /// @dev Please note, if the owner wants to enforce the admin change it must execute both `setPendingAdmin` and /// `acceptAdmin` atomically. Otherwise `admin` can set different pending admin and so fail to accept the admin rights. @@ -86,198 +205,267 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus emit NewAdmin(previousAdmin, currentPendingAdmin); } - ///// Getters + /// @notice To set the addresses of some of the ecosystem contracts, only Owner. Not done in initialize, as + /// the order of deployment is Bridgehub, other contracts, and then we call this. + /// @param _assetRouter the shared bridge address + /// @param _l1CtmDeployer the ctm deployment tracker address. Note, that the address of the L1 CTM deployer is provided. + /// @param _messageRoot the message root address + function setAddresses( + address _assetRouter, + ICTMDeploymentTracker _l1CtmDeployer, + IMessageRoot _messageRoot + ) external onlyOwner { + assetRouter = _assetRouter; + l1CtmDeployer = _l1CtmDeployer; + messageRoot = _messageRoot; + } - /// @notice return the state transition chain contract for a chainId - function getHyperchain(uint256 _chainId) public view returns (address) { - return IStateTransitionManager(stateTransitionManager[_chainId]).getHyperchain(_chainId); + /// @notice Used to set the legacy chain data for the upgrade. + /// @param _chainId The chainId of the legacy chain we are migrating. + function registerLegacyChain(uint256 _chainId) external override { + address ctm = chainTypeManager[_chainId]; + if (ctm == address(0)) { + revert ChainNotLegacy(); + } + if (zkChainMap.contains(_chainId)) { + revert ChainIdAlreadyPresent(); + } + + // From now on, since `zkChainMap` did not contain the chain, we assume + // that the chain is a legacy chain in the process of migration, i.e. + // its stored `baseTokenAssetId`, etc. + + address token = __DEPRECATED_baseToken[_chainId]; + if (token == address(0)) { + revert ChainNotLegacy(); + } + + bytes32 assetId = DataEncoding.encodeNTVAssetId(block.chainid, token); + + baseTokenAssetId[_chainId] = assetId; + assetIdIsRegistered[assetId] = true; + + address chainAddress = IChainTypeManager(ctm).getZKChainLegacy(_chainId); + if (chainAddress == address(0)) { + revert ChainNotPresentInCTM(); + } + _registerNewZKChain(_chainId, chainAddress, false); + messageRoot.addNewChain(_chainId); + settlementLayer[_chainId] = block.chainid; } //// Registry - /// @notice State Transition can be any contract with the appropriate interface/functionality - function addStateTransitionManager(address _stateTransitionManager) external onlyOwner { - if (_stateTransitionManager == address(0)) { + /// @notice Chain Type Manager can be any contract with the appropriate interface/functionality + /// @param _chainTypeManager the state transition manager address to be added + function addChainTypeManager(address _chainTypeManager) external onlyOwner { + if (_chainTypeManager == address(0)) { revert ZeroAddress(); } - if (stateTransitionManagerIsRegistered[_stateTransitionManager]) { - revert STMAlreadyRegistered(); + if (chainTypeManagerIsRegistered[_chainTypeManager]) { + revert CTMAlreadyRegistered(); } - stateTransitionManagerIsRegistered[_stateTransitionManager] = true; + chainTypeManagerIsRegistered[_chainTypeManager] = true; + + emit ChainTypeManagerAdded(_chainTypeManager); } - /// @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 { - if (_stateTransitionManager == address(0)) { + /// @notice Chain Type Manager can be any contract with the appropriate interface/functionality + /// @notice this stops new Chains from using the CTM, old chains are not affected + /// @param _chainTypeManager the state transition manager address to be removed + function removeChainTypeManager(address _chainTypeManager) external onlyOwner { + if (_chainTypeManager == address(0)) { revert ZeroAddress(); } - if (!stateTransitionManagerIsRegistered[_stateTransitionManager]) { - revert STMNotRegistered(); + if (!chainTypeManagerIsRegistered[_chainTypeManager]) { + revert CTMNotRegistered(); } - stateTransitionManagerIsRegistered[_stateTransitionManager] = false; + chainTypeManagerIsRegistered[_chainTypeManager] = false; + + emit ChainTypeManagerRemoved(_chainTypeManager); } - /// @notice token can be any contract with the appropriate interface/functionality - function addToken(address _token) external onlyOwnerOrAdmin { - if (tokenIsRegistered[_token]) { - revert TokenAlreadyRegistered(_token); + /// @notice asset id can represent any token contract with the appropriate interface/functionality + /// @param _baseTokenAssetId asset id of base token to be registered + function addTokenAssetId(bytes32 _baseTokenAssetId) external onlyOwnerOrAdmin { + if (assetIdIsRegistered[_baseTokenAssetId]) { + revert AssetIdAlreadyRegistered(); } - tokenIsRegistered[_token] = true; + assetIdIsRegistered[_baseTokenAssetId] = true; + + emit BaseTokenAssetIdRegistered(_baseTokenAssetId); } - /// @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 { - if (_sharedBridge == address(0)) { - revert ZeroAddress(); + /// @notice Used to register a chain as a settlement layer. + /// @param _newSettlementLayerChainId the chainId of the chain + /// @param _isWhitelisted whether the chain is a whitelisted settlement layer + function registerSettlementLayer( + uint256 _newSettlementLayerChainId, + bool _isWhitelisted + ) external onlyOwner onlyL1 { + if (settlementLayer[_newSettlementLayerChainId] != block.chainid) { + revert SettlementLayersMustSettleOnL1(); + } + whitelistedSettlementLayers[_newSettlementLayerChainId] = _isWhitelisted; + emit SettlementLayerRegistered(_newSettlementLayerChainId, _isWhitelisted); + } + + /// @dev Used to set the assetAddress for a given assetInfo. + /// @param _additionalData the additional data to identify the asset + /// @param _assetAddress the asset handler address + function setCTMAssetAddress(bytes32 _additionalData, address _assetAddress) external { + // It is a simplified version of the logic used by the AssetRouter to manage asset handlers. + // CTM's assetId is `keccak256(abi.encode(L1_CHAIN_ID, l1CtmDeployer, ctmAddress))`. + // And the l1CtmDeployer is considered the deployment tracker for the CTM asset. + // + // The l1CtmDeployer will call this method to set the asset handler address for the assetId. + // If the chain is not the same as L1, we assume that it is done via L1->L2 communication and so we unalias the sender. + // + // For simpler handling we allow anyone to call this method. It is okay, since during bridging operations + // it is double checked that `assetId` is indeed derived from the `l1CtmDeployer`. + // TODO(EVM-703): This logic should be revised once interchain communication is implemented. + + address sender = L1_CHAIN_ID == block.chainid ? msg.sender : AddressAliasHelper.undoL1ToL2Alias(msg.sender); + // This method can be accessed by l1CtmDeployer only + if (sender != address(l1CtmDeployer)) { + revert Unauthorized(sender); + } + if (!chainTypeManagerIsRegistered[_assetAddress]) { + revert CTMNotRegistered(); } - sharedBridge = IL1SharedBridge(_sharedBridge); + + bytes32 ctmAssetId = DataEncoding.encodeAssetId(L1_CHAIN_ID, _additionalData, sender); + ctmAssetIdToAddress[ctmAssetId] = _assetAddress; + ctmAssetIdFromAddress[_assetAddress] = ctmAssetId; + emit AssetRegistered(ctmAssetId, _assetAddress, _additionalData, msg.sender); } - /// @notice register new chain + /*////////////////////////////////////////////////////////////// + Chain Registration + //////////////////////////////////////////////////////////////*/ + + /// @notice register new chain. New chains can be only registered on Bridgehub deployed on L1. Later they can be moved to any other layer. /// @notice for Eth the baseToken address is 1 + /// @param _chainId the chainId of the chain + /// @param _chainTypeManager the state transition manager address + /// @param _baseTokenAssetId the base token asset id of the chain + /// @param _salt the salt for the chainId, currently not used + /// @param _admin the admin of the chain + /// @param _initData the fixed initialization data for the chain + /// @param _factoryDeps the factory dependencies for the chain's deployment function createNewChain( uint256 _chainId, - address _stateTransitionManager, - address _baseToken, + address _chainTypeManager, + bytes32 _baseTokenAssetId, // solhint-disable-next-line no-unused-vars uint256 _salt, address _admin, - bytes calldata _initData - ) external onlyOwnerOrAdmin nonReentrant whenNotPaused returns (uint256) { - if (_chainId == 0) { - revert ZeroChainId(); - } - if (_chainId > type(uint48).max) { - revert ChainIdTooBig(); - } - if (_stateTransitionManager == address(0)) { - revert ZeroAddress(); - } - if (_baseToken == address(0)) { - revert ZeroAddress(); - } - - if (!stateTransitionManagerIsRegistered[_stateTransitionManager]) { - revert STMNotRegistered(); - } - if (!tokenIsRegistered[_baseToken]) { - revert TokenNotRegistered(_baseToken); - } - if (address(sharedBridge) == address(0)) { - revert SharedBridgeNotSet(); - } + bytes calldata _initData, + bytes[] calldata _factoryDeps + ) external onlyOwnerOrAdmin nonReentrant whenNotPaused onlyL1 returns (uint256) { + _validateChainParams({_chainId: _chainId, _assetId: _baseTokenAssetId, _chainTypeManager: _chainTypeManager}); - if (stateTransitionManager[_chainId] != address(0)) { - revert BridgeHubAlreadyRegistered(); - } + chainTypeManager[_chainId] = _chainTypeManager; - stateTransitionManager[_chainId] = _stateTransitionManager; - baseToken[_chainId] = _baseToken; + baseTokenAssetId[_chainId] = _baseTokenAssetId; + settlementLayer[_chainId] = block.chainid; - IStateTransitionManager(_stateTransitionManager).createNewChain({ + address chainAddress = IChainTypeManager(_chainTypeManager).createNewChain({ _chainId: _chainId, - _baseToken: _baseToken, - _sharedBridge: address(sharedBridge), + _baseTokenAssetId: _baseTokenAssetId, _admin: _admin, - _diamondCut: _initData + _initData: _initData, + _factoryDeps: _factoryDeps }); + _registerNewZKChain(_chainId, chainAddress, true); + messageRoot.addNewChain(_chainId); - emit NewChain(_chainId, _stateTransitionManager, _admin); + emit NewChain(_chainId, _chainTypeManager, _admin); return _chainId; } - //// Mailbox forwarder + /// @notice This internal function is used to register a new zkChain in the system. + /// @param _chainId The chain ID of the ZK chain + /// @param _zkChain The address of the ZK chain's DiamondProxy contract. + /// @param _checkMaxNumberOfZKChains Whether to check that the limit for the number + /// of chains has not been crossed. + /// @dev Providing `_checkMaxNumberOfZKChains = false` may be preferable in cases + /// where we want to guarantee that a chain can be added. These include: + /// - Migration of a chain from the mapping in the old CTM + /// - Migration of a chain to a new settlement layer + function _registerNewZKChain(uint256 _chainId, address _zkChain, bool _checkMaxNumberOfZKChains) internal { + // slither-disable-next-line unused-return + zkChainMap.set(_chainId, _zkChain); + if (_checkMaxNumberOfZKChains && zkChainMap.length() > MAX_NUMBER_OF_ZK_CHAINS) { + revert ZKChainLimitReached(); + } + } - /// @notice forwards function call to Mailbox based on ChainId - /// @param _chainId The chain ID of the hyperchain where to prove L2 message inclusion. - /// @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 _chainId, - uint256 _batchNumber, - uint256 _index, - L2Message calldata _message, - bytes32[] calldata _proof - ) external view override returns (bool) { - address hyperchain = getHyperchain(_chainId); - return IZkSyncHyperchain(hyperchain).proveL2MessageInclusion(_batchNumber, _index, _message, _proof); + /*////////////////////////////////////////////////////////////// + Getters + //////////////////////////////////////////////////////////////*/ + + /// @notice baseToken function, which takes chainId as input, reads assetHandler from AR, and tokenAddress from AH + function baseToken(uint256 _chainId) public view returns (address) { + bytes32 baseTokenAssetId = baseTokenAssetId[_chainId]; + address assetHandlerAddress = IAssetRouterBase(assetRouter).assetHandlerAddress(baseTokenAssetId); + + // It is possible that the asset handler is not deployed for a chain on the current layer. + // In this case we throw an error. + if (assetHandlerAddress == address(0)) { + revert AssetHandlerNotRegistered(baseTokenAssetId); + } + return IL1BaseTokenAssetHandler(assetHandlerAddress).tokenAddress(baseTokenAssetId); } - /// @notice forwards function call to Mailbox based on ChainId - /// @param _chainId The chain ID of the hyperchain where to prove L2 log inclusion. - /// @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 _chainId, - uint256 _batchNumber, - uint256 _index, - L2Log calldata _log, - bytes32[] calldata _proof - ) external view override returns (bool) { - address hyperchain = getHyperchain(_chainId); - return IZkSyncHyperchain(hyperchain).proveL2LogInclusion(_batchNumber, _index, _log, _proof); + /// @notice Returns all the registered zkChain addresses + function getAllZKChains() public view override returns (address[] memory chainAddresses) { + uint256[] memory keys = zkChainMap.keys(); + chainAddresses = new address[](keys.length); + uint256 keysLength = keys.length; + for (uint256 i = 0; i < keysLength; ++i) { + chainAddresses[i] = zkChainMap.get(keys[i]); + } } - /// @notice forwards function call to Mailbox based on ChainId - /// @param _chainId The chain ID of the hyperchain where to prove L1->L2 tx status. - /// @param _l2TxHash The L2 canonical transaction hash - /// @param _l2BatchNumber The L2 batch number where the transaction 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 _merkleProof The Merkle proof of the processing L1 -> L2 transaction - /// @param _status The execution status of the L1 -> L2 transaction (true - success & 0 - fail) - /// @return Whether the proof is correct and the transaction was actually executed with provided status - /// NOTE: It may return `false` for incorrect proof, but it doesn't mean that the L1 -> L2 transaction has an opposite status! - function proveL1ToL2TransactionStatus( - uint256 _chainId, - bytes32 _l2TxHash, - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes32[] calldata _merkleProof, - TxStatus _status - ) external view override returns (bool) { - address hyperchain = getHyperchain(_chainId); - return - IZkSyncHyperchain(hyperchain).proveL1ToL2TransactionStatus({ - _l2TxHash: _l2TxHash, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _merkleProof: _merkleProof, - _status: _status - }); + /// @notice Returns all the registered zkChain chainIDs + function getAllZKChainChainIDs() public view override returns (uint256[] memory) { + return zkChainMap.keys(); } - /// @notice forwards function call to Mailbox based on ChainId - function l2TransactionBaseCost( - uint256 _chainId, - uint256 _gasPrice, - uint256 _l2GasLimit, - uint256 _l2GasPerPubdataByteLimit - ) external view returns (uint256) { - address hyperchain = getHyperchain(_chainId); - return IZkSyncHyperchain(hyperchain).l2TransactionBaseCost(_gasPrice, _l2GasLimit, _l2GasPerPubdataByteLimit); + /// @notice Returns the address of the ZK chain with the corresponding chainID + /// @param _chainId the chainId of the chain + /// @return chainAddress the address of the ZK chain + function getZKChain(uint256 _chainId) public view override returns (address chainAddress) { + // slither-disable-next-line unused-return + (, chainAddress) = zkChainMap.tryGet(_chainId); } - /// @notice the mailbox is called directly after the sharedBridge received the deposit + function ctmAssetIdFromChainId(uint256 _chainId) public view override returns (bytes32) { + address ctmAddress = chainTypeManager[_chainId]; + if (ctmAddress == address(0)) { + revert ChainIdNotRegistered(_chainId); + } + return ctmAssetIdFromAddress[ctmAddress]; + } + + /*////////////////////////////////////////////////////////////// + Mailbox forwarder + //////////////////////////////////////////////////////////////*/ + + /// @notice the mailbox is called directly after the assetRouter 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 + /// the msg.sender has approved mintValue allowance for the nativeTokenVault. + /// This means this is not ideal for contract calls, as the contract would have to handle token allowance of the base Token. + /// In case allowance is provided to the Asset Router, then it will be transferred to NTV. function requestL2TransactionDirect( L2TransactionRequestDirect calldata _request - ) external payable override nonReentrant whenNotPaused returns (bytes32 canonicalTxHash) { + ) external payable override nonReentrant whenNotPaused onlyL1 returns (bytes32 canonicalTxHash) { + // Note: If the ZK chain with corresponding `chainId` is not yet created, + // the transaction will revert on `bridgehubRequestL2Transaction` as call to zero address. { - address token = baseToken[_request.chainId]; - if (token == ETH_TOKEN_ADDRESS) { + bytes32 tokenAssetId = baseTokenAssetId[_request.chainId]; + if (tokenAssetId == ETH_TOKEN_ASSET_ID) { if (msg.value != _request.mintValue) { revert MsgValueMismatch(_request.mintValue, msg.value); } @@ -288,17 +476,17 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus } // slither-disable-next-line arbitrary-send-eth - sharedBridge.bridgehubDepositBaseToken{value: msg.value}( + IL1AssetRouter(assetRouter).bridgehubDepositBaseToken{value: msg.value}( _request.chainId, + tokenAssetId, msg.sender, - token, _request.mintValue ); } - address hyperchain = getHyperchain(_request.chainId); - address refundRecipient = AddressAliasHelper.actualRefundRecipient(_request.refundRecipient, msg.sender); - canonicalTxHash = IZkSyncHyperchain(hyperchain).bridgehubRequestL2Transaction( + canonicalTxHash = _sendRequest( + _request.chainId, + _request.refundRecipient, BridgehubL2TransactionRequest({ sender: msg.sender, contractL2: _request.l2Contract, @@ -308,27 +496,33 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus l2GasLimit: _request.l2GasLimit, l2GasPerPubdataByteLimit: _request.l2GasPerPubdataByteLimit, factoryDeps: _request.factoryDeps, - refundRecipient: refundRecipient + refundRecipient: address(0) }) ); } - /// @notice After depositing funds to the sharedBridge, the secondBridge is called + /// @notice After depositing funds to the assetRouter, 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, + /// the msg.sender has approved the nativeTokenVault with the mintValue, /// and also the necessary approvals are given for the second bridge. + /// In case allowance is provided to the Shared Bridge, then it will be transferred to NTV. /// @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. + /// @param _request the request for the L2 transaction function requestL2TransactionTwoBridges( L2TransactionRequestTwoBridgesOuter calldata _request - ) external payable override nonReentrant whenNotPaused returns (bytes32 canonicalTxHash) { + ) external payable override nonReentrant whenNotPaused onlyL1 returns (bytes32 canonicalTxHash) { + if (_request.secondBridgeAddress <= BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS) { + revert SecondBridgeAddressTooLow(_request.secondBridgeAddress, BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS); + } + { - address token = baseToken[_request.chainId]; + bytes32 tokenAssetId = baseTokenAssetId[_request.chainId]; uint256 baseTokenMsgValue; - if (token == ETH_TOKEN_ADDRESS) { + if (tokenAssetId == ETH_TOKEN_ASSET_ID) { if (msg.value != _request.mintValue + _request.secondBridgeValue) { revert MsgValueMismatch(_request.mintValue + _request.secondBridgeValue, msg.value); } @@ -339,19 +533,18 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus } baseTokenMsgValue = 0; } + // slither-disable-next-line arbitrary-send-eth - sharedBridge.bridgehubDepositBaseToken{value: baseTokenMsgValue}( + IL1AssetRouter(assetRouter).bridgehubDepositBaseToken{value: baseTokenMsgValue}( _request.chainId, + tokenAssetId, msg.sender, - token, _request.mintValue ); } - address hyperchain = getHyperchain(_request.chainId); - // slither-disable-next-line arbitrary-send-eth - L2TransactionRequestTwoBridgesInner memory outputRequest = IL1SharedBridge(_request.secondBridgeAddress) + L2TransactionRequestTwoBridgesInner memory outputRequest = IL1AssetRouter(_request.secondBridgeAddress) .bridgehubDeposit{value: _request.secondBridgeValue}( _request.chainId, msg.sender, @@ -363,13 +556,9 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus revert WrongMagicValue(uint256(TWO_BRIDGES_MAGIC_VALUE), uint256(outputRequest.magicValue)); } - address refundRecipient = AddressAliasHelper.actualRefundRecipient(_request.refundRecipient, msg.sender); - - if (_request.secondBridgeAddress <= BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS) { - revert AddressTooLow(_request.secondBridgeAddress); - } - // to avoid calls to precompiles - canonicalTxHash = IZkSyncHyperchain(hyperchain).bridgehubRequestL2Transaction( + canonicalTxHash = _sendRequest( + _request.chainId, + _request.refundRecipient, BridgehubL2TransactionRequest({ sender: _request.secondBridgeAddress, contractL2: outputRequest.l2Contract, @@ -379,17 +568,345 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus l2GasLimit: _request.l2GasLimit, l2GasPerPubdataByteLimit: _request.l2GasPerPubdataByteLimit, factoryDeps: outputRequest.factoryDeps, - refundRecipient: refundRecipient + refundRecipient: address(0) }) ); - IL1SharedBridge(_request.secondBridgeAddress).bridgehubConfirmL2Transaction( + IL1AssetRouter(_request.secondBridgeAddress).bridgehubConfirmL2Transaction( _request.chainId, outputRequest.txDataHash, canonicalTxHash ); } + /// @notice This function is used to send a request to the ZK chain. + /// @param _chainId the chainId of the chain + /// @param _refundRecipient the refund recipient + /// @param _request the request + /// @return canonicalTxHash the canonical transaction hash + function _sendRequest( + uint256 _chainId, + address _refundRecipient, + BridgehubL2TransactionRequest memory _request + ) internal returns (bytes32 canonicalTxHash) { + address refundRecipient = AddressAliasHelper.actualRefundRecipient(_refundRecipient, msg.sender); + _request.refundRecipient = refundRecipient; + address zkChain = zkChainMap.get(_chainId); + + canonicalTxHash = IZKChain(zkChain).bridgehubRequestL2Transaction(_request); + } + + /// @notice Used to forward a transaction on the gateway to the chains mailbox (from L1). + /// @param _chainId the chainId of the chain + /// @param _canonicalTxHash the canonical transaction hash + /// @param _expirationTimestamp the expiration timestamp for the transaction + function forwardTransactionOnGateway( + uint256 _chainId, + bytes32 _canonicalTxHash, + uint64 _expirationTimestamp + ) external override onlySettlementLayerRelayedSender { + if (L1_CHAIN_ID == block.chainid) { + revert NotInGatewayMode(); + } + address zkChain = zkChainMap.get(_chainId); + IZKChain(zkChain).bridgehubRequestL2TransactionOnGateway(_canonicalTxHash, _expirationTimestamp); + } + + /// @notice forwards function call to Mailbox based on ChainId + /// @param _chainId The chain ID of the ZK chain where to prove L2 message inclusion. + /// @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 _chainId, + uint256 _batchNumber, + uint256 _index, + L2Message calldata _message, + bytes32[] calldata _proof + ) external view override returns (bool) { + address zkChain = zkChainMap.get(_chainId); + return IZKChain(zkChain).proveL2MessageInclusion(_batchNumber, _index, _message, _proof); + } + + /// @notice forwards function call to Mailbox based on ChainId + /// @param _chainId The chain ID of the ZK chain where to prove L2 log inclusion. + /// @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 _chainId, + uint256 _batchNumber, + uint256 _index, + L2Log calldata _log, + bytes32[] calldata _proof + ) external view override returns (bool) { + address zkChain = zkChainMap.get(_chainId); + return IZKChain(zkChain).proveL2LogInclusion(_batchNumber, _index, _log, _proof); + } + + /// @notice forwards function call to Mailbox based on ChainId + /// @param _chainId The chain ID of the ZK chain where to prove L1->L2 tx status. + /// @param _l2TxHash The L2 canonical transaction hash + /// @param _l2BatchNumber The L2 batch number where the transaction 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 _merkleProof The Merkle proof of the processing L1 -> L2 transaction + /// @param _status The execution status of the L1 -> L2 transaction (true - success & 0 - fail) + /// @return Whether the proof is correct and the transaction was actually executed with provided status + /// NOTE: It may return `false` for incorrect proof, but it doesn't mean that the L1 -> L2 transaction has an opposite status! + function proveL1ToL2TransactionStatus( + uint256 _chainId, + bytes32 _l2TxHash, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes32[] calldata _merkleProof, + TxStatus _status + ) external view override returns (bool) { + address zkChain = zkChainMap.get(_chainId); + return + IZKChain(zkChain).proveL1ToL2TransactionStatus({ + _l2TxHash: _l2TxHash, + _l2BatchNumber: _l2BatchNumber, + _l2MessageIndex: _l2MessageIndex, + _l2TxNumberInBatch: _l2TxNumberInBatch, + _merkleProof: _merkleProof, + _status: _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 zkChain = zkChainMap.get(_chainId); + return IZKChain(zkChain).l2TransactionBaseCost(_gasPrice, _l2GasLimit, _l2GasPerPubdataByteLimit); + } + + /*////////////////////////////////////////////////////////////// + Chain migration + //////////////////////////////////////////////////////////////*/ + + /// @notice IL1AssetHandler interface, used to migrate (transfer) a chain to the settlement layer. + /// @param _settlementChainId the chainId of the settlement chain, i.e. where the message and the migrating chain is sent. + /// @param _assetId the assetId of the migrating chain's CTM + /// @param _originalCaller the message sender initiated a set of calls that leads to bridge burn + /// @param _data the data for the migration + function bridgeBurn( + uint256 _settlementChainId, + uint256 _l2MsgValue, + bytes32 _assetId, + address _originalCaller, + bytes calldata _data + ) + external + payable + override + requireZeroValue(_l2MsgValue + msg.value) + onlyAssetRouter + whenMigrationsNotPaused + returns (bytes memory bridgehubMintData) + { + if (!whitelistedSettlementLayers[_settlementChainId]) { + revert SLNotWhitelisted(); + } + + BridgehubBurnCTMAssetData memory bridgehubBurnData = abi.decode(_data, (BridgehubBurnCTMAssetData)); + if (_assetId != ctmAssetIdFromChainId(bridgehubBurnData.chainId)) { + revert IncorrectChainAssetId(_assetId, ctmAssetIdFromChainId(bridgehubBurnData.chainId)); + } + if (settlementLayer[bridgehubBurnData.chainId] != block.chainid) { + revert NotCurrentSL(settlementLayer[bridgehubBurnData.chainId], block.chainid); + } + settlementLayer[bridgehubBurnData.chainId] = _settlementChainId; + + if (whitelistedSettlementLayers[bridgehubBurnData.chainId]) { + revert SettlementLayersMustSettleOnL1(); + } + + address zkChain = zkChainMap.get(bridgehubBurnData.chainId); + if (zkChain == address(0)) { + revert HyperchainNotRegistered(); + } + if (_originalCaller != IZKChain(zkChain).getAdmin()) { + revert IncorrectSender(_originalCaller, IZKChain(zkChain).getAdmin()); + } + + bytes memory ctmMintData = IChainTypeManager(chainTypeManager[bridgehubBurnData.chainId]).forwardedBridgeBurn( + bridgehubBurnData.chainId, + bridgehubBurnData.ctmData + ); + bytes memory chainMintData = IZKChain(zkChain).forwardedBridgeBurn( + _settlementChainId == L1_CHAIN_ID + ? L1_SETTLEMENT_LAYER_VIRTUAL_ADDRESS + : zkChainMap.get(_settlementChainId), + _originalCaller, + bridgehubBurnData.chainData + ); + BridgehubMintCTMAssetData memory bridgeMintStruct = BridgehubMintCTMAssetData({ + chainId: bridgehubBurnData.chainId, + baseTokenAssetId: baseTokenAssetId[bridgehubBurnData.chainId], + ctmData: ctmMintData, + chainData: chainMintData + }); + bridgehubMintData = abi.encode(bridgeMintStruct); + + emit MigrationStarted(bridgehubBurnData.chainId, _assetId, _settlementChainId); + } + + /// @dev IL1AssetHandler interface, used to receive a chain on the settlement layer. + /// @param _assetId the assetId of the chain's CTM + /// @param _bridgehubMintData the data for the mint + function bridgeMint( + uint256, // originChainId + bytes32 _assetId, + bytes calldata _bridgehubMintData + ) external payable override requireZeroValue(msg.value) onlyAssetRouter whenMigrationsNotPaused { + BridgehubMintCTMAssetData memory bridgehubMintData = abi.decode( + _bridgehubMintData, + (BridgehubMintCTMAssetData) + ); + + address ctm = ctmAssetIdToAddress[_assetId]; + if (ctm == address(0)) { + revert NoCTMForAssetId(_assetId); + } + if (settlementLayer[bridgehubMintData.chainId] == block.chainid) { + revert AlreadyCurrentSL(block.chainid); + } + + settlementLayer[bridgehubMintData.chainId] = block.chainid; + chainTypeManager[bridgehubMintData.chainId] = ctm; + baseTokenAssetId[bridgehubMintData.chainId] = bridgehubMintData.baseTokenAssetId; + // To keep `assetIdIsRegistered` consistent, we'll also automatically register the base token. + // It is assumed that if the bridging happened, the token was approved on L1 already. + assetIdIsRegistered[bridgehubMintData.baseTokenAssetId] = true; + + address zkChain = getZKChain(bridgehubMintData.chainId); + bool contractAlreadyDeployed = zkChain != address(0); + if (!contractAlreadyDeployed) { + zkChain = IChainTypeManager(ctm).forwardedBridgeMint(bridgehubMintData.chainId, bridgehubMintData.ctmData); + if (zkChain == address(0)) { + revert ChainIdNotRegistered(bridgehubMintData.chainId); + } + // We want to allow any chain to be migrated, + _registerNewZKChain(bridgehubMintData.chainId, zkChain, false); + messageRoot.addNewChain(bridgehubMintData.chainId); + } + + IZKChain(zkChain).forwardedBridgeMint(bridgehubMintData.chainData, contractAlreadyDeployed); + + emit MigrationFinalized(bridgehubMintData.chainId, _assetId, zkChain); + } + + /// @dev IL1AssetHandler interface, used to undo a failed migration of a chain. + // / @param _chainId the chainId of the chain + /// @param _assetId the assetId of the chain's CTM + /// @param _data the data for the recovery. + function bridgeRecoverFailedTransfer( + uint256, + bytes32 _assetId, + address _depositSender, + bytes calldata _data + ) external payable override requireZeroValue(msg.value) onlyAssetRouter onlyL1 { + BridgehubBurnCTMAssetData memory bridgehubBurnData = abi.decode(_data, (BridgehubBurnCTMAssetData)); + + settlementLayer[bridgehubBurnData.chainId] = block.chainid; + + IChainTypeManager(chainTypeManager[bridgehubBurnData.chainId]).forwardedBridgeRecoverFailedTransfer({ + _chainId: bridgehubBurnData.chainId, + _assetInfo: _assetId, + _depositSender: _depositSender, + _ctmData: bridgehubBurnData.ctmData + }); + + IZKChain(getZKChain(bridgehubBurnData.chainId)).forwardedBridgeRecoverFailedTransfer({ + _chainId: bridgehubBurnData.chainId, + _assetInfo: _assetId, + _originalCaller: _depositSender, + _chainData: bridgehubBurnData.chainData + }); + } + + /// @dev Registers an already deployed chain with the bridgehub + /// @param _chainId The chain Id of the chain + /// @param _zkChain Address of the zkChain + function registerAlreadyDeployedZKChain(uint256 _chainId, address _zkChain) external onlyOwner onlyL1 { + if (_zkChain == address(0)) { + revert ZeroAddress(); + } + if (zkChainMap.contains(_chainId)) { + revert ChainIdAlreadyExists(); + } + if (IZKChain(_zkChain).getChainId() != _chainId) { + revert ChainIdMismatch(); + } + + address ctm = IZKChain(_zkChain).getChainTypeManager(); + address chainAdmin = IZKChain(_zkChain).getAdmin(); + bytes32 chainBaseTokenAssetId = IZKChain(_zkChain).getBaseTokenAssetId(); + address bridgeHub = IZKChain(_zkChain).getBridgehub(); + + if (bridgeHub != address(this)) { + revert IncorrectBridgeHubAddress(bridgeHub); + } + + _validateChainParams({_chainId: _chainId, _assetId: chainBaseTokenAssetId, _chainTypeManager: ctm}); + + chainTypeManager[_chainId] = ctm; + + baseTokenAssetId[_chainId] = chainBaseTokenAssetId; + settlementLayer[_chainId] = block.chainid; + + _registerNewZKChain(_chainId, _zkChain, true); + messageRoot.addNewChain(_chainId); + + emit NewChain(_chainId, ctm, chainAdmin); + } + + function _validateChainParams(uint256 _chainId, bytes32 _assetId, address _chainTypeManager) internal view { + if (_chainId == 0) { + revert ZeroChainId(); + } + + if (_chainId > type(uint48).max) { + revert ChainIdTooBig(); + } + + if (_chainId == block.chainid) { + revert ChainIdCantBeCurrentChain(); + } + + if (_chainTypeManager == address(0)) { + revert ZeroAddress(); + } + if (_assetId == bytes32(0)) { + revert EmptyAssetId(); + } + + if (!chainTypeManagerIsRegistered[_chainTypeManager]) { + revert CTMNotRegistered(); + } + + if (!assetIdIsRegistered[_assetId]) { + revert AssetIdNotSupported(_assetId); + } + + if (assetRouter == address(0)) { + revert SharedBridgeNotSet(); + } + if (chainTypeManager[_chainId] != address(0)) { + revert BridgeHubAlreadyRegistered(); + } + } + /*////////////////////////////////////////////////////////////// PAUSE //////////////////////////////////////////////////////////////*/ @@ -403,4 +920,28 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus function unpause() external onlyOwner { _unpause(); } + + /// @notice Pauses migration functions. + function pauseMigration() external onlyOwner { + migrationPaused = true; + } + + /// @notice Unpauses migration functions. + function unpauseMigration() external onlyOwner { + migrationPaused = false; + } + + /*////////////////////////////////////////////////////////////// + Legacy functions + //////////////////////////////////////////////////////////////*/ + + /// @notice return the ZK chain contract for a chainId + function getHyperchain(uint256 _chainId) public view returns (address) { + return getZKChain(_chainId); + } + + /// @notice return the asset router + function sharedBridge() public view returns (address) { + return assetRouter; + } } diff --git a/l1-contracts/contracts/bridgehub/CTMDeploymentTracker.sol b/l1-contracts/contracts/bridgehub/CTMDeploymentTracker.sol new file mode 100644 index 000000000..4409af221 --- /dev/null +++ b/l1-contracts/contracts/bridgehub/CTMDeploymentTracker.sol @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; + +import {IBridgehub, L2TransactionRequestTwoBridgesInner} from "./IBridgehub.sol"; +import {ICTMDeploymentTracker} from "./ICTMDeploymentTracker.sol"; + +import {IAssetRouterBase} from "../bridge/asset-router/IAssetRouterBase.sol"; +import {TWO_BRIDGES_MAGIC_VALUE} from "../common/Config.sol"; +import {L2_BRIDGEHUB_ADDR} from "../common/L2ContractAddresses.sol"; +import {OnlyBridgehub, CTMNotRegistered, NotOwnerViaRouter, NoEthAllowed, NotOwner, WrongCounterPart} from "./L1BridgehubErrors.sol"; +import {UnsupportedEncodingVersion} from "../common/L1ContractErrors.sol"; + +/// @dev The encoding version of the data. +bytes1 constant CTM_DEPLOYMENT_TRACKER_ENCODING_VERSION = 0x01; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @dev Contract to be deployed on L1, can link together other contracts based on AssetInfo. +contract CTMDeploymentTracker is ICTMDeploymentTracker, Ownable2StepUpgradeable { + /// @dev Bridgehub smart contract that is used to operate with L2 via asynchronous L2 <-> L1 communication. + IBridgehub public immutable override BRIDGE_HUB; + + /// @dev L1AssetRouter smart contract that is used to bridge assets (including chains) between L1 and L2. + IAssetRouterBase public immutable override L1_ASSET_ROUTER; + + /// @notice Checks that the message sender is the bridgehub. + modifier onlyBridgehub() { + if (msg.sender != address(BRIDGE_HUB)) { + revert OnlyBridgehub(msg.sender, address(BRIDGE_HUB)); + } + _; + } + + /// @notice Checks that the message sender is the bridgehub. + modifier onlyOwnerViaRouter(address _originalCaller) { + if (msg.sender != address(L1_ASSET_ROUTER) || _originalCaller != owner()) { + revert NotOwnerViaRouter(msg.sender, _originalCaller); + } + _; + } + + /// @dev Contract is expected to be used as proxy implementation on L1. + /// @dev Initialize the implementation to prevent Parity hack. + constructor(IBridgehub _bridgehub, IAssetRouterBase _l1AssetRouter) { + _disableInitializers(); + BRIDGE_HUB = _bridgehub; + L1_ASSET_ROUTER = _l1AssetRouter; + } + + /// @notice used to initialize the contract + /// @param _owner the owner of the contract + function initialize(address _owner) external initializer { + _transferOwnership(_owner); + } + + /// @notice Used to register the ctm asset in L1 contracts, AssetRouter and Bridgehub. + /// @param _ctmAddress the address of the ctm asset + function registerCTMAssetOnL1(address _ctmAddress) external onlyOwner { + if (!BRIDGE_HUB.chainTypeManagerIsRegistered(_ctmAddress)) { + revert CTMNotRegistered(); + } + L1_ASSET_ROUTER.setAssetHandlerAddressThisChain(bytes32(uint256(uint160(_ctmAddress))), address(BRIDGE_HUB)); + BRIDGE_HUB.setCTMAssetAddress(bytes32(uint256(uint160(_ctmAddress))), _ctmAddress); + } + + /// @notice The function responsible for registering the L2 counterpart of an CTM asset on the L2 Bridgehub. + /// @dev The function is called by the Bridgehub contract during the `Bridgehub.requestL2TransactionTwoBridges`. + /// @dev Since the L2 settlement layers `_chainId` might potentially have ERC20 tokens as native assets, + /// there are two ways to perform the L1->L2 transaction: + /// - via the `Bridgehub.requestL2TransactionDirect`. However, this would require the CTMDeploymentTracker to + /// handle the ERC20 balances to be used in the transaction. + /// - via the `Bridgehub.requestL2TransactionTwoBridges`. This way it will be the sender that provides the funds + /// for the L2 transaction. + /// The second approach is used due to its simplicity even though it gives the sender slightly more control over the call: + /// `gasLimit`, etc. + /// @param _chainId the chainId of the chain + /// @param _originalCaller the previous message sender + /// @param _data the data of the transaction + // slither-disable-next-line locked-ether + function bridgehubDeposit( + uint256 _chainId, + address _originalCaller, + uint256, + bytes calldata _data + ) external payable onlyBridgehub returns (L2TransactionRequestTwoBridgesInner memory request) { + if (msg.value != 0) { + revert NoEthAllowed(); + } + + if (_originalCaller != owner()) { + revert NotOwner(_originalCaller, owner()); + } + bytes1 encodingVersion = _data[0]; + if (encodingVersion != CTM_DEPLOYMENT_TRACKER_ENCODING_VERSION) { + revert UnsupportedEncodingVersion(); + } + (address _ctmL1Address, address _ctmL2Address) = abi.decode(_data[1:], (address, address)); + + request = _registerCTMAssetOnL2Bridgehub(_chainId, _ctmL1Address, _ctmL2Address); + } + + /// @notice The function called by the Bridgehub after the L2 transaction has been initiated. + /// @dev Not used in this contract. In case the transaction fails, we can just re-try it. + function bridgehubConfirmL2Transaction( + uint256 _chainId, + bytes32 _txDataHash, + bytes32 _txHash + ) external onlyBridgehub {} + + /// @notice Used to register the ctm asset in L2 AssetRouter. + /// @param _originalCaller the address that called the Router + /// @param _assetHandlerAddressOnCounterpart the address of the asset handler on the counterpart chain. + function bridgeCheckCounterpartAddress( + uint256, + bytes32, + address _originalCaller, + address _assetHandlerAddressOnCounterpart + ) external view override onlyOwnerViaRouter(_originalCaller) { + if (_assetHandlerAddressOnCounterpart != L2_BRIDGEHUB_ADDR) { + revert WrongCounterPart(_assetHandlerAddressOnCounterpart, L2_BRIDGEHUB_ADDR); + } + } + + function calculateAssetId(address _l1CTM) public view override returns (bytes32) { + return keccak256(abi.encode(block.chainid, address(this), bytes32(uint256(uint160(_l1CTM))))); + } + + /// @notice Used to register the ctm asset in L2 Bridgehub. + /// @param _chainId the chainId of the chain + function _registerCTMAssetOnL2Bridgehub( + // solhint-disable-next-line no-unused-vars + uint256 _chainId, + address _ctmL1Address, + address _ctmL2Address + ) internal pure returns (L2TransactionRequestTwoBridgesInner memory request) { + bytes memory l2TxCalldata = abi.encodeCall( + IBridgehub.setCTMAssetAddress, + (bytes32(uint256(uint160(_ctmL1Address))), _ctmL2Address) + ); + + request = L2TransactionRequestTwoBridgesInner({ + magicValue: TWO_BRIDGES_MAGIC_VALUE, + l2Contract: L2_BRIDGEHUB_ADDR, + l2Calldata: l2TxCalldata, + factoryDeps: new bytes[](0), + // The `txDataHash` is typically used in usual ERC20 bridges to commit to the transaction data + // so that the user can recover funds in case the bridging fails on L2. + // However, this contract uses the `requestL2TransactionTwoBridges` method just to perform an L1->L2 transaction. + // We do not need to recover anything and so `bytes32(0)` here is okay. + txDataHash: bytes32(0) + }); + } +} diff --git a/l1-contracts/contracts/bridgehub/IBridgehub.sol b/l1-contracts/contracts/bridgehub/IBridgehub.sol index 4216a6840..ff6f32d47 100644 --- a/l1-contracts/contracts/bridgehub/IBridgehub.sol +++ b/l1-contracts/contracts/bridgehub/IBridgehub.sol @@ -2,8 +2,11 @@ // We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. pragma solidity ^0.8.21; -import {IL1SharedBridge} from "../bridge/interfaces/IL1SharedBridge.sol"; import {L2Message, L2Log, TxStatus} from "../common/Messaging.sol"; +import {IL1AssetHandler} from "../bridge/interfaces/IL1AssetHandler.sol"; +import {ICTMDeploymentTracker} from "./ICTMDeploymentTracker.sol"; +import {IMessageRoot} from "./IMessageRoot.sol"; +import {IAssetHandler} from "../bridge/interfaces/IAssetHandler.sol"; struct L2TransactionRequestDirect { uint256 chainId; @@ -37,7 +40,22 @@ struct L2TransactionRequestTwoBridgesInner { bytes32 txDataHash; } -interface IBridgehub { +struct BridgehubMintCTMAssetData { + uint256 chainId; + bytes32 baseTokenAssetId; + bytes ctmData; + bytes chainData; +} + +struct BridgehubBurnCTMAssetData { + uint256 chainId; + bytes ctmData; + bytes chainData; +} + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +interface IBridgehub is IAssetHandler, IL1AssetHandler { /// @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); @@ -45,7 +63,29 @@ interface IBridgehub { /// @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 CTM asset registered + event AssetRegistered( + bytes32 indexed assetInfo, + address indexed _assetAddress, + bytes32 indexed additionalData, + address sender + ); + + event SettlementLayerRegistered(uint256 indexed chainId, bool indexed isWhitelisted); + + /// @notice Emitted when the bridging to the chain is started. + /// @param chainId Chain ID of the ZK chain + /// @param assetId Asset ID of the token for the zkChain's CTM + /// @param settlementLayerChainId The chain id of the settlement layer the chain migrates to. + event MigrationStarted(uint256 indexed chainId, bytes32 indexed assetId, uint256 indexed settlementLayerChainId); + + /// @notice Emitted when the bridging to the chain is complete. + /// @param chainId Chain ID of the ZK chain + /// @param assetId Asset ID of the token for the zkChain's CTM + /// @param zkChain The address of the ZK chain on the chain where it is migrated to. + event MigrationFinalized(uint256 indexed chainId, bytes32 indexed assetId, address indexed zkChain); + + /// @notice Starts the transfer of admin rights. Only the current admin or owner 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; @@ -54,17 +94,31 @@ interface IBridgehub { function acceptAdmin() external; /// Getters - function stateTransitionManagerIsRegistered(address _stateTransitionManager) external view returns (bool); + function chainTypeManagerIsRegistered(address _chainTypeManager) external view returns (bool); - function stateTransitionManager(uint256 _chainId) external view returns (address); + function chainTypeManager(uint256 _chainId) external view returns (address); - function tokenIsRegistered(address _baseToken) external view returns (bool); + function assetIdIsRegistered(bytes32 _baseTokenAssetId) external view returns (bool); function baseToken(uint256 _chainId) external view returns (address); - function sharedBridge() external view returns (IL1SharedBridge); + function baseTokenAssetId(uint256 _chainId) external view returns (bytes32); - function getHyperchain(uint256 _chainId) external view returns (address); + function sharedBridge() external view returns (address); + + function messageRoot() external view returns (IMessageRoot); + + function getZKChain(uint256 _chainId) external view returns (address); + + function getAllZKChains() external view returns (address[] memory); + + function getAllZKChainChainIDs() external view returns (uint256[] memory); + + function migrationPaused() external view returns (bool); + + function admin() external view returns (address); + + function assetRouter() external view returns (address); /// Mailbox forwarder @@ -113,20 +167,73 @@ interface IBridgehub { function createNewChain( uint256 _chainId, - address _stateTransitionManager, - address _baseToken, + address _chainTypeManager, + bytes32 _baseTokenAssetId, uint256 _salt, address _admin, - bytes calldata _initData + bytes calldata _initData, + bytes[] calldata _factoryDeps ) external returns (uint256 chainId); - function addStateTransitionManager(address _stateTransitionManager) external; + function addChainTypeManager(address _chainTypeManager) external; + + function removeChainTypeManager(address _chainTypeManager) external; + + function addTokenAssetId(bytes32 _baseTokenAssetId) external; + + function setAddresses( + address _sharedBridge, + ICTMDeploymentTracker _l1CtmDeployer, + IMessageRoot _messageRoot + ) external; - function removeStateTransitionManager(address _stateTransitionManager) external; + event NewChain(uint256 indexed chainId, address chainTypeManager, address indexed chainGovernance); - function addToken(address _token) external; + event ChainTypeManagerAdded(address indexed chainTypeManager); - function setSharedBridge(address _sharedBridge) external; + event ChainTypeManagerRemoved(address indexed chainTypeManager); + + event BaseTokenAssetIdRegistered(bytes32 indexed assetId); + + function whitelistedSettlementLayers(uint256 _chainId) external view returns (bool); + + function registerSettlementLayer(uint256 _newSettlementLayerChainId, bool _isWhitelisted) external; + + function settlementLayer(uint256 _chainId) external view returns (uint256); + + // function finalizeMigrationToGateway( + // uint256 _chainId, + // address _baseToken, + // address _sharedBridge, + // address _admin, + // uint256 _expectedProtocolVersion, + // ZKChainCommitment calldata _commitment, + // bytes calldata _diamondCut + // ) external; + + function forwardTransactionOnGateway( + uint256 _chainId, + bytes32 _canonicalTxHash, + uint64 _expirationTimestamp + ) external; + + function ctmAssetIdFromChainId(uint256 _chainId) external view returns (bytes32); + + function ctmAssetIdFromAddress(address _ctmAddress) external view returns (bytes32); + + function l1CtmDeployer() external view returns (ICTMDeploymentTracker); + + function ctmAssetIdToAddress(bytes32 _assetInfo) external view returns (address); + + function setCTMAssetAddress(bytes32 _additionalData, address _assetAddress) external; + + function L1_CHAIN_ID() external view returns (uint256); + + function registerAlreadyDeployedZKChain(uint256 _chainId, address _hyperchain) external; + + /// @notice return the ZK chain contract for a chainId + /// @dev It is a legacy method. Do not use! + function getHyperchain(uint256 _chainId) external view returns (address); - event NewChain(uint256 indexed chainId, address stateTransitionManager, address indexed chainGovernance); + function registerLegacyChain(uint256 _chainId) external; } diff --git a/l1-contracts/contracts/bridgehub/ICTMDeploymentTracker.sol b/l1-contracts/contracts/bridgehub/ICTMDeploymentTracker.sol new file mode 100644 index 000000000..1b7558f29 --- /dev/null +++ b/l1-contracts/contracts/bridgehub/ICTMDeploymentTracker.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {L2TransactionRequestTwoBridgesInner, IBridgehub} from "./IBridgehub.sol"; +import {IAssetRouterBase} from "../bridge/asset-router/IAssetRouterBase.sol"; +import {IL1AssetDeploymentTracker} from "../bridge/interfaces/IL1AssetDeploymentTracker.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +interface ICTMDeploymentTracker is IL1AssetDeploymentTracker { + function bridgehubDeposit( + uint256 _chainId, + address _originalCaller, + uint256 _l2Value, + bytes calldata _data + ) external payable returns (L2TransactionRequestTwoBridgesInner memory request); + + function BRIDGE_HUB() external view returns (IBridgehub); + + function L1_ASSET_ROUTER() external view returns (IAssetRouterBase); + + function registerCTMAssetOnL1(address _ctmAddress) external; + + function calculateAssetId(address _l1CTM) external view returns (bytes32); +} diff --git a/l1-contracts/contracts/bridgehub/IMessageRoot.sol b/l1-contracts/contracts/bridgehub/IMessageRoot.sol new file mode 100644 index 000000000..d4a3c7d7b --- /dev/null +++ b/l1-contracts/contracts/bridgehub/IMessageRoot.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IBridgehub} from "./IBridgehub.sol"; + +/** + * @author Matter Labs + * @notice MessageRoot contract is responsible for storing and aggregating the roots of the batches from different chains into the MessageRoot. + * @custom:security-contact security@matterlabs.dev + */ +interface IMessageRoot { + function BRIDGE_HUB() external view returns (IBridgehub); + + function addNewChain(uint256 _chainId) external; + + function addChainBatchRoot(uint256 _chainId, uint256 _batchNumber, bytes32 _chainBatchRoot) external; +} diff --git a/l1-contracts/contracts/bridgehub/L1BridgehubErrors.sol b/l1-contracts/contracts/bridgehub/L1BridgehubErrors.sol new file mode 100644 index 000000000..9481f2ff8 --- /dev/null +++ b/l1-contracts/contracts/bridgehub/L1BridgehubErrors.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.21; + +// 0xa2ac02a0 +error NotRelayedSender(address msgSender, address settlementLayerRelaySender); + +// 0xf306a770 +error NotAssetRouter(address msgSender, address sharedBridge); + +// 0xff514c10 +error ChainIdAlreadyPresent(); + +// 0x4bd4ae07 +error ChainNotPresentInCTM(); + +// 0xfe919e28 +error AssetIdAlreadyRegistered(); + +// 0xc630ef3c +error CTMNotRegistered(); + +// 0x4c0f5001 +error ChainIdNotRegistered(); + +// 0xb78dbaa7 +error SecondBridgeAddressTooLow(address secondBridgeAddress, address minSecondBridgeAddress); + +// 0x472477e2 +error NotInGatewayMode(); + +// 0x90c7cbf1 +error SLNotWhitelisted(); + +// 0x48857c1d +error IncorrectChainAssetId(bytes32 assetId, bytes32 assetIdFromChainId); + +// 0xc0ca9182 +error NotCurrentSL(uint256 settlementLayerChainId, uint256 blockChainId); + +// 0xeab895aa +error HyperchainNotRegistered(); + +// 0xf5e39c1f +error IncorrectSender(address prevMsgSender, address chainAdmin); + +// 0x587df426 +error AlreadyCurrentSL(uint256 blockChainId); + +// 0x65e8a019 +error ChainExists(); + +// 0x913183d8 +error MessageRootNotRegistered(); + +// 0x7f4316f3 +error NoEthAllowed(); + +// 0x23295f0e +error NotOwner(address sender, address owner); + +// 0x92626457 +error WrongCounterPart(address addressOnCounterPart, address l2BridgehubAddress); + +// 0xecb34449 +error NotL1(uint256 l1ChainId, uint256 blockChainId); + +// 0x527b87c7 +error OnlyBridgehub(address msgSender, address bridgehub); + +// 0x73fe6c1b +error OnlyChain(address msgSender, address zkChainAddress); + +// 0x693cd3dc +error NotOwnerViaRouter(address msgSender, address originalCaller); + +// 0x5de72107 +error ChainNotLegacy(); diff --git a/l1-contracts/contracts/bridgehub/MessageRoot.sol b/l1-contracts/contracts/bridgehub/MessageRoot.sol new file mode 100644 index 000000000..14bf5c1b8 --- /dev/null +++ b/l1-contracts/contracts/bridgehub/MessageRoot.sol @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {DynamicIncrementalMerkle} from "../common/libraries/DynamicIncrementalMerkle.sol"; +import {Initializable} from "@openzeppelin/contracts-v4/proxy/utils/Initializable.sol"; + +import {IBridgehub} from "./IBridgehub.sol"; +import {IMessageRoot} from "./IMessageRoot.sol"; +import {OnlyBridgehub, OnlyChain, ChainExists, MessageRootNotRegistered} from "./L1BridgehubErrors.sol"; +import {FullMerkle} from "../common/libraries/FullMerkle.sol"; + +import {MessageHashing} from "../common/libraries/MessageHashing.sol"; + +// Chain tree consists of batch commitments as their leaves. We use hash of "new bytes(96)" as the hash of an empty leaf. +bytes32 constant CHAIN_TREE_EMPTY_ENTRY_HASH = bytes32( + 0x46700b4d40ac5c35af2c22dda2787a91eb567b06c924a8fb8ae9a05b20c08c21 +); + +// The single shared tree consists of the roots of chain trees as its leaves. We use hash of "new bytes(96)" as the hash of an empty leaf. +bytes32 constant SHARED_ROOT_TREE_EMPTY_HASH = bytes32( + 0x46700b4d40ac5c35af2c22dda2787a91eb567b06c924a8fb8ae9a05b20c08c21 +); + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @dev The MessageRoot contract is responsible for storing the cross message roots of the chains and the aggregated root of all chains. +contract MessageRoot is IMessageRoot, Initializable { + using FullMerkle for FullMerkle.FullTree; + using DynamicIncrementalMerkle for DynamicIncrementalMerkle.Bytes32PushTree; + + event AddedChain(uint256 indexed chainId, uint256 indexed chainIndex); + + event AppendedChainBatchRoot(uint256 indexed chainId, uint256 indexed batchNumber, bytes32 batchRoot); + + event Preimage(bytes32 one, bytes32 two); + + /// @dev Bridgehub smart contract that is used to operate with L2 via asynchronous L2 <-> L1 communication. + IBridgehub public immutable override BRIDGE_HUB; + + /// @notice The number of chains that are registered. + uint256 public chainCount; + + /// @notice The mapping from chainId to chainIndex. Note index 0 is maintained for the chain the contract is on. + mapping(uint256 chainId => uint256 chainIndex) public chainIndex; + + /// @notice The mapping from chainIndex to chainId. + mapping(uint256 chainIndex => uint256 chainId) public chainIndexToId; + + /// @notice The shared full merkle tree storing the aggregate hash. + FullMerkle.FullTree public sharedTree; + + /// @dev The incremental merkle tree storing the chain message roots. + mapping(uint256 chainId => DynamicIncrementalMerkle.Bytes32PushTree tree) internal chainTree; + + /// @notice only the bridgehub can call + modifier onlyBridgehub() { + if (msg.sender != address(BRIDGE_HUB)) { + revert OnlyBridgehub(msg.sender, address(BRIDGE_HUB)); + } + _; + } + + /// @notice only the bridgehub can call + /// @param _chainId the chainId of the chain + modifier onlyChain(uint256 _chainId) { + if (msg.sender != BRIDGE_HUB.getZKChain(_chainId)) { + revert OnlyChain(msg.sender, BRIDGE_HUB.getZKChain(_chainId)); + } + _; + } + + /// @dev Contract is expected to be used as proxy implementation on L1, but as a system contract on L2. + /// This means we call the _initialize in both the constructor and the initialize functions. + /// @dev Initialize the implementation to prevent Parity hack. + constructor(IBridgehub _bridgehub) { + BRIDGE_HUB = _bridgehub; + _initialize(); + _disableInitializers(); + } + + /// @dev Initializes a contract for later use. Expected to be used in the proxy on L1, on L2 it is a system contract without a proxy. + function initialize() external initializer { + _initialize(); + } + + function addNewChain(uint256 _chainId) external onlyBridgehub { + if (chainRegistered(_chainId)) { + revert ChainExists(); + } + _addNewChain(_chainId); + } + + function chainRegistered(uint256 _chainId) public view returns (bool) { + return (_chainId == block.chainid || chainIndex[_chainId] != 0); + } + + /// @dev add a new chainBatchRoot to the chainTree + function addChainBatchRoot( + uint256 _chainId, + uint256 _batchNumber, + bytes32 _chainBatchRoot + ) external onlyChain(_chainId) { + if (!chainRegistered(_chainId)) { + revert MessageRootNotRegistered(); + } + bytes32 chainRoot; + // slither-disable-next-line unused-return + (, chainRoot) = chainTree[_chainId].push(MessageHashing.batchLeafHash(_chainBatchRoot, _batchNumber)); + + // slither-disable-next-line unused-return + sharedTree.updateLeaf(chainIndex[_chainId], MessageHashing.chainIdLeafHash(chainRoot, _chainId)); + + emit Preimage(chainRoot, MessageHashing.chainIdLeafHash(chainRoot, _chainId)); + + emit AppendedChainBatchRoot(_chainId, _batchNumber, _chainBatchRoot); + } + + /// @dev Gets the aggregated root of all chains. + function getAggregatedRoot() external view returns (bytes32) { + if (chainCount == 0) { + return SHARED_ROOT_TREE_EMPTY_HASH; + } + return sharedTree.root(); + } + + /// @dev Gets the message root of a single chain. + /// @param _chainId the chainId of the chain + function getChainRoot(uint256 _chainId) external view returns (bytes32) { + return chainTree[_chainId].root(); + } + + function updateFullTree() public { + uint256 cachedChainCount = chainCount; + bytes32[] memory newLeaves = new bytes32[](cachedChainCount); + for (uint256 i = 0; i < cachedChainCount; ++i) { + newLeaves[i] = MessageHashing.chainIdLeafHash(chainTree[chainIndexToId[i]].root(), chainIndexToId[i]); + } + // slither-disable-next-line unused-return + sharedTree.updateAllLeaves(newLeaves); + } + + function _initialize() internal { + // slither-disable-next-line unused-return + sharedTree.setup(SHARED_ROOT_TREE_EMPTY_HASH); + _addNewChain(block.chainid); + } + + /// @dev Adds a single chain to the message root. + /// @param _chainId the chainId of the chain + function _addNewChain(uint256 _chainId) internal { + uint256 cachedChainCount = chainCount; + + // Since only the bridgehub can add new chains to the message root, it is expected that + // it will be responsible for ensuring that the number of chains does not exceed the limit. + ++chainCount; + chainIndex[_chainId] = cachedChainCount; + chainIndexToId[cachedChainCount] = _chainId; + + // slither-disable-next-line unused-return + bytes32 initialHash = chainTree[_chainId].setup(CHAIN_TREE_EMPTY_ENTRY_HASH); + + // slither-disable-next-line unused-return + sharedTree.pushNewLeaf(MessageHashing.chainIdLeafHash(initialHash, _chainId)); + + emit AddedChain(_chainId, cachedChainCount); + } +} diff --git a/l1-contracts/contracts/common/Config.sol b/l1-contracts/contracts/common/Config.sol index c0e05f1fc..da2aa92fd 100644 --- a/l1-contracts/contracts/common/Config.sol +++ b/l1-contracts/contracts/common/Config.sol @@ -18,7 +18,6 @@ uint256 constant MAX_L2_TO_L1_LOGS_COMMITMENT_BYTES = 4 + L2_TO_L1_LOG_SERIALIZE /// @dev Actually equal to the `keccak256(new bytes(L2_TO_L1_LOG_SERIALIZE_SIZE))` bytes32 constant L2_L1_LOGS_TREE_DEFAULT_LEAF_HASH = 0x72abee45b59e344af8a6e520241c4744aff26ed411f4c4b00f8af09adada43ba; -// TODO: change constant to the real root hash of empty Merkle tree (SMA-184) bytes32 constant DEFAULT_L2_LOGS_TREE_ROOT_HASH = bytes32(0); /// @dev Denotes the type of the ZKsync transaction that came from L1. @@ -73,7 +72,7 @@ uint256 constant L1_TX_DELTA_FACTORY_DEPS_L2_GAS = 2473; uint256 constant L1_TX_DELTA_FACTORY_DEPS_PUBDATA = 64; /// @dev The number of pubdata an L1->L2 transaction requires with each new factory dependency -uint256 constant MAX_NEW_FACTORY_DEPS = 32; +uint256 constant MAX_NEW_FACTORY_DEPS = 64; /// @dev The L2 gasPricePerPubdata required to be used in bridges. uint256 constant REQUIRED_L2_GAS_PRICE_PER_PUBDATA = 800; @@ -102,9 +101,63 @@ uint256 constant MEMORY_OVERHEAD_GAS = 10; /// @dev The maximum gas limit for a priority transaction in L2. uint256 constant PRIORITY_TX_MAX_GAS_LIMIT = 72_000_000; +/// @dev the address used to identify eth as the base token for chains. address constant ETH_TOKEN_ADDRESS = address(1); +/// @dev the value returned in bridgehubDeposit in the TwoBridges function. 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)); + +/// @dev the maximum number of supported chains, this is an arbitrary limit. +/// @dev Note, that in case of a malicious Bridgehub admin, the total number of chains +/// can be up to 2 times higher. This may be possible, in case the old ChainTypeManager +/// had `100` chains and these were migrated to the Bridgehub only after `MAX_NUMBER_OF_ZK_CHAINS` +/// were added to the bridgehub via creation of new chains. +uint256 constant MAX_NUMBER_OF_ZK_CHAINS = 100; + +/// @dev Used as the `msg.sender` for transactions that relayed via a settlement layer. +address constant SETTLEMENT_LAYER_RELAY_SENDER = address(uint160(0x1111111111111111111111111111111111111111)); + +/// @dev The metadata version that is supported by the ZK Chains to prove that an L2->L1 log was included in a batch. +uint256 constant SUPPORTED_PROOF_METADATA_VERSION = 1; + +/// @dev The virtual address of the L1 settlement layer. +address constant L1_SETTLEMENT_LAYER_VIRTUAL_ADDRESS = address( + uint160(uint256(keccak256("L1_SETTLEMENT_LAYER_VIRTUAL_ADDRESS")) - 1) +); + +struct PriorityTreeCommitment { + uint256 nextLeafIndex; + uint256 startIndex; + uint256 unprocessedIndex; + bytes32[] sides; +} + +// Info that allows to restore a chain. +struct ZKChainCommitment { + /// @notice Total number of executed batches i.e. batches[totalBatchesExecuted] points at the latest executed batch + /// (batch 0 is genesis) + uint256 totalBatchesExecuted; + /// @notice Total number of proved batches i.e. batches[totalBatchesProved] points at the latest proved batch + uint256 totalBatchesVerified; + /// @notice Total number of committed batches i.e. batches[totalBatchesCommitted] points at the latest committed + /// batch + uint256 totalBatchesCommitted; + /// @notice The hash of the L2 system contracts ugpgrade transaction. + /// @dev It is non zero if the migration happens while the upgrade is not yet finalized. + bytes32 l2SystemContractsUpgradeTxHash; + /// @notice The batch when the system contracts upgrade transaction was executed. + /// @dev It is non-zero if the migration happens while the batch where the upgrade tx was present + /// has not been finalized (executed) yet. + uint256 l2SystemContractsUpgradeBatchNumber; + /// @notice The hashes of the batches that are needed to keep the blockchain working. + /// @dev The length of the array is equal to the `totalBatchesCommitted - totalBatchesExecuted + 1`, i.e. we need + /// to store all the unexecuted batches' hashes + 1 latest executed one. + bytes32[] batchHashes; + /// @notice Commitment to the priority merkle tree. + PriorityTreeCommitment priorityTree; + /// @notice Whether a chain is a permanent rollup. + bool isPermanentRollup; +} diff --git a/l1-contracts/contracts/common/L1ContractErrors.sol b/l1-contracts/contracts/common/L1ContractErrors.sol index 73ff72cc9..e8754aa35 100644 --- a/l1-contracts/contracts/common/L1ContractErrors.sol +++ b/l1-contracts/contracts/common/L1ContractErrors.sol @@ -1,14 +1,42 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.21; +// 0x5ecf2d7a +error AccessToFallbackDenied(address target, address invoker); +// 0x3995f750 +error AccessToFunctionDenied(address target, bytes4 selector, address invoker); +// 0x6c167909 +error OnlySelfAllowed(); +// 0x52e22c98 +error RestrictionWasNotPresent(address restriction); +// 0xf126e113 +error RestrictionWasAlreadyPresent(address restriction); +// 0x3331e9c0 +error CallNotAllowed(bytes call); +// 0xf6fd7071 +error RemovingPermanentRestriction(); +// 0xfcb9b2e1 +error UnallowedImplementation(bytes32 implementationHash); // 0x1ff9d522 error AddressAlreadyUsed(address addr); +// 0x0dfb42bf +error AddressAlreadySet(address addr); // 0x86bb51b8 error AddressHasNoCode(address); -// 0x1eee5481 -error AddressTooLow(address); -// 0x6afd6c20 -error BadReturnData(); +// 0x1f73225f +error AddressMismatch(address expected, address supplied); +// 0x5e85ae73 +error AmountMustBeGreaterThanZero(); +// 0xfde974f4 +error AssetHandlerDoesNotExist(bytes32 assetId); +// 0x1294e9e1 +error AssetIdMismatch(bytes32 expected, bytes32 supplied); +// 0xfe919e28 +error AssetIdAlreadyRegistered(); +// 0x0bfcef28 +error AlreadyWhitelisted(address); +// 0x04a0b7e9 +error AssetIdNotSupported(bytes32 assetId); // 0x6ef9a972 error BaseTokenGasPriceDenominatorNotSet(); // 0x55ad3fd3 @@ -17,48 +45,54 @@ error BatchHashMismatch(bytes32 expected, bytes32 actual); error BatchNotExecuted(uint256 batchNumber); // 0xbd4455ff error BatchNumberMismatch(uint256 expectedBatchNumber, uint256 providedBatchNumber); -// 0xafd53e2f -error BlobHashCommitmentError(uint256 index, bool blobHashEmpty, bool blobCommitmentEmpty); // 0x6cf12312 error BridgeHubAlreadyRegistered(); -// 0xcf102c5a -error CalldataLengthTooBig(); +// 0xdb538614 +error BridgeMintNotImplemented(); // 0xe85392f9 error CanOnlyProcessOneBatch(); // 0x00c6ead2 error CantExecuteUnprovenBatches(); // 0xe18cb383 error CantRevertExecutedBatch(); -// 0x78d2ed02 -error ChainAlreadyLive(); +// 0x24591d89 +error ChainIdAlreadyExists(); +// 0x717a1656 +error ChainIdCantBeCurrentChain(); +// 0xa179f8c9 +error ChainIdMismatch(); +// 0x23f3c357 +error ChainIdNotRegistered(uint256 chainId); // 0x8f620a06 error ChainIdTooBig(); // 0xf7a01e4d error DelegateCallFailed(bytes returnData); // 0x0a8ed92c error DenominatorIsZero(); +// 0xb4f54111 +error DeployFailed(); +// 0x138ee1a3 +error DeployingBridgedTokenForNativeToken(); // 0xc7c9660f error DepositDoesNotExist(); // 0xad2fa98e error DepositExists(); -// 0x79cacff1 -error DepositFailed(); -// 0xae08e4af -error DepositIncorrectAmount(uint256 expectedAmt, uint256 providedAmt); // 0x0e7ee319 error DiamondAlreadyFrozen(); -// 0x682dabb4 -error DiamondFreezeIncorrectState(); // 0xa7151b9a error DiamondNotFrozen(); -// 0xfc7ab1d3 -error EmptyBlobVersionHash(uint256 index); +// 0x7138356f +error EmptyAddress(); +// 0x2d4d012f +error EmptyAssetId(); +// 0x1c25715b +error EmptyBytes32(); // 0x95b66fe9 error EmptyDeposit(); +// 0x627e0872 +error ETHDepositNotSupported(); // 0xac4a3f98 error FacetExists(bytes4 selector, address); -// 0x79e12cc3 -error FacetIsFrozen(bytes4 func); // 0xc91cf3b1 error GasPerPubdataMismatch(); // 0x6d4a7df8 @@ -73,18 +107,16 @@ error GenesisUpgradeZero(); error HashedLogIsDefault(); // 0x0b08d5be error HashMismatch(bytes32 expected, bytes32 actual); -// 0xb615c2b1 -error HyperchainLimitReached(); +// 0x601b6882 +error ZKChainLimitReached(); +// 0xdd381a4c +error IncorrectBridgeHubAddress(address bridgehub); // 0x826fb11e error InsufficientChainBalance(); -// 0x356680b7 -error InsufficientFunds(); -// 0x7a47c9a2 -error InvalidChainId(); +// 0xcbd9d2e0 +error InvalidCaller(address); // 0x4fbe5dba error InvalidDelay(); -// 0x0af806e0 -error InvalidHash(); // 0xc1780bd6 error InvalidLogSender(address sender, uint256 logKey); // 0xd8e9405c @@ -93,54 +125,36 @@ error InvalidNumberOfBlobs(uint256 expected, uint256 numCommitments, uint256 num error InvalidProof(); // 0x5428eae7 error InvalidProtocolVersion(); -// 0x53e6d04d -error InvalidPubdataCommitmentsSize(); // 0x5513177c error InvalidPubdataHash(bytes32 expectedHash, bytes32 provided); -// 0x9094af7e -error InvalidPubdataLength(); -// 0xc5d09071 -error InvalidPubdataMode(); // 0x6f1cf752 error InvalidPubdataPricingMode(); // 0x12ba286f error InvalidSelector(bytes4 func); // 0x5cb29523 error InvalidTxType(uint256 txType); -// 0x5f1aa154 +// 0x0214acb6 error InvalidUpgradeTxn(UpgradeTxVerifyParam); -// 0xaa7feadc -error InvalidValue(); -// 0xa4f62e33 -error L2BridgeNotDeployed(uint256 chainId); -// 0xff8811ff -error L2BridgeNotSet(uint256 chainId); -// 0xcb5e4247 -error L2BytecodeHashMismatch(bytes32 expected, bytes32 provided); // 0xfb5c22e6 error L2TimestampTooBig(); // 0xd2c011d6 error L2UpgradeNonceNotEqualToNewProtocolVersion(uint256 nonce, uint256 protocolVersion); // 0x97e1359e error L2WithdrawalMessageWrongLength(uint256 messageLen); -// 0x32eb8b2f -error LegacyMethodIsSupportedOnlyForEra(); // 0xe37d2c02 error LengthIsNotDivisibleBy32(uint256 length); // 0x1b6825bb error LogAlreadyProcessed(uint8); -// 0x43e266b0 +// 0xcea34703 error MalformedBytecode(BytecodeError); -// 0x59170bf0 -error MalformedCalldata(); -// 0x16509b9a -error MalformedMessage(); // 0x9bb54c35 error MerkleIndexOutOfBounds(); // 0x8e23ac1a error MerklePathEmpty(); // 0x1c500385 error MerklePathOutOfBounds(); +// 0x3312a450 +error MigrationPaused(); // 0xfa44b527 error MissingSystemLogs(uint256 expected, uint256 actual); // 0x4a094431 @@ -155,8 +169,6 @@ error NoCallsProvided(); error NoFunctionsForDiamondCut(); // 0xcab098d8 error NoFundsTransferred(); -// 0x92290acc -error NonEmptyBlobVersionHash(uint256 index); // 0xc21b1ab7 error NonEmptyCalldata(); // 0x536ec84b @@ -165,12 +177,14 @@ error NonEmptyMsgValue(); error NonIncreasingTimestamp(); // 0x0105f9c0 error NonSequentialBatch(); -// 0x4ef79e5a -error NonZeroAddress(address); +// 0x0ac76f01 +error NonSequentialVersion(); // 0xdd629f86 error NotEnoughGas(); // 0xdd7e3621 error NotInitializedReentrancyGuard(); +// 0xdf17e316 +error NotWhitelisted(address); // 0xf3ed9dfa error OnlyEraSupported(); // 0x1a21feed @@ -179,16 +193,14 @@ error OperationExists(); error OperationMustBePending(); // 0xe1c1ff37 error OperationMustBeReady(); +// 0xb926450e +error OriginChainIdNotFound(); // 0xd7f50a9d error PatchCantSetUpgradeTxn(); // 0x962fd7d0 error PatchUpgradeCantSetBootloader(); // 0x559cc34e error PatchUpgradeCantSetDefaultAccount(); -// 0x8d5851de -error PointEvalCallFailed(bytes); -// 0x4daa985d -error PointEvalFailed(bytes); // 0x9b48e060 error PreviousOperationNotExecuted(); // 0x5c598b60 @@ -211,12 +223,8 @@ error ProtocolVersionMinorDeltaTooBig(uint256 limit, uint256 proposed); error ProtocolVersionTooSmall(); // 0x53dee67b error PubdataCommitmentsEmpty(); -// 0x7734c31a -error PubdataCommitmentsTooBig(); // 0x959f26fb error PubdataGreaterThanLimit(uint256 limit, uint256 length); -// 0x2a4a14df -error PubdataPerBatchIsLessThanTxn(); // 0x63c36549 error QueueIsEmpty(); // 0xab143c06 @@ -227,42 +235,30 @@ error RemoveFunctionFacetAddressNotZero(address facet); error RemoveFunctionFacetAddressZero(); // 0x3580370c error ReplaceFunctionFacetAddressZero(); -// 0xdab52f4b -error RevertedBatchBeforeNewBatch(); // 0x9a67c1cb error RevertedBatchNotAfterNewLastBatch(); // 0xd3b6535b error SelectorsMustAllHaveSameFreezability(); -// 0x7774d2f9 +// 0xd7a6b5e6 error SharedBridgeValueNotSet(SharedBridgeKey); -// 0xc1d9246c -error SharedBridgeBalanceMismatch(); // 0x856d5b77 error SharedBridgeNotSet(); -// 0xcac5fc40 -error SharedBridgeValueAlreadySet(SharedBridgeKey); // 0xdf3a8fdd error SlotOccupied(); -// 0xd0bc70cf -error STMAlreadyRegistered(); -// 0x09865e10 -error STMNotRegistered(); +// 0xec273439 +error CTMAlreadyRegistered(); +// 0xc630ef3c +error CTMNotRegistered(); // 0xae43b424 error SystemLogsSizeTooBig(); // 0x08753982 error TimeNotReached(uint256 expectedTimestamp, uint256 actualTimestamp); // 0x2d50c33b error TimestampError(); -// 0x4f4b634e -error TokenAlreadyRegistered(address token); -// 0xddef98d7 -error TokenNotRegistered(address token); // 0x06439c6b error TokenNotSupported(address token); // 0x23830e28 error TokensWithFeesNotSupported(); -// 0xf640f0e5 -error TooManyBlobs(); // 0x76da24b9 error TooManyFactoryDeps(); // 0xf0b4e88f @@ -277,34 +273,124 @@ error TxnBodyGasLimitNotEnoughGas(); error Unauthorized(address caller); // 0xe52478c7 error UndefinedDiamondCutAction(); -// 0x07218375 -error UnexpectedNumberOfFactoryDeps(); // 0x6aa39880 error UnexpectedSystemLog(uint256 logKey); // 0xf093c2e5 error UpgradeBatchNumberIsNotZero(); +// 0x084a1449 +error UnsupportedEncodingVersion(); // 0x47b3b145 error ValidateTxnNotEnoughGas(); // 0x626ade30 error ValueMismatch(uint256 expected, uint256 actual); // 0xe1022469 error VerifiedBatchesExceedsCommittedBatches(); -// 0x2dbdba00 -error VerifyProofCommittedVerifiedMismatch(); // 0xae899454 error WithdrawalAlreadyFinalized(); -// 0x27fcd9d1 -error WithdrawalFailed(); // 0x750b219c error WithdrawFailed(); // 0x15e8e429 error WrongMagicValue(uint256 expectedMagicValue, uint256 providedMagicValue); // 0xd92e233d error ZeroAddress(); -// 0x669567ea -error ZeroBalance(); // 0xc84885d4 error ZeroChainId(); +// 0x99d8fec9 +error EmptyData(); +// 0xf3dd1b9c +error UnsupportedCommitBatchEncoding(uint8 version); +// 0xf338f830 +error UnsupportedProofBatchEncoding(uint8 version); +// 0x14d2ed8a +error UnsupportedExecuteBatchEncoding(uint8 version); +// 0xd7d93e1f +error IncorrectBatchBounds( + uint256 processFromExpected, + uint256 processToExpected, + uint256 processFromProvided, + uint256 processToProvided +); +// 0x64107968 +error AssetHandlerNotRegistered(bytes32 assetId); +// 0x64846fe4 +error NotARestriction(address addr); +// 0xfa5cd00f +error NotAllowed(address addr); +// 0xccdd18d2 +error BytecodeAlreadyPublished(bytes32 bytecodeHash); +// 0x25d8333c +error CallerNotTimerAdmin(); +// 0x907f8e51 +error DeadlineNotYetPassed(); +// 0x6eef58d1 +error NewDeadlineNotGreaterThanCurrent(); +// 0x8b7e144a +error NewDeadlineExceedsMaxDeadline(); +// 0x2a5989a0 +error AlreadyPermanentRollup(); +// 0x92daded2 +error InvalidDAForPermanentRollup(); +// 0xd0266e26 +error NotSettlementLayer(); +// 0x7a4902ad +error TimerAlreadyStarted(); + +// 0x09aa9830 +error MerklePathLengthMismatch(uint256 pathLength, uint256 expectedLength); + +// 0xc33e6128 +error MerkleNothingToProve(); + +// 0xafbb7a4e +error MerkleIndexOrHeightMismatch(); + +// 0x1b582fcf +error MerkleWrongIndex(uint256 index, uint256 maxNodeNumber); + +// 0x485cfcaa +error MerkleWrongLength(uint256 newLeavesLength, uint256 leafNumber); + +// 0xce63ce17 +error NoCTMForAssetId(bytes32 assetId); +// 0x02181a13 +error SettlementLayersMustSettleOnL1(); +// 0x1850b46b +error TokenNotLegacy(); +// 0x1929b7de +error IncorrectTokenAddressFromNTV(bytes32 assetId, address tokenAddress); +// 0x48c5fa28 +error InvalidProofLengthForFinalNode(); +// 0x7acd7817 +error TokenIsNotLegacy(); +// 0xfade089a +error LegacyEncodingUsedForNonL1Token(); +// 0xa51fa558 +error TokenIsLegacy(); +// 0x29963361 +error LegacyBridgeUsesNonNativeToken(); +// 0x11832de8 +error AssetRouterAllowanceNotZero(); +// 0xaa5f6180 +error BurningNativeWETHNotSupported(); +// 0xb20b58ce +error NoLegacySharedBridge(); +// 0x8e3ce3cb +error TooHighDeploymentNonce(); +// 0x78d2ed02 +error ChainAlreadyLive(); +// 0x4e98b356 +error MigrationsNotPaused(); +// 0xf20c5c2a +error WrappedBaseTokenAlreadyRegistered(); + +// 0xde4c0b96 +error InvalidNTVBurnData(); +// 0xbe7193d4 +error InvalidSystemLogsLength(); +// 0x8efef97a +error LegacyBridgeNotSet(); +// 0x767eed08 +error LegacyMethodForNonL1Token(); enum SharedBridgeKey { PostUpgradeFirstBatch, diff --git a/l1-contracts/contracts/common/L2ContractAddresses.sol b/l1-contracts/contracts/common/L2ContractAddresses.sol index 571ef3d76..3a130b73d 100644 --- a/l1-contracts/contracts/common/L2ContractAddresses.sol +++ b/l1-contracts/contracts/common/L2ContractAddresses.sol @@ -31,3 +31,52 @@ address constant L2_SYSTEM_CONTEXT_SYSTEM_CONTRACT_ADDR = address(0x800b); /// @dev The address of the pubdata chunk publisher contract address constant L2_PUBDATA_CHUNK_PUBLISHER_ADDR = address(0x8011); + +/// @dev The address used to execute complex upgragedes, also used for the genesis upgrade +address constant L2_COMPLEX_UPGRADER_ADDR = address(0x800f); + +/// @dev The address used to execute the genesis upgrade +address constant L2_GENESIS_UPGRADE_ADDR = address(0x10001); + +/// @dev The address of the L2 bridge hub system contract, used to start L1->L2 transactions +address constant L2_BRIDGEHUB_ADDR = address(0x10002); + +/// @dev the address of the l2 asset router. +address constant L2_ASSET_ROUTER_ADDR = address(0x10003); + +/** + * @author Matter Labs + * @custom:security-contact security@matterlabs.dev + * @notice Smart contract for sending arbitrary length messages to L1 + * @dev by default ZkSync can send fixed-length messages on L1. + * A fixed length message has 4 parameters `senderAddress`, `isService`, `key`, `value`, + * the first one is taken from the context, the other three are chosen by the sender. + * @dev To send a variable-length message we use this trick: + * - This system contract accepts an arbitrary length message and sends a fixed length message with + * parameters `senderAddress == this`, `isService == true`, `key == msg.sender`, `value == keccak256(message)`. + * - The contract on L1 accepts all sent messages and if the message came from this system contract + * it requires that the preimage of `value` be provided. + */ +interface IL2Messenger { + /// @notice Sends an arbitrary length message to L1. + /// @param _message The variable length message to be sent to L1. + /// @return Returns the keccak256 hashed value of the message. + function sendToL1(bytes calldata _message) external returns (bytes32); +} + +/// @dev An l2 system contract address, used in the assetId calculation for native assets. +/// This is needed for automatic bridging, i.e. without deploying the AssetHandler contract, +/// if the assetId can be calculated with this address then it is in fact an NTV asset +address constant L2_NATIVE_TOKEN_VAULT_ADDR = address(0x10004); + +/// @dev the address of the l2 asset router. +address constant L2_MESSAGE_ROOT_ADDR = address(0x10005); + +/// @dev the offset for the system contracts +uint160 constant SYSTEM_CONTRACTS_OFFSET = 0x8000; // 2^15 + +/// @dev the address of the l2 messenger system contract +IL2Messenger constant L2_MESSENGER = IL2Messenger(address(SYSTEM_CONTRACTS_OFFSET + 0x08)); + +/// @dev the address of the msg value system contract +address constant MSG_VALUE_SYSTEM_CONTRACT = address(SYSTEM_CONTRACTS_OFFSET + 0x09); diff --git a/l1-contracts/contracts/common/interfaces/IL1Messenger.sol b/l1-contracts/contracts/common/interfaces/IL1Messenger.sol new file mode 100644 index 000000000..a2aa35fa0 --- /dev/null +++ b/l1-contracts/contracts/common/interfaces/IL1Messenger.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; +/** + * @author Matter Labs + * @custom:security-contact security@matterlabs.dev + * @notice The interface of the L1 Messenger contract, responsible for sending messages to L1. + */ +interface IL1Messenger { + function sendToL1(bytes calldata _message) external returns (bytes32); +} diff --git a/l1-contracts/contracts/common/interfaces/IL2ContractDeployer.sol b/l1-contracts/contracts/common/interfaces/IL2ContractDeployer.sol index 3d5b597df..015442dd9 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.21; /** * @author Matter Labs - * @notice System smart contract that is responsible for deploying other smart contracts on a ZKsync hyperchain. + * @notice System smart contract that is responsible for deploying other smart contracts on a ZK chain. */ interface IL2ContractDeployer { /// @notice A struct that describes a forced deployment on an address. diff --git a/l1-contracts/contracts/common/libraries/DataEncoding.sol b/l1-contracts/contracts/common/libraries/DataEncoding.sol new file mode 100644 index 000000000..4c623febb --- /dev/null +++ b/l1-contracts/contracts/common/libraries/DataEncoding.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {L2_NATIVE_TOKEN_VAULT_ADDR} from "../L2ContractAddresses.sol"; +import {LEGACY_ENCODING_VERSION, NEW_ENCODING_VERSION} from "../../bridge/asset-router/IAssetRouterBase.sol"; +import {INativeTokenVault} from "../../bridge/ntv/INativeTokenVault.sol"; +import {IncorrectTokenAddressFromNTV, UnsupportedEncodingVersion, InvalidNTVBurnData} from "../L1ContractErrors.sol"; + +/** + * @author Matter Labs + * @custom:security-contact security@matterlabs.dev + * @notice Helper library for transfer data encoding and decoding to reduce possibility of errors. + */ +library DataEncoding { + /// @notice Abi.encodes the data required for bridgeBurn for NativeTokenVault. + /// @param _amount The amount of token to be transferred. + /// @param _remoteReceiver The address which to receive tokens on remote chain. + /// @param _maybeTokenAddress The helper field that should be either equal to 0 (in this case + /// it is assumed that the token has been registered within NativeTokenVault already) or it + /// can be equal to the address of the token on the current chain. Providing non-zero address + /// allows it to be automatically registered in case it is not yet a part of NativeTokenVault. + /// @return The encoded bridgeBurn data + function encodeBridgeBurnData( + uint256 _amount, + address _remoteReceiver, + address _maybeTokenAddress + ) internal pure returns (bytes memory) { + return abi.encode(_amount, _remoteReceiver, _maybeTokenAddress); + } + + /// @notice Function decoding bridgeBurn data previously encoded with this library. + /// @param _data The encoded data for bridgeBurn + /// @return amount The amount of token to be transferred. + /// @return receiver The address which to receive tokens on remote chain. + /// @return maybeTokenAddress The helper field that should be either equal to 0 (in this case + /// it is assumed that the token has been registered within NativeTokenVault already) or it + /// can be equal to the address of the token on the current chain. Providing non-zero address + /// allows it to be automatically registered in case it is not yet a part of NativeTokenVault. + function decodeBridgeBurnData( + bytes memory _data + ) internal pure returns (uint256 amount, address receiver, address maybeTokenAddress) { + if (_data.length != 96) { + // For better error handling + revert InvalidNTVBurnData(); + } + + (amount, receiver, maybeTokenAddress) = abi.decode(_data, (uint256, address, address)); + } + + /// @notice Abi.encodes the data required for bridgeMint on remote chain. + /// @param _originalCaller The address which initiated the transfer. + /// @param _remoteReceiver The address which to receive tokens on remote chain. + /// @param _originToken The transferred token address. + /// @param _amount The amount of token to be transferred. + /// @param _erc20Metadata The transferred token metadata. + /// @return The encoded bridgeMint data + function encodeBridgeMintData( + address _originalCaller, + address _remoteReceiver, + address _originToken, + uint256 _amount, + bytes memory _erc20Metadata + ) internal pure returns (bytes memory) { + // solhint-disable-next-line func-named-parameters + return abi.encode(_originalCaller, _remoteReceiver, _originToken, _amount, _erc20Metadata); + } + + /// @notice Function decoding transfer data previously encoded with this library. + /// @param _bridgeMintData The encoded bridgeMint data + /// @return _originalCaller The address which initiated the transfer. + /// @return _remoteReceiver The address which to receive tokens on remote chain. + /// @return _parsedOriginToken The transferred token address. + /// @return _amount The amount of token to be transferred. + /// @return _erc20Metadata The transferred token metadata. + function decodeBridgeMintData( + bytes memory _bridgeMintData + ) + internal + pure + returns ( + address _originalCaller, + address _remoteReceiver, + address _parsedOriginToken, + uint256 _amount, + bytes memory _erc20Metadata + ) + { + (_originalCaller, _remoteReceiver, _parsedOriginToken, _amount, _erc20Metadata) = abi.decode( + _bridgeMintData, + (address, address, address, uint256, bytes) + ); + } + + /// @notice Encodes the asset data by combining chain id, asset deployment tracker and asset data. + /// @param _chainId The id of the chain token is native to. + /// @param _assetData The asset data that has to be encoded. + /// @param _sender The asset deployment tracker address. + /// @return The encoded asset data. + function encodeAssetId(uint256 _chainId, bytes32 _assetData, address _sender) internal pure returns (bytes32) { + return keccak256(abi.encode(_chainId, _sender, _assetData)); + } + + /// @notice Encodes the asset data by combining chain id, asset deployment tracker and asset data. + /// @param _chainId The id of the chain token is native to. + /// @param _tokenAddress The address of token that has to be encoded (asset data is the address itself). + /// @param _sender The asset deployment tracker address. + /// @return The encoded asset data. + function encodeAssetId(uint256 _chainId, address _tokenAddress, address _sender) internal pure returns (bytes32) { + return keccak256(abi.encode(_chainId, _sender, _tokenAddress)); + } + + /// @notice Encodes the asset data by combining chain id, NTV as asset deployment tracker and asset data. + /// @param _chainId The id of the chain token is native to. + /// @param _assetData The asset data that has to be encoded. + /// @return The encoded asset data. + function encodeNTVAssetId(uint256 _chainId, bytes32 _assetData) internal pure returns (bytes32) { + return keccak256(abi.encode(_chainId, L2_NATIVE_TOKEN_VAULT_ADDR, _assetData)); + } + + /// @notice Encodes the asset data by combining chain id, NTV as asset deployment tracker and token address. + /// @param _chainId The id of the chain token is native to. + /// @param _tokenAddress The address of token that has to be encoded (asset data is the address itself). + /// @return The encoded asset data. + function encodeNTVAssetId(uint256 _chainId, address _tokenAddress) internal pure returns (bytes32) { + return keccak256(abi.encode(_chainId, L2_NATIVE_TOKEN_VAULT_ADDR, _tokenAddress)); + } + + /// @dev Encodes the transaction data hash using either the latest encoding standard or the legacy standard. + /// @param _encodingVersion EncodingVersion. + /// @param _originalCaller The address of the entity that initiated the deposit. + /// @param _assetId The unique identifier of the deposited L1 token. + /// @param _nativeTokenVault The address of the token, only used if the encoding version is legacy. + /// @param _transferData The encoded transfer data, which includes both the deposit amount and the address of the L2 receiver. + /// @return txDataHash The resulting encoded transaction data hash. + function encodeTxDataHash( + bytes1 _encodingVersion, + address _originalCaller, + bytes32 _assetId, + address _nativeTokenVault, + bytes memory _transferData + ) internal view returns (bytes32 txDataHash) { + if (_encodingVersion == LEGACY_ENCODING_VERSION) { + address tokenAddress = INativeTokenVault(_nativeTokenVault).tokenAddress(_assetId); + + // This is a double check to ensure that the used token for the legacy encoding is correct. + // This revert should never be emitted and in real life and should only serve as a guard in + // case of inconsistent state of Native Token Vault. + bytes32 expectedAssetId = encodeNTVAssetId(block.chainid, tokenAddress); + if (_assetId != expectedAssetId) { + revert IncorrectTokenAddressFromNTV(_assetId, tokenAddress); + } + + (uint256 depositAmount, , ) = decodeBridgeBurnData(_transferData); + txDataHash = keccak256(abi.encode(_originalCaller, tokenAddress, depositAmount)); + } else if (_encodingVersion == NEW_ENCODING_VERSION) { + // Similarly to calldata, the txDataHash is collision-resistant. + // In the legacy data hash, the first encoded variable was the address, which is padded with zeros during `abi.encode`. + txDataHash = keccak256( + bytes.concat(_encodingVersion, abi.encode(_originalCaller, _assetId, _transferData)) + ); + } else { + revert UnsupportedEncodingVersion(); + } + } + + /// @notice Decodes the token data by combining chain id, asset deployment tracker and asset data. + function decodeTokenData( + bytes calldata _tokenData + ) internal pure returns (uint256 chainId, bytes memory name, bytes memory symbol, bytes memory decimals) { + bytes1 encodingVersion = _tokenData[0]; + if (encodingVersion == LEGACY_ENCODING_VERSION) { + (name, symbol, decimals) = abi.decode(_tokenData, (bytes, bytes, bytes)); + } else if (encodingVersion == NEW_ENCODING_VERSION) { + return abi.decode(_tokenData[1:], (uint256, bytes, bytes, bytes)); + } else { + revert UnsupportedEncodingVersion(); + } + } + + /// @notice Encodes the token data by combining chain id, and its metadata. + /// @dev Note that all the metadata of the token is expected to be ABI encoded. + /// @param _chainId The id of the chain token is native to. + /// @param _name The name of the token. + /// @param _symbol The symbol of the token. + /// @param _decimals The decimals of the token. + /// @return The encoded token data. + function encodeTokenData( + uint256 _chainId, + bytes memory _name, + bytes memory _symbol, + bytes memory _decimals + ) internal pure returns (bytes memory) { + return bytes.concat(NEW_ENCODING_VERSION, abi.encode(_chainId, _name, _symbol, _decimals)); + } +} diff --git a/l1-contracts/contracts/common/libraries/DynamicIncrementalMerkle.sol b/l1-contracts/contracts/common/libraries/DynamicIncrementalMerkle.sol new file mode 100644 index 000000000..b41b665d3 --- /dev/null +++ b/l1-contracts/contracts/common/libraries/DynamicIncrementalMerkle.sol @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {Merkle} from "./Merkle.sol"; +import {Arrays} from "@openzeppelin/contracts-v4/utils/Arrays.sol"; + +/** + * @dev Library for managing https://wikipedia.org/wiki/Merkle_Tree[Merkle Tree] data structures. + * + * Each tree is a complete binary tree with the ability to sequentially insert leaves, changing them from a zero to a + * non-zero value and updating its root. This structure allows inserting commitments (or other entries) that are not + * stored, but can be proven to be part of the tree at a later time if the root is kept. See {MerkleProof}. + * + * A tree is defined by the following parameters: + * + * * Depth: The number of levels in the tree, it also defines the maximum number of leaves as 2**depth. + * * Zero value: The value that represents an empty leaf. Used to avoid regular zero values to be part of the tree. + * * Hashing function: A cryptographic hash function used to produce internal nodes. + * + * This is a fork of OpenZeppelin's [`MerkleTree`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/9af280dc4b45ee5bda96ba47ff829b407eaab67e/contracts/utils/structs/MerkleTree.sol) + * library, with the changes to support dynamic tree growth (doubling the size when full). + */ +library DynamicIncrementalMerkle { + /** + * @dev A complete `bytes32` Merkle tree. + * + * The `sides` and `zero` arrays are set to have a length equal to the depth of the tree during setup. + * + * Struct members have an underscore prefix indicating that they are "private" and should not be read or written to + * directly. Use the functions provided below instead. Modifying the struct manually may violate assumptions and + * lead to unexpected behavior. + * + * NOTE: The `root` and the updates history is not stored within the tree. Consider using a secondary structure to + * store a list of historical roots from the values returned from {setup} and {push} (e.g. a mapping, {BitMaps} or + * {Checkpoints}). + * + * WARNING: Updating any of the tree's parameters after the first insertion will result in a corrupted tree. + */ + struct Bytes32PushTree { + uint256 _nextLeafIndex; + bytes32[] _sides; + bytes32[] _zeros; + } + + /** + * @dev Initialize a {Bytes32PushTree} using {Hashes-Keccak256} to hash internal nodes. + * The capacity of the tree (i.e. number of leaves) is set to `2**levels`. + * + * IMPORTANT: The zero value should be carefully chosen since it will be stored in the tree representing + * empty leaves. It should be a value that is not expected to be part of the tree. + */ + function setup(Bytes32PushTree storage self, bytes32 zero) internal returns (bytes32 initialRoot) { + self._nextLeafIndex = 0; + self._zeros.push(zero); + self._sides.push(bytes32(0)); + return bytes32(0); + } + + /** + * @dev Resets the tree to a blank state. + * Calling this function on MerkleTree that was already setup and used will reset it to a blank state. + * @param zero The value that represents an empty leaf. + * @return initialRoot The initial root of the tree. + */ + function reset(Bytes32PushTree storage self, bytes32 zero) internal returns (bytes32 initialRoot) { + self._nextLeafIndex = 0; + uint256 length = self._zeros.length; + for (uint256 i = length; 0 < i; --i) { + self._zeros.pop(); + } + length = self._sides.length; + for (uint256 i = length; 0 < i; --i) { + self._sides.pop(); + } + self._zeros.push(zero); + self._sides.push(bytes32(0)); + return bytes32(0); + } + + /** + * @dev Insert a new leaf in the tree, and compute the new root. Returns the position of the inserted leaf in the + * tree, and the resulting root. + * + * Hashing the leaf before calling this function is recommended as a protection against + * second pre-image attacks. + */ + function push(Bytes32PushTree storage self, bytes32 leaf) internal returns (uint256 index, bytes32 newRoot) { + // Cache read + uint256 levels = self._zeros.length - 1; + + // Get leaf index + // solhint-disable-next-line gas-increment-by-one + index = self._nextLeafIndex++; + + // Check if tree is full. + if (index == 1 << levels) { + bytes32 zero = self._zeros[levels]; + bytes32 newZero = Merkle.efficientHash(zero, zero); + self._zeros.push(newZero); + self._sides.push(bytes32(0)); + ++levels; + } + + // Rebuild branch from leaf to root + uint256 currentIndex = index; + bytes32 currentLevelHash = leaf; + bool updatedSides = false; + for (uint32 i = 0; i < levels; ++i) { + // Reaching the parent node, is currentLevelHash the left child? + bool isLeft = currentIndex % 2 == 0; + + // If so, next time we will come from the right, so we need to save it + if (isLeft && !updatedSides) { + Arrays.unsafeAccess(self._sides, i).value = currentLevelHash; + updatedSides = true; + } + + // Compute the current node hash by using the hash function + // with either its sibling (side) or the zero value for that level. + currentLevelHash = Merkle.efficientHash( + isLeft ? currentLevelHash : Arrays.unsafeAccess(self._sides, i).value, + isLeft ? Arrays.unsafeAccess(self._zeros, i).value : currentLevelHash + ); + + // Update node index + currentIndex >>= 1; + } + + Arrays.unsafeAccess(self._sides, levels).value = currentLevelHash; + return (index, currentLevelHash); + } + + /** + * @dev Tree's root. + */ + function root(Bytes32PushTree storage self) internal view returns (bytes32) { + return Arrays.unsafeAccess(self._sides, self._sides.length - 1).value; + } + + /** + * @dev Tree's height (does not include the root node). + */ + function height(Bytes32PushTree storage self) internal view returns (uint256) { + return self._sides.length - 1; + } +} diff --git a/l1-contracts/contracts/common/libraries/FullMerkle.sol b/l1-contracts/contracts/common/libraries/FullMerkle.sol new file mode 100644 index 000000000..4dbab106b --- /dev/null +++ b/l1-contracts/contracts/common/libraries/FullMerkle.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {UncheckedMath} from "../../common/libraries/UncheckedMath.sol"; +import {Merkle} from "./Merkle.sol"; +import {MerkleWrongIndex, MerkleWrongLength} from "../L1ContractErrors.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +library FullMerkle { + using UncheckedMath for uint256; + + struct FullTree { + uint256 _height; + uint256 _leafNumber; + bytes32[][] _nodes; + bytes32[] _zeros; + } + + /** + * @dev Initialize a {FullTree} using {Merkle.efficientHash} to hash internal nodes. + * The capacity of the tree (i.e. number of leaves) is set to `2**levels`. + * + * IMPORTANT: The zero value should be carefully chosen since it will be stored in the tree representing + * empty leaves. It should be a value that is not expected to be part of the tree. + * @param zero The zero value to be used in the tree. + */ + function setup(FullTree storage self, bytes32 zero) internal returns (bytes32 initialRoot) { + // Store depth in the dynamic array + self._zeros.push(zero); + self._nodes.push([zero]); + + return zero; + } + + /** + * @dev Push a new leaf to the tree. + * @param _leaf The leaf to be added to the tree. + */ + function pushNewLeaf(FullTree storage self, bytes32 _leaf) internal returns (bytes32 newRoot) { + // solhint-disable-next-line gas-increment-by-one + uint256 index = self._leafNumber++; + + if (index == 1 << self._height) { + uint256 newHeight = self._height.uncheckedInc(); + self._height = newHeight; + bytes32 topZero = self._zeros[newHeight - 1]; + bytes32 newZero = Merkle.efficientHash(topZero, topZero); + self._zeros.push(newZero); + self._nodes.push([newZero]); + } + if (index != 0) { + uint256 oldMaxNodeNumber = index - 1; + uint256 maxNodeNumber = index; + for (uint256 i; i < self._height; i = i.uncheckedInc()) { + if (oldMaxNodeNumber == maxNodeNumber) { + break; + } + self._nodes[i].push(self._zeros[i]); + maxNodeNumber /= 2; + oldMaxNodeNumber /= 2; + } + } + return updateLeaf(self, index, _leaf); + } + + /** + * @dev Update a leaf at index in the tree. + * @param _index The index of the leaf to be updated. + * @param _itemHash The new hash of the leaf. + */ + function updateLeaf(FullTree storage self, uint256 _index, bytes32 _itemHash) internal returns (bytes32) { + uint256 maxNodeNumber = self._leafNumber - 1; + if (_index > maxNodeNumber) { + revert MerkleWrongIndex(_index, maxNodeNumber); + } + self._nodes[0][_index] = _itemHash; + bytes32 currentHash = _itemHash; + for (uint256 i; i < self._height; i = i.uncheckedInc()) { + if (_index % 2 == 0) { + currentHash = Merkle.efficientHash( + currentHash, + maxNodeNumber == _index ? self._zeros[i] : self._nodes[i][_index + 1] + ); + } else { + currentHash = Merkle.efficientHash(self._nodes[i][_index - 1], currentHash); + } + _index /= 2; + maxNodeNumber /= 2; + self._nodes[i + 1][_index] = currentHash; + } + return currentHash; + } + + /** + * @dev Updated all leaves in the tree. + * @param _newLeaves The new leaves to be added to the tree. + */ + function updateAllLeaves(FullTree storage self, bytes32[] memory _newLeaves) internal returns (bytes32) { + if (_newLeaves.length != self._leafNumber) { + revert MerkleWrongLength(_newLeaves.length, self._leafNumber); + } + return updateAllNodesAtHeight(self, 0, _newLeaves); + } + + /** + * @dev Update all nodes at a certain height in the tree. + * @param _height The height of the nodes to be updated. + * @param _newNodes The new nodes to be added to the tree. + */ + function updateAllNodesAtHeight( + FullTree storage self, + uint256 _height, + bytes32[] memory _newNodes + ) internal returns (bytes32) { + if (_height == self._height) { + self._nodes[_height][0] = _newNodes[0]; + return _newNodes[0]; + } + + uint256 newRowLength = (_newNodes.length + 1) / 2; + bytes32[] memory _newRow = new bytes32[](newRowLength); + + uint256 length = _newNodes.length; + for (uint256 i; i < length; i = i.uncheckedAdd(2)) { + self._nodes[_height][i] = _newNodes[i]; + if (i + 1 < length) { + self._nodes[_height][i + 1] = _newNodes[i + 1]; + _newRow[i / 2] = Merkle.efficientHash(_newNodes[i], _newNodes[i + 1]); + } else { + // Handle odd number of nodes by hashing the last node with zero + _newRow[i / 2] = Merkle.efficientHash(_newNodes[i], self._zeros[_height]); + } + } + return updateAllNodesAtHeight(self, _height + 1, _newRow); + } + + /** + * @dev Returns the root of the tree. + */ + function root(FullTree storage self) internal view returns (bytes32) { + return self._nodes[self._height][0]; + } +} diff --git a/l1-contracts/contracts/common/libraries/L2ContractHelper.sol b/l1-contracts/contracts/common/libraries/L2ContractHelper.sol index 21199f069..d33ced05d 100644 --- a/l1-contracts/contracts/common/libraries/L2ContractHelper.sol +++ b/l1-contracts/contracts/common/libraries/L2ContractHelper.sol @@ -4,15 +4,61 @@ pragma solidity ^0.8.21; import {BytecodeError, MalformedBytecode, LengthIsNotDivisibleBy32} from "../L1ContractErrors.sol"; +import {UncheckedMath} from "./UncheckedMath.sol"; +import {L2_MESSENGER} from "../L2ContractAddresses.sol"; + +/** + * @author Matter Labs + * @custom:security-contact security@matterlabs.dev + * @notice Interface for the contract that is used to deploy contracts on L2. + */ +interface IContractDeployer { + /// @notice A struct that describes a forced deployment on an address. + /// @param bytecodeHash The bytecode hash to put on an address. + /// @param newAddress The address on which to deploy the bytecodehash to. + /// @param callConstructor Whether to run the constructor on the force deployment. + /// @param value The `msg.value` with which to initialize a contract. + /// @param input The constructor calldata. + struct ForceDeployment { + bytes32 bytecodeHash; + address newAddress; + bool callConstructor; + uint256 value; + bytes input; + } + + /// @notice This method is to be used only during an upgrade to set bytecodes on specific addresses. + /// @param _deployParams A set of parameters describing force deployment. + function forceDeployOnAddresses(ForceDeployment[] calldata _deployParams) external payable; + + /// @notice Creates a new contract at a determined address using the `CREATE2` salt on L2 + /// @param _salt a unique value to create the deterministic address of the new contract + /// @param _bytecodeHash the bytecodehash of the new contract to be deployed + /// @param _input the calldata to be sent to the constructor of the new contract + function create2(bytes32 _salt, bytes32 _bytecodeHash, bytes calldata _input) external returns (address); +} + /** * @author Matter Labs * @custom:security-contact security@matterlabs.dev * @notice Helper library for working with L2 contracts on L1. */ library L2ContractHelper { + using UncheckedMath for uint256; + /// @dev The prefix used to create CREATE2 addresses. bytes32 private constant CREATE2_PREFIX = keccak256("zksyncCreate2"); + /// @dev Prefix used during derivation of account addresses using CREATE + bytes32 private constant CREATE_PREFIX = 0x63bae3a9951d38e8a3fbb7b70909afc1200610fc5bc55ade242f815974674f23; + + /// @notice Sends L2 -> L1 arbitrary-long message through the system contract messenger. + /// @param _message Data to be sent to L1. + /// @return keccak256 hash of the sent message. + function sendMessageToL1(bytes memory _message) internal returns (bytes32) { + return L2_MESSENGER.sendToL1(_message); + } + /// @notice Validate the bytecode format and calculate its hash. /// @param _bytecode The bytecode to hash. /// @return hashedBytecode The 32-byte hash of the bytecode. @@ -42,6 +88,35 @@ library L2ContractHelper { hashedBytecode = hashedBytecode | bytes32(bytecodeLenInWords << 224); } + /// @notice Validate the bytecode format and calculate its hash. + /// @param _bytecode The bytecode to hash. + /// @return hashedBytecode The 32-byte hash of the bytecode. + /// Note: The function reverts the execution if the bytecode has non expected format: + /// - Bytecode bytes length is not a multiple of 32 + /// - Bytecode bytes length is not less than 2^21 bytes (2^16 words) + /// - Bytecode words length is not odd + function hashL2BytecodeCalldata(bytes calldata _bytecode) internal pure returns (bytes32 hashedBytecode) { + // Note that the length of the bytecode must be provided in 32-byte words. + if (_bytecode.length % 32 != 0) { + revert LengthIsNotDivisibleBy32(_bytecode.length); + } + + uint256 bytecodeLenInWords = _bytecode.length / 32; + // bytecode length must be less than 2^16 words + if (bytecodeLenInWords >= 2 ** 16) { + revert MalformedBytecode(BytecodeError.NumberOfWords); + } + // bytecode length in words must be odd + if (bytecodeLenInWords % 2 == 0) { + revert MalformedBytecode(BytecodeError.WordsMustBeOdd); + } + hashedBytecode = sha256(_bytecode) & 0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + // Setting the version of the hash + hashedBytecode = (hashedBytecode | bytes32(uint256(1 << 248))); + // Setting the length + hashedBytecode = hashedBytecode | bytes32(bytecodeLenInWords << 224); + } + /// @notice Validates the format of the given bytecode hash. /// @dev Due to the specification of the L2 bytecode hash, not every 32 bytes could be a legit bytecode hash. /// @dev The function reverts on invalid bytecode hash format. @@ -87,4 +162,32 @@ library L2ContractHelper { return address(uint160(uint256(data))); } + + /// @notice Calculates the address of a deployed contract via create + /// @param _sender The account that deploys the contract. + /// @param _senderNonce The deploy nonce of the sender's account. + /// NOTE: L2 create derivation is different from L1 derivation! + function computeCreateAddress(address _sender, uint256 _senderNonce) internal pure returns (address) { + // No collision is possible with the Ethereum's CREATE, since + // the prefix begins with 0x63.... + bytes32 hash = keccak256( + bytes.concat(CREATE_PREFIX, bytes32(uint256(uint160(_sender))), bytes32(_senderNonce)) + ); + + return address(uint160(uint256(hash))); + } + + /// @notice Hashes the L2 bytecodes and returns them in the format in which they are processed by the bootloader + 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()) { + bytes32 hashedBytecode = hashL2Bytecode(_factoryDeps[i]); + + // Store the resulting hash sequentially in bytes. + assembly { + mstore(add(hashedFactoryDeps, mul(add(i, 1), 32)), hashedBytecode) + } + } + } } diff --git a/l1-contracts/contracts/common/libraries/Merkle.sol b/l1-contracts/contracts/common/libraries/Merkle.sol new file mode 100644 index 000000000..c6fd737c7 --- /dev/null +++ b/l1-contracts/contracts/common/libraries/Merkle.sol @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: MIT +// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. +pragma solidity ^0.8.21; + +import {UncheckedMath} from "../../common/libraries/UncheckedMath.sol"; +import {MerklePathEmpty, MerklePathOutOfBounds, MerkleIndexOutOfBounds, MerklePathLengthMismatch, MerkleNothingToProve, MerkleIndexOrHeightMismatch} from "../../common/L1ContractErrors.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +library Merkle { + using UncheckedMath for uint256; + + /// @dev Calculate Merkle root by the provided Merkle proof. + /// NOTE: When using this function, check that the _path length is equal to the tree height to prevent shorter/longer paths attack + /// however, for chains settling on GW the proof includes the GW proof, so the path increases. See Mailbox for more details. + /// @param _path Merkle path from the leaf to the root + /// @param _index Leaf index in the tree + /// @param _itemHash Hash of leaf content + /// @return The Merkle root + function calculateRoot( + bytes32[] calldata _path, + uint256 _index, + bytes32 _itemHash + ) internal pure returns (bytes32) { + uint256 pathLength = _path.length; + _validatePathLengthForSingleProof(_index, pathLength); + + bytes32 currentHash = _itemHash; + for (uint256 i; i < pathLength; i = i.uncheckedInc()) { + currentHash = (_index % 2 == 0) + ? efficientHash(currentHash, _path[i]) + : efficientHash(_path[i], currentHash); + _index /= 2; + } + + return currentHash; + } + + /// @dev Calculate Merkle root by the provided Merkle proof. + /// NOTE: When using this function, check that the _path length is equal to the tree height to prevent shorter/longer paths attack + /// @param _path Merkle path from the leaf to the root + /// @param _index Leaf index in the tree + /// @param _itemHash Hash of leaf content + /// @return The Merkle root + function calculateRootMemory( + bytes32[] memory _path, + uint256 _index, + bytes32 _itemHash + ) internal pure returns (bytes32) { + uint256 pathLength = _path.length; + _validatePathLengthForSingleProof(_index, pathLength); + + bytes32 currentHash = _itemHash; + for (uint256 i; i < pathLength; i = i.uncheckedInc()) { + currentHash = (_index % 2 == 0) + ? efficientHash(currentHash, _path[i]) + : efficientHash(_path[i], currentHash); + _index /= 2; + } + + return currentHash; + } + + /// @dev Calculate Merkle root by the provided Merkle proof for a range of elements + /// NOTE: When using this function, check that the _startPath and _endPath lengths are equal to the tree height to prevent shorter/longer paths attack + /// @param _startPath Merkle path from the first element of the range to the root + /// @param _endPath Merkle path from the last element of the range to the root + /// @param _startIndex Index of the first element of the range in the tree + /// @param _itemHashes Hashes of the elements in the range + /// @return The Merkle root + function calculateRootPaths( + bytes32[] memory _startPath, + bytes32[] memory _endPath, + uint256 _startIndex, + bytes32[] memory _itemHashes + ) internal pure returns (bytes32) { + uint256 pathLength = _startPath.length; + if (pathLength != _endPath.length) { + revert MerklePathLengthMismatch(pathLength, _endPath.length); + } + if (pathLength >= 256) { + revert MerklePathOutOfBounds(); + } + uint256 levelLen = _itemHashes.length; + // Edge case: we want to be able to prove an element in a single-node tree. + if (pathLength == 0 && (_startIndex != 0 || levelLen != 1)) { + revert MerklePathEmpty(); + } + if (levelLen == 0) { + revert MerkleNothingToProve(); + } + if (_startIndex + levelLen > (1 << pathLength)) { + revert MerkleIndexOrHeightMismatch(); + } + bytes32[] memory itemHashes = _itemHashes; + + for (uint256 level; level < pathLength; level = level.uncheckedInc()) { + uint256 parity = _startIndex % 2; + // We get an extra element on the next level if on the current level elements either + // start on an odd index (`parity == 1`) or end on an even index (`levelLen % 2 == 1`) + uint256 nextLevelLen = levelLen / 2 + (parity | (levelLen % 2)); + for (uint256 i; i < nextLevelLen; i = i.uncheckedInc()) { + bytes32 lhs = (i == 0 && parity == 1) ? _startPath[level] : itemHashes[2 * i - parity]; + bytes32 rhs = (i == nextLevelLen - 1 && (levelLen - parity) % 2 == 1) + ? _endPath[level] + : itemHashes[2 * i + 1 - parity]; + itemHashes[i] = efficientHash(lhs, rhs); + } + levelLen = nextLevelLen; + _startIndex /= 2; + } + + return itemHashes[0]; + } + + /// @dev Keccak hash of the concatenation of two 32-byte words + function efficientHash(bytes32 _lhs, bytes32 _rhs) internal pure returns (bytes32 result) { + assembly { + mstore(0x00, _lhs) + mstore(0x20, _rhs) + result := keccak256(0x00, 0x40) + } + } + + function _validatePathLengthForSingleProof(uint256 _index, uint256 _pathLength) private pure { + if (_pathLength >= 256) { + revert MerklePathOutOfBounds(); + } + if (_index >= (1 << _pathLength)) { + revert MerkleIndexOutOfBounds(); + } + } +} diff --git a/l1-contracts/contracts/common/libraries/MessageHashing.sol b/l1-contracts/contracts/common/libraries/MessageHashing.sol new file mode 100644 index 000000000..b7009482d --- /dev/null +++ b/l1-contracts/contracts/common/libraries/MessageHashing.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +bytes32 constant BATCH_LEAF_PADDING = keccak256("zkSync:BatchLeaf"); +bytes32 constant CHAIN_ID_LEAF_PADDING = keccak256("zkSync:ChainIdLeaf"); + +library MessageHashing { + /// @dev Returns the leaf hash for a chain with batch number and batch root. + /// @param batchRoot The root hash of the batch. + /// @param batchNumber The number of the batch. + function batchLeafHash(bytes32 batchRoot, uint256 batchNumber) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(BATCH_LEAF_PADDING, batchRoot, batchNumber)); + } + + /// @dev Returns the leaf hash for a chain with chain root and chain id. + /// @param chainIdRoot The root hash of the chain. + /// @param chainId The id of the chain. + function chainIdLeafHash(bytes32 chainIdRoot, uint256 chainId) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(CHAIN_ID_LEAF_PADDING, chainIdRoot, chainId)); + } +} diff --git a/l1-contracts/contracts/common/libraries/SystemContractsCaller.sol b/l1-contracts/contracts/common/libraries/SystemContractsCaller.sol new file mode 100644 index 000000000..30dbf3a81 --- /dev/null +++ b/l1-contracts/contracts/common/libraries/SystemContractsCaller.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: MIT + +// solhint-disable one-contract-per-file + +pragma solidity 0.8.24; + +import {MSG_VALUE_SYSTEM_CONTRACT} from "../L2ContractAddresses.sol"; + +address constant SYSTEM_CALL_CALL_ADDRESS = address((1 << 16) - 11); +/// @dev If the bitwise AND of the extraAbi[2] param when calling the MSG_VALUE_SIMULATOR +/// is non-zero, the call will be assumed to be a system one. +uint256 constant MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT = 1; + +/// @notice The way to forward the calldata: +/// - Use the current heap (i.e. the same as on EVM). +/// - Use the auxiliary heap. +/// - Forward via a pointer +/// @dev Note, that currently, users do not have access to the auxiliary +/// heap and so the only type of forwarding that will be used by the users +/// are UseHeap and ForwardFatPointer for forwarding a slice of the current calldata +/// to the next call. +enum CalldataForwardingMode { + UseHeap, + ForwardFatPointer, + UseAuxHeap +} + +/// @notice Error thrown a cast from uint256 to u32 is not possible. +error U32CastOverflow(); + +library Utils { + function safeCastToU32(uint256 _x) internal pure returns (uint32) { + if (_x > type(uint32).max) { + revert U32CastOverflow(); + } + + return uint32(_x); + } +} + +/// @notice The library contains the functions to make system calls. +/// @dev A more detailed description of the library and its methods can be found in the `system-contracts` repo. +library SystemContractsCaller { + function systemCall(uint32 gasLimit, address to, uint256 value, bytes memory data) internal returns (bool success) { + address callAddr = SYSTEM_CALL_CALL_ADDRESS; + + uint32 dataStart; + assembly { + dataStart := add(data, 0x20) + } + uint32 dataLength = Utils.safeCastToU32(data.length); + + uint256 farCallAbi = getFarCallABI({ + dataOffset: 0, + memoryPage: 0, + dataStart: dataStart, + dataLength: dataLength, + gasPassed: gasLimit, + // Only rollup is supported for now + shardId: 0, + forwardingMode: CalldataForwardingMode.UseHeap, + isConstructorCall: false, + isSystemCall: true + }); + + if (value == 0) { + // Doing the system call directly + assembly { + success := call(to, callAddr, 0, 0, farCallAbi, 0, 0) + } + } else { + address msgValueSimulator = MSG_VALUE_SYSTEM_CONTRACT; + // We need to supply the mask to the MsgValueSimulator to denote + // that the call should be a system one. + uint256 forwardMask = MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT; + + assembly { + success := call(msgValueSimulator, callAddr, value, to, farCallAbi, forwardMask, 0) + } + } + } + + function systemCallWithReturndata( + uint32 gasLimit, + address to, + uint128 value, + bytes memory data + ) internal returns (bool success, bytes memory returnData) { + success = systemCall(gasLimit, to, value, data); + + uint256 size; + assembly { + size := returndatasize() + } + + returnData = new bytes(size); + assembly { + returndatacopy(add(returnData, 0x20), 0, size) + } + } + + function getFarCallABI( + uint32 dataOffset, + uint32 memoryPage, + uint32 dataStart, + uint32 dataLength, + uint32 gasPassed, + uint8 shardId, + CalldataForwardingMode forwardingMode, + bool isConstructorCall, + bool isSystemCall + ) internal pure returns (uint256 farCallAbi) { + // Fill in the call parameter fields + farCallAbi = getFarCallABIWithEmptyFatPointer({ + gasPassed: gasPassed, + shardId: shardId, + forwardingMode: forwardingMode, + isConstructorCall: isConstructorCall, + isSystemCall: isSystemCall + }); + // Fill in the fat pointer fields + farCallAbi |= dataOffset; + farCallAbi |= (uint256(memoryPage) << 32); + farCallAbi |= (uint256(dataStart) << 64); + farCallAbi |= (uint256(dataLength) << 96); + } + + function getFarCallABIWithEmptyFatPointer( + uint32 gasPassed, + uint8 shardId, + CalldataForwardingMode forwardingMode, + bool isConstructorCall, + bool isSystemCall + ) internal pure returns (uint256 farCallAbiWithEmptyFatPtr) { + farCallAbiWithEmptyFatPtr |= (uint256(gasPassed) << 192); + farCallAbiWithEmptyFatPtr |= (uint256(forwardingMode) << 224); + farCallAbiWithEmptyFatPtr |= (uint256(shardId) << 232); + if (isConstructorCall) { + farCallAbiWithEmptyFatPtr |= (1 << 240); + } + if (isSystemCall) { + farCallAbiWithEmptyFatPtr |= (1 << 248); + } + } +} diff --git a/l1-contracts/contracts/common/libraries/UnsafeBytes.sol b/l1-contracts/contracts/common/libraries/UnsafeBytes.sol index d20bcbac8..e2680d9e0 100644 --- a/l1-contracts/contracts/common/libraries/UnsafeBytes.sol +++ b/l1-contracts/contracts/common/libraries/UnsafeBytes.sol @@ -43,4 +43,13 @@ library UnsafeBytes { result := mload(add(_bytes, offset)) } } + + function readRemainingBytes(bytes memory _bytes, uint256 _start) internal pure returns (bytes memory result) { + uint256 arrayLen = _bytes.length - _start; + result = new bytes(arrayLen); + + assembly { + mcopy(add(result, 0x20), add(_bytes, add(0x20, _start)), arrayLen) + } + } } diff --git a/l1-contracts/contracts/dev-contracts/DummyL1ERC20Bridge.sol b/l1-contracts/contracts/dev-contracts/DummyL1ERC20Bridge.sol index 8155ddf6b..5ca21d4ba 100644 --- a/l1-contracts/contracts/dev-contracts/DummyL1ERC20Bridge.sol +++ b/l1-contracts/contracts/dev-contracts/DummyL1ERC20Bridge.sol @@ -3,14 +3,24 @@ pragma solidity 0.8.24; import {L1ERC20Bridge} from "../bridge/L1ERC20Bridge.sol"; -import {IL1SharedBridge} from "../bridge/interfaces/IL1SharedBridge.sol"; +import {IL1AssetRouter} from "../bridge/asset-router/IL1AssetRouter.sol"; +import {IL1NativeTokenVault} from "../bridge/ntv/IL1NativeTokenVault.sol"; +import {IL1Nullifier} from "../bridge/interfaces/IL1Nullifier.sol"; contract DummyL1ERC20Bridge is L1ERC20Bridge { - constructor(IL1SharedBridge _l1SharedBridge) L1ERC20Bridge(_l1SharedBridge) {} + constructor( + IL1Nullifier _l1Nullifier, + IL1AssetRouter _l1SharedBridge, + IL1NativeTokenVault _l1NativeTokenVault, + uint256 _eraChainId + ) L1ERC20Bridge(_l1Nullifier, _l1SharedBridge, _l1NativeTokenVault, _eraChainId) {} - function setValues(address _l2Bridge, address _l2TokenBeacon, bytes32 _l2TokenProxyBytecodeHash) external { - l2Bridge = _l2Bridge; + function setValues(address _l2SharedBridge, address _l2TokenBeacon, bytes32 _l2TokenProxyBytecodeHash) external { + l2Bridge = _l2SharedBridge; l2TokenBeacon = _l2TokenBeacon; l2TokenProxyBytecodeHash = _l2TokenProxyBytecodeHash; } + + // add this to be excluded from coverage report + function test() internal virtual {} } diff --git a/l1-contracts/contracts/dev-contracts/DummyRestriction.sol b/l1-contracts/contracts/dev-contracts/DummyRestriction.sol new file mode 100644 index 000000000..8ce2680db --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/DummyRestriction.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {Call} from "../governance/Common.sol"; +import {IRestriction, RESTRICTION_MAGIC} from "../governance/restriction/IRestriction.sol"; + +/// @title Restriction contract interface +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +contract DummyRestriction is IRestriction { + bool immutable correctMagic; + + constructor(bool useCorrectMagic) { + correctMagic = useCorrectMagic; + } + + /// @notice A method used to check that the contract supports this interface. + /// @return Returns the `RESTRICTION_MAGIC` + function getSupportsRestrictionMagic() external view returns (bytes32) { + if (correctMagic) { + return RESTRICTION_MAGIC; + } else { + // Invalid magic + return bytes32(0); + } + } + + /// @notice Ensures that the invoker has the required role to call the function. + /// @param _call The call data. + /// @param _invoker The address of the invoker. + function validateCall(Call calldata _call, address _invoker) external view virtual { + // nothing + } +} diff --git a/l1-contracts/contracts/dev-contracts/L1NullifierDev.sol b/l1-contracts/contracts/dev-contracts/L1NullifierDev.sol new file mode 100644 index 000000000..062d168cd --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/L1NullifierDev.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {L1Nullifier, IBridgehub} from "../bridge/L1Nullifier.sol"; + +contract L1NullifierDev is L1Nullifier { + constructor( + IBridgehub _bridgehub, + uint256 _eraChainId, + address _eraDiamondProxy + ) L1Nullifier(_bridgehub, _eraChainId, _eraDiamondProxy) {} + + function setL2LegacySharedBridge(uint256 _chainId, address _l2Bridge) external { + __DEPRECATED_l2BridgeAddress[_chainId] = _l2Bridge; + } + + // add this to be excluded from coverage report + function test() internal virtual {} +} diff --git a/l1-contracts/contracts/dev-contracts/L2SharedBridgeLegacyDev.sol b/l1-contracts/contracts/dev-contracts/L2SharedBridgeLegacyDev.sol new file mode 100644 index 000000000..c80685a81 --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/L2SharedBridgeLegacyDev.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; + +import {BridgedStandardERC20} from "../bridge/BridgedStandardERC20.sol"; + +import {L2SharedBridgeLegacy} from "../bridge/L2SharedBridgeLegacy.sol"; +import {InvalidCaller, ZeroAddress, EmptyBytes32, Unauthorized, AmountMustBeGreaterThanZero, DeployFailed} from "../common/L1ContractErrors.sol"; + +contract L2SharedBridgeLegacyDev is L2SharedBridgeLegacy { + constructor() L2SharedBridgeLegacy() {} + + /// @notice Initializes the bridge contract for later use. Expected to be used in the proxy. + /// @param _legacyBridge The address of the L1 Bridge contract. + /// @param _l1SharedBridge The address of the L1 Bridge contract. + /// @param _l2TokenProxyBytecodeHash The bytecode hash of the proxy for tokens deployed by the bridge. + /// @param _aliasedOwner The address of the governor contract. + function initializeDevBridge( + address _legacyBridge, + address _l1SharedBridge, + bytes32 _l2TokenProxyBytecodeHash, + address _aliasedOwner + ) external reinitializer(2) { + if (_l1SharedBridge == address(0)) { + revert ZeroAddress(); + } + + if (_l2TokenProxyBytecodeHash == bytes32(0)) { + revert EmptyBytes32(); + } + + if (_aliasedOwner == address(0)) { + revert ZeroAddress(); + } + + l1SharedBridge = _l1SharedBridge; + l1Bridge = _legacyBridge; + + // The following statement is true only in freshly deployed environments. However, + // for those environments we do not need to deploy this contract at all. + // This check is primarily for local testing purposes. + if (l2TokenProxyBytecodeHash == bytes32(0) && address(l2TokenBeacon) == address(0)) { + address l2StandardToken = address(new BridgedStandardERC20{salt: bytes32(0)}()); + l2TokenBeacon = new UpgradeableBeacon{salt: bytes32(0)}(l2StandardToken); + l2TokenProxyBytecodeHash = _l2TokenProxyBytecodeHash; + l2TokenBeacon.transferOwnership(_aliasedOwner); + } + } +} diff --git a/l1-contracts/contracts/dev-contracts/WETH9.sol b/l1-contracts/contracts/dev-contracts/WETH9.sol index e094ba89e..fccb38b8d 100644 --- a/l1-contracts/contracts/dev-contracts/WETH9.sol +++ b/l1-contracts/contracts/dev-contracts/WETH9.sol @@ -30,7 +30,17 @@ contract WETH9 { function withdraw(uint256 wad) public { require(balanceOf[msg.sender] >= wad, "weth9, 1"); balanceOf[msg.sender] -= wad; - payable(msg.sender).transfer(wad); + // this is a hack so that zkfoundry works, but we are deploying WETH9 on L2 as well. + // payable(msg.sender).transfer(wad); + bool callSuccess; + address sender = msg.sender; + // Low-level assembly call, to avoid any memory copying (save gas) + assembly { + callSuccess := call(gas(), sender, wad, 0, 0, 0, 0) + } + if (!callSuccess) { + require(false, "Withdraw failed"); + } emit Withdrawal(msg.sender, wad); } @@ -50,7 +60,6 @@ contract WETH9 { function transferFrom(address src, address dst, uint256 wad) public returns (bool) { require(balanceOf[src] >= wad, "weth9, 2"); - if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) { require(allowance[src][msg.sender] >= wad, "weth9, 3"); allowance[src][msg.sender] -= wad; diff --git a/l1-contracts/contracts/dev-contracts/test/AdminFacetTest.sol b/l1-contracts/contracts/dev-contracts/test/AdminFacetTest.sol index 614c34bb9..ae8825eba 100644 --- a/l1-contracts/contracts/dev-contracts/test/AdminFacetTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/AdminFacetTest.sol @@ -3,14 +3,15 @@ pragma solidity 0.8.24; import {AdminFacet} from "../../state-transition/chain-deps/facets/Admin.sol"; +import {RollupDAManager} from "../../state-transition/data-availability/RollupDAManager.sol"; contract AdminFacetTest is AdminFacet { // add this to be excluded from coverage report function test() internal virtual {} - constructor() { + constructor(uint256 _l1ChainId) AdminFacet(_l1ChainId, RollupDAManager(address(0))) { s.admin = msg.sender; - s.stateTransitionManager = msg.sender; + s.chainTypeManager = msg.sender; } function getPorterAvailability() external view returns (bool) { diff --git a/l1-contracts/contracts/dev-contracts/test/CustomUpgradeTest.sol b/l1-contracts/contracts/dev-contracts/test/CustomUpgradeTest.sol index 7055ce557..e20d44863 100644 --- a/l1-contracts/contracts/dev-contracts/test/CustomUpgradeTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/CustomUpgradeTest.sol @@ -25,7 +25,7 @@ contract CustomUpgradeTest is BaseZkSyncUpgrade { /// upgrade. function _postUpgrade(bytes calldata _customCallDataForUpgrade) internal override {} - /// @notice The main function that will be called by the upgrade proxy. + /// @notice The main function that will be delegate-called by the chain. /// @param _proposedUpgrade The upgrade to be executed. function upgrade(ProposedUpgrade calldata _proposedUpgrade) public override returns (bytes32) { (uint32 newMinorVersion, bool isPatchOnly) = _setNewProtocolVersion(_proposedUpgrade.newProtocolVersion); @@ -34,12 +34,7 @@ contract CustomUpgradeTest is BaseZkSyncUpgrade { _setBaseSystemContracts(_proposedUpgrade.bootloaderHash, _proposedUpgrade.defaultAccountHash, isPatchOnly); bytes32 txHash; - txHash = _setL2SystemContractUpgrade( - _proposedUpgrade.l2ProtocolUpgradeTx, - _proposedUpgrade.factoryDeps, - newMinorVersion, - isPatchOnly - ); + txHash = _setL2SystemContractUpgrade(_proposedUpgrade.l2ProtocolUpgradeTx, newMinorVersion, isPatchOnly); _postUpgrade(_proposedUpgrade.postUpgradeCalldata); diff --git a/l1-contracts/contracts/dev-contracts/test/DiamondProxyTest.sol b/l1-contracts/contracts/dev-contracts/test/DiamondProxyTest.sol index 212a2b76a..a8ae37582 100644 --- a/l1-contracts/contracts/dev-contracts/test/DiamondProxyTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/DiamondProxyTest.sol @@ -3,9 +3,9 @@ pragma solidity 0.8.24; import {Diamond} from "../../state-transition/libraries/Diamond.sol"; -import {ZkSyncHyperchainBase} from "../../state-transition/chain-deps/facets/ZkSyncHyperchainBase.sol"; +import {ZKChainBase} from "../../state-transition/chain-deps/facets/ZKChainBase.sol"; -contract DiamondProxyTest is ZkSyncHyperchainBase { +contract DiamondProxyTest is ZKChainBase { // add this to be excluded from coverage report function test() internal virtual {} diff --git a/l1-contracts/contracts/dev-contracts/test/DummyAdminFacet.sol b/l1-contracts/contracts/dev-contracts/test/DummyAdminFacet.sol index 0a27a7e1c..82c64c4e8 100644 --- a/l1-contracts/contracts/dev-contracts/test/DummyAdminFacet.sol +++ b/l1-contracts/contracts/dev-contracts/test/DummyAdminFacet.sol @@ -2,9 +2,9 @@ pragma solidity 0.8.24; -import {ZkSyncHyperchainBase} from "../../state-transition/chain-deps/facets/ZkSyncHyperchainBase.sol"; +import {ZKChainBase} from "../../state-transition/chain-deps/facets/ZKChainBase.sol"; -contract DummyAdminFacet is ZkSyncHyperchainBase { +contract DummyAdminFacet is ZKChainBase { // add this to be excluded from coverage report function test() internal virtual {} diff --git a/l1-contracts/contracts/dev-contracts/test/DummyAdminFacetNoOverlap.sol b/l1-contracts/contracts/dev-contracts/test/DummyAdminFacetNoOverlap.sol index 0805e535c..b66c76bf0 100644 --- a/l1-contracts/contracts/dev-contracts/test/DummyAdminFacetNoOverlap.sol +++ b/l1-contracts/contracts/dev-contracts/test/DummyAdminFacetNoOverlap.sol @@ -3,10 +3,12 @@ pragma solidity 0.8.24; import {Diamond} from "../../state-transition/libraries/Diamond.sol"; -import {ZkSyncHyperchainBase} from "../../state-transition/chain-deps/facets/ZkSyncHyperchainBase.sol"; +import {ZKChainBase} from "../../state-transition/chain-deps/facets/ZKChainBase.sol"; +import {IL1AssetRouter} from "../../bridge/asset-router/IL1AssetRouter.sol"; +import {DataEncoding} from "../../common/libraries/DataEncoding.sol"; /// selectors do not overlap with normal facet selectors (getName does not count) -contract DummyAdminFacetNoOverlap is ZkSyncHyperchainBase { +contract DummyAdminFacetNoOverlap is ZKChainBase { // add this to be excluded from coverage report function test() internal virtual {} diff --git a/l1-contracts/contracts/dev-contracts/test/DummyBridgehub.sol b/l1-contracts/contracts/dev-contracts/test/DummyBridgehub.sol index 79f9dc6e6..f178fc0ed 100644 --- a/l1-contracts/contracts/dev-contracts/test/DummyBridgehub.sol +++ b/l1-contracts/contracts/dev-contracts/test/DummyBridgehub.sol @@ -2,17 +2,49 @@ pragma solidity 0.8.24; -import {Bridgehub} from "../../bridgehub/Bridgehub.sol"; +import {ETH_TOKEN_ADDRESS} from "../../common/Config.sol"; +import {L2_NATIVE_TOKEN_VAULT_ADDR} from "../../common/L2ContractAddresses.sol"; +import {IMessageRoot} from "../../bridgehub/IMessageRoot.sol"; + +import {IGetters} from "../../state-transition/chain-interfaces/IGetters.sol"; /// @title DummyBridgehub /// @notice A test smart contract that allows to set State Transition Manager for a given chain -contract DummyBridgehub is Bridgehub { +contract DummyBridgehub { + IMessageRoot public messageRoot; + + address public zkChain; + + address public sharedBridge; + // add this to be excluded from coverage report function test() internal virtual {} - constructor() Bridgehub() {} + function baseTokenAssetId(uint256) external view returns (bytes32) { + return + keccak256( + abi.encode( + block.chainid, + L2_NATIVE_TOKEN_VAULT_ADDR, + ETH_TOKEN_ADDRESS + // bytes32(uint256(uint160(IGetters(msg.sender).getBaseToken()))) + ) + ); + } + + function setMessageRoot(address _messageRoot) public { + messageRoot = IMessageRoot(_messageRoot); + } + + function setZKChain(uint256, address _zkChain) external { + zkChain = _zkChain; + } + + function getZKChain(uint256) external view returns (address) { + return address(0); + } - function setStateTransitionManager(uint256 _chainId, address _stm) external { - stateTransitionManager[_chainId] = _stm; + function setSharedBridge(address addr) external { + sharedBridge = addr; } } diff --git a/l1-contracts/contracts/dev-contracts/test/DummyBridgehubSetter.sol b/l1-contracts/contracts/dev-contracts/test/DummyBridgehubSetter.sol new file mode 100644 index 000000000..a84f476f1 --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/test/DummyBridgehubSetter.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {Bridgehub} from "../../bridgehub/Bridgehub.sol"; + +contract DummyBridgehubSetter is Bridgehub { + // add this to be excluded from coverage report + function test() internal virtual {} + + /// @notice Constructor + constructor( + uint256 _l1ChainId, + address _owner, + uint256 _maxNumberOfZKChains + ) Bridgehub(_l1ChainId, _owner, _maxNumberOfZKChains) {} + + function setZKChain(uint256 _chainId, address _zkChain) external { + _registerNewZKChain(_chainId, _zkChain, true); + } + + function setCTM(uint256 _chainId, address _ctm) external { + chainTypeManager[_chainId] = _ctm; + } +} diff --git a/l1-contracts/contracts/dev-contracts/test/DummyStateTransitionManager.sol b/l1-contracts/contracts/dev-contracts/test/DummyChainTypeManager.sol similarity index 57% rename from l1-contracts/contracts/dev-contracts/test/DummyStateTransitionManager.sol rename to l1-contracts/contracts/dev-contracts/test/DummyChainTypeManager.sol index fba37465e..20cc25328 100644 --- a/l1-contracts/contracts/dev-contracts/test/DummyStateTransitionManager.sol +++ b/l1-contracts/contracts/dev-contracts/test/DummyChainTypeManager.sol @@ -4,20 +4,22 @@ pragma solidity 0.8.24; import {EnumerableMap} from "@openzeppelin/contracts-v4/utils/structs/EnumerableMap.sol"; -import {StateTransitionManager} from "../../state-transition/StateTransitionManager.sol"; +import {ChainTypeManager} from "../../state-transition/ChainTypeManager.sol"; /// @title DummyExecutor /// @notice A test smart contract implementing the IExecutor interface to simulate Executor behavior for testing purposes. -contract DummyStateTransitionManager is StateTransitionManager { +contract DummyChainTypeManager is ChainTypeManager { using EnumerableMap for EnumerableMap.UintToAddressMap; // add this to be excluded from coverage report function test() internal virtual {} + address zkChain; + /// @notice Constructor - constructor() StateTransitionManager(address(0), type(uint256).max) {} + constructor() ChainTypeManager(address(0)) {} - function setHyperchain(uint256 _chainId, address _hyperchain) external { - hyperchainMap.set(_chainId, _hyperchain); + function setZKChain(uint256 _chainId, address _zkChain) external { + zkChain = _zkChain; } } diff --git a/l1-contracts/contracts/dev-contracts/test/DummyChainTypeManagerForValidatorTimelock.sol b/l1-contracts/contracts/dev-contracts/test/DummyChainTypeManagerForValidatorTimelock.sol new file mode 100644 index 000000000..75c8cd121 --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/test/DummyChainTypeManagerForValidatorTimelock.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +/// @title DummyChainTypeManagerForValidatorTimelock +/// @notice A test smart contract implementing the IExecutor interface to simulate Executor behavior for testing purposes. +contract DummyChainTypeManagerForValidatorTimelock { + // add this to be excluded from coverage report + function test() internal virtual {} + + address public chainAdmin; + address public zkChainAddress; + + constructor(address _chainAdmin, address _zkChain) { + chainAdmin = _chainAdmin; + zkChainAddress = _zkChain; + } + + function getChainAdmin(uint256) external view returns (address) { + return chainAdmin; + } + + function getZKChain(uint256) public view returns (address) { + return zkChainAddress; + } + + function getHyperchain(uint256 _chainId) external view returns (address) { + return getZKChain(_chainId); + } + + function setZKChain(uint256, address _zkChain) external { + zkChainAddress = _zkChain; + } +} diff --git a/l1-contracts/contracts/dev-contracts/test/DummyChainTypeManagerWithBridgeHubAddress.sol b/l1-contracts/contracts/dev-contracts/test/DummyChainTypeManagerWithBridgeHubAddress.sol new file mode 100644 index 000000000..9f6acd198 --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/test/DummyChainTypeManagerWithBridgeHubAddress.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {EnumerableMap} from "@openzeppelin/contracts-v4/utils/structs/EnumerableMap.sol"; + +import {ChainTypeManager} from "../../state-transition/ChainTypeManager.sol"; + +/// @title DummyExecutor +/// @notice A test smart contract implementing the IExecutor interface to simulate Executor behavior for testing purposes. +contract DummyChainTypeManagerWBH is ChainTypeManager { + using EnumerableMap for EnumerableMap.UintToAddressMap; + + address zkChain; + /// @notice Constructor + constructor(address bridgeHub) ChainTypeManager(bridgeHub) {} + + function setZKChain(uint256 _chainId, address _zkChain) external { + zkChain = _zkChain; + } + + // add this to be excluded from coverage report + function test() internal {} +} diff --git a/l1-contracts/contracts/dev-contracts/test/DummyEraBaseTokenBridge.sol b/l1-contracts/contracts/dev-contracts/test/DummyEraBaseTokenBridge.sol index 96382c44f..bb450b261 100644 --- a/l1-contracts/contracts/dev-contracts/test/DummyEraBaseTokenBridge.sol +++ b/l1-contracts/contracts/dev-contracts/test/DummyEraBaseTokenBridge.sol @@ -8,7 +8,7 @@ contract DummyEraBaseTokenBridge { function bridgehubDepositBaseToken( uint256 _chainId, - address _prevMsgSender, + address _originalCaller, 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 deleted file mode 100644 index 7da7113b2..000000000 --- a/l1-contracts/contracts/dev-contracts/test/DummyExecutor.sol +++ /dev/null @@ -1,153 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -import {IExecutor} from "../../state-transition/chain-interfaces/IExecutor.sol"; - -/// @title DummyExecutor -/// @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 - bool shouldRevertOnCommitBatches; - bool shouldRevertOnProveBatches; - bool shouldRevertOnExecuteBatches; - - // Counters to track the total number of committed, verified, and executed batches - uint256 public getTotalBatchesCommitted; - uint256 public getTotalBatchesVerified; - uint256 public getTotalBatchesExecuted; - string public constant override getName = "DummyExecutor"; - - /// @notice Constructor sets the contract owner to the message sender - constructor() { - owner = msg.sender; - } - - /// @notice Modifier that only allows the owner to call certain functions - modifier onlyOwner() { - require(msg.sender == owner); - _; - } - - function getAdmin() external view returns (address) { - return owner; - } - - /// @notice Removing txs from the priority queue - function removePriorityQueueFront(uint256 _index) external {} - - /// @notice Allows the owner to set whether the contract should revert during commit blocks operation - function setShouldRevertOnCommitBatches(bool _shouldRevert) external onlyOwner { - shouldRevertOnCommitBatches = _shouldRevert; - } - - /// @notice Allows the owner to set whether the contract should revert during prove batches operation - function setShouldRevertOnProveBatches(bool _shouldRevert) external onlyOwner { - shouldRevertOnProveBatches = _shouldRevert; - } - - /// @notice Allows the owner to set whether the contract should revert during execute batches operation - function setShouldRevertOnExecuteBatches(bool _shouldRevert) external onlyOwner { - shouldRevertOnExecuteBatches = _shouldRevert; - } - - function commitBatches( - StoredBatchInfo calldata _lastCommittedBatchData, - CommitBatchInfo[] calldata _newBatchesData - ) public { - require(!shouldRevertOnCommitBatches, "DummyExecutor: shouldRevertOnCommitBatches"); - require( - _lastCommittedBatchData.batchNumber == getTotalBatchesCommitted, - "DummyExecutor: Invalid last committed batch number" - ); - - uint256 batchesLength = _newBatchesData.length; - for (uint256 i = 0; i < batchesLength; ++i) { - require(getTotalBatchesCommitted + i + 1 == _newBatchesData[i].batchNumber); - } - - 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 - ) 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 1: Can't prove batch out of order" - ); - - getTotalBatchesVerified += 1; - require( - getTotalBatchesVerified <= getTotalBatchesCommitted, - "DummyExecutor: prove more batches than were committed" - ); - } - - 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) { - require(_batchesData[i].batchNumber == getTotalBatchesExecuted + i + 1); - } - getTotalBatchesExecuted += nBatches; - require( - getTotalBatchesExecuted <= getTotalBatchesVerified, - "DummyExecutor 2: Can't execute batches more than committed and proven currently" - ); - } - - 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" - ); - uint256 newTotalBatchesCommitted = _maxU256(_newLastBatch, getTotalBatchesExecuted); - - if (newTotalBatchesCommitted < getTotalBatchesVerified) { - getTotalBatchesVerified = newTotalBatchesCommitted; - } - 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 index bebdbe49d..ae8855496 100644 --- a/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol +++ b/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol @@ -5,34 +5,43 @@ pragma solidity 0.8.24; import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; import {L2TransactionRequestTwoBridgesInner} from "../../bridgehub/IBridgehub.sol"; -import {TWO_BRIDGES_MAGIC_VALUE, ETH_TOKEN_ADDRESS} from "../../common/Config.sol"; import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/security/PausableUpgradeable.sol"; +import {TWO_BRIDGES_MAGIC_VALUE, ETH_TOKEN_ADDRESS} from "../../common/Config.sol"; +import {IL1NativeTokenVault} from "../../bridge/ntv/L1NativeTokenVault.sol"; +import {L2_NATIVE_TOKEN_VAULT_ADDR} from "../../common/L2ContractAddresses.sol"; import {SafeERC20} from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; -import {IL2Bridge} from "../../bridge/interfaces/IL2Bridge.sol"; +import {IL2SharedBridgeLegacy} from "../../bridge/interfaces/IL2SharedBridgeLegacy.sol"; +import {IL2SharedBridgeLegacyFunctions} from "../../bridge/interfaces/IL2SharedBridgeLegacyFunctions.sol"; contract DummySharedBridge is PausableUpgradeable { using SafeERC20 for IERC20; + IL1NativeTokenVault public nativeTokenVault; + event BridgehubDepositBaseTokenInitiated( uint256 indexed chainId, address indexed from, - address l1Token, + bytes32 assetId, uint256 amount ); bytes32 dummyL2DepositTxHash; - /// @dev Maps token balances for each chain to prevent unauthorized spending across hyperchains. + /// @dev Maps token balances for each chain to prevent unauthorized spending across zkChains. /// This serves as a security measure until hyperbridging is implemented. mapping(uint256 chainId => mapping(address l1Token => uint256 balance)) public 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; + /// @dev A mapping assetId => assetHandlerAddress + /// @dev Tracks the address of Asset Handler contracts, where bridged funds are locked for each asset + /// @dev P.S. this liquidity was locked directly in SharedBridge before + mapping(bytes32 assetId => address assetHandlerAddress) public assetHandlerAddress; + constructor(bytes32 _dummyL2DepositTxHash) { dummyL2DepositTxHash = _dummyL2DepositTxHash; } @@ -48,13 +57,16 @@ contract DummySharedBridge is PausableUpgradeable { function depositLegacyErc20Bridge( address, //_msgSender, address, //_l2Receiver, - address, //_l1Token, - uint256, //_amount, + address _l1Token, + uint256 _amount, uint256, //_l2TxGasLimit, uint256, //_l2TxGasPerPubdataByte, address //_refundRecipient ) external payable returns (bytes32 txHash) { txHash = dummyL2DepositTxHash; + + // Legacy bridge requires this logic to work properly + IERC20(_l1Token).transferFrom(msg.sender, address(this), _amount); } function claimFailedDepositLegacyErc20Bridge( @@ -68,6 +80,18 @@ contract DummySharedBridge is PausableUpgradeable { bytes32[] calldata // _merkleProof ) external {} + function claimFailedDeposit( + uint256, // _chainId, + address, // _depositSender, + address, // _l1Asset, + uint256, // _amount, + bytes32, // _l2TxHash, + uint256, // _l2BatchNumber, + uint256, // _l2MessageIndex, + uint16, // _l2TxNumberInBatch, + bytes32[] calldata //_merkleProof + ) external {} + function finalizeWithdrawalLegacyErc20Bridge( uint256, //_l2BatchNumber, uint256, //_l2MessageIndex, @@ -105,8 +129,8 @@ contract DummySharedBridge is PausableUpgradeable { uint16 _l2TxNumberInBatch, bytes calldata _message, bytes32[] calldata _merkleProof - ) external { - (address l1Receiver, address l1Token, uint256 amount) = _parseL2WithdrawalMessage(_message); + ) external returns (address l1Receiver, address l1Token, uint256 amount) { + (l1Receiver, l1Token, amount) = _parseL2WithdrawalMessage(_message); if (l1Token == address(1)) { bool callSuccess; @@ -123,26 +147,17 @@ contract DummySharedBridge is PausableUpgradeable { function bridgehubDepositBaseToken( uint256 _chainId, - address _prevMsgSender, - address _l1Token, + bytes32 _assetId, + address _originalCaller, uint256 _amount ) external payable whenNotPaused { - 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 - } + // Dummy bridge supports only working with ETH for simplicity. + require(msg.value == _amount, "L1AR: msg.value not equal to amount"); - if (!hyperbridgingEnabled[_chainId]) { - chainBalance[_chainId][_l1Token] += _amount; - } + chainBalance[_chainId][address(1)] += _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); + emit BridgehubDepositBaseTokenInitiated(_chainId, _originalCaller, _assetId, _amount); } function _depositFunds(address _from, IERC20 _token, uint256 _amount) internal returns (uint256) { @@ -155,7 +170,7 @@ contract DummySharedBridge is PausableUpgradeable { function bridgehubDeposit( uint256, - address _prevMsgSender, + address _originalCaller, uint256, bytes calldata _data ) external payable returns (L2TransactionRequestTwoBridgesInner memory request) { @@ -172,15 +187,15 @@ contract DummySharedBridge is PausableUpgradeable { require(msg.value == 0, "ShB m.v > 0 for BH d.it 2"); amount = _depositAmount; - uint256 withdrawAmount = _depositFunds(_prevMsgSender, IERC20(_l1Token), _depositAmount); + uint256 withdrawAmount = _depositFunds(_originalCaller, IERC20(_l1Token), _depositAmount); require(withdrawAmount == _depositAmount, "5T"); // The token has non-standard transfer logic } bytes memory l2TxCalldata = abi.encodeCall( - IL2Bridge.finalizeDeposit, - (_prevMsgSender, _l2Receiver, _l1Token, amount, new bytes(0)) + IL2SharedBridgeLegacyFunctions.finalizeDeposit, + (_originalCaller, _l2Receiver, _l1Token, amount, new bytes(0)) ); - bytes32 txDataHash = keccak256(abi.encode(_prevMsgSender, _l1Token, amount)); + bytes32 txDataHash = keccak256(abi.encode(_originalCaller, _l1Token, amount)); request = L2TransactionRequestTwoBridgesInner({ magicValue: TWO_BRIDGES_MAGIC_VALUE, @@ -192,4 +207,23 @@ contract DummySharedBridge is PausableUpgradeable { } function bridgehubConfirmL2Transaction(uint256 _chainId, bytes32 _txDataHash, bytes32 _txHash) external {} + + /// @dev Sets the L1ERC20Bridge contract address. Should be called only once. + function setNativeTokenVault(IL1NativeTokenVault _nativeTokenVault) external { + require(address(nativeTokenVault) == address(0), "L1AR: legacy bridge already set"); + require(address(_nativeTokenVault) != address(0), "L1AR: legacy bridge 0"); + nativeTokenVault = _nativeTokenVault; + } + + /// @dev Used to set the assedAddress for a given assetId. + function setAssetHandlerAddressThisChain(bytes32 _additionalData, address _assetHandlerAddress) external { + address sender = msg.sender == address(nativeTokenVault) ? L2_NATIVE_TOKEN_VAULT_ADDR : msg.sender; + bytes32 assetId = keccak256(abi.encode(uint256(block.chainid), sender, _additionalData)); + assetHandlerAddress[assetId] = _assetHandlerAddress; + // assetDeploymentTracker[assetId] = sender; + // emit AssetDeploymentTrackerRegistered(assetId, _assetHandlerAddress, _additionalData, sender); + } + + // add this to be excluded from coverage report + function test() internal {} } diff --git a/l1-contracts/contracts/dev-contracts/test/DummyStateTransitionManagerForValidatorTimelock.sol b/l1-contracts/contracts/dev-contracts/test/DummyStateTransitionManagerForValidatorTimelock.sol deleted file mode 100644 index f2944d3a8..000000000 --- a/l1-contracts/contracts/dev-contracts/test/DummyStateTransitionManagerForValidatorTimelock.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -/// @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 hyperchainAddress; - - constructor(address _chainAdmin, address _hyperchain) { - chainAdmin = _chainAdmin; - hyperchainAddress = _hyperchain; - } - - function getChainAdmin(uint256) external view returns (address) { - return chainAdmin; - } - - function getHyperchain(uint256) external view returns (address) { - return hyperchainAddress; - } -} diff --git a/l1-contracts/contracts/dev-contracts/test/DummyStateTransitionManagerWithBridgeHubAddress.sol b/l1-contracts/contracts/dev-contracts/test/DummyStateTransitionManagerWithBridgeHubAddress.sol deleted file mode 100644 index 5109f7b77..000000000 --- a/l1-contracts/contracts/dev-contracts/test/DummyStateTransitionManagerWithBridgeHubAddress.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -import {EnumerableMap} from "@openzeppelin/contracts-v4/utils/structs/EnumerableMap.sol"; - -import {StateTransitionManager} from "../../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 { - using EnumerableMap for EnumerableMap.UintToAddressMap; - - /// @notice Constructor - constructor(address bridgeHub) StateTransitionManager(bridgeHub, type(uint256).max) {} - - function setHyperchain(uint256 _chainId, address _hyperchain) external { - hyperchainMap.set(_chainId, _hyperchain); - } -} diff --git a/l1-contracts/contracts/dev-contracts/test/DummyHyperchain.sol b/l1-contracts/contracts/dev-contracts/test/DummyZKChain.sol similarity index 74% rename from l1-contracts/contracts/dev-contracts/test/DummyHyperchain.sol rename to l1-contracts/contracts/dev-contracts/test/DummyZKChain.sol index 11be65a2b..9a535affe 100644 --- a/l1-contracts/contracts/dev-contracts/test/DummyHyperchain.sol +++ b/l1-contracts/contracts/dev-contracts/test/DummyZKChain.sol @@ -2,10 +2,14 @@ pragma solidity 0.8.24; import {MailboxFacet} from "../../state-transition/chain-deps/facets/Mailbox.sol"; -import {FeeParams, PubdataPricingMode} from "../../state-transition/chain-deps/ZkSyncHyperchainStorage.sol"; - -contract DummyHyperchain is MailboxFacet { - constructor(address bridgeHubAddress, uint256 _eraChainId) MailboxFacet(_eraChainId) { +import {FeeParams, PubdataPricingMode} from "../../state-transition/chain-deps/ZKChainStorage.sol"; + +contract DummyZKChain is MailboxFacet { + constructor( + address bridgeHubAddress, + uint256 _eraChainId, + uint256 _l1ChainId + ) MailboxFacet(_eraChainId, _l1ChainId) { s.bridgehub = bridgeHubAddress; } @@ -43,4 +47,13 @@ contract DummyHyperchain is MailboxFacet { minimalL2GasPrice: 250_000_000 }); } + + function genesisUpgrade( + address _l1GenesisUpgrade, + bytes calldata _forceDeploymentData, + bytes[] calldata _factoryDeps + ) external {} + + // add this to be excluded from coverage report + function test() internal {} } diff --git a/l1-contracts/contracts/dev-contracts/test/ExecutorProvingTest.sol b/l1-contracts/contracts/dev-contracts/test/ExecutorProvingTest.sol index 50bccb744..3df69577f 100644 --- a/l1-contracts/contracts/dev-contracts/test/ExecutorProvingTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/ExecutorProvingTest.sol @@ -3,11 +3,13 @@ pragma solidity 0.8.24; import {ExecutorFacet} from "../../state-transition/chain-deps/facets/Executor.sol"; -import {PubdataPricingMode} from "../../state-transition/chain-deps/ZkSyncHyperchainStorage.sol"; +import {PubdataPricingMode} from "../../state-transition/chain-deps/ZKChainStorage.sol"; import {LogProcessingOutput} from "../../state-transition/chain-interfaces/IExecutor.sol"; import {LogProcessingOutput} from "../../state-transition/chain-interfaces/IExecutor.sol"; contract ExecutorProvingTest is ExecutorFacet { + constructor() ExecutorFacet(block.chainid) {} + function getBatchProofPublicInput( bytes32 _prevBatchCommitment, bytes32 _currentBatchCommitment @@ -28,7 +30,7 @@ contract ExecutorProvingTest is ExecutorFacet { CommitBatchInfo calldata _newBatch, bytes32 _expectedSystemContractUpgradeTxHash, PubdataPricingMode - ) external pure returns (LogProcessingOutput memory logOutput) { + ) external view returns (LogProcessingOutput memory logOutput) { return _processL2Logs(_newBatch, _expectedSystemContractUpgradeTxHash); } @@ -38,4 +40,7 @@ contract ExecutorProvingTest is ExecutorFacet { s.l2BootloaderBytecodeHash = l2BootloaderBytecodeHash; s.zkPorterIsAvailable = false; } + + // add this to be excluded from coverage report + function test() internal {} } diff --git a/l1-contracts/contracts/dev-contracts/test/FullMerkleTest.sol b/l1-contracts/contracts/dev-contracts/test/FullMerkleTest.sol new file mode 100644 index 000000000..0a7245800 --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/test/FullMerkleTest.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {FullMerkle} from "../../common/libraries/FullMerkle.sol"; + +contract FullMerkleTest { + using FullMerkle for FullMerkle.FullTree; + + FullMerkle.FullTree internal tree; + + constructor(bytes32 zero) { + tree.setup(zero); + } + + function pushNewLeaf(bytes32 _item) external { + tree.pushNewLeaf(_item); + } + + function updateLeaf(uint256 _index, bytes32 _item) external { + tree.updateLeaf(_index, _item); + } + + function updateAllLeaves(bytes32[] memory _items) external { + tree.updateAllLeaves(_items); + } + + function updateAllNodesAtHeight(uint256 _height, bytes32[] memory _items) external { + tree.updateAllNodesAtHeight(_height, _items); + } + + function root() external view returns (bytes32) { + return tree.root(); + } + + function height() external view returns (uint256) { + return tree._height; + } + + function index() external view returns (uint256) { + return tree._leafNumber; + } + + function node(uint256 _height, uint256 _index) external view returns (bytes32) { + return tree._nodes[_height][_index]; + } + + function nodeCount(uint256 _height) external view returns (uint256) { + return tree._nodes[_height].length; + } + + function zeros(uint256 _index) external view returns (bytes32) { + return tree._zeros[_index]; + } +} diff --git a/l1-contracts/contracts/dev-contracts/test/IncrementalMerkleTest.sol b/l1-contracts/contracts/dev-contracts/test/IncrementalMerkleTest.sol new file mode 100644 index 000000000..b5850bb42 --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/test/IncrementalMerkleTest.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {DynamicIncrementalMerkle} from "../../common/libraries/DynamicIncrementalMerkle.sol"; + +contract IncrementalMerkleTest { + using DynamicIncrementalMerkle for DynamicIncrementalMerkle.Bytes32PushTree; + + DynamicIncrementalMerkle.Bytes32PushTree internal tree; + + constructor(bytes32 zero) { + tree.setup(zero); + } + + function push(bytes32 _item) external { + tree.push(_item); + } + + function root() external view returns (bytes32) { + return tree.root(); + } + + function height() external view returns (uint256) { + return tree.height(); + } + + function index() external view returns (uint256) { + return tree._nextLeafIndex; + } + + function side(uint256 _index) external view returns (bytes32) { + return tree._sides[_index]; + } + + function zeros(uint256 _index) external view returns (bytes32) { + return tree._zeros[_index]; + } +} diff --git a/l1-contracts/contracts/dev-contracts/test/L1ERC20BridgeTest.sol b/l1-contracts/contracts/dev-contracts/test/L1ERC20BridgeTest.sol index 034fb33d9..2f8eda079 100644 --- a/l1-contracts/contracts/dev-contracts/test/L1ERC20BridgeTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/L1ERC20BridgeTest.sol @@ -3,12 +3,17 @@ pragma solidity 0.8.24; import {L1ERC20Bridge} from "../../bridge/L1ERC20Bridge.sol"; -import {IBridgehub, IL1SharedBridge} from "../../bridge/interfaces/IL1SharedBridge.sol"; +import {IL1NativeTokenVault} from "../../bridge/ntv/IL1NativeTokenVault.sol"; +import {IBridgehub} from "../../bridgehub/IBridgehub.sol"; +import {IL1AssetRouter} from "../../bridge/asset-router/IL1AssetRouter.sol"; +import {IL1Nullifier} from "../../bridge/interfaces/IL1Nullifier.sol"; /// @author Matter Labs contract L1ERC20BridgeTest is L1ERC20Bridge { // add this to be excluded from coverage report function test() internal virtual {} - constructor(IBridgehub _zkSync) L1ERC20Bridge(IL1SharedBridge(address(0))) {} + constructor( + IBridgehub _zkSync + ) L1ERC20Bridge(IL1Nullifier(address(0)), IL1AssetRouter(address(0)), IL1NativeTokenVault(address(0)), 1) {} } diff --git a/l1-contracts/contracts/dev-contracts/test/L2NativeTokenVaultDev.sol b/l1-contracts/contracts/dev-contracts/test/L2NativeTokenVaultDev.sol new file mode 100644 index 000000000..896197fea --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/test/L2NativeTokenVaultDev.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; +import {Create2} from "@openzeppelin/contracts-v4/utils/Create2.sol"; +import {IBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/IBeacon.sol"; +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; + +import {INativeTokenVault} from "contracts/bridge/ntv/INativeTokenVault.sol"; +import {NativeTokenVault} from "contracts/bridge/ntv/NativeTokenVault.sol"; +import {L2NativeTokenVault} from "contracts/bridge/ntv/L2NativeTokenVault.sol"; +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; + +/// @author Matter Labs +/// @notice This is used for fast debugging of the L2NTV by running it in L1 context, i.e. normal foundry instead of foundry --zksync. +contract L2NativeTokenVaultDev is L2NativeTokenVault { + constructor( + uint256 _l1ChainId, + address _aliasedOwner, + bytes32 _l2TokenProxyBytecodeHash, + address _legacySharedBridge, + address _bridgedTokenBeacon, + bool _contractsDeployedAlready, + address _wethToken, + bytes32 _baseTokenAssetId + ) + L2NativeTokenVault( + _l1ChainId, + _aliasedOwner, + _l2TokenProxyBytecodeHash, + _legacySharedBridge, + _bridgedTokenBeacon, + _contractsDeployedAlready, + _wethToken, + _baseTokenAssetId + ) + {} + + /// @notice copied from L1NTV for L1 compilation + function calculateCreate2TokenAddress( + uint256 _originChainId, + address _l1Token + ) public view override(L2NativeTokenVault) returns (address) { + bytes32 salt = _getCreate2Salt(_originChainId, _l1Token); + return + Create2.computeAddress( + salt, + keccak256(abi.encodePacked(type(BeaconProxy).creationCode, abi.encode(bridgedTokenBeacon, ""))) + ); + } + + function deployBridgedStandardERC20(address _owner) external { + _transferOwnership(_owner); + + address l2StandardToken = address(new BridgedStandardERC20{salt: bytes32(0)}()); + + UpgradeableBeacon tokenBeacon = new UpgradeableBeacon{salt: bytes32(0)}(l2StandardToken); + + tokenBeacon.transferOwnership(owner()); + bridgedTokenBeacon = IBeacon(address(tokenBeacon)); + emit L2TokenBeaconUpdated(address(bridgedTokenBeacon), L2_TOKEN_PROXY_BYTECODE_HASH); + } + + function test() external pure { + // test + } + + function _deployBeaconProxy(bytes32 _salt, uint256) internal virtual override returns (BeaconProxy proxy) { + // Use CREATE2 to deploy the BeaconProxy + address proxyAddress = Create2.deploy( + 0, + _salt, + abi.encodePacked(type(BeaconProxy).creationCode, abi.encode(bridgedTokenBeacon, "")) + ); + return BeaconProxy(payable(proxyAddress)); + } +} diff --git a/l1-contracts/contracts/dev-contracts/test/MailboxFacetTest.sol b/l1-contracts/contracts/dev-contracts/test/MailboxFacetTest.sol index d5a415510..5b132f64c 100644 --- a/l1-contracts/contracts/dev-contracts/test/MailboxFacetTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/MailboxFacetTest.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.24; -import {FeeParams} from "../../state-transition/chain-deps/ZkSyncHyperchainStorage.sol"; +import {FeeParams} from "../../state-transition/chain-deps/ZKChainStorage.sol"; import {MailboxFacet} from "../../state-transition/chain-deps/facets/Mailbox.sol"; import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA} from "../../common/Config.sol"; @@ -10,7 +10,7 @@ contract MailboxFacetTest is MailboxFacet { // add this to be excluded from coverage report function test() internal virtual {} - constructor(uint256 _eraChainId) MailboxFacet(_eraChainId) { + constructor(uint256 _eraChainId, uint256 _l1ChainId) MailboxFacet(_eraChainId, _l1ChainId) { s.admin = msg.sender; } diff --git a/l1-contracts/contracts/dev-contracts/test/MerkleTest.sol b/l1-contracts/contracts/dev-contracts/test/MerkleTest.sol index 7db97f8be..f270ba30e 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.24; -import {Merkle} from "../../state-transition/libraries/Merkle.sol"; +import {Merkle} from "../../common/libraries/Merkle.sol"; contract MerkleTest { function calculateRoot( @@ -12,4 +12,13 @@ contract MerkleTest { ) external pure returns (bytes32) { return Merkle.calculateRoot(_path, _index, _itemHash); } + + function calculateRoot( + bytes32[] calldata _startPath, + bytes32[] calldata _endPath, + uint256 _startIndex, + bytes32[] calldata _itemHashes + ) external pure returns (bytes32) { + return Merkle.calculateRootPaths(_startPath, _endPath, _startIndex, _itemHashes); + } } diff --git a/l1-contracts/contracts/dev-contracts/test/MockExecutor.sol b/l1-contracts/contracts/dev-contracts/test/MockExecutor.sol index 9c7878b77..954c32ca2 100644 --- a/l1-contracts/contracts/dev-contracts/test/MockExecutor.sol +++ b/l1-contracts/contracts/dev-contracts/test/MockExecutor.sol @@ -2,9 +2,9 @@ pragma solidity 0.8.24; -import {ZkSyncHyperchainBase} from "../../state-transition/chain-deps/facets/ZkSyncHyperchainBase.sol"; +import {ZKChainBase} from "../../state-transition/chain-deps/facets/ZKChainBase.sol"; -contract MockExecutorFacet is ZkSyncHyperchainBase { +contract MockExecutorFacet is ZKChainBase { // add this to be excluded from coverage report function test() internal virtual {} @@ -12,4 +12,10 @@ contract MockExecutorFacet is ZkSyncHyperchainBase { s.totalBatchesExecuted = _batchNumber; s.l2LogsRootHashes[_batchNumber] = _l2LogsTreeRoot; } + + function setExecutedBatches(uint256 _batchNumber) external { + s.totalBatchesExecuted = _batchNumber; + s.totalBatchesCommitted = _batchNumber; + s.totalBatchesVerified = _batchNumber; + } } diff --git a/l1-contracts/contracts/dev-contracts/test/PriorityTreeTest.sol b/l1-contracts/contracts/dev-contracts/test/PriorityTreeTest.sol new file mode 100644 index 000000000..0409c1a4c --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/test/PriorityTreeTest.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {PriorityTree, PriorityOpsBatchInfo, PriorityTreeCommitment} from "../../state-transition/libraries/PriorityTree.sol"; + +contract PriorityTreeTest { + PriorityTree.Tree priorityTree; + + constructor() { + PriorityTree.setup(priorityTree, 0); + } + + function getFirstUnprocessedPriorityTx() external view returns (uint256) { + return PriorityTree.getFirstUnprocessedPriorityTx(priorityTree); + } + + function getTotalPriorityTxs() external view returns (uint256) { + return PriorityTree.getTotalPriorityTxs(priorityTree); + } + + function getSize() external view returns (uint256) { + return PriorityTree.getSize(priorityTree); + } + + function push(bytes32 _hash) external { + return PriorityTree.push(priorityTree, _hash); + } + + function getRoot() external view returns (bytes32) { + return PriorityTree.getRoot(priorityTree); + } + + function processBatch(PriorityOpsBatchInfo calldata _priorityOpsData) external { + PriorityTree.processBatch(priorityTree, _priorityOpsData); + } + + function getCommitment() external view returns (PriorityTreeCommitment memory) { + return PriorityTree.getCommitment(priorityTree); + } + + function initFromCommitment(PriorityTreeCommitment calldata _commitment) external { + PriorityTree.initFromCommitment(priorityTree, _commitment); + } + + function getZero() external view returns (bytes32) { + return priorityTree.tree._zeros[0]; + } +} diff --git a/l1-contracts/contracts/dev-contracts/test/ReenterGovernance.sol b/l1-contracts/contracts/dev-contracts/test/ReenterGovernance.sol index 0d619c5ba..193f8085f 100644 --- a/l1-contracts/contracts/dev-contracts/test/ReenterGovernance.sol +++ b/l1-contracts/contracts/dev-contracts/test/ReenterGovernance.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.24; import {IGovernance} from "../../governance/IGovernance.sol"; +import {Call} from "../../governance/Common.sol"; contract ReenterGovernance { // add this to be excluded from coverage report @@ -12,7 +13,7 @@ contract ReenterGovernance { // Store call, predecessor and salt separately, // because Operation struct can't be stored on storage. - IGovernance.Call call; + Call call; bytes32 predecessor; bytes32 salt; @@ -45,7 +46,7 @@ contract ReenterGovernance { fallback() external payable { if (!alreadyReentered) { alreadyReentered = true; - IGovernance.Call[] memory calls = new IGovernance.Call[](1); + Call[] memory calls = new Call[](1); calls[0] = call; IGovernance.Operation memory op = IGovernance.Operation({ calls: calls, diff --git a/l1-contracts/contracts/dev-contracts/test/TestCalldataDA.sol b/l1-contracts/contracts/dev-contracts/test/TestCalldataDA.sol new file mode 100644 index 000000000..aff7e50ca --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/test/TestCalldataDA.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {CalldataDA} from "../../state-transition/data-availability/CalldataDA.sol"; + +contract TestCalldataDA is CalldataDA { + function processL2RollupDAValidatorOutputHash( + bytes32 _l2DAValidatorOutputHash, + uint256 _maxBlobsSupported, + bytes calldata _operatorDAInput + ) + external + pure + returns ( + bytes32 stateDiffHash, + bytes32 fullPubdataHash, + bytes32[] memory blobsLinearHashes, + uint256 blobsProvided, + bytes calldata l1DaInput + ) + { + return _processL2RollupDAValidatorOutputHash(_l2DAValidatorOutputHash, _maxBlobsSupported, _operatorDAInput); + } + + function processCalldataDA( + uint256 _blobsProvided, + bytes32 _fullPubdataHash, + uint256 _maxBlobsSupported, + bytes calldata _pubdataInput + ) external pure returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { + return _processCalldataDA(_blobsProvided, _fullPubdataHash, _maxBlobsSupported, _pubdataInput); + } +} diff --git a/l1-contracts/contracts/dev-contracts/test/TestExecutor.sol b/l1-contracts/contracts/dev-contracts/test/TestExecutor.sol index 8da6425b3..045ded7ba 100644 --- a/l1-contracts/contracts/dev-contracts/test/TestExecutor.sol +++ b/l1-contracts/contracts/dev-contracts/test/TestExecutor.sol @@ -1,14 +1,28 @@ // SPDX-License-Identifier: MIT import {ExecutorFacet} from "../../state-transition/chain-deps/facets/Executor.sol"; +import {PriorityQueue, PriorityOperation} from "../../state-transition/libraries/PriorityQueue.sol"; pragma solidity 0.8.24; contract TestExecutor is ExecutorFacet { - /// @dev Since we want to test the blob functionality we want mock the calls to the blobhash opcode. - function _getBlobVersionedHash(uint256 _index) internal view virtual override returns (bytes32 versionedHash) { - (bool success, bytes memory data) = s.blobVersionedHashRetriever.staticcall(abi.encode(_index)); - require(success, "vc"); - versionedHash = abi.decode(data, (bytes32)); + constructor() ExecutorFacet(block.chainid) {} + using PriorityQueue for PriorityQueue.Queue; + + function setPriorityTreeStartIndex(uint256 _startIndex) external { + s.priorityTree.startIndex = _startIndex; + } + + function appendPriorityOp(bytes32 _hash) external { + s.priorityQueue.pushBack( + PriorityOperation({canonicalTxHash: _hash, expirationTimestamp: type(uint64).max, layer2Tip: 0}) + ); } + + // /// @dev Since we want to test the blob functionality we want mock the calls to the blobhash opcode. + // function _getBlobVersionedHash(uint256 _index) internal view virtual override returns (bytes32 versionedHash) { + // (bool success, bytes memory data) = s.blobVersionedHashRetriever.staticcall(abi.encode(_index)); + // require(success, "vc"); + // versionedHash = abi.decode(data, (bytes32)); + // } } diff --git a/l1-contracts/contracts/governance/AccessControlRestriction.sol b/l1-contracts/contracts/governance/AccessControlRestriction.sol new file mode 100644 index 000000000..fb430fd7d --- /dev/null +++ b/l1-contracts/contracts/governance/AccessControlRestriction.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {AccessToFallbackDenied, AccessToFunctionDenied, ZeroAddress} from "../common/L1ContractErrors.sol"; +import {IAccessControlRestriction} from "./IAccessControlRestriction.sol"; +import {AccessControlDefaultAdminRules} from "@openzeppelin/contracts-v4/access/AccessControlDefaultAdminRules.sol"; +import {Restriction} from "./restriction/Restriction.sol"; +import {Call} from "./Common.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice The Restriction that is designed to provide the access control logic for the `ChainAdmin` contract. +/// @dev It inherits from `AccessControlDefaultAdminRules` without overriding `_setRoleAdmin` functionality. In other +/// words, the `DEFAULT_ADMIN_ROLE` is the only role that can manage roles. This is done for simplicity. +/// @dev An instance of this restriction should be deployed separately for each `ChainAdmin` contract. +/// @dev IMPORTANT: this function does not validate the ability of the invoker to use `msg.value`. Thus, +/// either all callers with access to functions should be trusted to not steal ETH from the `ChainAdmin` account +/// or no ETH should be passively stored in `ChainAdmin` account. +contract AccessControlRestriction is Restriction, IAccessControlRestriction, AccessControlDefaultAdminRules { + /// @notice Required roles to call a specific function. + /// @dev Note, that the role 0 means the `DEFAULT_ADMIN_ROLE` from the `AccessControlDefaultAdminRules` contract. + mapping(address target => mapping(bytes4 selector => bytes32 requiredRole)) public requiredRoles; + + /// @notice Required roles to call a fallback function. + mapping(address target => bytes32 requiredRole) public requiredRolesForFallback; + + constructor( + uint48 initialDelay, + address initialDefaultAdmin + ) AccessControlDefaultAdminRules(initialDelay, initialDefaultAdmin) {} + + /// @notice Sets the required role for a specific function call. + /// @param _target The address of the contract. + /// @param _selector The selector of the function. + /// @param _requiredRole The required role. + function setRequiredRoleForCall( + address _target, + bytes4 _selector, + bytes32 _requiredRole + ) external onlyRole(DEFAULT_ADMIN_ROLE) { + if (_target == address(0)) { + revert ZeroAddress(); + } + requiredRoles[_target][_selector] = _requiredRole; + + emit RoleSet(_target, _selector, _requiredRole); + } + + /// @notice Sets the required role for a fallback function call. + /// @param _target The address of the contract. + /// @param _requiredRole The required role. + function setRequiredRoleForFallback(address _target, bytes32 _requiredRole) external onlyRole(DEFAULT_ADMIN_ROLE) { + if (_target == address(0)) { + revert ZeroAddress(); + } + requiredRolesForFallback[_target] = _requiredRole; + + emit FallbackRoleSet(_target, _requiredRole); + } + + /// @inheritdoc Restriction + function validateCall(Call calldata _call, address _invoker) external view override { + // Note, that since `DEFAULT_ADMIN_ROLE` is 0 and the default storage value for the + // `requiredRoles` and `requiredRolesForFallback` is 0, the default admin is by default a required + // role for all the functions. + if (_call.data.length < 4) { + // Note, that the following restriction protects only for targets that were compiled after + // Solidity v0.4.18, since before a substring of selector could still call the function. + if (!hasRole(requiredRolesForFallback[_call.target], _invoker)) { + revert AccessToFallbackDenied(_call.target, _invoker); + } + } else { + bytes4 selector = bytes4(_call.data[:4]); + if (!hasRole(requiredRoles[_call.target][selector], _invoker)) { + revert AccessToFunctionDenied(_call.target, selector, _invoker); + } + } + } +} diff --git a/l1-contracts/contracts/governance/ChainAdmin.sol b/l1-contracts/contracts/governance/ChainAdmin.sol index 4d9ff858f..6423f5a91 100644 --- a/l1-contracts/contracts/governance/ChainAdmin.sol +++ b/l1-contracts/contracts/governance/ChainAdmin.sol @@ -2,48 +2,80 @@ pragma solidity 0.8.24; -import {Ownable2Step} from "@openzeppelin/contracts-v4/access/Ownable2Step.sol"; +// solhint-disable gas-length-in-loops + +import {NoCallsProvided, OnlySelfAllowed, RestrictionWasNotPresent, RestrictionWasAlreadyPresent} from "../common/L1ContractErrors.sol"; import {IChainAdmin} from "./IChainAdmin.sol"; -import {IAdmin} from "../state-transition/chain-interfaces/IAdmin.sol"; -import {NoCallsProvided, Unauthorized, ZeroAddress} from "../common/L1ContractErrors.sol"; +import {Restriction} from "./restriction/Restriction.sol"; +import {RestrictionValidator} from "./restriction/RestrictionValidator.sol"; +import {Call} from "./Common.sol"; + +import {EnumerableSet} from "@openzeppelin/contracts-v4/utils/structs/EnumerableSet.sol"; +import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev /// @notice The contract is designed to hold the `admin` role in ZKSync Chain (State Transition) contracts. -/// The owner of the contract can perform any external calls and also save the information needed for -/// the blockchain node to accept the protocol upgrade. Another role - `tokenMultiplierSetter` can be used in the contract -/// to change the base token gas price in the Chain contract. -contract ChainAdmin is IChainAdmin, Ownable2Step { +/// @dev Note, that it does not implement any form of access control by default, but instead utilizes +/// so called "restrictions": contracts that implement the `IRestriction` interface and ensure that +/// particular restrictions are ensured for the contract, including access control, security invariants, etc. +/// @dev This is a new EXPERIMENTAL version of the `ChainAdmin` implementation. While chains may opt into using it, +/// using the old `ChainAdminSingleOwner` is recommended. +contract ChainAdmin is IChainAdmin, ReentrancyGuard { + using EnumerableSet for EnumerableSet.AddressSet; + /// @notice Mapping of protocol versions to their expected upgrade timestamps. /// @dev Needed for the offchain node administration to know when to start building batches with the new protocol version. mapping(uint256 protocolVersion => uint256 upgradeTimestamp) public protocolVersionToUpgradeTimestamp; - /// @notice The address which can call `setTokenMultiplier` function to change the base token gas price in the Chain contract. - /// @dev The token base price can be changed quite often, so the private key for this role is supposed to be stored in the node - /// and used by the automated service in a way similar to the sequencer workflow. - address public tokenMultiplierSetter; + /// @notice The set of active restrictions. + EnumerableSet.AddressSet internal activeRestrictions; - constructor(address _initialOwner, address _initialTokenMultiplierSetter) { - if (_initialOwner == address(0)) { - revert ZeroAddress(); + /// @notice Ensures that only the `ChainAdmin` contract itself can call the function. + /// @dev All functions that require access-control should use `onlySelf` modifier, while the access control logic + /// should be implemented in the restriction contracts. + modifier onlySelf() { + if (msg.sender != address(this)) { + revert OnlySelfAllowed(); + } + _; + } + + constructor(address[] memory _initialRestrictions) reentrancyGuardInitializer { + unchecked { + for (uint256 i = 0; i < _initialRestrictions.length; ++i) { + _addRestriction(_initialRestrictions[i]); + } } - _transferOwnership(_initialOwner); - // Can be zero if no one has this permission. - tokenMultiplierSetter = _initialTokenMultiplierSetter; - emit NewTokenMultiplierSetter(address(0), _initialTokenMultiplierSetter); } - /// @notice Updates the address responsible for setting token multipliers on the Chain contract . - /// @param _tokenMultiplierSetter The new address to be set as the token multiplier setter. - function setTokenMultiplierSetter(address _tokenMultiplierSetter) external onlyOwner { - emit NewTokenMultiplierSetter(tokenMultiplierSetter, _tokenMultiplierSetter); - tokenMultiplierSetter = _tokenMultiplierSetter; + /// @notice Returns the list of active restrictions. + function getRestrictions() public view returns (address[] memory) { + return activeRestrictions.values(); + } + + /// @inheritdoc IChainAdmin + function isRestrictionActive(address _restriction) external view returns (bool) { + return activeRestrictions.contains(_restriction); + } + + /// @inheritdoc IChainAdmin + function addRestriction(address _restriction) external onlySelf { + _addRestriction(_restriction); + } + + /// @inheritdoc IChainAdmin + function removeRestriction(address _restriction) external onlySelf { + if (!activeRestrictions.remove(_restriction)) { + revert RestrictionWasNotPresent(_restriction); + } + emit RestrictionRemoved(_restriction); } /// @notice Set the expected upgrade timestamp for a specific protocol version. /// @param _protocolVersion The ZKsync chain protocol version. /// @param _upgradeTimestamp The timestamp at which the chain node should expect the upgrade to happen. - function setUpgradeTimestamp(uint256 _protocolVersion, uint256 _upgradeTimestamp) external onlyOwner { + function setUpgradeTimestamp(uint256 _protocolVersion, uint256 _upgradeTimestamp) external onlySelf { protocolVersionToUpgradeTimestamp[_protocolVersion] = _upgradeTimestamp; emit UpdateUpgradeTimestamp(_protocolVersion, _upgradeTimestamp); } @@ -52,12 +84,16 @@ contract ChainAdmin is IChainAdmin, Ownable2Step { /// @param _calls Array of Call structures defining target, value, and data for each call. /// @param _requireSuccess If true, reverts transaction on any call failure. /// @dev Intended for batch processing of contract interactions, managing gas efficiency and atomicity of operations. - function multicall(Call[] calldata _calls, bool _requireSuccess) external payable onlyOwner { + /// @dev Note, that this function lacks access control. It is expected that the access control is implemented in a separate restriction contract. + /// @dev Even though all the validation from external modules is executed via `staticcall`, the function + /// is marked as `nonReentrant` to prevent reentrancy attacks in case the staticcall restriction is lifted in the future. + function multicall(Call[] calldata _calls, bool _requireSuccess) external payable nonReentrant { if (_calls.length == 0) { revert NoCallsProvided(); } - // solhint-disable-next-line gas-length-in-loops for (uint256 i = 0; i < _calls.length; ++i) { + _validateCall(_calls[i]); + // slither-disable-next-line arbitrary-send-eth (bool success, bytes memory returnData) = _calls[i].target.call{value: _calls[i].value}(_calls[i].data); if (_requireSuccess && !success) { @@ -70,17 +106,29 @@ contract ChainAdmin is IChainAdmin, Ownable2Step { } } - /// @notice Sets the token multiplier in the specified Chain contract. - /// @param _chainContract The chain contract address where the token multiplier will be set. - /// @param _nominator The numerator part of the token multiplier. - /// @param _denominator The denominator part of the token multiplier. - function setTokenMultiplier(IAdmin _chainContract, uint128 _nominator, uint128 _denominator) external { - if (msg.sender != tokenMultiplierSetter) { - revert Unauthorized(msg.sender); + /// @dev Contract might receive/hold ETH as part of the maintenance process. + receive() external payable {} + + /// @notice Function that ensures that the current admin can perform the call. + /// @dev Reverts in case the call can not be performed. Successfully executes otherwise + function _validateCall(Call calldata _call) private view { + address[] memory restrictions = getRestrictions(); + + unchecked { + for (uint256 i = 0; i < restrictions.length; ++i) { + Restriction(restrictions[i]).validateCall(_call, msg.sender); + } } - _chainContract.setTokenMultiplier(_nominator, _denominator); } - /// @dev Contract might receive/hold ETH as part of the maintenance process. - receive() external payable {} + /// @notice Adds a new restriction to the active restrictions set. + /// @param _restriction The address of the restriction contract to be added. + function _addRestriction(address _restriction) private { + RestrictionValidator.validateRestriction(_restriction); + + if (!activeRestrictions.add(_restriction)) { + revert RestrictionWasAlreadyPresent(_restriction); + } + emit RestrictionAdded(_restriction); + } } diff --git a/l1-contracts/contracts/governance/ChainAdminSingleOwner.sol b/l1-contracts/contracts/governance/ChainAdminSingleOwner.sol new file mode 100644 index 000000000..7f4074f22 --- /dev/null +++ b/l1-contracts/contracts/governance/ChainAdminSingleOwner.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {Ownable2Step} from "@openzeppelin/contracts-v4/access/Ownable2Step.sol"; +import {IChainAdminSingleOwner} from "./IChainAdminSingleOwner.sol"; +import {IAdmin} from "../state-transition/chain-interfaces/IAdmin.sol"; +import {NoCallsProvided, Unauthorized, ZeroAddress} from "../common/L1ContractErrors.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice The contract is designed to hold the `admin` role in ZKSync Chain (State Transition) contracts. +/// The owner of the contract can perform any external calls and also save the information needed for +/// the blockchain node to accept the protocol upgrade. Another role - `tokenMultiplierSetter` can be used in the contract +/// to change the base token gas price in the Chain contract. +contract ChainAdminSingleOwner is IChainAdminSingleOwner, Ownable2Step { + /// @notice Mapping of protocol versions to their expected upgrade timestamps. + /// @dev Needed for the offchain node administration to know when to start building batches with the new protocol version. + mapping(uint256 protocolVersion => uint256 upgradeTimestamp) public protocolVersionToUpgradeTimestamp; + + /// @notice The address which can call `setTokenMultiplier` function to change the base token gas price in the Chain contract. + /// @dev The token base price can be changed quite often, so the private key for this role is supposed to be stored in the node + /// and used by the automated service in a way similar to the sequencer workflow. + address public tokenMultiplierSetter; + + constructor(address _initialOwner, address _initialTokenMultiplierSetter) { + if (_initialOwner == address(0)) { + revert ZeroAddress(); + } + _transferOwnership(_initialOwner); + // Can be zero if no one has this permission. + tokenMultiplierSetter = _initialTokenMultiplierSetter; + emit NewTokenMultiplierSetter(address(0), _initialTokenMultiplierSetter); + } + + /// @notice Updates the address responsible for setting token multipliers on the Chain contract . + /// @param _tokenMultiplierSetter The new address to be set as the token multiplier setter. + function setTokenMultiplierSetter(address _tokenMultiplierSetter) external onlyOwner { + emit NewTokenMultiplierSetter(tokenMultiplierSetter, _tokenMultiplierSetter); + tokenMultiplierSetter = _tokenMultiplierSetter; + } + + /// @notice Set the expected upgrade timestamp for a specific protocol version. + /// @param _protocolVersion The ZKsync chain protocol version. + /// @param _upgradeTimestamp The timestamp at which the chain node should expect the upgrade to happen. + function setUpgradeTimestamp(uint256 _protocolVersion, uint256 _upgradeTimestamp) external onlyOwner { + protocolVersionToUpgradeTimestamp[_protocolVersion] = _upgradeTimestamp; + emit UpdateUpgradeTimestamp(_protocolVersion, _upgradeTimestamp); + } + + /// @notice Execute multiple calls as part of contract administration. + /// @param _calls Array of Call structures defining target, value, and data for each call. + /// @param _requireSuccess If true, reverts transaction on any call failure. + /// @dev Intended for batch processing of contract interactions, managing gas efficiency and atomicity of operations. + function multicall(Call[] calldata _calls, bool _requireSuccess) external payable onlyOwner { + if (_calls.length == 0) { + revert NoCallsProvided(); + } + // solhint-disable-next-line gas-length-in-loops + for (uint256 i = 0; i < _calls.length; ++i) { + // slither-disable-next-line arbitrary-send-eth + (bool success, bytes memory returnData) = _calls[i].target.call{value: _calls[i].value}(_calls[i].data); + if (_requireSuccess && !success) { + // Propagate an error if the call fails. + assembly { + revert(add(returnData, 0x20), mload(returnData)) + } + } + emit CallExecuted(_calls[i], success, returnData); + } + } + + /// @notice Sets the token multiplier in the specified Chain contract. + /// @param _chainContract The chain contract address where the token multiplier will be set. + /// @param _nominator The numerator part of the token multiplier. + /// @param _denominator The denominator part of the token multiplier. + function setTokenMultiplier(IAdmin _chainContract, uint128 _nominator, uint128 _denominator) external { + if (msg.sender != tokenMultiplierSetter) { + revert Unauthorized(msg.sender); + } + _chainContract.setTokenMultiplier(_nominator, _denominator); + } + + /// @dev Contract might receive/hold ETH as part of the maintenance process. + receive() external payable {} +} diff --git a/l1-contracts/contracts/governance/Common.sol b/l1-contracts/contracts/governance/Common.sol new file mode 100644 index 000000000..fd73dd793 --- /dev/null +++ b/l1-contracts/contracts/governance/Common.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +/// @dev Represents a call to be made during multicall. +/// @param target The address to which the call will be made. +/// @param value The amount of Ether (in wei) to be sent along with the call. +/// @param data The calldata to be executed on the `target` address. +struct Call { + address target; + uint256 value; + bytes data; +} diff --git a/l1-contracts/contracts/governance/Governance.sol b/l1-contracts/contracts/governance/Governance.sol index 790b79a26..7b2182e1c 100644 --- a/l1-contracts/contracts/governance/Governance.sol +++ b/l1-contracts/contracts/governance/Governance.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.24; import {Ownable2Step} from "@openzeppelin/contracts-v4/access/Ownable2Step.sol"; import {IGovernance} from "./IGovernance.sol"; +import {Call} from "./Common.sol"; import {ZeroAddress, Unauthorized, OperationMustBeReady, OperationMustBePending, OperationExists, InvalidDelay, PreviousOperationNotExecuted} from "../common/L1ContractErrors.sol"; /// @author Matter Labs @@ -12,7 +13,7 @@ import {ZeroAddress, Unauthorized, OperationMustBeReady, OperationMustBePending, /// @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 hyperchain governed contracts. +/// and changes in all ZK chain 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/governance/IAccessControlRestriction.sol b/l1-contracts/contracts/governance/IAccessControlRestriction.sol new file mode 100644 index 000000000..3c9cfb5c5 --- /dev/null +++ b/l1-contracts/contracts/governance/IAccessControlRestriction.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +/// @title AccessControlRestriction contract interface +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +interface IAccessControlRestriction { + /// @notice Emitted when the required role for a specific function is set. + event RoleSet(address indexed target, bytes4 indexed selector, bytes32 requiredRole); + + /// @notice Emitted when the required role for a fallback function is set. + event FallbackRoleSet(address indexed target, bytes32 requiredRole); +} diff --git a/l1-contracts/contracts/governance/IChainAdmin.sol b/l1-contracts/contracts/governance/IChainAdmin.sol index d5d8f117c..6dba4dfa8 100644 --- a/l1-contracts/contracts/governance/IChainAdmin.sol +++ b/l1-contracts/contracts/governance/IChainAdmin.sol @@ -2,36 +2,46 @@ pragma solidity 0.8.24; -import {IAdmin} from "../state-transition/chain-interfaces/IAdmin.sol"; +import {Call} from "./Common.sol"; /// @title ChainAdmin contract interface /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev interface IChainAdmin { - /// @dev Represents a call to be made during multicall. - /// @param target The address to which the call will be made. - /// @param value The amount of Ether (in wei) to be sent along with the call. - /// @param data The calldata to be executed on the `target` address. - struct Call { - address target; - uint256 value; - bytes data; - } - /// @notice Emitted when the expected upgrade timestamp for a specific protocol version is set. - event UpdateUpgradeTimestamp(uint256 indexed _protocolVersion, uint256 _upgradeTimestamp); + event UpdateUpgradeTimestamp(uint256 indexed protocolVersion, uint256 upgradeTimestamp); /// @notice Emitted when the call is executed from the contract. - event CallExecuted(Call _call, bool _success, bytes _returnData); + event CallExecuted(Call call, bool success, bytes returnData); - /// @notice Emitted when the new token multiplier address is set. - event NewTokenMultiplierSetter(address _oldTokenMultiplierSetter, address _newTokenMultiplierSetter); + /// @notice Emitted when a new restriction is added. + event RestrictionAdded(address indexed restriction); - function setTokenMultiplierSetter(address _tokenMultiplierSetter) external; + /// @notice Emitted when a restriction is removed. + event RestrictionRemoved(address indexed restriction); - function setUpgradeTimestamp(uint256 _protocolVersion, uint256 _upgradeTimestamp) external; + /// @notice Returns the list of active restrictions. + function getRestrictions() external view returns (address[] memory); - function multicall(Call[] calldata _calls, bool _requireSuccess) external payable; + /// @notice Checks if the restriction is active. + /// @param _restriction The address of the restriction contract. + function isRestrictionActive(address _restriction) external view returns (bool); + + /// @notice Adds a new restriction to the active restrictions set. + /// @param _restriction The address of the restriction contract. + function addRestriction(address _restriction) external; - function setTokenMultiplier(IAdmin _chainContract, uint128 _nominator, uint128 _denominator) external; + /// @notice Removes a restriction from the active restrictions set. + /// @param _restriction The address of the restriction contract. + /// @dev Sometimes restrictions might need to enforce their permanence (e.g. if a chain should be a rollup forever). + function removeRestriction(address _restriction) external; + + /// @notice Execute multiple calls as part of contract administration. + /// @param _calls Array of Call structures defining target, value, and data for each call. + /// @param _requireSuccess If true, reverts transaction on any call failure. + /// @dev Intended for batch processing of contract interactions, managing gas efficiency and atomicity of operations. + /// @dev Note, that this function lacks access control. It is expected that the access control is implemented in a separate restriction contract. + /// @dev Even though all the validation from external modules is executed via `staticcall`, the function + /// is marked as `nonReentrant` to prevent reentrancy attacks in case the staticcall restriction is lifted in the future. + function multicall(Call[] calldata _calls, bool _requireSuccess) external payable; } diff --git a/l1-contracts/contracts/governance/IChainAdminSingleOwner.sol b/l1-contracts/contracts/governance/IChainAdminSingleOwner.sol new file mode 100644 index 000000000..9de89a9db --- /dev/null +++ b/l1-contracts/contracts/governance/IChainAdminSingleOwner.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IAdmin} from "../state-transition/chain-interfaces/IAdmin.sol"; + +/// @title ChainAdmin contract interface +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +interface IChainAdminSingleOwner { + /// @dev Represents a call to be made during multicall. + /// @param target The address to which the call will be made. + /// @param value The amount of Ether (in wei) to be sent along with the call. + /// @param data The calldata to be executed on the `target` address. + struct Call { + address target; + uint256 value; + bytes data; + } + + /// @notice Emitted when the expected upgrade timestamp for a specific protocol version is set. + event UpdateUpgradeTimestamp(uint256 indexed _protocolVersion, uint256 _upgradeTimestamp); + + /// @notice Emitted when the call is executed from the contract. + event CallExecuted(Call _call, bool _success, bytes _returnData); + + /// @notice Emitted when the new token multiplier address is set. + event NewTokenMultiplierSetter(address _oldTokenMultiplierSetter, address _newTokenMultiplierSetter); + + function setTokenMultiplierSetter(address _tokenMultiplierSetter) external; + + function setUpgradeTimestamp(uint256 _protocolVersion, uint256 _upgradeTimestamp) external; + + function multicall(Call[] calldata _calls, bool _requireSuccess) external payable; + + function setTokenMultiplier(IAdmin _chainContract, uint128 _nominator, uint128 _denominator) external; +} diff --git a/l1-contracts/contracts/governance/IGovernance.sol b/l1-contracts/contracts/governance/IGovernance.sol index 2b03ed4c9..0cb478573 100644 --- a/l1-contracts/contracts/governance/IGovernance.sol +++ b/l1-contracts/contracts/governance/IGovernance.sol @@ -2,6 +2,8 @@ // We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. pragma solidity ^0.8.21; +import {Call} from "./Common.sol"; + /// @title Governance contract interface /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev @@ -18,16 +20,6 @@ interface IGovernance { Done } - /// @dev Represents a call to be made during an operation. - /// @param target The address to which the call will be made. - /// @param value The amount of Ether (in wei) to be sent along with the call. - /// @param data The calldata to be executed on the `target` address. - struct Call { - address target; - uint256 value; - bytes data; - } - /// @dev Defines the structure of an operation that Governance executes. /// @param calls An array of `Call` structs, each representing a call to be made during the operation. /// @param predecessor The hash of the predecessor operation, that should be executed before this operation. diff --git a/l1-contracts/contracts/governance/IPermanentRestriction.sol b/l1-contracts/contracts/governance/IPermanentRestriction.sol new file mode 100644 index 000000000..5fb015e33 --- /dev/null +++ b/l1-contracts/contracts/governance/IPermanentRestriction.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +/// @notice The interface for the permanent restriction contract. +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +interface IPermanentRestriction { + /// @notice Emitted when the implementation is allowed or disallowed. + event AdminImplementationAllowed(bytes32 indexed implementationHash, bool isAllowed); + + /// @notice Emitted when a certain calldata is allowed or disallowed. + event AllowedDataChanged(bytes data, bool isAllowed); + + /// @notice Emitted when the selector is labeled as validated or not. + event SelectorValidationChanged(bytes4 indexed selector, bool isValidated); + + /// @notice Emitted when the L2 admin is whitelisted or not. + event AllowL2Admin(address indexed adminAddress); +} diff --git a/l1-contracts/contracts/governance/L2AdminFactory.sol b/l1-contracts/contracts/governance/L2AdminFactory.sol new file mode 100644 index 000000000..95a2a2312 --- /dev/null +++ b/l1-contracts/contracts/governance/L2AdminFactory.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {ChainAdmin} from "./ChainAdmin.sol"; +import {RestrictionValidator} from "./restriction/RestrictionValidator.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @dev Contract used to deploy ChainAdmin contracts on L2. +/// @dev It can be used to ensure that certain L2 admins are deployed with +/// predefined restrictions. E.g. it can be used to deploy admins that ensure that +/// a chain is a permanent rollup. +/// @dev This contract is expected to be deployed in zkEVM (L2) environment. +/// @dev The contract is immutable, in case the restrictions need to be changed, +/// a new contract should be deployed. +contract L2AdminFactory { + /// @notice Emitted when an admin is deployed on the L2. + /// @param admin The address of the newly deployed admin. + event AdminDeployed(address indexed admin); + + /// @dev We use storage instead of immutable variables due to the + /// specifics of the zkEVM environment, where storage is actually cheaper. + address[] public requiredRestrictions; + + constructor(address[] memory _requiredRestrictions) { + _validateRestrctions(_requiredRestrictions); + requiredRestrictions = _requiredRestrictions; + } + + /// @notice Deploys a new L2 admin contract. + /// @return admin The address of the deployed admin contract. + // solhint-disable-next-line gas-calldata-parameters + function deployAdmin(address[] memory _additionalRestrictions) external returns (address admin) { + // Even though the chain admin will likely perform similar checks, + // we keep those here just in case, since it is not expensive, while allowing to fail fast. + _validateRestrctions(_additionalRestrictions); + uint256 cachedRequired = requiredRestrictions.length; + uint256 cachedAdditional = _additionalRestrictions.length; + address[] memory restrictions = new address[](cachedRequired + cachedAdditional); + + unchecked { + for (uint256 i = 0; i < cachedRequired; ++i) { + restrictions[i] = requiredRestrictions[i]; + } + for (uint256 i = 0; i < cachedAdditional; ++i) { + restrictions[cachedRequired + i] = _additionalRestrictions[i]; + } + } + + // Note, that we are using CREATE instead of CREATE2 to prevent + // an attack where malicious deployer could select malicious `seed1` and `seed2` where + // this factory with `seed1` produces the same address as some other random factory with `seed2`, + // allowing to deploy a malicious contract. + admin = address(new ChainAdmin(restrictions)); + + emit AdminDeployed(address(admin)); + } + + /// @notice Checks that the provided list of restrictions is correct. + /// @param _restrictions List of the restrictions to check. + /// @dev In case either of the restrictions is not correct, the function reverts. + function _validateRestrctions(address[] memory _restrictions) internal view { + unchecked { + uint256 length = _restrictions.length; + for (uint256 i = 0; i < length; ++i) { + RestrictionValidator.validateRestriction(_restrictions[i]); + } + } + } +} diff --git a/l1-contracts/contracts/governance/L2ProxyAdminDeployer.sol b/l1-contracts/contracts/governance/L2ProxyAdminDeployer.sol new file mode 100644 index 000000000..144f951bf --- /dev/null +++ b/l1-contracts/contracts/governance/L2ProxyAdminDeployer.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +// solhint-disable gas-length-in-loops + +import {ProxyAdmin} from "@openzeppelin/contracts-v4/proxy/transparent/ProxyAdmin.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice The contract that deterministically deploys a ProxyAdmin, while +/// ensuring that its owner is the aliased governance contract +contract L2ProxyAdminDeployer { + address public immutable PROXY_ADMIN_ADDRESS; + + constructor(address _aliasedGovernance) { + ProxyAdmin admin = new ProxyAdmin{salt: bytes32(0)}(); + admin.transferOwnership(_aliasedGovernance); + + PROXY_ADMIN_ADDRESS = address(admin); + } +} diff --git a/l1-contracts/contracts/governance/PermanentRestriction.sol b/l1-contracts/contracts/governance/PermanentRestriction.sol new file mode 100644 index 000000000..1b1c33792 --- /dev/null +++ b/l1-contracts/contracts/governance/PermanentRestriction.sol @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {TooHighDeploymentNonce, CallNotAllowed, RemovingPermanentRestriction, ZeroAddress, UnallowedImplementation, AlreadyWhitelisted, NotAllowed} from "../common/L1ContractErrors.sol"; + +import {L2TransactionRequestTwoBridgesOuter, BridgehubBurnCTMAssetData} from "../bridgehub/IBridgehub.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; +import {L2ContractHelper} from "../common/libraries/L2ContractHelper.sol"; +import {NEW_ENCODING_VERSION, IAssetRouterBase} from "../bridge/asset-router/IAssetRouterBase.sol"; + +import {Call} from "./Common.sol"; +import {Restriction} from "./restriction/Restriction.sol"; +import {IChainAdmin} from "./IChainAdmin.sol"; +import {IBridgehub} from "../bridgehub/IBridgehub.sol"; +import {IZKChain} from "../state-transition/chain-interfaces/IZKChain.sol"; +import {IGetters} from "../state-transition/chain-interfaces/IGetters.sol"; +import {IAdmin} from "../state-transition/chain-interfaces/IAdmin.sol"; + +import {IPermanentRestriction} from "./IPermanentRestriction.sol"; + +/// @dev The value up to which the nonces of the L2AdminDeployer could be used. This is needed +/// to limit the impact of the birthday paradox attack, where an attack could craft a malicious +/// address on L1. +uint256 constant MAX_ALLOWED_NONCE = (1 << 48); + +/// @title PermanentRestriction contract +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice This contract should be used by chains that wish to guarantee that certain security +/// properties are preserved forever. +/// @dev To be deployed as a transparent upgradable proxy, owned by a trusted decentralized governance. +/// @dev Once of the instances of such contract is to ensure that a ZkSyncHyperchain is a rollup forever. +contract PermanentRestriction is Restriction, IPermanentRestriction, Ownable2StepUpgradeable { + /// @notice The address of the Bridgehub contract. + IBridgehub public immutable BRIDGE_HUB; + + /// @notice The address of the L2 admin factory that should be used to deploy the chain admins + /// for chains that migrated on top of an L2 settlement layer. + /// @dev If this contract is deployed on L2, this address is 0. + /// @dev This address is expected to be the same on all L2 chains. + address public immutable L2_ADMIN_FACTORY; + + /// @notice The mapping of the allowed admin implementations. + mapping(bytes32 implementationCodeHash => bool isAllowed) public allowedAdminImplementations; + + /// @notice The mapping of the allowed calls. + mapping(bytes allowedCalldata => bool isAllowed) public allowedCalls; + + /// @notice The mapping of the validated selectors. + mapping(bytes4 selector => bool isValidated) public selectorsToValidate; + + /// @notice The mapping of whitelisted L2 admins. + mapping(address adminAddress => bool isWhitelisted) public allowedL2Admins; + + constructor(IBridgehub _bridgehub, address _l2AdminFactory) { + _disableInitializers(); + BRIDGE_HUB = _bridgehub; + L2_ADMIN_FACTORY = _l2AdminFactory; + } + + /// @notice The initialization function for the proxy contract. + /// @param _initialOwner The initial owner of the permanent restriction. + /// @dev Expected to be delegatecalled by the `TransparentUpgradableProxy` + /// upon initialization. + function initialize(address _initialOwner) external initializer { + if (_initialOwner == address(0)) { + revert ZeroAddress(); + } + _transferOwnership(_initialOwner); + } + + /// @notice Allows a certain `ChainAdmin` implementation to be used as an admin. + /// @param _implementationHash The hash of the implementation code. + /// @param _isAllowed The flag that indicates if the implementation is allowed. + function setAllowedAdminImplementation(bytes32 _implementationHash, bool _isAllowed) external onlyOwner { + allowedAdminImplementations[_implementationHash] = _isAllowed; + + emit AdminImplementationAllowed(_implementationHash, _isAllowed); + } + + /// @notice Allows a certain calldata for a selector to be used. + /// @param _data The calldata for the function. + /// @param _isAllowed The flag that indicates if the calldata is allowed. + function setAllowedData(bytes calldata _data, bool _isAllowed) external onlyOwner { + allowedCalls[_data] = _isAllowed; + + emit AllowedDataChanged(_data, _isAllowed); + } + + /// @notice Allows a certain selector to be validated. + /// @param _selector The selector of the function. + /// @param _isValidated The flag that indicates if the selector is validated. + function setSelectorShouldBeValidated(bytes4 _selector, bool _isValidated) external onlyOwner { + selectorsToValidate[_selector] = _isValidated; + + emit SelectorValidationChanged(_selector, _isValidated); + } + + /// @notice Whitelists a certain L2 admin. + /// @param deploymentNonce The deployment nonce of the `L2_ADMIN_FACTORY` used for the deployment. + function allowL2Admin(uint256 deploymentNonce) external { + if (deploymentNonce > MAX_ALLOWED_NONCE) { + revert TooHighDeploymentNonce(); + } + + // We do not do any additional validations for constructor data or the bytecode, + // we expect that only admins of the allowed format are to be deployed. + address expectedAddress = L2ContractHelper.computeCreateAddress(L2_ADMIN_FACTORY, deploymentNonce); + + if (allowedL2Admins[expectedAddress]) { + revert AlreadyWhitelisted(expectedAddress); + } + + allowedL2Admins[expectedAddress] = true; + emit AllowL2Admin(expectedAddress); + } + + /// @inheritdoc Restriction + function validateCall( + Call calldata _call, + address // _invoker + ) external view override { + _validateAsChainAdmin(_call); + _validateMigrationToL2(_call); + _validateRemoveRestriction(_call); + } + + /// @notice Validates the migration to an L2 settlement layer. + /// @param _call The call data. + /// @dev Note that we do not need to validate the migration to the L1 layer as the admin + /// is not changed in this case. + function _validateMigrationToL2(Call calldata _call) private view { + (address admin, bool isMigration) = _getNewAdminFromMigration(_call); + if (isMigration) { + if (!allowedL2Admins[admin]) { + revert NotAllowed(admin); + } + } + } + + /// @notice Validates the call as the chain admin + /// @param _call The call data. + function _validateAsChainAdmin(Call calldata _call) private view { + if (!_isAdminOfAChain(_call.target)) { + // We only validate calls related to being an admin of a chain + return; + } + + // All calls with the length of the data below 4 will get into `receive`/`fallback` functions, + // we consider it to always be allowed. + if (_call.data.length < 4) { + return; + } + + bytes4 selector = bytes4(_call.data[:4]); + + if (selector == IAdmin.setPendingAdmin.selector) { + _validateNewAdmin(_call); + return; + } + + if (!selectorsToValidate[selector]) { + // The selector is not validated, any data is allowed. + return; + } + + if (!allowedCalls[_call.data]) { + revert CallNotAllowed(_call.data); + } + } + + /// @notice Validates the correctness of the new admin. + /// @param _call The call data. + /// @dev Ensures that the admin has a whitelisted implementation and does not remove this restriction. + function _validateNewAdmin(Call calldata _call) private view { + address newChainAdmin = abi.decode(_call.data[4:], (address)); + + bytes32 implementationCodeHash = newChainAdmin.codehash; + + if (!allowedAdminImplementations[implementationCodeHash]) { + revert UnallowedImplementation(implementationCodeHash); + } + + // Since the implementation is known to be correct (from the checks above), we + // can safely trust the returned value from the call below + if (!IChainAdmin(newChainAdmin).isRestrictionActive(address(this))) { + revert RemovingPermanentRestriction(); + } + } + + /// @notice Validates the removal of the restriction. + /// @param _call The call data. + /// @dev Ensures that this restriction is not removed. + function _validateRemoveRestriction(Call calldata _call) private view { + if (_call.target != msg.sender) { + return; + } + + if (_call.data.length < 4) { + return; + } + + if (bytes4(_call.data[:4]) != IChainAdmin.removeRestriction.selector) { + return; + } + + address removedRestriction = abi.decode(_call.data[4:], (address)); + + if (removedRestriction == address(this)) { + revert RemovingPermanentRestriction(); + } + } + + /// @notice Checks if the `msg.sender` is an admin of a certain ZkSyncHyperchain. + /// @param _chain The address of the chain. + function _isAdminOfAChain(address _chain) internal view returns (bool) { + if (_chain == address(0)) { + return false; + } + + // Unfortunately there is no easy way to double check that indeed the `_chain` is a ZkSyncHyperchain. + // So we do the following: + // - Query it for `chainId`. If it reverts, it is not a ZkSyncHyperchain. + // - Query the Bridgehub for the Hyperchain with the given `chainId`. + // - We compare the corresponding addresses + + // Note, that we do use assembly here to ensure that the function does not panic in case of + // either incorrect `_chain` address or in case the returndata is too large + + (uint256 chainId, bool chainIdQuerySuccess) = _getChainIdUnffallibleCall(_chain); + + if (!chainIdQuerySuccess) { + // It is not a hyperchain, so we can return `false` here. + return false; + } + + // Note, that here it is important to use the legacy `getHyperchain` function, so that the contract + // is compatible with the legacy ones. + if (BRIDGE_HUB.getHyperchain(chainId) != _chain) { + // It is not a hyperchain, so we can return `false` here. + return false; + } + + // Now, the chain is known to be a hyperchain, so it must implement the corresponding interface + address admin = IZKChain(_chain).getAdmin(); + + return admin == msg.sender; + } + + /// @notice Tries to call `IGetters.getChainId()` function on the `_potentialChainAddress`. + /// It ensures that the returndata is of correct format and if not, it returns false. + /// @param _chain The address of the potential chain + /// @return chainId The chainId of the chain. + /// @return success Whether the `chain` is indeed an address of a ZK Chain. + /// @dev Returns a tuple of the chainId and whether the call was successful. + /// If the second item is `false`, the caller should ignore the first value. + function _getChainIdUnffallibleCall(address _chain) private view returns (uint256 chainId, bool success) { + bytes4 selector = IGetters.getChainId.selector; + assembly { + // We use scratch space here, so it is safe + mstore(0, selector) + success := staticcall(gas(), _chain, 0, 4, 0, 0) + + let isReturndataSizeCorrect := eq(returndatasize(), 32) + + success := and(success, isReturndataSizeCorrect) + + if success { + // We use scratch space here, so it is safe + returndatacopy(0, 0, 32) + + chainId := mload(0) + } + } + } + + /// @notice Tries to get the new admin from the migration. + /// @param _call The call data. + /// @return Returns a tuple of of the new admin and whether the transaction is indeed the migration. + /// If the second item is `false`, the caller should ignore the first value. + /// @dev If any other error is returned, it is assumed to be out of gas or some other unexpected + /// error that should be bubbled up by the caller. + function _getNewAdminFromMigration(Call calldata _call) internal view returns (address, bool) { + if (_call.target != address(BRIDGE_HUB)) { + return (address(0), false); + } + + if (_call.data.length < 4) { + return (address(0), false); + } + + if (bytes4(_call.data[:4]) != IBridgehub.requestL2TransactionTwoBridges.selector) { + return (address(0), false); + } + + address sharedBridge = BRIDGE_HUB.sharedBridge(); + + // Assuming that correctly encoded calldata is provided, the following line must never fail, + // since the correct selector was checked before. + L2TransactionRequestTwoBridgesOuter memory request = abi.decode( + _call.data[4:], + (L2TransactionRequestTwoBridgesOuter) + ); + + if (request.secondBridgeAddress != sharedBridge) { + return (address(0), false); + } + + bytes memory secondBridgeData = request.secondBridgeCalldata; + if (secondBridgeData.length == 0) { + return (address(0), false); + } + + if (secondBridgeData[0] != NEW_ENCODING_VERSION) { + return (address(0), false); + } + bytes memory encodedData = new bytes(secondBridgeData.length - 1); + assembly { + mcopy(add(encodedData, 0x20), add(secondBridgeData, 0x21), mload(encodedData)) + } + + // From now on, we know that the used encoding version is `NEW_ENCODING_VERSION` that is + // supported only in the new protocol version with Gateway support, so we can assume + // that the methods like e.g. Bridgehub.ctmAssetIdToAddress must exist. + + // This is the format of the `secondBridgeData` under the `NEW_ENCODING_VERSION`. + // If it fails, it would mean that the data is not correct and the call would eventually fail anyway. + (bytes32 chainAssetId, bytes memory bridgehubData) = abi.decode(encodedData, (bytes32, bytes)); + + // We will just check that the chainAssetId is a valid chainAssetId. + // For now, for simplicity, we do not check that the admin is exactly the admin + // of this chain. + address ctmAddress = BRIDGE_HUB.ctmAssetIdToAddress(chainAssetId); + if (ctmAddress == address(0)) { + return (address(0), false); + } + + // Almost certainly it will be Bridgehub, but we add this check just in case we have circumstances + // that require us to use a different asset handler. + address assetHandlerAddress = IAssetRouterBase(sharedBridge).assetHandlerAddress(chainAssetId); + if (assetHandlerAddress != address(BRIDGE_HUB)) { + return (address(0), false); + } + + // The asset handler of CTM is the bridgehub and so the following decoding should work + BridgehubBurnCTMAssetData memory burnData = abi.decode(bridgehubData, (BridgehubBurnCTMAssetData)); + (address l2Admin, ) = abi.decode(burnData.ctmData, (address, bytes)); + + return (l2Admin, true); + } +} diff --git a/l1-contracts/contracts/governance/TransitionaryOwner.sol b/l1-contracts/contracts/governance/TransitionaryOwner.sol new file mode 100644 index 000000000..9248204bf --- /dev/null +++ b/l1-contracts/contracts/governance/TransitionaryOwner.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +// solhint-disable gas-length-in-loops + +import {Ownable2Step} from "@openzeppelin/contracts-v4/access/Ownable2Step.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice The contract that is used a temporary owner for Ownable2Step contracts until the +/// governance can accept the ownership +contract TransitionaryOwner { + address public immutable GOVERNANCE_ADDRESS; + + constructor(address _governanceAddress) { + GOVERNANCE_ADDRESS = _governanceAddress; + } + + /// @notice Claims that ownership of a contract and transfers it to the governance + function claimOwnershipAndGiveToGovernance(address target) external { + Ownable2Step(target).acceptOwnership(); + Ownable2Step(target).transferOwnership(GOVERNANCE_ADDRESS); + } +} diff --git a/l1-contracts/contracts/governance/restriction/IRestriction.sol b/l1-contracts/contracts/governance/restriction/IRestriction.sol new file mode 100644 index 000000000..9124d1f67 --- /dev/null +++ b/l1-contracts/contracts/governance/restriction/IRestriction.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {Call} from "../Common.sol"; + +/// @dev The magic value that has to be returned by the `getSupportsRestrictionMagic` +bytes32 constant RESTRICTION_MAGIC = keccak256("Restriction"); + +/// @title Restriction contract interface +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +interface IRestriction { + /// @notice A method used to check that the contract supports this interface. + /// @return Returns the `RESTRICTION_MAGIC` + function getSupportsRestrictionMagic() external view returns (bytes32); + + /// @notice Ensures that the invoker has the required role to call the function. + /// @param _call The call data. + /// @param _invoker The address of the invoker. + function validateCall(Call calldata _call, address _invoker) external view; +} diff --git a/l1-contracts/contracts/governance/restriction/Restriction.sol b/l1-contracts/contracts/governance/restriction/Restriction.sol new file mode 100644 index 000000000..e516b9b0e --- /dev/null +++ b/l1-contracts/contracts/governance/restriction/Restriction.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {Call} from "../Common.sol"; +import {IRestriction, RESTRICTION_MAGIC} from "./IRestriction.sol"; + +/// @title Restriction contract interface +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +abstract contract Restriction is IRestriction { + /// @notice A method used to check that the contract supports this interface. + /// @return Returns the `RESTRICTION_MAGIC` + function getSupportsRestrictionMagic() external view returns (bytes32) { + return RESTRICTION_MAGIC; + } + + /// @notice Ensures that the invoker has the required role to call the function. + /// @param _call The call data. + /// @param _invoker The address of the invoker. + function validateCall(Call calldata _call, address _invoker) external view virtual; +} diff --git a/l1-contracts/contracts/governance/restriction/RestrictionValidator.sol b/l1-contracts/contracts/governance/restriction/RestrictionValidator.sol new file mode 100644 index 000000000..e0d110947 --- /dev/null +++ b/l1-contracts/contracts/governance/restriction/RestrictionValidator.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {NotARestriction} from "../../common/L1ContractErrors.sol"; +import {IRestriction, RESTRICTION_MAGIC} from "./IRestriction.sol"; + +/// @title Restriction validator +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice The library which validates whether an address can be a valid restriction +library RestrictionValidator { + /// @notice Ensures that the provided address implements the restriction interface + /// @dev Note that it *can not guarantee* that the corresponding address indeed implements + /// the interface completely or that it is implemented correctly. It is mainly used to + /// ensure that invalid restrictions can not be accidentally added. + function validateRestriction(address _restriction) internal view { + if (IRestriction(_restriction).getSupportsRestrictionMagic() != RESTRICTION_MAGIC) { + revert NotARestriction(_restriction); + } + } +} diff --git a/l1-contracts/contracts/state-transition/ChainTypeManager.sol b/l1-contracts/contracts/state-transition/ChainTypeManager.sol new file mode 100644 index 000000000..7b17dc7f0 --- /dev/null +++ b/l1-contracts/contracts/state-transition/ChainTypeManager.sol @@ -0,0 +1,549 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {EnumerableMap} from "@openzeppelin/contracts-v4/utils/structs/EnumerableMap.sol"; +import {SafeCast} from "@openzeppelin/contracts-v4/utils/math/SafeCast.sol"; + +import {Diamond} from "./libraries/Diamond.sol"; +import {DiamondProxy} from "./chain-deps/DiamondProxy.sol"; +import {IAdmin} from "./chain-interfaces/IAdmin.sol"; +import {IDiamondInit} from "./chain-interfaces/IDiamondInit.sol"; +import {IExecutor} from "./chain-interfaces/IExecutor.sol"; +import {IChainTypeManager, ChainTypeManagerInitializeData, ChainCreationParams} from "./IChainTypeManager.sol"; +import {IZKChain} from "./chain-interfaces/IZKChain.sol"; +import {FeeParams} from "./chain-deps/ZKChainStorage.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; +import {L2_TO_L1_LOG_SERIALIZE_SIZE, DEFAULT_L2_LOGS_TREE_ROOT_HASH, EMPTY_STRING_KECCAK} from "../common/Config.sol"; +import {InitialForceDeploymentMismatch, AdminZero, OutdatedProtocolVersion} from "./L1StateTransitionErrors.sol"; +import {ChainAlreadyLive, Unauthorized, ZeroAddress, HashMismatch, GenesisUpgradeZero, GenesisBatchHashZero, GenesisIndexStorageZero, GenesisBatchCommitmentZero, MigrationsNotPaused} from "../common/L1ContractErrors.sol"; +import {SemVer} from "../common/libraries/SemVer.sol"; +import {IBridgehub} from "../bridgehub/IBridgehub.sol"; + +import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; + +/// @title State Transition Manager contract +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +contract ChainTypeManager is IChainTypeManager, ReentrancyGuard, Ownable2StepUpgradeable { + using EnumerableMap for EnumerableMap.UintToAddressMap; + + /// @notice Address of the bridgehub + address public immutable BRIDGE_HUB; + + /// @notice The map from chainId => zkChain contract + EnumerableMap.UintToAddressMap internal __DEPRECATED_zkChainMap; + + /// @dev The batch zero hash, calculated at initialization + bytes32 public storedBatchZero; + + /// @dev The stored cutData for diamond cut + bytes32 public initialCutHash; + + /// @dev The l1GenesisUpgrade contract address, used to set chainId + address public l1GenesisUpgrade; + + /// @dev The current packed protocolVersion. To access human-readable version, use `getSemverProtocolVersion` function. + uint256 public protocolVersion; + + /// @dev The timestamp when protocolVersion can be last used + mapping(uint256 _protocolVersion => uint256) public protocolVersionDeadline; + + /// @dev The validatorTimelock contract address + address public validatorTimelock; + + /// @dev The stored cutData for upgrade diamond cut. protocolVersion => cutHash + mapping(uint256 protocolVersion => bytes32 cutHash) public upgradeCutHash; + + /// @dev The address used to manage non critical updates + address public admin; + + /// @dev The address to accept the admin role + address private pendingAdmin; + + /// @dev The initial force deployment hash + bytes32 public initialForceDeploymentHash; + + /// @dev Contract is expected to be used as proxy implementation. + /// @dev Initialize the implementation to prevent Parity hack. + /// @dev Note, that while the contract does not use `nonReentrant` modifier, we still keep the `reentrancyGuardInitializer` + /// here for two reasons: + /// - It prevents the function from being called twice (including in the proxy impl). + /// - It makes the local version consistent with the one in production, which already had the reentrancy guard + /// initialized. + constructor(address _bridgehub) reentrancyGuardInitializer { + BRIDGE_HUB = _bridgehub; + + // 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); + + _disableInitializers(); + } + + /// @notice only the bridgehub can call + modifier onlyBridgehub() { + if (msg.sender != BRIDGE_HUB) { + revert Unauthorized(msg.sender); + } + _; + } + + /// @notice the admin can call, for non-critical updates + modifier onlyOwnerOrAdmin() { + if (msg.sender != admin && msg.sender != owner()) { + revert Unauthorized(msg.sender); + } + _; + } + + /// @return The tuple of (major, minor, patch) protocol version. + function getSemverProtocolVersion() external view returns (uint32, uint32, uint32) { + // slither-disable-next-line unused-return + return SemVer.unpackSemVer(SafeCast.toUint96(protocolVersion)); + } + + /// @notice return the chain contract address for a chainId + function getZKChain(uint256 _chainId) public view returns (address) { + return IBridgehub(BRIDGE_HUB).getZKChain(_chainId); + } + + /// @notice return the chain contract address for a chainId + /// @notice Do not use! use getZKChain instead. This will be removed. + function getZKChainLegacy(uint256 _chainId) public view returns (address chainAddress) { + // slither-disable-next-line unused-return + (, chainAddress) = __DEPRECATED_zkChainMap.tryGet(_chainId); + } + + /// @notice Returns the address of the ZK chain admin with the corresponding chainID. + /// @notice Not related to the CTM, but it is here for legacy reasons. + /// @param _chainId the chainId of the chain + function getChainAdmin(uint256 _chainId) external view override returns (address) { + return IZKChain(getZKChain(_chainId)).getAdmin(); + } + + /// @dev initialize + /// @dev Note, that while the contract does not use `nonReentrant` modifier, we still keep the `reentrancyGuardInitializer` + /// here for two reasons: + /// - It prevents the function from being called twice (including in the proxy impl). + /// - It makes the local version consistent with the one in production, which already had the reentrancy guard + /// initialized. + function initialize(ChainTypeManagerInitializeData calldata _initializeData) external reentrancyGuardInitializer { + if (_initializeData.owner == address(0)) { + revert ZeroAddress(); + } + _transferOwnership(_initializeData.owner); + + protocolVersion = _initializeData.protocolVersion; + _setProtocolVersionDeadline(_initializeData.protocolVersion, type(uint256).max); + validatorTimelock = _initializeData.validatorTimelock; + + _setChainCreationParams(_initializeData.chainCreationParams); + } + + /// @notice Updates the parameters with which a new chain is created + /// @param _chainCreationParams The new chain creation parameters + function _setChainCreationParams(ChainCreationParams calldata _chainCreationParams) internal { + if (_chainCreationParams.genesisUpgrade == address(0)) { + revert GenesisUpgradeZero(); + } + if (_chainCreationParams.genesisBatchHash == bytes32(0)) { + revert GenesisBatchHashZero(); + } + if (_chainCreationParams.genesisIndexRepeatedStorageChanges == uint64(0)) { + revert GenesisIndexStorageZero(); + } + if (_chainCreationParams.genesisBatchCommitment == bytes32(0)) { + revert GenesisBatchCommitmentZero(); + } + + l1GenesisUpgrade = _chainCreationParams.genesisUpgrade; + + // We need to initialize the state hash because it is used in the commitment of the next batch + IExecutor.StoredBatchInfo memory batchZero = IExecutor.StoredBatchInfo({ + batchNumber: 0, + batchHash: _chainCreationParams.genesisBatchHash, + indexRepeatedStorageChanges: _chainCreationParams.genesisIndexRepeatedStorageChanges, + numberOfLayer1Txs: 0, + priorityOperationsHash: EMPTY_STRING_KECCAK, + l2LogsTreeRoot: DEFAULT_L2_LOGS_TREE_ROOT_HASH, + timestamp: 0, + commitment: _chainCreationParams.genesisBatchCommitment + }); + storedBatchZero = keccak256(abi.encode(batchZero)); + bytes32 newInitialCutHash = keccak256(abi.encode(_chainCreationParams.diamondCut)); + initialCutHash = newInitialCutHash; + bytes32 forceDeploymentHash = keccak256(abi.encode(_chainCreationParams.forceDeploymentsData)); + initialForceDeploymentHash = forceDeploymentHash; + + emit NewChainCreationParams({ + genesisUpgrade: _chainCreationParams.genesisUpgrade, + genesisBatchHash: _chainCreationParams.genesisBatchHash, + genesisIndexRepeatedStorageChanges: _chainCreationParams.genesisIndexRepeatedStorageChanges, + genesisBatchCommitment: _chainCreationParams.genesisBatchCommitment, + newInitialCutHash: newInitialCutHash, + forceDeploymentHash: forceDeploymentHash + }); + } + + /// @notice Updates the parameters with which a new chain is created + /// @param _chainCreationParams The new chain creation parameters + function setChainCreationParams(ChainCreationParams calldata _chainCreationParams) external onlyOwner { + _setChainCreationParams(_chainCreationParams); + } + + /// @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 + /// @dev Please note, if the owner wants to enforce the admin change it must execute both `setPendingAdmin` and + /// `acceptAdmin` atomically. Otherwise `admin` can set different pending admin and so fail to accept the admin rights. + 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); + } + + /// @notice Accepts transfer of admin rights. Only pending admin can accept the role. + function acceptAdmin() external { + address currentPendingAdmin = pendingAdmin; + // Only proposed by current admin address can claim the admin rights + if (msg.sender != currentPendingAdmin) { + revert Unauthorized(msg.sender); + } + + address previousAdmin = admin; + admin = currentPendingAdmin; + delete pendingAdmin; + + emit NewPendingAdmin(currentPendingAdmin, address(0)); + emit NewAdmin(previousAdmin, currentPendingAdmin); + } + + /// @dev set validatorTimelock. Cannot do it during initialization, as validatorTimelock is deployed after CTM + /// @param _validatorTimelock the new validatorTimelock address + function setValidatorTimelock(address _validatorTimelock) external onlyOwner { + address oldValidatorTimelock = validatorTimelock; + validatorTimelock = _validatorTimelock; + emit NewValidatorTimelock(oldValidatorTimelock, _validatorTimelock); + } + + /// @dev set New Version with upgrade from old version + /// @param _cutData the new diamond cut data + /// @param _oldProtocolVersion the old protocol version + /// @param _oldProtocolVersionDeadline the deadline for the old protocol version + /// @param _newProtocolVersion the new protocol version + function setNewVersionUpgrade( + Diamond.DiamondCutData calldata _cutData, + uint256 _oldProtocolVersion, + uint256 _oldProtocolVersionDeadline, + uint256 _newProtocolVersion + ) external onlyOwner { + if (!IBridgehub(BRIDGE_HUB).migrationPaused()) { + revert MigrationsNotPaused(); + } + + bytes32 newCutHash = keccak256(abi.encode(_cutData)); + uint256 previousProtocolVersion = protocolVersion; + upgradeCutHash[_oldProtocolVersion] = newCutHash; + _setProtocolVersionDeadline(_oldProtocolVersion, _oldProtocolVersionDeadline); + _setProtocolVersionDeadline(_newProtocolVersion, type(uint256).max); + protocolVersion = _newProtocolVersion; + emit NewProtocolVersion(previousProtocolVersion, _newProtocolVersion); + emit NewUpgradeCutHash(_oldProtocolVersion, newCutHash); + emit NewUpgradeCutData(_newProtocolVersion, _cutData); + } + + /// @dev check that the protocolVersion is active + /// @param _protocolVersion the protocol version to check + function protocolVersionIsActive(uint256 _protocolVersion) external view override returns (bool) { + return block.timestamp <= protocolVersionDeadline[_protocolVersion]; + } + + /// @notice Set the protocol version deadline + /// @param _protocolVersion the protocol version + /// @param _timestamp the timestamp is the deadline + function setProtocolVersionDeadline(uint256 _protocolVersion, uint256 _timestamp) external onlyOwner { + _setProtocolVersionDeadline(_protocolVersion, _timestamp); + } + + /// @dev set upgrade for some protocolVersion + /// @param _cutData the new diamond cut data + /// @param _oldProtocolVersion the old protocol version + function setUpgradeDiamondCut( + Diamond.DiamondCutData calldata _cutData, + uint256 _oldProtocolVersion + ) external onlyOwner { + bytes32 newCutHash = keccak256(abi.encode(_cutData)); + upgradeCutHash[_oldProtocolVersion] = newCutHash; + emit NewUpgradeCutHash(_oldProtocolVersion, newCutHash); + } + + /// @dev freezes the specified chain + /// @param _chainId the chainId of the chain + function freezeChain(uint256 _chainId) external onlyOwner { + IZKChain(getZKChain(_chainId)).freezeDiamond(); + } + + /// @dev freezes the specified chain + /// @param _chainId the chainId of the chain + function unfreezeChain(uint256 _chainId) external onlyOwner { + IZKChain(getZKChain(_chainId)).unfreezeDiamond(); + } + + /// @dev reverts batches on the specified chain + /// @param _chainId the chainId of the chain + /// @param _newLastBatch the new last batch + function revertBatches(uint256 _chainId, uint256 _newLastBatch) external onlyOwnerOrAdmin { + IZKChain(getZKChain(_chainId)).revertBatchesSharedBridge(_chainId, _newLastBatch); + } + + /// @dev execute predefined upgrade + /// @param _chainId the chainId of the chain + /// @param _oldProtocolVersion the old protocol version + /// @param _diamondCut the diamond cut data + function upgradeChainFromVersion( + uint256 _chainId, + uint256 _oldProtocolVersion, + Diamond.DiamondCutData calldata _diamondCut + ) external onlyOwner { + IZKChain(getZKChain(_chainId)).upgradeChainFromVersion(_oldProtocolVersion, _diamondCut); + } + + /// @dev executes upgrade on chain + /// @param _chainId the chainId of the chain + /// @param _diamondCut the diamond cut data + function executeUpgrade(uint256 _chainId, Diamond.DiamondCutData calldata _diamondCut) external onlyOwner { + IZKChain(getZKChain(_chainId)).executeUpgrade(_diamondCut); + } + + /// @dev setPriorityTxMaxGasLimit for the specified chain + /// @param _chainId the chainId of the chain + /// @param _maxGasLimit the new max gas limit + function setPriorityTxMaxGasLimit(uint256 _chainId, uint256 _maxGasLimit) external onlyOwner { + IZKChain(getZKChain(_chainId)).setPriorityTxMaxGasLimit(_maxGasLimit); + } + + /// @dev setTokenMultiplier for the specified chain + /// @param _chainId the chainId of the chain + /// @param _nominator the new nominator of the token multiplier + /// @param _denominator the new denominator of the token multiplier + function setTokenMultiplier(uint256 _chainId, uint128 _nominator, uint128 _denominator) external onlyOwner { + IZKChain(getZKChain(_chainId)).setTokenMultiplier(_nominator, _denominator); + } + + /// @dev changeFeeParams for the specified chain + /// @param _chainId the chainId of the chain + /// @param _newFeeParams the new fee params + function changeFeeParams(uint256 _chainId, FeeParams calldata _newFeeParams) external onlyOwner { + IZKChain(getZKChain(_chainId)).changeFeeParams(_newFeeParams); + } + + /// @dev setValidator for the specified chain + /// @param _chainId the chainId of the chain + /// @param _validator the new validator + /// @param _active whether the validator is active + function setValidator(uint256 _chainId, address _validator, bool _active) external onlyOwner { + IZKChain(getZKChain(_chainId)).setValidator(_validator, _active); + } + + /// @dev setPorterAvailability for the specified chain + /// @param _chainId the chainId of the chain + /// @param _zkPorterIsAvailable whether the zkPorter mode is available + function setPorterAvailability(uint256 _chainId, bool _zkPorterIsAvailable) external onlyOwner { + IZKChain(getZKChain(_chainId)).setPorterAvailability(_zkPorterIsAvailable); + } + + /// registration + + /// @notice deploys a full set of chains contracts + /// @param _chainId the chain's id + /// @param _baseTokenAssetId the base token asset id used to pay for gas fees + /// @param _admin the chain's admin address + /// @param _diamondCut the diamond cut data that initializes the chains Diamond Proxy + function _deployNewChain( + uint256 _chainId, + bytes32 _baseTokenAssetId, + address _admin, + bytes memory _diamondCut + ) internal returns (address zkChainAddress) { + if (getZKChain(_chainId) != address(0)) { + // ZKChain already registered + revert ChainAlreadyLive(); + } + + Diamond.DiamondCutData memory diamondCut = abi.decode(_diamondCut, (Diamond.DiamondCutData)); + + { + // check input + bytes32 cutHashInput = keccak256(_diamondCut); + if (cutHashInput != initialCutHash) { + revert HashMismatch(initialCutHash, cutHashInput); + } + } + + // construct init data + bytes memory initData; + /// all together 4+9*32=292 bytes for the selector + mandatory data + // solhint-disable-next-line func-named-parameters + initData = bytes.concat( + IDiamondInit.initialize.selector, + bytes32(_chainId), + bytes32(uint256(uint160(BRIDGE_HUB))), + bytes32(uint256(uint160(address(this)))), + bytes32(protocolVersion), + bytes32(uint256(uint160(_admin))), + bytes32(uint256(uint160(validatorTimelock))), + _baseTokenAssetId, + storedBatchZero, + diamondCut.initCalldata + ); + + diamondCut.initCalldata = initData; + // deploy zkChainContract + // slither-disable-next-line reentrancy-no-eth + DiamondProxy zkChainContract = new DiamondProxy{salt: bytes32(0)}(block.chainid, diamondCut); + // save data + zkChainAddress = address(zkChainContract); + emit NewZKChain(_chainId, zkChainAddress); + } + + /// @notice called by Bridgehub when a chain registers + /// @param _chainId the chain's id + /// @param _baseTokenAssetId the base token asset id used to pay for gas fees + /// @param _admin the chain's admin address + /// @param _initData the diamond cut data, force deployments and factoryDeps encoded + /// @param _factoryDeps the factory dependencies used for the genesis upgrade + /// that initializes the chains Diamond Proxy + function createNewChain( + uint256 _chainId, + bytes32 _baseTokenAssetId, + address _admin, + bytes calldata _initData, + bytes[] calldata _factoryDeps + ) external onlyBridgehub returns (address zkChainAddress) { + (bytes memory _diamondCut, bytes memory _forceDeploymentData) = abi.decode(_initData, (bytes, bytes)); + + // solhint-disable-next-line func-named-parameters + zkChainAddress = _deployNewChain(_chainId, _baseTokenAssetId, _admin, _diamondCut); + + { + // check input + bytes32 forceDeploymentHash = keccak256(abi.encode(_forceDeploymentData)); + if (forceDeploymentHash != initialForceDeploymentHash) { + revert InitialForceDeploymentMismatch(forceDeploymentHash, initialForceDeploymentHash); + } + } + // genesis upgrade, deploys some contracts, sets chainId + IAdmin(zkChainAddress).genesisUpgrade( + l1GenesisUpgrade, + address(IBridgehub(BRIDGE_HUB).l1CtmDeployer()), + _forceDeploymentData, + _factoryDeps + ); + } + + /// @param _chainId the chainId of the chain + function getProtocolVersion(uint256 _chainId) public view returns (uint256) { + return IZKChain(getZKChain(_chainId)).getProtocolVersion(); + } + + /// @notice Called by the bridgehub during the migration of a chain to another settlement layer. + /// @param _chainId The chain id of the chain to be migrated. + /// @param _data The data needed to perform the migration. + function forwardedBridgeBurn( + uint256 _chainId, + bytes calldata _data + ) external view override onlyBridgehub returns (bytes memory ctmForwardedBridgeMintData) { + // Note that the `_diamondCut` here is not for the current chain, for the chain where the migration + // happens. The correctness of it will be checked on the CTM on the new settlement layer. + (address _newSettlementLayerAdmin, bytes memory _diamondCut) = abi.decode(_data, (address, bytes)); + if (_newSettlementLayerAdmin == address(0)) { + revert AdminZero(); + } + + // We ensure that the chain has the latest protocol version to avoid edge cases + // related to different protocol version support. + uint256 chainProtocolVersion = IZKChain(getZKChain(_chainId)).getProtocolVersion(); + if (chainProtocolVersion != protocolVersion) { + revert OutdatedProtocolVersion(chainProtocolVersion, protocolVersion); + } + + return + abi.encode( + IBridgehub(BRIDGE_HUB).baseTokenAssetId(_chainId), + _newSettlementLayerAdmin, + protocolVersion, + _diamondCut + ); + } + + /// @notice Called by the bridgehub during the migration of a chain to the current settlement layer. + /// @param _chainId The chain id of the chain to be migrated. + /// @param _ctmData The data returned from `forwardedBridgeBurn` for the chain. + function forwardedBridgeMint( + uint256 _chainId, + bytes calldata _ctmData + ) external override onlyBridgehub returns (address chainAddress) { + (bytes32 _baseTokenAssetId, address _admin, uint256 _protocolVersion, bytes memory _diamondCut) = abi.decode( + _ctmData, + (bytes32, address, uint256, bytes) + ); + + // We ensure that the chain has the latest protocol version to avoid edge cases + // related to different protocol version support. + if (_protocolVersion != protocolVersion) { + revert OutdatedProtocolVersion(_protocolVersion, protocolVersion); + } + chainAddress = _deployNewChain({ + _chainId: _chainId, + _baseTokenAssetId: _baseTokenAssetId, + _admin: _admin, + _diamondCut: _diamondCut + }); + } + + /// @notice Called by the bridgehub during the failed migration of a chain. + /// param _chainId the chainId of the chain + /// param _assetInfo the assetInfo of the chain + /// param _depositSender the address of that sent the deposit + /// param _ctmData the data of the migration + function forwardedBridgeRecoverFailedTransfer( + uint256 /* _chainId */, + bytes32 /* _assetInfo */, + address /* _depositSender */, + bytes calldata /* _ctmData */ + ) external { + // Function is empty due to the fact that when calling `forwardedBridgeBurn` there are no + // state updates that occur. + } + + /// @notice Set the protocol version deadline + /// @param _protocolVersion the protocol version + /// @param _timestamp the timestamp is the deadline + function _setProtocolVersionDeadline(uint256 _protocolVersion, uint256 _timestamp) internal { + protocolVersionDeadline[_protocolVersion] = _timestamp; + emit UpdateProtocolVersionDeadline(_protocolVersion, _timestamp); + } + + /*////////////////////////////////////////////////////////////// + Legacy functions + //////////////////////////////////////////////////////////////*/ + + /// @notice return the chain contract address for a chainId + function getHyperchain(uint256 _chainId) public view returns (address) { + // During upgrade, there will be a period when the zkChains mapping on + // bridgehub will not be filled yet, while the ValidatorTimelock + // will still query the address to obtain the chain id. + // + // To cover this case, we firstly use the existing storage and only then + // we use the bridgehub if the former was not present. + // This logic should be deleted in one of the future upgrades. + address legacyAddress = getZKChainLegacy(_chainId); + if (legacyAddress != address(0)) { + return legacyAddress; + } + return getZKChain(_chainId); + } +} diff --git a/l1-contracts/contracts/state-transition/IStateTransitionManager.sol b/l1-contracts/contracts/state-transition/IChainTypeManager.sol similarity index 75% rename from l1-contracts/contracts/state-transition/IStateTransitionManager.sol rename to l1-contracts/contracts/state-transition/IChainTypeManager.sol index fec1c31fd..b8d50d22c 100644 --- a/l1-contracts/contracts/state-transition/IStateTransitionManager.sol +++ b/l1-contracts/contracts/state-transition/IChainTypeManager.sol @@ -4,15 +4,17 @@ pragma solidity ^0.8.21; import {Diamond} from "./libraries/Diamond.sol"; import {L2CanonicalTransaction} from "../common/Messaging.sol"; -import {FeeParams} from "./chain-deps/ZkSyncHyperchainStorage.sol"; +import {FeeParams} from "./chain-deps/ZKChainStorage.sol"; -/// @notice Struct that holds all data needed for initializing STM Proxy. +// import {IBridgehub} from "../bridgehub/IBridgehub.sol"; + +/// @notice Struct that holds all data needed for initializing CTM Proxy. /// @dev We use struct instead of raw parameters in `initialize` function to prevent "Stack too deep" error /// @param owner The address who can manage non-critical updates in the contract /// @param validatorTimelock The address that serves as consensus, i.e. can submit blocks to be processed /// @param chainCreationParams The struct that contains the fields that define how a new chain should be created /// @param protocolVersion The initial protocol version on the newly deployed chain -struct StateTransitionManagerInitializeData { +struct ChainTypeManagerInitializeData { address owner; address validatorTimelock; ChainCreationParams chainCreationParams; @@ -20,7 +22,7 @@ struct StateTransitionManagerInitializeData { } /// @notice The struct that contains the fields that define how a new chain should be created -/// within this STM. +/// within this CTM. /// @param genesisUpgrade The address that is used in the diamond cut initialize address on chain creation /// @param genesisBatchHash Batch hash of the genesis (initial) batch /// @param genesisIndexRepeatedStorageChanges The serial number of the shortcut storage key for the genesis batch @@ -33,15 +35,16 @@ struct ChainCreationParams { uint64 genesisIndexRepeatedStorageChanges; bytes32 genesisBatchCommitment; Diamond.DiamondCutData diamondCut; + bytes forceDeploymentsData; } -interface IStateTransitionManager { - /// @dev Emitted when a new Hyperchain is added - event NewHyperchain(uint256 indexed _chainId, address indexed _hyperchainContract); +interface IChainTypeManager { + /// @dev Emitted when a new ZKChain is added + event NewZKChain(uint256 indexed _chainId, address indexed _zkChainContract); - /// @dev emitted when an chain registers and a SetChainIdUpgrade happens - event SetChainIdUpgrade( - address indexed _hyperchain, + /// @dev emitted when an chain registers and a GenesisUpgrade happens + event GenesisUpgrade( + address indexed _zkChain, L2CanonicalTransaction _l2Transaction, uint256 indexed _protocolVersion ); @@ -62,7 +65,8 @@ interface IStateTransitionManager { bytes32 genesisBatchHash, uint64 genesisIndexRepeatedStorageChanges, bytes32 genesisBatchCommitment, - bytes32 newInitialCutHash + bytes32 newInitialCutHash, + bytes32 forceDeploymentHash ); /// @notice New UpgradeCutHash @@ -74,23 +78,26 @@ interface IStateTransitionManager { /// @notice New ProtocolVersion event NewProtocolVersion(uint256 indexed oldProtocolVersion, uint256 indexed newProtocolVersion); + /// @notice Updated ProtocolVersion deadline + event UpdateProtocolVersionDeadline(uint256 indexed protocolVersion, uint256 deadline); + function BRIDGE_HUB() external view returns (address); function setPendingAdmin(address _newPendingAdmin) external; function acceptAdmin() external; - function getAllHyperchains() external view returns (address[] memory); - - function getAllHyperchainChainIDs() external view returns (uint256[] memory); + function getZKChain(uint256 _chainId) external view returns (address); function getHyperchain(uint256 _chainId) external view returns (address); + function getZKChainLegacy(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 l1GenesisUpgrade() external view returns (address); function upgradeCutHash(uint256 _protocolVersion) external view returns (bytes32); @@ -100,7 +107,9 @@ interface IStateTransitionManager { function protocolVersionIsActive(uint256 _protocolVersion) external view returns (bool); - function initialize(StateTransitionManagerInitializeData calldata _initializeData) external; + function getProtocolVersion(uint256 _chainId) external view returns (uint256); + + function initialize(ChainTypeManagerInitializeData calldata _initializeData) external; function setValidatorTimelock(address _validatorTimelock) external; @@ -110,13 +119,11 @@ interface IStateTransitionManager { function createNewChain( uint256 _chainId, - address _baseToken, - address _sharedBridge, + bytes32 _baseTokenAssetId, address _admin, - bytes calldata _diamondCut - ) external; - - function registerAlreadyDeployedHyperchain(uint256 _chainId, address _hyperchain) external; + bytes calldata _initData, + bytes[] calldata _factoryDeps + ) external returns (address); function setNewVersionUpgrade( Diamond.DiamondCutData calldata _cutData, @@ -150,4 +157,18 @@ interface IStateTransitionManager { ) external; function getSemverProtocolVersion() external view returns (uint32, uint32, uint32); + + function forwardedBridgeBurn( + uint256 _chainId, + bytes calldata _data + ) external returns (bytes memory _bridgeMintData); + + function forwardedBridgeMint(uint256 _chainId, bytes calldata _data) external returns (address); + + function forwardedBridgeRecoverFailedTransfer( + uint256 _chainId, + bytes32 _assetInfo, + address _depositSender, + bytes calldata _ctmData + ) external; } diff --git a/l1-contracts/contracts/state-transition/L1StateTransitionErrors.sol b/l1-contracts/contracts/state-transition/L1StateTransitionErrors.sol new file mode 100644 index 000000000..7ba2540d4 --- /dev/null +++ b/l1-contracts/contracts/state-transition/L1StateTransitionErrors.sol @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.21; + +// 0x2e89f517 +error L1DAValidatorAddressIsZero(); + +// 0x944bc075 +error L2DAValidatorAddressIsZero(); + +// 0xca1c3cbc +error AlreadyMigrated(); + +// 0xf05c64c6 +error NotChainAdmin(address prevMsgSender, address admin); + +// 0xc59d372c +error ProtocolVersionNotUpToDate(uint256 currentProtocolVersion, uint256 protocolVersion); + +// 0xedae13f3 +error ExecutedIsNotConsistentWithVerified(uint256 batchesExecuted, uint256 batchesVerified); + +// 0x712d02d2 +error VerifiedIsNotConsistentWithCommitted(uint256 batchesVerified, uint256 batchesCommitted); + +// 0xfb1a3b59 +error InvalidNumberOfBatchHashes(uint256 batchHashesLength, uint256 expected); + +// 0xa840274f +error PriorityQueueNotReady(); + +// 0x79274f04 +error UnsupportedProofMetadataVersion(uint256 metadataVersion); + +// 0xa969e486 +error LocalRootIsZero(); + +// 0xbdaf7d42 +error LocalRootMustBeZero(); + +// 0xd0266e26 +error NotSettlementLayer(); + +// 0x32ddf9a2 +error NotHyperchain(); + +// 0x2237c426 +error MismatchL2DAValidator(); + +// 0x2c01a4af +error MismatchNumberOfLayer1Txs(uint256 numberOfLayer1Txs, uint256 expectedLength); + +// 0xfbd630b8 +error InvalidBatchesDataLength(uint256 batchesDataLength, uint256 priorityOpsDataLength); + +// 0x55008233 +error PriorityOpsDataLeftPathLengthIsNotZero(); + +// 0x8be936a9 +error PriorityOpsDataRightPathLengthIsNotZero(); + +// 0x99d44739 +error PriorityOpsDataItemHashesLengthIsNotZero(); + +// 0x885ae069 +error OperatorDAInputTooSmall(uint256 operatorDAInputLength, uint256 minAllowedLength); + +// 0xbeb96791 +error InvalidNumberOfBlobs(uint256 blobsProvided, uint256 maxBlobsSupported); + +// 0xd2531c15 +error InvalidL2DAOutputHash(bytes32 l2DAValidatorOutputHash); + +// 0x04e05fd1 +error OnlyOneBlobWithCalldataAllowed(); + +// 0x2dc9747d +error PubdataInputTooSmall(uint256 pubdataInputLength, uint256 totalBlobsCommitmentSize); + +// 0x9044dff9 +error PubdataLengthTooBig(uint256 pubdataLength, uint256 totalBlobSizeBytes); + +// 0x5513177c +error InvalidPubdataHash(bytes32 fullPubdataHash, bytes32 providedPubdataHash); + +// 0x5717f940 +error InvalidPubdataSource(uint8 pubdataSource); + +// 0x125d99b0 +error BlobHashBlobCommitmentMismatchValue(); + +// 0x7fbff2dd +error L1DAValidatorInvalidSender(address msgSender); + +// 0xc06789fa +error InvalidCommitment(); + +// 0xc866ff2c +error InitialForceDeploymentMismatch(bytes32 forceDeploymentHash, bytes32 initialForceDeploymentHash); + +// 0xb325f767 +error AdminZero(); + +// 0x681150be +error OutdatedProtocolVersion(uint256 protocolVersion, uint256 currentProtocolVersion); + +// 0x87470e36 +error NotL1(uint256 blockChainId); + +// 0x90f67ecf +error InvalidStartIndex(uint256 treeStartIndex, uint256 commitmentStartIndex); + +// 0x0f67bc0a +error InvalidUnprocessedIndex(uint256 treeUnprocessedIndex, uint256 commitmentUnprocessedIndex); + +// 0x30043900 +error InvalidNextLeafIndex(uint256 treeNextLeafIndex, uint256 commitmentNextLeafIndex); + +// 0xf9ba09d6 +error NotAllBatchesExecuted(); + +// 0x9b53b101 +error NotHistoricalRoot(); + +// 0xc02d3ee3 +error ContractNotDeployed(); + +// 0xd7b2559b +error NotMigrated(); + +// 0x52595598 +error ValL1DAWrongInputLength(uint256 inputLength, uint256 expectedLength); diff --git a/l1-contracts/contracts/state-transition/StateTransitionManager.sol b/l1-contracts/contracts/state-transition/StateTransitionManager.sol deleted file mode 100644 index 0dbdd2ad1..000000000 --- a/l1-contracts/contracts/state-transition/StateTransitionManager.sol +++ /dev/null @@ -1,460 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -import {EnumerableMap} from "@openzeppelin/contracts-v4/utils/structs/EnumerableMap.sol"; -import {SafeCast} from "@openzeppelin/contracts-v4/utils/math/SafeCast.sol"; - -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, ChainCreationParams} from "./IStateTransitionManager.sol"; -import {ISystemContext} from "./l2-deps/ISystemContext.sol"; -import {IZkSyncHyperchain} from "./chain-interfaces/IZkSyncHyperchain.sol"; -import {FeeParams} from "./chain-deps/ZkSyncHyperchainStorage.sol"; -import {L2_SYSTEM_CONTEXT_SYSTEM_CONTRACT_ADDR, L2_FORCE_DEPLOYER_ADDR} from "../common/L2ContractAddresses.sol"; -import {L2CanonicalTransaction} from "../common/Messaging.sol"; -import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.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, PRIORITY_TX_MAX_GAS_LIMIT} from "../common/Config.sol"; -import {VerifierParams} from "./chain-interfaces/IVerifier.sol"; -import {Unauthorized, ZeroAddress, HashMismatch, HyperchainLimitReached, GenesisUpgradeZero, GenesisBatchHashZero, GenesisIndexStorageZero, GenesisBatchCommitmentZero} from "../common/L1ContractErrors.sol"; -import {SemVer} from "../common/libraries/SemVer.sol"; - -/// @title State Transition Manager contract -/// @author Matter Labs -/// @custom:security-contact security@matterlabs.dev -contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Ownable2StepUpgradeable { - using EnumerableMap for EnumerableMap.UintToAddressMap; - - /// @notice Address of the bridgehub - address public immutable BRIDGE_HUB; - - /// @notice The total number of hyperchains can be created/connected to this STM. - /// This is the temporary security measure. - uint256 public immutable MAX_NUMBER_OF_HYPERCHAINS; - - /// @notice The map from chainId => hyperchain contract - EnumerableMap.UintToAddressMap internal hyperchainMap; - - /// @dev The batch zero hash, calculated at initialization - bytes32 public storedBatchZero; - - /// @dev The stored cutData for diamond cut - bytes32 public initialCutHash; - - /// @dev The genesisUpgrade contract address, used to setChainId - address public genesisUpgrade; - - /// @dev The current packed protocolVersion. To access human-readable version, use `getSemverProtocolVersion` function. - uint256 public protocolVersion; - - /// @dev The timestamp when protocolVersion can be last used - mapping(uint256 _protocolVersion => uint256) public protocolVersionDeadline; - - /// @dev The validatorTimelock contract address, used to setChainId - address public validatorTimelock; - - /// @dev The stored cutData for upgrade diamond cut. protocolVersion => cutHash - mapping(uint256 protocolVersion => bytes32 cutHash) public upgradeCutHash; - - /// @dev The address used to manage non critical updates - address public admin; - - /// @dev The address 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, uint256 _maxNumberOfHyperchains) reentrancyGuardInitializer { - BRIDGE_HUB = _bridgehub; - MAX_NUMBER_OF_HYPERCHAINS = _maxNumberOfHyperchains; - - // 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); - } - - /// @notice only the bridgehub can call - modifier onlyBridgehub() { - if (msg.sender != BRIDGE_HUB) { - revert Unauthorized(msg.sender); - } - _; - } - - /// @notice the admin can call, for non-critical updates - modifier onlyOwnerOrAdmin() { - if (msg.sender != admin && msg.sender != owner()) { - revert Unauthorized(msg.sender); - } - _; - } - - /// @return The tuple of (major, minor, patch) protocol version. - function getSemverProtocolVersion() external view returns (uint32, uint32, uint32) { - // slither-disable-next-line unused-return - return SemVer.unpackSemVer(SafeCast.toUint96(protocolVersion)); - } - - /// @notice Returns all the registered hyperchain addresses - function getAllHyperchains() public view override returns (address[] memory chainAddresses) { - uint256[] memory keys = hyperchainMap.keys(); - chainAddresses = new address[](keys.length); - uint256 keysLength = keys.length; - for (uint256 i = 0; i < keysLength; ++i) { - chainAddresses[i] = hyperchainMap.get(keys[i]); - } - } - - /// @notice Returns all the registered hyperchain chainIDs - function getAllHyperchainChainIDs() public view override returns (uint256[] memory) { - return hyperchainMap.keys(); - } - - /// @notice Returns the address of the hyperchain with the corresponding chainID - function getHyperchain(uint256 _chainId) public view override returns (address chainAddress) { - // slither-disable-next-line unused-return - (, chainAddress) = hyperchainMap.tryGet(_chainId); - } - - /// @notice Returns the address of the hyperchain admin with the corresponding chainID - function getChainAdmin(uint256 _chainId) external view override returns (address) { - return IZkSyncHyperchain(hyperchainMap.get(_chainId)).getAdmin(); - } - - /// @dev initialize - function initialize( - StateTransitionManagerInitializeData calldata _initializeData - ) external reentrancyGuardInitializer { - if (_initializeData.owner == address(0)) { - revert ZeroAddress(); - } - _transferOwnership(_initializeData.owner); - - protocolVersion = _initializeData.protocolVersion; - protocolVersionDeadline[_initializeData.protocolVersion] = type(uint256).max; - validatorTimelock = _initializeData.validatorTimelock; - - _setChainCreationParams(_initializeData.chainCreationParams); - } - - /// @notice Updates the parameters with which a new chain is created - /// @param _chainCreationParams The new chain creation parameters - function _setChainCreationParams(ChainCreationParams calldata _chainCreationParams) internal { - if (_chainCreationParams.genesisUpgrade == address(0)) { - revert GenesisUpgradeZero(); - } - if (_chainCreationParams.genesisBatchHash == bytes32(0)) { - revert GenesisBatchHashZero(); - } - if (_chainCreationParams.genesisIndexRepeatedStorageChanges == uint64(0)) { - revert GenesisIndexStorageZero(); - } - if (_chainCreationParams.genesisBatchCommitment == bytes32(0)) { - revert GenesisBatchCommitmentZero(); - } - - genesisUpgrade = _chainCreationParams.genesisUpgrade; - - // We need to initialize the state hash because it is used in the commitment of the next batch - IExecutor.StoredBatchInfo memory batchZero = IExecutor.StoredBatchInfo({ - batchNumber: 0, - batchHash: _chainCreationParams.genesisBatchHash, - indexRepeatedStorageChanges: _chainCreationParams.genesisIndexRepeatedStorageChanges, - numberOfLayer1Txs: 0, - priorityOperationsHash: EMPTY_STRING_KECCAK, - l2LogsTreeRoot: DEFAULT_L2_LOGS_TREE_ROOT_HASH, - timestamp: 0, - commitment: _chainCreationParams.genesisBatchCommitment - }); - storedBatchZero = keccak256(abi.encode(batchZero)); - bytes32 newInitialCutHash = keccak256(abi.encode(_chainCreationParams.diamondCut)); - initialCutHash = newInitialCutHash; - - emit NewChainCreationParams({ - genesisUpgrade: _chainCreationParams.genesisUpgrade, - genesisBatchHash: _chainCreationParams.genesisBatchHash, - genesisIndexRepeatedStorageChanges: _chainCreationParams.genesisIndexRepeatedStorageChanges, - genesisBatchCommitment: _chainCreationParams.genesisBatchCommitment, - newInitialCutHash: newInitialCutHash - }); - } - - /// @notice Updates the parameters with which a new chain is created - /// @param _chainCreationParams The new chain creation parameters - function setChainCreationParams(ChainCreationParams calldata _chainCreationParams) external onlyOwner { - _setChainCreationParams(_chainCreationParams); - } - - /// @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 - /// @dev Please note, if the owner wants to enforce the admin change it must execute both `setPendingAdmin` and - /// `acceptAdmin` atomically. Otherwise `admin` can set different pending admin and so fail to accept the admin rights. - 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); - } - - /// @notice Accepts transfer of admin rights. Only pending admin can accept the role. - function acceptAdmin() external { - address currentPendingAdmin = pendingAdmin; - // Only proposed by current admin address can claim the admin rights - if (msg.sender != currentPendingAdmin) { - revert Unauthorized(msg.sender); - } - - address previousAdmin = admin; - admin = currentPendingAdmin; - delete pendingAdmin; - - emit NewPendingAdmin(currentPendingAdmin, address(0)); - emit NewAdmin(previousAdmin, currentPendingAdmin); - } - - /// @dev set validatorTimelock. Cannot do it during initialization, as validatorTimelock is deployed after STM - function setValidatorTimelock(address _validatorTimelock) external onlyOwnerOrAdmin { - address oldValidatorTimelock = validatorTimelock; - validatorTimelock = _validatorTimelock; - emit NewValidatorTimelock(oldValidatorTimelock, _validatorTimelock); - } - - /// @dev set New Version with upgrade from old version - function setNewVersionUpgrade( - Diamond.DiamondCutData calldata _cutData, - uint256 _oldProtocolVersion, - uint256 _oldProtocolVersionDeadline, - uint256 _newProtocolVersion - ) external onlyOwner { - bytes32 newCutHash = keccak256(abi.encode(_cutData)); - uint256 previousProtocolVersion = protocolVersion; - upgradeCutHash[_oldProtocolVersion] = newCutHash; - protocolVersionDeadline[_oldProtocolVersion] = _oldProtocolVersionDeadline; - protocolVersionDeadline[_newProtocolVersion] = type(uint256).max; - protocolVersion = _newProtocolVersion; - emit NewProtocolVersion(previousProtocolVersion, _newProtocolVersion); - emit NewUpgradeCutHash(_oldProtocolVersion, newCutHash); - emit NewUpgradeCutData(_newProtocolVersion, _cutData); - } - - /// @dev check that the protocolVersion is active - function protocolVersionIsActive(uint256 _protocolVersion) external view override returns (bool) { - return block.timestamp <= protocolVersionDeadline[_protocolVersion]; - } - - /// @dev set the protocol version timestamp - function setProtocolVersionDeadline(uint256 _protocolVersion, uint256 _timestamp) external onlyOwner { - protocolVersionDeadline[_protocolVersion] = _timestamp; - } - - /// @dev set upgrade for some protocolVersion - function setUpgradeDiamondCut( - Diamond.DiamondCutData calldata _cutData, - uint256 _oldProtocolVersion - ) external onlyOwner { - bytes32 newCutHash = keccak256(abi.encode(_cutData)); - upgradeCutHash[_oldProtocolVersion] = newCutHash; - emit NewUpgradeCutHash(_oldProtocolVersion, newCutHash); - } - - /// @dev freezes the specified chain - function freezeChain(uint256 _chainId) external onlyOwner { - IZkSyncHyperchain(hyperchainMap.get(_chainId)).freezeDiamond(); - } - - /// @dev freezes the specified chain - function unfreezeChain(uint256 _chainId) external onlyOwner { - IZkSyncHyperchain(hyperchainMap.get(_chainId)).unfreezeDiamond(); - } - - /// @dev reverts batches on the specified chain - function revertBatches(uint256 _chainId, uint256 _newLastBatch) external onlyOwnerOrAdmin { - IZkSyncHyperchain(hyperchainMap.get(_chainId)).revertBatches(_newLastBatch); - } - - /// @dev execute predefined upgrade - function upgradeChainFromVersion( - uint256 _chainId, - uint256 _oldProtocolVersion, - Diamond.DiamondCutData calldata _diamondCut - ) external onlyOwner { - IZkSyncHyperchain(hyperchainMap.get(_chainId)).upgradeChainFromVersion(_oldProtocolVersion, _diamondCut); - } - - /// @dev executes upgrade on chain - function executeUpgrade(uint256 _chainId, Diamond.DiamondCutData calldata _diamondCut) external onlyOwner { - IZkSyncHyperchain(hyperchainMap.get(_chainId)).executeUpgrade(_diamondCut); - } - - /// @dev setPriorityTxMaxGasLimit for the specified chain - function setPriorityTxMaxGasLimit(uint256 _chainId, uint256 _maxGasLimit) external onlyOwner { - IZkSyncHyperchain(hyperchainMap.get(_chainId)).setPriorityTxMaxGasLimit(_maxGasLimit); - } - - /// @dev setTokenMultiplier for the specified chain - function setTokenMultiplier(uint256 _chainId, uint128 _nominator, uint128 _denominator) external onlyOwner { - IZkSyncHyperchain(hyperchainMap.get(_chainId)).setTokenMultiplier(_nominator, _denominator); - } - - /// @dev changeFeeParams for the specified chain - function changeFeeParams(uint256 _chainId, FeeParams calldata _newFeeParams) external onlyOwner { - IZkSyncHyperchain(hyperchainMap.get(_chainId)).changeFeeParams(_newFeeParams); - } - - /// @dev setValidator for the specified chain - function setValidator(uint256 _chainId, address _validator, bool _active) external onlyOwnerOrAdmin { - IZkSyncHyperchain(hyperchainMap.get(_chainId)).setValidator(_validator, _active); - } - - /// @dev setPorterAvailability for the specified chain - function setPorterAvailability(uint256 _chainId, bool _zkPorterIsAvailable) external onlyOwner { - IZkSyncHyperchain(hyperchainMap.get(_chainId)).setPorterAvailability(_zkPorterIsAvailable); - } - - /// 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; - - uint256 cachedProtocolVersion = protocolVersion; - // slither-disable-next-line unused-return - (, uint32 minorVersion, ) = SemVer.unpackSemVer(SafeCast.toUint96(cachedProtocolVersion)); - - 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 `minor` of the protocol version is used as "nonce" for system upgrade transactions - nonce: uint256(minorVersion), - 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: cachedProtocolVersion - }); - - 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, cachedProtocolVersion); - } - - /// @dev used to register already deployed hyperchain contracts - /// @param _chainId the chain's id - /// @param _hyperchain the chain's contract address - function registerAlreadyDeployedHyperchain(uint256 _chainId, address _hyperchain) external onlyOwner { - if (_hyperchain == address(0)) { - revert ZeroAddress(); - } - - _registerNewHyperchain(_chainId, _hyperchain); - } - - /// @notice called by Bridgehub when a chain registers - /// @param _chainId the chain's id - /// @param _baseToken the base token address used to pay for gas fees - /// @param _sharedBridge the shared bridge address, used as base token bridge - /// @param _admin the chain's admin address - /// @param _diamondCut the diamond cut data that initializes the chains Diamond Proxy - function createNewChain( - uint256 _chainId, - address _baseToken, - address _sharedBridge, - address _admin, - bytes calldata _diamondCut - ) external onlyBridgehub { - if (getHyperchain(_chainId) != address(0)) { - // Hyperchain already registered - return; - } - - // check not registered - Diamond.DiamondCutData memory diamondCut = abi.decode(_diamondCut, (Diamond.DiamondCutData)); - - // check input - bytes32 cutHashInput = keccak256(_diamondCut); - if (cutHashInput != initialCutHash) { - revert HashMismatch(initialCutHash, cutHashInput); - } - - // construct init data - bytes memory initData; - /// all together 4+9*32=292 bytes - // solhint-disable-next-line func-named-parameters - initData = bytes.concat( - IDiamondInit.initialize.selector, - bytes32(_chainId), - bytes32(uint256(uint160(BRIDGE_HUB))), - bytes32(uint256(uint160(address(this)))), - bytes32(protocolVersion), - bytes32(uint256(uint160(_admin))), - bytes32(uint256(uint160(validatorTimelock))), - bytes32(uint256(uint160(_baseToken))), - bytes32(uint256(uint160(_sharedBridge))), - storedBatchZero, - diamondCut.initCalldata - ); - - diamondCut.initCalldata = initData; - // deploy hyperchainContract - // slither-disable-next-line reentrancy-no-eth - DiamondProxy hyperchainContract = new DiamondProxy{salt: bytes32(0)}(block.chainid, diamondCut); - // save data - address hyperchainAddress = address(hyperchainContract); - - _registerNewHyperchain(_chainId, hyperchainAddress); - - // set chainId in VM - _setChainIdUpgrade(_chainId, hyperchainAddress); - } - - /// @dev This internal function is used to register a new hyperchain in the system. - function _registerNewHyperchain(uint256 _chainId, address _hyperchain) internal { - // slither-disable-next-line unused-return - hyperchainMap.set(_chainId, _hyperchain); - if (hyperchainMap.length() > MAX_NUMBER_OF_HYPERCHAINS) { - revert HyperchainLimitReached(); - } - emit NewHyperchain(_chainId, _hyperchain); - } -} diff --git a/l1-contracts/contracts/state-transition/TestnetVerifier.sol b/l1-contracts/contracts/state-transition/TestnetVerifier.sol index 6e97fed05..a347c3537 100644 --- a/l1-contracts/contracts/state-transition/TestnetVerifier.sol +++ b/l1-contracts/contracts/state-transition/TestnetVerifier.sol @@ -18,17 +18,13 @@ contract TestnetVerifier is Verifier { /// @dev Verifies a zk-SNARK proof, skipping the verification if the proof is empty. /// @inheritdoc IVerifier - function verify( - uint256[] calldata _publicInputs, - uint256[] calldata _proof, - uint256[] calldata _recursiveAggregationInput - ) public view override returns (bool) { + function verify(uint256[] calldata _publicInputs, uint256[] calldata _proof) public view override returns (bool) { // We allow skipping the zkp verification for the test(net) environment // If the proof is not empty, verify it, otherwise, skip the verification if (_proof.length == 0) { return true; } - return super.verify(_publicInputs, _proof, _recursiveAggregationInput); + return super.verify(_publicInputs, _proof); } } diff --git a/l1-contracts/contracts/state-transition/ValidatorTimelock.sol b/l1-contracts/contracts/state-transition/ValidatorTimelock.sol index 61576f74c..550d39e2c 100644 --- a/l1-contracts/contracts/state-transition/ValidatorTimelock.sol +++ b/l1-contracts/contracts/state-transition/ValidatorTimelock.sol @@ -5,14 +5,14 @@ pragma solidity 0.8.24; import {Ownable2Step} from "@openzeppelin/contracts-v4/access/Ownable2Step.sol"; import {LibMap} from "./libraries/LibMap.sol"; import {IExecutor} from "./chain-interfaces/IExecutor.sol"; -import {IStateTransitionManager} from "./IStateTransitionManager.sol"; +import {IChainTypeManager} from "./IChainTypeManager.sol"; import {Unauthorized, TimeNotReached, ZeroAddress} from "../common/L1ContractErrors.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. +/// @notice Intermediate smart contract between the validator EOA account and the ZK chains 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 +/// modifying the main zkChain 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. @@ -40,8 +40,8 @@ contract ValidatorTimelock is IExecutor, Ownable2Step { /// @notice Error for when an address is not a validator. error ValidatorDoesNotExist(uint256 _chainId); - /// @dev The stateTransitionManager smart contract. - IStateTransitionManager public stateTransitionManager; + /// @dev The chainTypeManager smart contract. + IChainTypeManager public chainTypeManager; /// @dev The mapping of L2 chainId => batch number => timestamp when it was committed. mapping(uint256 chainId => LibMap.Uint32Map batchNumberToTimestampMapping) internal committedBatchTimestamp; @@ -52,18 +52,14 @@ contract ValidatorTimelock is IExecutor, Ownable2Step { /// @dev The delay between committing and executing batches. uint32 public executionDelay; - /// @dev Era's chainID - uint256 internal immutable ERA_CHAIN_ID; - - constructor(address _initialOwner, uint32 _executionDelay, uint256 _eraChainId) { + constructor(address _initialOwner, uint32 _executionDelay) { _transferOwnership(_initialOwner); executionDelay = _executionDelay; - ERA_CHAIN_ID = _eraChainId; } /// @notice Checks if the caller is the admin of the chain. modifier onlyChainAdmin(uint256 _chainId) { - if (msg.sender != stateTransitionManager.getChainAdmin(_chainId)) { + if (msg.sender != chainTypeManager.getChainAdmin(_chainId)) { revert Unauthorized(msg.sender); } _; @@ -78,11 +74,11 @@ contract ValidatorTimelock is IExecutor, Ownable2Step { } /// @dev Sets a new state transition manager. - function setStateTransitionManager(IStateTransitionManager _stateTransitionManager) external onlyOwner { - if (address(_stateTransitionManager) == address(0)) { + function setChainTypeManager(IChainTypeManager _chainTypeManager) external onlyOwner { + if (address(_chainTypeManager) == address(0)) { revert ZeroAddress(); } - stateTransitionManager = _stateTransitionManager; + chainTypeManager = _chainTypeManager; } /// @dev Sets an address as a validator. @@ -115,98 +111,57 @@ contract ValidatorTimelock is IExecutor, Ownable2Step { } /// @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) { - _commitBatchesInner(ERA_CHAIN_ID, _newBatchesData); - } - - /// @dev Records the timestamp for all provided committed batches and make - /// a call to the hyperchain diamond contract with the same calldata. + /// a call to the zkChain diamond contract with the same calldata. function commitBatchesSharedBridge( uint256 _chainId, - StoredBatchInfo calldata, - CommitBatchInfo[] calldata _newBatchesData + uint256 _processBatchFrom, + uint256 _processBatchTo, + bytes calldata ) external onlyValidator(_chainId) { - _commitBatchesInner(_chainId, _newBatchesData); - } - - function _commitBatchesInner(uint256 _chainId, CommitBatchInfo[] calldata _newBatchesData) internal { 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); // We disable this check because calldata array length is cheap. - // solhint-disable-next-line gas-length-in-loops - for (uint256 i = 0; i < _newBatchesData.length; ++i) { - committedBatchTimestamp[_chainId].set(_newBatchesData[i].batchNumber, timestamp); + for (uint256 i = _processBatchFrom; i <= _processBatchTo; ++i) { + committedBatchTimestamp[_chainId].set(i, timestamp); } } - - _propagateToZkSyncHyperchain(_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) { - _propagateToZkSyncHyperchain(ERA_CHAIN_ID); + _propagateToZKChain(_chainId); } - /// @dev Make a call to the hyperchain diamond contract with the same calldata. + /// @dev Make a call to the zkChain 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) { - _propagateToZkSyncHyperchain(_chainId); + _propagateToZKChain(_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) { - _propagateToZkSyncHyperchain(ERA_CHAIN_ID); - } - - /// @dev Make a call to the hyperchain diamond contract with the same calldata. + /// @dev Make a call to the zkChain 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 + uint256, // _processBatchFrom + uint256, // _processBatchTo + bytes calldata ) external onlyValidator(_chainId) { - _propagateToZkSyncHyperchain(_chainId); + _propagateToZKChain(_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) { - _executeBatchesInner(ERA_CHAIN_ID, _newBatchesData); - } - - /// @dev Check that batches were committed at least X time ago and - /// make a call to the hyperchain diamond contract with the same calldata. + /// make a call to the zkChain diamond contract with the same calldata. function executeBatchesSharedBridge( uint256 _chainId, - StoredBatchInfo[] calldata _newBatchesData + uint256 _processBatchFrom, + uint256 _processBatchTo, + bytes calldata ) external onlyValidator(_chainId) { - _executeBatchesInner(_chainId, _newBatchesData); - } - - function _executeBatchesInner(uint256 _chainId, StoredBatchInfo[] calldata _newBatchesData) internal { uint256 delay = executionDelay; // uint32 unchecked { // We disable this check because calldata array length is cheap. - // solhint-disable-next-line gas-length-in-loops - for (uint256 i = 0; i < _newBatchesData.length; ++i) { - uint256 commitBatchTimestamp = committedBatchTimestamp[_chainId].get(_newBatchesData[i].batchNumber); + for (uint256 i = _processBatchFrom; i <= _processBatchTo; ++i) { + uint256 commitBatchTimestamp = committedBatchTimestamp[_chainId].get(i); // Note: if the `commitBatchTimestamp` is zero, that means either: // * The batch was committed, but not through this contract. @@ -218,17 +173,23 @@ contract ValidatorTimelock is IExecutor, Ownable2Step { } } } - _propagateToZkSyncHyperchain(_chainId); + _propagateToZKChain(_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 _propagateToZkSyncHyperchain(uint256 _chainId) internal { - address contractAddress = stateTransitionManager.getHyperchain(_chainId); + /// @dev Call the zkChain diamond contract with the same calldata as this contract was called. + /// Note: it is called the zkChain diamond contract, not delegatecalled! + function _propagateToZKChain(uint256 _chainId) internal { + // Note, that it is important to use chain type manager and + // the legacy method here for obtaining the chain id in order for + // this contract to before the CTM upgrade is finalized. + address contractAddress = chainTypeManager.getHyperchain(_chainId); + if (contractAddress == address(0)) { + revert ZeroAddress(); + } 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 + // Call method of the ZK chain 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() diff --git a/l1-contracts/contracts/state-transition/Verifier.sol b/l1-contracts/contracts/state-transition/Verifier.sol index a74ecb12c..3072c2c5a 100644 --- a/l1-contracts/contracts/state-transition/Verifier.sol +++ b/l1-contracts/contracts/state-transition/Verifier.sol @@ -8,7 +8,7 @@ import {IVerifier} from "./chain-interfaces/IVerifier.sol"; /// @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 hyperchain circuits. +/// Modifications have been made to optimize the proof system for ZK chain circuits. /// @dev Contract was generated from a verification key with a hash of 0x14f97b81e54b35fe673d8708cc1a19e1ea5b5e348e12d31e39824ed4f42bbca2 /// @dev It uses a custom memory layout inside the inline assembly block. Each reserved memory cell is declared in the /// constants below. @@ -343,8 +343,7 @@ contract Verifier is IVerifier { /// @inheritdoc IVerifier function verify( uint256[] calldata, // _publicInputs - uint256[] calldata, // _proof - uint256[] calldata // _recursiveAggregationInput + uint256[] calldata // _proof ) public view virtual returns (bool) { // No memory was accessed yet, so keys can be loaded into the right place and not corrupt any other memory. _loadVerificationKey(); @@ -523,7 +522,17 @@ contract Verifier is IVerifier { // 2. Load the proof (except for the recursive part) offset := calldataload(0x24) let proofLengthInWords := calldataload(add(offset, 0x04)) - isValid := and(eq(proofLengthInWords, 44), isValid) + + // Check the proof length depending on whether the recursive part is present + let expectedProofLength + switch mload(VK_RECURSIVE_FLAG_SLOT) + case 0 { + expectedProofLength := 44 + } + default { + expectedProofLength := 48 + } + isValid := and(eq(proofLengthInWords, expectedProofLength), isValid) // PROOF_STATE_POLYS_0 { @@ -670,21 +679,13 @@ contract Verifier is IVerifier { } // 3. Load the recursive part of the proof - offset := calldataload(0x44) - let recursiveProofLengthInWords := calldataload(add(offset, 0x04)) - - switch mload(VK_RECURSIVE_FLAG_SLOT) - case 0 { - // recursive part should be empty - isValid := and(iszero(recursiveProofLengthInWords), isValid) - } - default { + if mload(VK_RECURSIVE_FLAG_SLOT) { // recursive part should be consist of 2 points - isValid := and(eq(recursiveProofLengthInWords, 4), isValid) + // PROOF_RECURSIVE_PART_P1 { - let x := mod(calldataload(add(offset, 0x024)), Q_MOD) - let y := mod(calldataload(add(offset, 0x044)), Q_MOD) + let x := mod(calldataload(add(offset, 0x5a4)), Q_MOD) + let y := mod(calldataload(add(offset, 0x5c4)), Q_MOD) let xx := mulmod(x, x, Q_MOD) isValid := and(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD)), isValid) mstore(PROOF_RECURSIVE_PART_P1_X_SLOT, x) @@ -692,8 +693,8 @@ contract Verifier is IVerifier { } // PROOF_RECURSIVE_PART_P2 { - let x := mod(calldataload(add(offset, 0x064)), Q_MOD) - let y := mod(calldataload(add(offset, 0x084)), Q_MOD) + let x := mod(calldataload(add(offset, 0x5e4)), Q_MOD) + let y := mod(calldataload(add(offset, 0x604)), Q_MOD) let xx := mulmod(x, x, Q_MOD) isValid := and(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD)), isValid) mstore(PROOF_RECURSIVE_PART_P2_X_SLOT, x) diff --git a/l1-contracts/contracts/state-transition/chain-deps/DiamondInit.sol b/l1-contracts/contracts/state-transition/chain-deps/DiamondInit.sol index 663cf260a..65637aadc 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/DiamondInit.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/DiamondInit.sol @@ -3,19 +3,24 @@ pragma solidity 0.8.24; import {Diamond} from "../libraries/Diamond.sol"; -import {ZkSyncHyperchainBase} from "./facets/ZkSyncHyperchainBase.sol"; +import {ZKChainBase} from "./facets/ZKChainBase.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 {ZeroAddress, TooMuchGas} from "../../common/L1ContractErrors.sol"; +import {PriorityQueue} from "../libraries/PriorityQueue.sol"; +import {PriorityTree} from "../libraries/PriorityTree.sol"; +import {ZeroAddress, EmptyAssetId, TooMuchGas} from "../../common/L1ContractErrors.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 ZkSyncHyperchainBase, IDiamondInit { +contract DiamondInit is ZKChainBase, IDiamondInit { + using PriorityQueue for PriorityQueue.Queue; + using PriorityTree for PriorityTree.Tree; + /// @dev Initialize the implementation to prevent any possibility of a Parity hack. constructor() reentrancyGuardInitializer {} - /// @notice hyperchain diamond contract initialization + /// @notice ZK chain 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) { @@ -34,14 +39,11 @@ contract DiamondInit is ZkSyncHyperchainBase, IDiamondInit { if (_initializeData.bridgehub == address(0)) { revert ZeroAddress(); } - if (_initializeData.stateTransitionManager == address(0)) { - revert ZeroAddress(); - } - if (_initializeData.baseToken == address(0)) { + if (_initializeData.chainTypeManager == address(0)) { revert ZeroAddress(); } - if (_initializeData.baseTokenBridge == address(0)) { - revert ZeroAddress(); + if (_initializeData.baseTokenAssetId == bytes32(0)) { + revert EmptyAssetId(); } if (_initializeData.blobVersionedHashRetriever == address(0)) { revert ZeroAddress(); @@ -49,9 +51,8 @@ contract DiamondInit is ZkSyncHyperchainBase, IDiamondInit { s.chainId = _initializeData.chainId; s.bridgehub = _initializeData.bridgehub; - s.stateTransitionManager = _initializeData.stateTransitionManager; - s.baseToken = _initializeData.baseToken; - s.baseTokenBridge = _initializeData.baseTokenBridge; + s.chainTypeManager = _initializeData.chainTypeManager; + s.baseTokenAssetId = _initializeData.baseTokenAssetId; s.protocolVersion = _initializeData.protocolVersion; s.verifier = _initializeData.verifier; @@ -65,6 +66,7 @@ contract DiamondInit is ZkSyncHyperchainBase, IDiamondInit { s.priorityTxMaxGasLimit = _initializeData.priorityTxMaxGasLimit; s.feeParams = _initializeData.feeParams; s.blobVersionedHashRetriever = _initializeData.blobVersionedHashRetriever; + s.priorityTree.setup(s.priorityQueue.getTotalPriorityTxs()); // 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 diff --git a/l1-contracts/contracts/state-transition/chain-deps/DiamondProxy.sol b/l1-contracts/contracts/state-transition/chain-deps/DiamondProxy.sol index db29da126..1e297d53a 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/DiamondProxy.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/DiamondProxy.sol @@ -2,6 +2,9 @@ pragma solidity 0.8.24; +// Note, that while we do try to make use of custom errors whenever possible, +// we do not change it for `DiamondProxy`, since it is a contract that can not be +// upgraded or changed, so we keep its code always consistent with the production version. // solhint-disable gas-custom-errors import {Diamond} from "../libraries/Diamond.sol"; diff --git a/l1-contracts/contracts/state-transition/chain-deps/GatewayCTMDeployer.sol b/l1-contracts/contracts/state-transition/chain-deps/GatewayCTMDeployer.sol new file mode 100644 index 000000000..dbfdf4ba8 --- /dev/null +++ b/l1-contracts/contracts/state-transition/chain-deps/GatewayCTMDeployer.sol @@ -0,0 +1,374 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {MailboxFacet} from "./facets/Mailbox.sol"; +import {ExecutorFacet} from "./facets/Executor.sol"; +import {GettersFacet} from "./facets/Getters.sol"; +import {AdminFacet} from "./facets/Admin.sol"; +import {Multicall3} from "../../dev-contracts/Multicall3.sol"; + +import {RollupDAManager} from "../data-availability/RollupDAManager.sol"; +import {RelayedSLDAValidator} from "../data-availability/RelayedSLDAValidator.sol"; +import {ValidiumL1DAValidator} from "../data-availability/ValidiumL1DAValidator.sol"; + +import {Verifier} from "../Verifier.sol"; +import {VerifierParams, IVerifier} from "../chain-interfaces/IVerifier.sol"; +import {TestnetVerifier} from "../TestnetVerifier.sol"; +import {ValidatorTimelock} from "../ValidatorTimelock.sol"; +import {FeeParams} from "../chain-deps/ZKChainStorage.sol"; + +import {DiamondInit} from "./DiamondInit.sol"; +import {L1GenesisUpgrade} from "../../upgrades/L1GenesisUpgrade.sol"; +import {Diamond} from "../libraries/Diamond.sol"; + +import {ChainTypeManager} from "../ChainTypeManager.sol"; + +import {L2_BRIDGEHUB_ADDR} from "../../common/L2ContractAddresses.sol"; + +import {ProxyAdmin} from "@openzeppelin/contracts-v4/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {InitializeDataNewChain as DiamondInitializeDataNewChain} from "../chain-interfaces/IDiamondInit.sol"; +import {ChainTypeManagerInitializeData, ChainCreationParams, IChainTypeManager} from "../IChainTypeManager.sol"; + +/// @notice Configuration parameters for deploying the GatewayCTMDeployer contract. +struct GatewayCTMDeployerConfig { + /// @notice Address of the aliased governance contract. + address aliasedGovernanceAddress; + /// @notice Salt used for deterministic deployments via CREATE2. + bytes32 salt; + /// @notice Chain ID of the Era chain. + uint256 eraChainId; + /// @notice Chain ID of the L1 chain. + uint256 l1ChainId; + /// @notice Address of the Rollup L2 Data Availability Validator. + address rollupL2DAValidatorAddress; + /// @notice Flag indicating whether to use the testnet verifier. + bool testnetVerifier; + /// @notice Array of function selectors for the Admin facet. + bytes4[] adminSelectors; + /// @notice Array of function selectors for the Executor facet. + bytes4[] executorSelectors; + /// @notice Array of function selectors for the Mailbox facet. + bytes4[] mailboxSelectors; + /// @notice Array of function selectors for the Getters facet. + bytes4[] gettersSelectors; + /// @notice Parameters for the verifier contract. + VerifierParams verifierParams; + /// @notice Parameters related to fees. + /// @dev They are mainly related to the L1->L2 transactions, fees for + /// which are not processed on Gateway. However, we still need these + /// values to deploy new chain's instances on Gateway. + FeeParams feeParams; + /// @notice Hash of the bootloader bytecode. + bytes32 bootloaderHash; + /// @notice Hash of the default account bytecode. + bytes32 defaultAccountHash; + /// @notice Maximum gas limit for priority transactions. + uint256 priorityTxMaxGasLimit; + /// @notice Root hash of the genesis state. + bytes32 genesisRoot; + /// @notice Leaf index in the genesis rollup. + uint256 genesisRollupLeafIndex; + /// @notice Commitment of the genesis batch. + bytes32 genesisBatchCommitment; + /// @notice Data for force deployments. + bytes forceDeploymentsData; + /// @notice The latest protocol version. + uint256 protocolVersion; +} + +/// @notice Addresses of state transition related contracts. +// solhint-disable-next-line gas-struct-packing +struct StateTransitionContracts { + /// @notice Address of the ChainTypeManager proxy contract. + address chainTypeManagerProxy; + /// @notice Address of the ChainTypeManager implementation contract. + address chainTypeManagerImplementation; + /// @notice Address of the Verifier contract. + address verifier; + /// @notice Address of the Admin facet contract. + address adminFacet; + /// @notice Address of the Mailbox facet contract. + address mailboxFacet; + /// @notice Address of the Executor facet contract. + address executorFacet; + /// @notice Address of the Getters facet contract. + address gettersFacet; + /// @notice Address of the DiamondInit contract. + address diamondInit; + /// @notice Address of the GenesisUpgrade contract. + address genesisUpgrade; + /// @notice Address of the ValidatorTimelock contract. + address validatorTimelock; + /// @notice Address of the ProxyAdmin for ChainTypeManager. + address chainTypeManagerProxyAdmin; +} + +/// @notice Addresses of Data Availability (DA) related contracts. +// solhint-disable-next-line gas-struct-packing +struct DAContracts { + /// @notice Address of the RollupDAManager contract. + address rollupDAManager; + /// @notice Address of the RelayedSLDAValidator contract. + address relayedSLDAValidator; + /// @notice Address of the ValidiumL1DAValidator contract. + address validiumDAValidator; +} + +/// @notice Collection of all deployed contracts by the GatewayCTMDeployer. +struct DeployedContracts { + /// @notice Address of the Multicall3 contract. + address multicall3; + /// @notice Struct containing state transition related contracts. + StateTransitionContracts stateTransition; + /// @notice Struct containing Data Availability related contracts. + DAContracts daContracts; + /// @notice Encoded data for the diamond cut operation. + bytes diamondCutData; +} + +/// @dev The constant address to be used for the blobHashRetriever inside the contracts. +/// At the time of this writing the blob hash retriever is not used at all, but the zero-address +/// check is still yet present, so we use address one as the substitution. +address constant BLOB_HASH_RETRIEVER_ADDR = address(uint160(1)); + +/// @title GatewayCTMDeployer +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice Contract responsible for deploying all the CTM-related contracts on top +/// of the Gateway contract. +/// @dev The expectation is that this contract will be deployed via the built-in L2 `Create2Factory`. +/// This will achieve the fact that the address of this contract (and thus, the addresses of the +/// contract it deploys are deterministic). An important role that this contract plays is in +/// being the first owner of some of the contracts (e.g. ValidatorTimelock), which helps it to initialize it properly +/// and transfer the ownership to the correct governance. +/// @dev Note, that it is expected to be used in zkEVM environment only. Since all of the deployments +/// are done by hash in zkEVM, this contract is actually quite small and cheap to execute in zkEVM environment. +contract GatewayCTMDeployer { + DeployedContracts internal deployedContracts; + + /// @notice Returns deployed contracts. + /// @dev Just using `public` mode for the `deployedContracts` field did not work + /// due to internal issues during testing. + /// @return contracts The struct with information about the deployed contracts. + function getDeployedContracts() external view returns (DeployedContracts memory contracts) { + contracts = deployedContracts; + } + + constructor(GatewayCTMDeployerConfig memory _config) { + // Caching some values + bytes32 salt = _config.salt; + uint256 eraChainId = _config.eraChainId; + uint256 l1ChainId = _config.l1ChainId; + + DeployedContracts memory contracts; + + contracts.multicall3 = address(new Multicall3{salt: salt}()); + + _deployFacetsAndUpgrades({ + _salt: salt, + _eraChainId: eraChainId, + _l1ChainId: l1ChainId, + _rollupL2DAValidatorAddress: _config.rollupL2DAValidatorAddress, + _aliasedGovernanceAddress: _config.aliasedGovernanceAddress, + _deployedContracts: contracts + }); + _deployVerifier(salt, _config.testnetVerifier, contracts); + + ValidatorTimelock timelock = new ValidatorTimelock{salt: salt}(address(this), 0); + contracts.stateTransition.validatorTimelock = address(timelock); + + _deployCTM(salt, _config, contracts); + _setChainTypeManagerInValidatorTimelock(_config.aliasedGovernanceAddress, timelock, contracts); + + deployedContracts = contracts; + } + + /// @notice Deploys facets and upgrade contracts. + /// @param _salt Salt used for CREATE2 deployments. + /// @param _eraChainId Era Chain ID. + /// @param _l1ChainId L1 Chain ID. + /// @param _rollupL2DAValidatorAddress The expected L2 DA Validator to be + /// used by permanent rollups. + /// @param _aliasedGovernanceAddress The aliased address of the governnace. + /// @param _deployedContracts The struct with deployed contracts, that will be mofiied + /// in the process of the execution of this function. + function _deployFacetsAndUpgrades( + bytes32 _salt, + uint256 _eraChainId, + uint256 _l1ChainId, + address _rollupL2DAValidatorAddress, + address _aliasedGovernanceAddress, + DeployedContracts memory _deployedContracts + ) internal { + _deployedContracts.stateTransition.mailboxFacet = address( + new MailboxFacet{salt: _salt}(_eraChainId, _l1ChainId) + ); + _deployedContracts.stateTransition.executorFacet = address(new ExecutorFacet{salt: _salt}(_l1ChainId)); + _deployedContracts.stateTransition.gettersFacet = address(new GettersFacet{salt: _salt}()); + + RollupDAManager rollupDAManager = _deployRollupDAContracts( + _salt, + _rollupL2DAValidatorAddress, + _aliasedGovernanceAddress, + _deployedContracts + ); + _deployedContracts.stateTransition.adminFacet = address( + new AdminFacet{salt: _salt}(_l1ChainId, rollupDAManager) + ); + + _deployedContracts.stateTransition.diamondInit = address(new DiamondInit{salt: _salt}()); + _deployedContracts.stateTransition.genesisUpgrade = address(new L1GenesisUpgrade{salt: _salt}()); + } + + /// @notice Deploys verifier. + /// @param _salt Salt used for CREATE2 deployments. + /// @param _testnetVerifier Whether testnet verifier should be used. + /// @param _deployedContracts The struct with deployed contracts, that will be mofiied + /// in the process of the execution of this function. + function _deployVerifier( + bytes32 _salt, + bool _testnetVerifier, + DeployedContracts memory _deployedContracts + ) internal { + if (_testnetVerifier) { + _deployedContracts.stateTransition.verifier = address(new TestnetVerifier{salt: _salt}()); + } else { + _deployedContracts.stateTransition.verifier = address(new Verifier{salt: _salt}()); + } + } + + /// @notice Deploys DA-related contracts. + /// @param _salt Salt used for CREATE2 deployments. + /// @param _rollupL2DAValidatorAddress The expected L2 DA Validator to be + /// used by permanent rollups. + /// @param _aliasedGovernanceAddress The aliased address of the governnace. + /// @param _deployedContracts The struct with deployed contracts, that will be mofiied + /// in the process of the execution of this function. + function _deployRollupDAContracts( + bytes32 _salt, + address _rollupL2DAValidatorAddress, + address _aliasedGovernanceAddress, + DeployedContracts memory _deployedContracts + ) internal returns (RollupDAManager rollupDAManager) { + rollupDAManager = new RollupDAManager{salt: _salt}(); + + ValidiumL1DAValidator validiumDAValidator = new ValidiumL1DAValidator{salt: _salt}(); + + RelayedSLDAValidator relayedSLDAValidator = new RelayedSLDAValidator{salt: _salt}(); + rollupDAManager.updateDAPair(address(relayedSLDAValidator), _rollupL2DAValidatorAddress, true); + + // Note, that the governance still has to accept it. + // It will happen in a separate voting after the deployment is done. + rollupDAManager.transferOwnership(_aliasedGovernanceAddress); + + _deployedContracts.daContracts.rollupDAManager = address(rollupDAManager); + _deployedContracts.daContracts.relayedSLDAValidator = address(relayedSLDAValidator); + _deployedContracts.daContracts.validiumDAValidator = address(validiumDAValidator); + } + + /// @notice Deploys DA-related contracts. + /// @param _salt Salt used for CREATE2 deployments. + /// @param _config The deployment config. + /// @param _deployedContracts The struct with deployed contracts, that will be mofiied + /// in the process of the execution of this function. + function _deployCTM( + bytes32 _salt, + GatewayCTMDeployerConfig memory _config, + DeployedContracts memory _deployedContracts + ) internal { + _deployedContracts.stateTransition.chainTypeManagerImplementation = address( + new ChainTypeManager{salt: _salt}(L2_BRIDGEHUB_ADDR) + ); + ProxyAdmin proxyAdmin = new ProxyAdmin{salt: _salt}(); + proxyAdmin.transferOwnership(_config.aliasedGovernanceAddress); + _deployedContracts.stateTransition.chainTypeManagerProxyAdmin = address(proxyAdmin); + + Diamond.FacetCut[] memory facetCuts = new Diamond.FacetCut[](4); + facetCuts[0] = Diamond.FacetCut({ + facet: _deployedContracts.stateTransition.adminFacet, + action: Diamond.Action.Add, + isFreezable: false, + selectors: _config.adminSelectors + }); + facetCuts[1] = Diamond.FacetCut({ + facet: _deployedContracts.stateTransition.gettersFacet, + action: Diamond.Action.Add, + isFreezable: false, + selectors: _config.gettersSelectors + }); + facetCuts[2] = Diamond.FacetCut({ + facet: _deployedContracts.stateTransition.mailboxFacet, + action: Diamond.Action.Add, + isFreezable: true, + selectors: _config.mailboxSelectors + }); + facetCuts[3] = Diamond.FacetCut({ + facet: _deployedContracts.stateTransition.executorFacet, + action: Diamond.Action.Add, + isFreezable: true, + selectors: _config.executorSelectors + }); + + DiamondInitializeDataNewChain memory initializeData = DiamondInitializeDataNewChain({ + verifier: IVerifier(_deployedContracts.stateTransition.verifier), + verifierParams: _config.verifierParams, + l2BootloaderBytecodeHash: _config.bootloaderHash, + l2DefaultAccountBytecodeHash: _config.defaultAccountHash, + priorityTxMaxGasLimit: _config.priorityTxMaxGasLimit, + feeParams: _config.feeParams, + blobVersionedHashRetriever: BLOB_HASH_RETRIEVER_ADDR + }); + + Diamond.DiamondCutData memory diamondCut = Diamond.DiamondCutData({ + facetCuts: facetCuts, + initAddress: _deployedContracts.stateTransition.diamondInit, + initCalldata: abi.encode(initializeData) + }); + + _deployedContracts.diamondCutData = abi.encode(diamondCut); + + ChainCreationParams memory chainCreationParams = ChainCreationParams({ + genesisUpgrade: _deployedContracts.stateTransition.genesisUpgrade, + genesisBatchHash: _config.genesisRoot, + genesisIndexRepeatedStorageChanges: uint64(_config.genesisRollupLeafIndex), + genesisBatchCommitment: _config.genesisBatchCommitment, + diamondCut: diamondCut, + // Note, it is the same as for contracts that are based on L2 + forceDeploymentsData: _config.forceDeploymentsData + }); + + ChainTypeManagerInitializeData memory diamondInitData = ChainTypeManagerInitializeData({ + owner: _config.aliasedGovernanceAddress, + validatorTimelock: _deployedContracts.stateTransition.validatorTimelock, + chainCreationParams: chainCreationParams, + protocolVersion: _config.protocolVersion + }); + + _deployedContracts.stateTransition.chainTypeManagerProxy = address( + new TransparentUpgradeableProxy{salt: _salt}( + _deployedContracts.stateTransition.chainTypeManagerImplementation, + address(proxyAdmin), + abi.encodeCall(ChainTypeManager.initialize, (diamondInitData)) + ) + ); + } + + /// @notice Sets the previously deployed CTM inside the ValidatorTimelock + /// @param _aliasedGovernanceAddress The aliased address of the governnace. + /// @param _timelock The address of the validator timelock + /// @param _deployedContracts The struct with deployed contracts, that will be mofiied + /// in the process of the execution of this function. + function _setChainTypeManagerInValidatorTimelock( + address _aliasedGovernanceAddress, + ValidatorTimelock _timelock, + DeployedContracts memory _deployedContracts + ) internal { + _timelock.setChainTypeManager(IChainTypeManager(_deployedContracts.stateTransition.chainTypeManagerProxy)); + + // Note, that the governance still has to accept it. + // It will happen in a separate voting after the deployment is done. + _timelock.transferOwnership(_aliasedGovernanceAddress); + } +} diff --git a/l1-contracts/contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol b/l1-contracts/contracts/state-transition/chain-deps/ZKChainStorage.sol similarity index 83% rename from l1-contracts/contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol rename to l1-contracts/contracts/state-transition/chain-deps/ZKChainStorage.sol index a06921fdb..b390cf752 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/ZKChainStorage.sol @@ -3,7 +3,9 @@ pragma solidity 0.8.24; import {IVerifier, VerifierParams} from "../chain-interfaces/IVerifier.sol"; +// import {IChainTypeManager} from "../IChainTypeManager.sol"; import {PriorityQueue} from "../../state-transition/libraries/PriorityQueue.sol"; +import {PriorityTree} from "../../state-transition/libraries/PriorityTree.sol"; /// @notice Indicates whether an upgrade is initiated and if yes what type /// @param None Upgrade is NOT initiated @@ -58,16 +60,16 @@ struct FeeParams { uint64 minimalL2GasPrice; } -/// @dev storing all storage variables for hyperchain diamond facets +/// @dev storing all storage variables for ZK chain 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. // solhint-disable-next-line gas-struct-packing -struct ZkSyncHyperchainStorage { +struct ZKChainStorage { /// @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). Replaced by STM + /// @notice Address which will exercise critical changes to the Diamond Proxy (upgrades, freezing & unfreezing). Replaced by CTM address __DEPRECATED_governor; /// @notice Address that the governor proposed as one that will replace it address __DEPRECATED_pendingGovernor; @@ -132,23 +134,42 @@ struct ZkSyncHyperchainStorage { 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. + /// @dev The value is only for the L1 deployment of the ZK Chain, since payment for all the priority transactions is + /// charged at that level. FeeParams feeParams; /// @dev Address of the blob versioned hash getter smart contract used for EIP-4844 versioned hashes. + /// @dev Used only for testing. address blobVersionedHashRetriever; /// @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 ChainTypeManager + address chainTypeManager; /// @dev The address of the baseToken contract. Eth is address(1) - address baseToken; + address __DEPRECATED_baseToken; /// @dev The address of the baseTokenbridge. Eth also uses the shared bridge - address baseTokenBridge; + address __DEPRECATED_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; /// @dev The optional address of the contract that has to be used for transaction filtering/whitelisting address transactionFilterer; + /// @dev The address of the l1DAValidator contract. + /// This contract is responsible for the verification of the correctness of the DA on L1. + address l1DAValidator; + /// @dev The address of the contract on L2 that is responsible for the data availability verification. + /// This contract sends `l2DAValidatorOutputHash` to L1 via L2->L1 system log and it will routed to the `l1DAValidator` contract. + address l2DAValidator; + /// @dev the Asset Id of the baseToken + bytes32 baseTokenAssetId; + /// @dev If this ZKchain settles on this chain, then this is zero. Otherwise it is the address of the ZKchain that is a + /// settlement layer for this ZKchain. (think about it as a 'forwarding' address for the chain that migrated away). + address settlementLayer; + /// @dev Priority tree, the new data structure for priority queue + PriorityTree.Tree priorityTree; + /// @dev Whether the chain is a permanent rollup. Note, that it only enforces the DA validator pair, but + /// it does not enforce any other parameters, e.g. `pubdataPricingMode` + bool isPermanentRollup; } diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol index 9f98c00ec..fc546c86b 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol @@ -4,22 +4,49 @@ pragma solidity 0.8.24; 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 "../ZkSyncHyperchainStorage.sol"; -import {ZkSyncHyperchainBase} from "./ZkSyncHyperchainBase.sol"; -import {IStateTransitionManager} from "../../IStateTransitionManager.sol"; -import {Unauthorized, TooMuchGas, PriorityTxPubdataExceedsMaxPubDataPerBatch, InvalidPubdataPricingMode, ProtocolIdMismatch, ChainAlreadyLive, HashMismatch, ProtocolIdNotGreater, DenominatorIsZero, DiamondAlreadyFrozen, DiamondNotFrozen} from "../../../common/L1ContractErrors.sol"; +import {MAX_GAS_PER_TRANSACTION, ZKChainCommitment} from "../../../common/Config.sol"; +import {FeeParams, PubdataPricingMode} from "../ZKChainStorage.sol"; +import {PriorityTree} from "../../../state-transition/libraries/PriorityTree.sol"; +import {PriorityQueue} from "../../../state-transition/libraries/PriorityQueue.sol"; +import {ZKChainBase} from "./ZKChainBase.sol"; +import {IChainTypeManager} from "../../IChainTypeManager.sol"; +import {IL1GenesisUpgrade} from "../../../upgrades/IL1GenesisUpgrade.sol"; +import {Unauthorized, TooMuchGas, PriorityTxPubdataExceedsMaxPubDataPerBatch, InvalidPubdataPricingMode, ProtocolIdMismatch, HashMismatch, ProtocolIdNotGreater, DenominatorIsZero, DiamondAlreadyFrozen, DiamondNotFrozen, InvalidDAForPermanentRollup, AlreadyPermanentRollup} from "../../../common/L1ContractErrors.sol"; +import {NotL1, L1DAValidatorAddressIsZero, L2DAValidatorAddressIsZero, AlreadyMigrated, NotChainAdmin, ProtocolVersionNotUpToDate, ExecutedIsNotConsistentWithVerified, VerifiedIsNotConsistentWithCommitted, InvalidNumberOfBatchHashes, PriorityQueueNotReady, VerifiedIsNotConsistentWithCommitted, NotAllBatchesExecuted, OutdatedProtocolVersion, NotHistoricalRoot, ContractNotDeployed, NotMigrated} from "../../L1StateTransitionErrors.sol"; +import {RollupDAManager} from "../../data-availability/RollupDAManager.sol"; // While formally the following import is not used, it is needed to inherit documentation from it -import {IZkSyncHyperchainBase} from "../../chain-interfaces/IZkSyncHyperchainBase.sol"; +import {IZKChainBase} from "../../chain-interfaces/IZKChainBase.sol"; /// @title Admin Contract controls access rights for contract management. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -contract AdminFacet is ZkSyncHyperchainBase, IAdmin { - /// @inheritdoc IZkSyncHyperchainBase +contract AdminFacet is ZKChainBase, IAdmin { + using PriorityTree for PriorityTree.Tree; + using PriorityQueue for PriorityQueue.Queue; + + /// @inheritdoc IZKChainBase string public constant override getName = "AdminFacet"; + /// @notice The chain id of L1. This contract can be deployed on multiple layers, but this value is still equal to the + /// L1 that is at the most base layer. + uint256 internal immutable L1_CHAIN_ID; + + /// @notice The address that is responsible for determining whether a certain DA pair is allowed for rollups. + RollupDAManager internal immutable ROLLUP_DA_MANAGER; + + constructor(uint256 _l1ChainId, RollupDAManager _rollupDAManager) { + L1_CHAIN_ID = _l1ChainId; + ROLLUP_DA_MANAGER = _rollupDAManager; + } + + modifier onlyL1() { + if (block.chainid != L1_CHAIN_ID) { + revert NotL1(block.chainid); + } + _; + } + /// @inheritdoc IAdmin function setPendingAdmin(address _newPendingAdmin) external onlyAdmin { // Save previous value into the stack to put it into the event later @@ -46,20 +73,20 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { } /// @inheritdoc IAdmin - function setValidator(address _validator, bool _active) external onlyStateTransitionManager { + function setValidator(address _validator, bool _active) external onlyChainTypeManager { s.validators[_validator] = _active; emit ValidatorStatusUpdate(_validator, _active); } /// @inheritdoc IAdmin - function setPorterAvailability(bool _zkPorterIsAvailable) external onlyStateTransitionManager { + function setPorterAvailability(bool _zkPorterIsAvailable) external onlyChainTypeManager { // Change the porter availability s.zkPorterIsAvailable = _zkPorterIsAvailable; emit IsPorterAvailableStatusUpdate(_zkPorterIsAvailable); } /// @inheritdoc IAdmin - function setPriorityTxMaxGasLimit(uint256 _newPriorityTxMaxGasLimit) external onlyStateTransitionManager { + function setPriorityTxMaxGasLimit(uint256 _newPriorityTxMaxGasLimit) external onlyChainTypeManager onlyL1 { if (_newPriorityTxMaxGasLimit > MAX_GAS_PER_TRANSACTION) { revert TooMuchGas(); } @@ -70,7 +97,7 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { } /// @inheritdoc IAdmin - function changeFeeParams(FeeParams calldata _newFeeParams) external onlyAdminOrStateTransitionManager { + function changeFeeParams(FeeParams calldata _newFeeParams) external onlyAdminOrChainTypeManager onlyL1 { // 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. if (_newFeeParams.maxPubdataPerBatch < _newFeeParams.priorityTxMaxPubdata) { @@ -90,7 +117,7 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { } /// @inheritdoc IAdmin - function setTokenMultiplier(uint128 _nominator, uint128 _denominator) external onlyAdminOrStateTransitionManager { + function setTokenMultiplier(uint128 _nominator, uint128 _denominator) external onlyAdminOrChainTypeManager onlyL1 { if (_denominator == 0) { revert DenominatorIsZero(); } @@ -104,21 +131,59 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { } /// @inheritdoc IAdmin - function setPubdataPricingMode(PubdataPricingMode _pricingMode) external onlyAdmin { - // Validium mode can be set only before the first batch is processed - if (s.totalBatchesCommitted != 0) { - revert ChainAlreadyLive(); - } + function setPubdataPricingMode(PubdataPricingMode _pricingMode) external onlyAdmin onlyL1 { s.feeParams.pubdataPricingMode = _pricingMode; - emit ValidiumModeStatusUpdate(_pricingMode); + emit PubdataPricingModeUpdate(_pricingMode); } - function setTransactionFilterer(address _transactionFilterer) external onlyAdmin { + /// @inheritdoc IAdmin + function setTransactionFilterer(address _transactionFilterer) external onlyAdmin onlyL1 { address oldTransactionFilterer = s.transactionFilterer; s.transactionFilterer = _transactionFilterer; emit NewTransactionFilterer(oldTransactionFilterer, _transactionFilterer); } + /// @notice Sets the DA validator pair with the given addresses. + /// @dev It does not check for these addresses to be non-zero, since when migrating to a new settlement + /// layer, we set them to zero. + function _setDAValidatorPair(address _l1DAValidator, address _l2DAValidator) internal { + emit NewL1DAValidator(s.l1DAValidator, _l1DAValidator); + emit NewL2DAValidator(s.l2DAValidator, _l2DAValidator); + + s.l1DAValidator = _l1DAValidator; + s.l2DAValidator = _l2DAValidator; + } + + /// @inheritdoc IAdmin + function setDAValidatorPair(address _l1DAValidator, address _l2DAValidator) external onlyAdmin { + if (_l1DAValidator == address(0)) { + revert L1DAValidatorAddressIsZero(); + } + if (_l2DAValidator == address(0)) { + revert L2DAValidatorAddressIsZero(); + } + + if (s.isPermanentRollup && !ROLLUP_DA_MANAGER.isPairAllowed(_l1DAValidator, _l2DAValidator)) { + revert InvalidDAForPermanentRollup(); + } + + _setDAValidatorPair(_l1DAValidator, _l2DAValidator); + } + + /// @inheritdoc IAdmin + function makePermanentRollup() external onlyAdmin onlySettlementLayer { + if (s.isPermanentRollup) { + revert AlreadyPermanentRollup(); + } + + if (!ROLLUP_DA_MANAGER.isPairAllowed(s.l1DAValidator, s.l2DAValidator)) { + // The correct data availability pair should be set beforehand. + revert InvalidDAForPermanentRollup(); + } + + s.isPermanentRollup = true; + } + /*////////////////////////////////////////////////////////////// UPGRADE EXECUTION //////////////////////////////////////////////////////////////*/ @@ -127,9 +192,9 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { function upgradeChainFromVersion( uint256 _oldProtocolVersion, Diamond.DiamondCutData calldata _diamondCut - ) external onlyAdminOrStateTransitionManager { + ) external onlyAdminOrChainTypeManager { bytes32 cutHashInput = keccak256(abi.encode(_diamondCut)); - bytes32 upgradeCutHash = IStateTransitionManager(s.stateTransitionManager).upgradeCutHash(_oldProtocolVersion); + bytes32 upgradeCutHash = IChainTypeManager(s.chainTypeManager).upgradeCutHash(_oldProtocolVersion); if (cutHashInput != upgradeCutHash) { revert HashMismatch(upgradeCutHash, cutHashInput); } @@ -145,17 +210,38 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { } /// @inheritdoc IAdmin - function executeUpgrade(Diamond.DiamondCutData calldata _diamondCut) external onlyStateTransitionManager { + function executeUpgrade(Diamond.DiamondCutData calldata _diamondCut) external onlyChainTypeManager { Diamond.diamondCut(_diamondCut); emit ExecuteUpgrade(_diamondCut); } + /// @dev we have to set the chainId at genesis, as blockhashzero is the same for all chains with the same chainId + function genesisUpgrade( + address _l1GenesisUpgrade, + address _ctmDeployer, + bytes calldata _forceDeploymentData, + bytes[] calldata _factoryDeps + ) external onlyChainTypeManager { + Diamond.FacetCut[] memory emptyArray; + Diamond.DiamondCutData memory cutData = Diamond.DiamondCutData({ + facetCuts: emptyArray, + initAddress: _l1GenesisUpgrade, + initCalldata: abi.encodeCall( + IL1GenesisUpgrade.genesisUpgrade, + (_l1GenesisUpgrade, s.chainId, s.protocolVersion, _ctmDeployer, _forceDeploymentData, _factoryDeps) + ) + }); + + Diamond.diamondCut(cutData); + emit ExecuteUpgrade(cutData); + } + /*////////////////////////////////////////////////////////////// CONTRACT FREEZING //////////////////////////////////////////////////////////////*/ /// @inheritdoc IAdmin - function freezeDiamond() external onlyStateTransitionManager { + function freezeDiamond() external onlyChainTypeManager { Diamond.DiamondStorage storage diamondStorage = Diamond.getDiamondStorage(); // diamond proxy is frozen already @@ -168,7 +254,7 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { } /// @inheritdoc IAdmin - function unfreezeDiamond() external onlyStateTransitionManager { + function unfreezeDiamond() external onlyChainTypeManager { Diamond.DiamondStorage storage diamondStorage = Diamond.getDiamondStorage(); // diamond proxy is not frozen @@ -179,4 +265,188 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { emit Unfreeze(); } + + /*////////////////////////////////////////////////////////////// + CHAIN MIGRATION + //////////////////////////////////////////////////////////////*/ + + /// @inheritdoc IAdmin + function forwardedBridgeBurn( + address _settlementLayer, + address _originalCaller, + bytes calldata _data + ) external payable override onlyBridgehub returns (bytes memory chainBridgeMintData) { + if (s.settlementLayer != address(0)) { + revert AlreadyMigrated(); + } + if (_originalCaller != s.admin) { + revert NotChainAdmin(_originalCaller, s.admin); + } + // As of now all we need in this function is the chainId so we encode it and pass it down in the _chainData field + uint256 protocolVersion = abi.decode(_data, (uint256)); + + uint256 currentProtocolVersion = s.protocolVersion; + + if (currentProtocolVersion != protocolVersion) { + revert ProtocolVersionNotUpToDate(currentProtocolVersion, protocolVersion); + } + + if (block.chainid != L1_CHAIN_ID) { + // We assume that GW -> L1 transactions can never fail and provide no recovery mechanism from it. + // That's why we need to bound the gas that can be consumed during such a migration. + if (s.totalBatchesCommitted != s.totalBatchesExecuted) { + revert NotAllBatchesExecuted(); + } + } + + s.settlementLayer = _settlementLayer; + chainBridgeMintData = abi.encode(prepareChainCommitment()); + } + + /// @inheritdoc IAdmin + function forwardedBridgeMint( + bytes calldata _data, + bool _contractAlreadyDeployed + ) external payable override onlyBridgehub { + ZKChainCommitment memory _commitment = abi.decode(_data, (ZKChainCommitment)); + + IChainTypeManager ctm = IChainTypeManager(s.chainTypeManager); + + uint256 currentProtocolVersion = s.protocolVersion; + uint256 protocolVersion = ctm.protocolVersion(); + if (currentProtocolVersion != protocolVersion) { + revert OutdatedProtocolVersion(protocolVersion, currentProtocolVersion); + } + uint256 batchesExecuted = _commitment.totalBatchesExecuted; + uint256 batchesVerified = _commitment.totalBatchesVerified; + uint256 batchesCommitted = _commitment.totalBatchesCommitted; + + s.totalBatchesCommitted = batchesCommitted; + s.totalBatchesVerified = batchesVerified; + s.totalBatchesExecuted = batchesExecuted; + s.isPermanentRollup = _commitment.isPermanentRollup; + + // Some consistency checks just in case. + if (batchesExecuted > batchesVerified) { + revert ExecutedIsNotConsistentWithVerified(batchesExecuted, batchesVerified); + } + if (batchesVerified > batchesCommitted) { + revert VerifiedIsNotConsistentWithCommitted(batchesVerified, batchesCommitted); + } + + // In the worst case, we may need to revert all the committed batches that were not executed. + // This means that the stored batch hashes should be stored for [batchesExecuted; batchesCommitted] batches, i.e. + // there should be batchesCommitted - batchesExecuted + 1 hashes. + if (_commitment.batchHashes.length != batchesCommitted - batchesExecuted + 1) { + revert InvalidNumberOfBatchHashes(_commitment.batchHashes.length, batchesCommitted - batchesExecuted + 1); + } + + // Note that this part is done in O(N), i.e. it is the responsibility of the admin of the chain to ensure that the total number of + // outstanding committed batches is not too long. + uint256 length = _commitment.batchHashes.length; + for (uint256 i = 0; i < length; ++i) { + s.storedBatchHashes[batchesExecuted + i] = _commitment.batchHashes[i]; + } + + if (block.chainid == L1_CHAIN_ID) { + // L1 PTree contains all L1->L2 transactions. + if ( + !s.priorityTree.isHistoricalRoot( + _commitment.priorityTree.sides[_commitment.priorityTree.sides.length - 1] + ) + ) { + revert NotHistoricalRoot(); + } + if (!_contractAlreadyDeployed) { + revert ContractNotDeployed(); + } + if (s.settlementLayer == address(0)) { + revert NotMigrated(); + } + s.priorityTree.l1Reinit(_commitment.priorityTree); + } else if (_contractAlreadyDeployed) { + if (s.settlementLayer == address(0)) { + revert NotMigrated(); + } + s.priorityTree.checkGWReinit(_commitment.priorityTree); + s.priorityTree.initFromCommitment(_commitment.priorityTree); + } else { + s.priorityTree.initFromCommitment(_commitment.priorityTree); + } + _forceDeactivateQueue(); + + s.l2SystemContractsUpgradeTxHash = _commitment.l2SystemContractsUpgradeTxHash; + s.l2SystemContractsUpgradeBatchNumber = _commitment.l2SystemContractsUpgradeBatchNumber; + + // Set the settlement to 0 - as this is the current settlement chain. + s.settlementLayer = address(0); + + _setDAValidatorPair(address(0), address(0)); + + emit MigrationComplete(); + } + + /// @inheritdoc IAdmin + function forwardedBridgeRecoverFailedTransfer( + uint256 /* _chainId */, + bytes32 /* _assetInfo */, + address /* _depositSender */, + bytes calldata _chainData + ) external payable override onlyBridgehub { + // As of now all we need in this function is the chainId so we encode it and pass it down in the _chainData field + uint256 protocolVersion = abi.decode(_chainData, (uint256)); + + if (s.settlementLayer == address(0)) { + revert NotMigrated(); + } + uint256 currentProtocolVersion = s.protocolVersion; + if (currentProtocolVersion != protocolVersion) { + revert OutdatedProtocolVersion(protocolVersion, currentProtocolVersion); + } + + s.settlementLayer = address(0); + } + + /// @notice Returns the commitment for a chain. + /// @dev Note, that this is a getter method helpful for debugging and should not be relied upon by clients. + /// @return commitment The commitment for the chain. + function prepareChainCommitment() public view returns (ZKChainCommitment memory commitment) { + if (_isPriorityQueueActive()) { + revert PriorityQueueNotReady(); + } + + commitment.totalBatchesCommitted = s.totalBatchesCommitted; + commitment.totalBatchesVerified = s.totalBatchesVerified; + commitment.totalBatchesExecuted = s.totalBatchesExecuted; + commitment.l2SystemContractsUpgradeBatchNumber = s.l2SystemContractsUpgradeBatchNumber; + commitment.l2SystemContractsUpgradeTxHash = s.l2SystemContractsUpgradeTxHash; + commitment.priorityTree = s.priorityTree.getCommitment(); + commitment.isPermanentRollup = s.isPermanentRollup; + + // just in case + if (commitment.totalBatchesExecuted > commitment.totalBatchesVerified) { + revert ExecutedIsNotConsistentWithVerified( + commitment.totalBatchesExecuted, + commitment.totalBatchesVerified + ); + } + if (commitment.totalBatchesVerified > commitment.totalBatchesCommitted) { + revert VerifiedIsNotConsistentWithCommitted( + commitment.totalBatchesVerified, + commitment.totalBatchesCommitted + ); + } + + uint256 blocksToRemember = commitment.totalBatchesCommitted - commitment.totalBatchesExecuted + 1; + + bytes32[] memory batchHashes = new bytes32[](blocksToRemember); + + for (uint256 i = 0; i < blocksToRemember; ++i) { + unchecked { + batchHashes[i] = s.storedBatchHashes[commitment.totalBatchesExecuted + i]; + } + } + + commitment.batchHashes = batchHashes; + } } diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol index 07995642b..63b083878 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol @@ -2,84 +2,68 @@ pragma solidity 0.8.24; -import {ZkSyncHyperchainBase} from "./ZkSyncHyperchainBase.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, TOTAL_BLOBS_IN_COMMITMENT, BLOB_SIZE_BYTES} from "../../chain-interfaces/IExecutor.sol"; +import {ZKChainBase} from "./ZKChainBase.sol"; +import {IBridgehub} from "../../../bridgehub/IBridgehub.sol"; +import {IMessageRoot} from "../../../bridgehub/IMessageRoot.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} from "../../../common/Config.sol"; +import {IExecutor, L2_LOG_ADDRESS_OFFSET, L2_LOG_KEY_OFFSET, L2_LOG_VALUE_OFFSET, SystemLogKey, LogProcessingOutput, TOTAL_BLOBS_IN_COMMITMENT} from "../../chain-interfaces/IExecutor.sol"; import {PriorityQueue, PriorityOperation} from "../../libraries/PriorityQueue.sol"; +import {BatchDecoder} from "../../libraries/BatchDecoder.sol"; import {UncheckedMath} from "../../../common/libraries/UncheckedMath.sol"; import {UnsafeBytes} from "../../../common/libraries/UnsafeBytes.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 "../ZkSyncHyperchainStorage.sol"; -import {IStateTransitionManager} from "../../IStateTransitionManager.sol"; -import {BatchNumberMismatch, TimeNotReached, TooManyBlobs, ValueMismatch, InvalidPubdataMode, InvalidPubdataLength, HashMismatch, NonIncreasingTimestamp, TimestampError, InvalidLogSender, TxHashMismatch, UnexpectedSystemLog, MissingSystemLogs, LogAlreadyProcessed, InvalidProtocolVersion, CanOnlyProcessOneBatch, BatchHashMismatch, UpgradeBatchNumberIsNotZero, NonSequentialBatch, CantExecuteUnprovenBatches, SystemLogsSizeTooBig, InvalidNumberOfBlobs, VerifiedBatchesExceedsCommittedBatches, InvalidProof, RevertedBatchNotAfterNewLastBatch, CantRevertExecutedBatch, PointEvalFailed, EmptyBlobVersionHash, NonEmptyBlobVersionHash, BlobHashCommitmentError, CalldataLengthTooBig, InvalidPubdataHash, L2TimestampTooBig, PriorityOperationsRollingHashMismatch, PubdataCommitmentsEmpty, PointEvalCallFailed, PubdataCommitmentsTooBig, InvalidPubdataCommitmentsSize} from "../../../common/L1ContractErrors.sol"; +import {L2_BOOTLOADER_ADDRESS, L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, L2_SYSTEM_CONTEXT_SYSTEM_CONTRACT_ADDR} from "../../../common/L2ContractAddresses.sol"; +import {IChainTypeManager} from "../../IChainTypeManager.sol"; +import {PriorityTree, PriorityOpsBatchInfo} from "../../libraries/PriorityTree.sol"; +import {IL1DAValidator, L1DAValidatorOutput} from "../../chain-interfaces/IL1DAValidator.sol"; +import {InvalidSystemLogsLength, MissingSystemLogs, BatchNumberMismatch, TimeNotReached, ValueMismatch, HashMismatch, NonIncreasingTimestamp, TimestampError, InvalidLogSender, TxHashMismatch, UnexpectedSystemLog, LogAlreadyProcessed, InvalidProtocolVersion, CanOnlyProcessOneBatch, BatchHashMismatch, UpgradeBatchNumberIsNotZero, NonSequentialBatch, CantExecuteUnprovenBatches, SystemLogsSizeTooBig, InvalidNumberOfBlobs, VerifiedBatchesExceedsCommittedBatches, InvalidProof, RevertedBatchNotAfterNewLastBatch, CantRevertExecutedBatch, L2TimestampTooBig, PriorityOperationsRollingHashMismatch} from "../../../common/L1ContractErrors.sol"; +import {InvalidBatchesDataLength, MismatchL2DAValidator, MismatchNumberOfLayer1Txs, PriorityOpsDataLeftPathLengthIsNotZero, PriorityOpsDataRightPathLengthIsNotZero, PriorityOpsDataItemHashesLengthIsNotZero} from "../../L1StateTransitionErrors.sol"; // While formally the following import is not used, it is needed to inherit documentation from it -import {IZkSyncHyperchainBase} from "../../chain-interfaces/IZkSyncHyperchainBase.sol"; +import {IZKChainBase} from "../../chain-interfaces/IZKChainBase.sol"; -/// @title ZKsync hyperchain Executor contract capable of processing events emitted in the ZKsync hyperchain protocol. +/// @title ZK chain Executor contract capable of processing events emitted in the ZK chain protocol. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { +contract ExecutorFacet is ZKChainBase, IExecutor { using UncheckedMath for uint256; using PriorityQueue for PriorityQueue.Queue; + using PriorityTree for PriorityTree.Tree; - /// @inheritdoc IZkSyncHyperchainBase + /// @inheritdoc IZKChainBase string public constant override getName = "ExecutorFacet"; + /// @notice The chain id of L1. This contract can be deployed on multiple layers, but this value is still equal to the + /// L1 that is at the most base layer. + uint256 internal immutable L1_CHAIN_ID; + + constructor(uint256 _l1ChainId) { + L1_CHAIN_ID = _l1ChainId; + } + /// @dev Process one batch commit using the previous batch StoredBatchInfo /// @dev returns new batch StoredBatchInfo /// @notice Does not change storage function _commitOneBatch( StoredBatchInfo memory _previousBatch, - CommitBatchInfo calldata _newBatch, + CommitBatchInfo memory _newBatch, bytes32 _expectedSystemContractUpgradeTxHash - ) internal view returns (StoredBatchInfo memory) { + ) internal returns (StoredBatchInfo memory) { // only commit next batch if (_newBatch.batchNumber != _previousBatch.batchNumber + 1) { revert BatchNumberMismatch(_previousBatch.batchNumber + 1, _newBatch.batchNumber); } - uint8 pubdataSource = uint8(bytes1(_newBatch.pubdataCommitments[0])); - PubdataPricingMode pricingMode = s.feeParams.pubdataPricingMode; - if ( - pricingMode != PubdataPricingMode.Validium && - pubdataSource != uint8(PubdataSource.Calldata) && - pubdataSource != uint8(PubdataSource.Blob) - ) { - revert InvalidPubdataMode(); - } - - // Check that batch contain all meta information for L2 logs. + // Check that batch contains all meta information for L2 logs. // Get the chained hash of priority transaction hashes. LogProcessingOutput memory logOutput = _processL2Logs(_newBatch, _expectedSystemContractUpgradeTxHash); - bytes32[] memory blobCommitments = new bytes32[](MAX_NUMBER_OF_BLOBS); - if (pricingMode == PubdataPricingMode.Validium) { - // skipping data validation for validium, we just check that the data is empty - if (_newBatch.pubdataCommitments.length != 1) { - revert CalldataLengthTooBig(); - } - for (uint8 i = uint8(SystemLogKey.BLOB_ONE_HASH_KEY); i <= uint8(SystemLogKey.BLOB_SIX_HASH_KEY); ++i) { - logOutput.blobHashes[i - uint8(SystemLogKey.BLOB_ONE_HASH_KEY)] = bytes32(0); - } - } else if (pubdataSource == uint8(PubdataSource.Blob)) { - // In this scenario, pubdataCommitments is a list of: opening point (16 bytes) || claimed value (32 bytes) || commitment (48 bytes) || proof (48 bytes)) = 144 bytes - blobCommitments = _verifyBlobInformation(_newBatch.pubdataCommitments[1:], logOutput.blobHashes); - } else if (pubdataSource == uint8(PubdataSource.Calldata)) { - // In this scenario pubdataCommitments is actual pubdata consisting of l2 to l1 logs, l2 to l1 message, compressed smart contract bytecode, and compressed state diffs - if (_newBatch.pubdataCommitments.length > BLOB_SIZE_BYTES) { - revert InvalidPubdataLength(); - } - bytes32 pubdataHash = keccak256(_newBatch.pubdataCommitments[1:_newBatch.pubdataCommitments.length - 32]); - if (logOutput.pubdataHash != pubdataHash) { - revert InvalidPubdataHash(pubdataHash, logOutput.pubdataHash); - } - blobCommitments[0] = bytes32( - _newBatch.pubdataCommitments[_newBatch.pubdataCommitments.length - 32:_newBatch - .pubdataCommitments - .length] - ); - } + L1DAValidatorOutput memory daOutput = IL1DAValidator(s.l1DAValidator).checkDA({ + _chainId: s.chainId, + _batchNumber: uint256(_newBatch.batchNumber), + _l2DAValidatorOutputHash: logOutput.l2DAValidatorOutputHash, + _operatorDAInput: _newBatch.operatorDAInput, + _maxBlobsSupported: TOTAL_BLOBS_IN_COMMITMENT + }); if (_previousBatch.batchHash != logOutput.previousBatchHash) { revert HashMismatch(logOutput.previousBatchHash, _previousBatch.batchHash); @@ -99,9 +83,9 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { // Create batch commitment for the proof verification bytes32 commitment = _createBatchCommitment( _newBatch, - logOutput.stateDiffHash, - blobCommitments, - logOutput.blobHashes + daOutput.stateDiffHash, + daOutput.blobsOpeningCommitments, + daOutput.blobsLinearHashes ); return @@ -159,20 +143,23 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { /// SystemLogKey enum in Constants.sol is processed per new batch. /// @dev Data returned from here will be used to form the batch commitment. function _processL2Logs( - CommitBatchInfo calldata _newBatch, + CommitBatchInfo memory _newBatch, bytes32 _expectedSystemContractUpgradeTxHash - ) internal pure returns (LogProcessingOutput memory logOutput) { + ) internal view returns (LogProcessingOutput memory logOutput) { // Copy L2 to L1 logs into memory. bytes memory emittedL2Logs = _newBatch.systemLogs; - logOutput.blobHashes = new bytes32[](MAX_NUMBER_OF_BLOBS); - // Used as bitmap to set/check log processing happens exactly once. // See SystemLogKey enum in Constants.sol for ordering. uint256 processedLogs = 0; // linear traversal of the logs uint256 logsLength = emittedL2Logs.length; + + if (logsLength % L2_TO_L1_LOG_SERIALIZE_SIZE != 0) { + revert InvalidSystemLogsLength(); + } + for (uint256 i = 0; i < logsLength; i = i.uncheckedAdd(L2_TO_L1_LOG_SERIALIZE_SIZE)) { // Extract the values to be compared to/used such as the log sender, key, and value // slither-disable-next-line unused-return @@ -194,16 +181,6 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { revert InvalidLogSender(logSender, logKey); } logOutput.l2LogsTreeRoot = logValue; - } else if (logKey == uint256(SystemLogKey.TOTAL_L2_TO_L1_PUBDATA_KEY)) { - if (logSender != L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR) { - revert InvalidLogSender(logSender, logKey); - } - logOutput.pubdataHash = logValue; - } else if (logKey == uint256(SystemLogKey.STATE_DIFF_HASH_KEY)) { - if (logSender != L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR) { - revert InvalidLogSender(logSender, logKey); - } - logOutput.stateDiffHash = logValue; } else if (logKey == uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY)) { if (logSender != L2_SYSTEM_CONTEXT_SYSTEM_CONTRACT_ADDR) { revert InvalidLogSender(logSender, logKey); @@ -224,19 +201,18 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { revert InvalidLogSender(logSender, logKey); } logOutput.numberOfLayer1Txs = uint256(logValue); - } else if ( - logKey >= uint256(SystemLogKey.BLOB_ONE_HASH_KEY) && logKey <= uint256(SystemLogKey.BLOB_SIX_HASH_KEY) - ) { - if (logSender != L2_PUBDATA_CHUNK_PUBLISHER_ADDR) { + } else if (logKey == uint256(SystemLogKey.USED_L2_DA_VALIDATOR_ADDRESS_KEY)) { + if (logSender != L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR) { revert InvalidLogSender(logSender, logKey); } - uint8 blobNumber = uint8(logKey) - uint8(SystemLogKey.BLOB_ONE_HASH_KEY); - - if (blobNumber >= MAX_NUMBER_OF_BLOBS) { - revert TooManyBlobs(); + if (s.l2DAValidator != address(uint160(uint256(logValue)))) { + revert MismatchL2DAValidator(); } - - logOutput.blobHashes[blobNumber] = logValue; + } else if (logKey == uint256(SystemLogKey.L2_DA_VALIDATOR_OUTPUT_HASH_KEY)) { + if (logSender != L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR) { + revert InvalidLogSender(logSender, logKey); + } + logOutput.l2DAValidatorOutputHash = logValue; } else if (logKey == uint256(SystemLogKey.EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY)) { if (logSender != L2_BOOTLOADER_ADDRESS) { revert InvalidLogSender(logSender, logKey); @@ -249,41 +225,25 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { } } - // We only require 13 logs to be checked, the 14th is if we are expecting a protocol upgrade - // Without the protocol upgrade we expect 13 logs: 2^13 - 1 = 8191 - // With the protocol upgrade we expect 14 logs: 2^14 - 1 = 16383 + // We only require 7 logs to be checked, the 8th is if we are expecting a protocol upgrade + // Without the protocol upgrade we expect 7 logs: 2^7 - 1 = 127 + // With the protocol upgrade we expect 8 logs: 2^8 - 1 = 255 if (_expectedSystemContractUpgradeTxHash == bytes32(0)) { - if (processedLogs != 8191) { - revert MissingSystemLogs(8191, processedLogs); - } - } else { - if (processedLogs != 16383) { - revert MissingSystemLogs(16383, processedLogs); + if (processedLogs != 127) { + revert MissingSystemLogs(127, processedLogs); } + } else if (processedLogs != 255) { + revert MissingSystemLogs(255, processedLogs); } } - /// @inheritdoc IExecutor - function commitBatches( - StoredBatchInfo calldata _lastCommittedBatchData, - CommitBatchInfo[] calldata _newBatchesData - ) external nonReentrant onlyValidator { - _commitBatches(_lastCommittedBatchData, _newBatchesData); - } - /// @inheritdoc IExecutor function commitBatchesSharedBridge( uint256, // _chainId - StoredBatchInfo calldata _lastCommittedBatchData, - CommitBatchInfo[] calldata _newBatchesData - ) external nonReentrant onlyValidator { - _commitBatches(_lastCommittedBatchData, _newBatchesData); - } - - function _commitBatches( - StoredBatchInfo memory _lastCommittedBatchData, - CommitBatchInfo[] calldata _newBatchesData - ) internal { + uint256 _processFrom, + uint256 _processTo, + bytes calldata _commitData + ) external nonReentrant onlyValidator onlySettlementLayer { // 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 @@ -291,35 +251,39 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { // 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 their 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 - if (!IStateTransitionManager(s.stateTransitionManager).protocolVersionIsActive(s.protocolVersion)) { + if (!IChainTypeManager(s.chainTypeManager).protocolVersionIsActive(s.protocolVersion)) { revert InvalidProtocolVersion(); } + (StoredBatchInfo memory lastCommittedBatchData, CommitBatchInfo[] memory newBatchesData) = BatchDecoder + .decodeAndCheckCommitData(_commitData, _processFrom, _processTo); // 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. - if (_newBatchesData.length != 1) { + // Note: Don't need to check that `_processFrom` == `_processTo` because there is only one batch, + // and so the range checked in the `decodeAndCheckCommitData` is enough. + if (newBatchesData.length != 1) { revert CanOnlyProcessOneBatch(); } // Check that we commit batches after last committed batch - if (s.storedBatchHashes[s.totalBatchesCommitted] != _hashStoredBatchInfo(_lastCommittedBatchData)) { + if (s.storedBatchHashes[s.totalBatchesCommitted] != _hashStoredBatchInfo(lastCommittedBatchData)) { // incorrect previous batch data revert BatchHashMismatch( s.storedBatchHashes[s.totalBatchesCommitted], - _hashStoredBatchInfo(_lastCommittedBatchData) + _hashStoredBatchInfo(lastCommittedBatchData) ); } bytes32 systemContractsUpgradeTxHash = s.l2SystemContractsUpgradeTxHash; // Upgrades are rarely done so we optimize a case with no active system contracts upgrade. if (systemContractsUpgradeTxHash == bytes32(0) || s.l2SystemContractsUpgradeBatchNumber != 0) { - _commitBatchesWithoutSystemContractsUpgrade(_lastCommittedBatchData, _newBatchesData); + _commitBatchesWithoutSystemContractsUpgrade(lastCommittedBatchData, newBatchesData); } else { _commitBatchesWithSystemContractsUpgrade( - _lastCommittedBatchData, - _newBatchesData, + lastCommittedBatchData, + newBatchesData, systemContractsUpgradeTxHash ); } - s.totalBatchesCommitted = s.totalBatchesCommitted + _newBatchesData.length; + s.totalBatchesCommitted = s.totalBatchesCommitted + newBatchesData.length; } /// @dev Commits new batches without any system contracts upgrade. @@ -327,7 +291,7 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { /// @param _newBatchesData An array of batch data that needs to be committed. function _commitBatchesWithoutSystemContractsUpgrade( StoredBatchInfo memory _lastCommittedBatchData, - CommitBatchInfo[] calldata _newBatchesData + CommitBatchInfo[] memory _newBatchesData ) internal { // We disable this check because calldata array length is cheap. // solhint-disable-next-line gas-length-in-loops @@ -349,7 +313,7 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { /// @param _systemContractUpgradeTxHash The transaction hash of the system contract upgrade. function _commitBatchesWithSystemContractsUpgrade( StoredBatchInfo memory _lastCommittedBatchData, - CommitBatchInfo[] calldata _newBatchesData, + CommitBatchInfo[] memory _newBatchesData, bytes32 _systemContractUpgradeTxHash ) internal { // The system contract upgrade is designed to be executed atomically with the new bootloader, a default account, @@ -393,13 +357,26 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { PriorityOperation memory priorityOp = s.priorityQueue.popFront(); concatHash = keccak256(abi.encode(concatHash, priorityOp.canonicalTxHash)); } + + s.priorityTree.skipUntil(s.priorityQueue.getFirstUnprocessedPriorityTx()); } - /// @dev Executes one batch - /// @dev 1. Processes all pending operations (Complete priority requests) - /// @dev 2. Finalizes batch on Ethereum - /// @dev _executedBatchIdx is an index in the array of the batches that we want to execute together - function _executeOneBatch(StoredBatchInfo memory _storedBatch, uint256 _executedBatchIdx) internal { + function _rollingHash(bytes32[] memory _hashes) internal pure returns (bytes32) { + bytes32 hash = EMPTY_STRING_KECCAK; + uint256 nHashes = _hashes.length; + for (uint256 i = 0; i < nHashes; i = i.uncheckedInc()) { + hash = keccak256(abi.encode(hash, _hashes[i])); + } + return hash; + } + + /// @dev Checks that the data of the batch is correct and can be executed + /// @dev Verifies that batch number, batch hash and priority operations hash are correct + function _checkBatchData( + StoredBatchInfo memory _storedBatch, + uint256 _executedBatchIdx, + bytes32 _priorityOperationsHash + ) internal view { uint256 currentBatchNumber = _storedBatch.batchNumber; if (currentBatchNumber != s.totalBatchesExecuted + _executedBatchIdx + 1) { revert NonSequentialBatch(); @@ -407,34 +384,96 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { if (_hashStoredBatchInfo(_storedBatch) != s.storedBatchHashes[currentBatchNumber]) { revert BatchHashMismatch(s.storedBatchHashes[currentBatchNumber], _hashStoredBatchInfo(_storedBatch)); } + if (_priorityOperationsHash != _storedBatch.priorityOperationsHash) { + revert PriorityOperationsRollingHashMismatch(); + } + } + /// @dev Executes one batch + /// @dev 1. Processes all pending operations (Complete priority requests) + /// @dev 2. Finalizes batch on Ethereum + /// @dev _executedBatchIdx is an index in the array of the batches that we want to execute together + function _executeOneBatch(StoredBatchInfo memory _storedBatch, uint256 _executedBatchIdx) internal { bytes32 priorityOperationsHash = _collectOperationsFromPriorityQueue(_storedBatch.numberOfLayer1Txs); - if (priorityOperationsHash != _storedBatch.priorityOperationsHash) { - revert PriorityOperationsRollingHashMismatch(); + _checkBatchData(_storedBatch, _executedBatchIdx, priorityOperationsHash); + + uint256 currentBatchNumber = _storedBatch.batchNumber; + + // Save root hash of L2 -> L1 logs tree + s.l2LogsRootHashes[currentBatchNumber] = _storedBatch.l2LogsTreeRoot; + _appendMessageRoot(currentBatchNumber, _storedBatch.l2LogsTreeRoot); + } + + /// @notice Executes one batch + /// @dev 1. Processes all pending operations (Complete priority requests) + /// @dev 2. Finalizes batch + /// @dev _executedBatchIdx is an index in the array of the batches that we want to execute together + function _executeOneBatch( + StoredBatchInfo memory _storedBatch, + PriorityOpsBatchInfo memory _priorityOpsData, + uint256 _executedBatchIdx + ) internal { + if (_priorityOpsData.itemHashes.length != _storedBatch.numberOfLayer1Txs) { + revert MismatchNumberOfLayer1Txs(_priorityOpsData.itemHashes.length, _storedBatch.numberOfLayer1Txs); } + bytes32 priorityOperationsHash = _rollingHash(_priorityOpsData.itemHashes); + _checkBatchData(_storedBatch, _executedBatchIdx, priorityOperationsHash); + s.priorityTree.processBatch(_priorityOpsData); + + uint256 currentBatchNumber = _storedBatch.batchNumber; // Save root hash of L2 -> L1 logs tree s.l2LogsRootHashes[currentBatchNumber] = _storedBatch.l2LogsTreeRoot; + _appendMessageRoot(currentBatchNumber, _storedBatch.l2LogsTreeRoot); } - /// @inheritdoc IExecutor - function executeBatchesSharedBridge( - uint256, - StoredBatchInfo[] calldata _batchesData - ) external nonReentrant onlyValidator { - _executeBatches(_batchesData); + /// @notice Appends the batch message root to the global message. + /// @param _batchNumber The number of the batch + /// @param _messageRoot The root of the merkle tree of the messages to L1. + /// @dev The logic of this function depends on the settlement layer as we support + /// message root aggregation only on non-L1 settlement layers for ease for migration. + function _appendMessageRoot(uint256 _batchNumber, bytes32 _messageRoot) internal { + // During migration to the new protocol version, there will be a period when + // the bridgehub does not yet provide the `messageRoot` functionality. + // To ease up the migration, we never append messages to message root on L1. + if (block.chainid != L1_CHAIN_ID) { + // Once the batch is executed, we include its message to the message root. + IMessageRoot messageRootContract = IBridgehub(s.bridgehub).messageRoot(); + messageRootContract.addChainBatchRoot(s.chainId, _batchNumber, _messageRoot); + } } /// @inheritdoc IExecutor - function executeBatches(StoredBatchInfo[] calldata _batchesData) external nonReentrant onlyValidator { - _executeBatches(_batchesData); - } + function executeBatchesSharedBridge( + uint256, // _chainId + uint256 _processFrom, + uint256 _processTo, + bytes calldata _executeData + ) external nonReentrant onlyValidator onlySettlementLayer { + (StoredBatchInfo[] memory batchesData, PriorityOpsBatchInfo[] memory priorityOpsData) = BatchDecoder + .decodeAndCheckExecuteData(_executeData, _processFrom, _processTo); + uint256 nBatches = batchesData.length; + if (batchesData.length != priorityOpsData.length) { + revert InvalidBatchesDataLength(batchesData.length, priorityOpsData.length); + } - function _executeBatches(StoredBatchInfo[] calldata _batchesData) internal { - uint256 nBatches = _batchesData.length; for (uint256 i = 0; i < nBatches; i = i.uncheckedInc()) { - _executeOneBatch(_batchesData[i], i); - emit BlockExecution(_batchesData[i].batchNumber, _batchesData[i].batchHash, _batchesData[i].commitment); + if (_isPriorityQueueActive()) { + if (priorityOpsData[i].leftPath.length != 0) { + revert PriorityOpsDataLeftPathLengthIsNotZero(); + } + if (priorityOpsData[i].rightPath.length != 0) { + revert PriorityOpsDataRightPathLengthIsNotZero(); + } + if (priorityOpsData[i].itemHashes.length != 0) { + revert PriorityOpsDataItemHashesLengthIsNotZero(); + } + + _executeOneBatch(batchesData[i], i); + } else { + _executeOneBatch(batchesData[i], priorityOpsData[i], i); + } + emit BlockExecution(batchesData[i].batchNumber, batchesData[i].batchHash, batchesData[i].commitment); } uint256 newTotalBatchesExecuted = s.totalBatchesExecuted + nBatches; @@ -450,56 +489,42 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { } } - /// @inheritdoc IExecutor - function proveBatches( - StoredBatchInfo calldata _prevBatch, - 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); - } + uint256 _processBatchFrom, + uint256 _processBatchTo, + bytes calldata _proofData + ) external nonReentrant onlyValidator onlySettlementLayer { + ( + StoredBatchInfo memory prevBatch, + StoredBatchInfo[] memory committedBatches, + uint256[] memory proof + ) = BatchDecoder.decodeAndCheckProofData(_proofData, _processBatchFrom, _processBatchTo); - 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; + uint256 committedBatchesLength = committedBatches.length; // Initialize the array, that will be used as public input to the ZKP uint256[] memory proofPublicInput = new uint256[](committedBatchesLength); // Check that the batch passed by the validator is indeed the first unverified batch - if (_hashStoredBatchInfo(_prevBatch) != s.storedBatchHashes[currentTotalBatchesVerified]) { - revert BatchHashMismatch( - s.storedBatchHashes[currentTotalBatchesVerified], - _hashStoredBatchInfo(_prevBatch) - ); + if (_hashStoredBatchInfo(prevBatch) != s.storedBatchHashes[currentTotalBatchesVerified]) { + revert BatchHashMismatch(s.storedBatchHashes[currentTotalBatchesVerified], _hashStoredBatchInfo(prevBatch)); } - bytes32 prevBatchCommitment = _prevBatch.commitment; + bytes32 prevBatchCommitment = prevBatch.commitment; for (uint256 i = 0; i < committedBatchesLength; i = i.uncheckedInc()) { currentTotalBatchesVerified = currentTotalBatchesVerified.uncheckedInc(); - if (_hashStoredBatchInfo(_committedBatches[i]) != s.storedBatchHashes[currentTotalBatchesVerified]) { + if (_hashStoredBatchInfo(committedBatches[i]) != s.storedBatchHashes[currentTotalBatchesVerified]) { revert BatchHashMismatch( s.storedBatchHashes[currentTotalBatchesVerified], - _hashStoredBatchInfo(_committedBatches[i]) + _hashStoredBatchInfo(committedBatches[i]) ); } - bytes32 currentBatchCommitment = _committedBatches[i].commitment; + bytes32 currentBatchCommitment = committedBatches[i].commitment; proofPublicInput[i] = _getBatchProofPublicInput(prevBatchCommitment, currentBatchCommitment); prevBatchCommitment = currentBatchCommitment; @@ -508,23 +533,19 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { revert VerifiedBatchesExceedsCommittedBatches(); } - _verifyProof(proofPublicInput, _proof); + _verifyProof(proofPublicInput, proof); emit BlocksVerification(s.totalBatchesVerified, currentTotalBatchesVerified); s.totalBatchesVerified = currentTotalBatchesVerified; } - function _verifyProof(uint256[] memory proofPublicInput, ProofInput calldata _proof) internal view { + function _verifyProof(uint256[] memory proofPublicInput, uint256[] memory _proof) internal view { // We can only process 1 batch proof at a time. if (proofPublicInput.length != 1) { revert CanOnlyProcessOneBatch(); } - bool successVerifyProof = s.verifier.verify( - proofPublicInput, - _proof.serializedProof, - _proof.recursiveAggregationInput - ); + bool successVerifyProof = s.verifier.verify(proofPublicInput, _proof); if (!successVerifyProof) { revert InvalidProof(); } @@ -540,16 +561,14 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { } /// @inheritdoc IExecutor - function revertBatches(uint256 _newLastBatch) external nonReentrant onlyValidatorOrStateTransitionManager { - _revertBatches(_newLastBatch); - } - - /// @inheritdoc IExecutor - function revertBatchesSharedBridge(uint256, uint256 _newLastBatch) external nonReentrant onlyValidator { + function revertBatchesSharedBridge( + uint256, + uint256 _newLastBatch + ) external nonReentrant onlyValidatorOrChainTypeManager { _revertBatches(_newLastBatch); } - function _revertBatches(uint256 _newLastBatch) internal { + function _revertBatches(uint256 _newLastBatch) internal onlySettlementLayer { if (s.totalBatchesCommitted <= _newLastBatch) { revert RevertedBatchNotAfterNewLastBatch(); } @@ -573,7 +592,7 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { /// @dev Creates batch commitment from its data function _createBatchCommitment( - CommitBatchInfo calldata _newBatchData, + CommitBatchInfo memory _newBatchData, bytes32 _stateDiffHash, bytes32[] memory _blobCommitments, bytes32[] memory _blobHashes @@ -587,7 +606,7 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { return keccak256(abi.encode(passThroughDataHash, metadataHash, auxiliaryOutputHash)); } - function _batchPassThroughData(CommitBatchInfo calldata _batch) internal pure returns (bytes memory) { + function _batchPassThroughData(CommitBatchInfo memory _batch) internal pure returns (bytes memory) { return abi.encodePacked( // solhint-disable-next-line func-named-parameters @@ -611,7 +630,7 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { } function _batchAuxiliaryOutput( - CommitBatchInfo calldata _batch, + CommitBatchInfo memory _batch, bytes32 _stateDiffHash, bytes32[] memory _blobCommitments, bytes32[] memory _blobHashes @@ -643,8 +662,8 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { ) internal pure returns (bytes32[] memory blobAuxOutputWords) { // These invariants should be checked by the caller of this function, but we double check // just in case. - if (_blobCommitments.length != MAX_NUMBER_OF_BLOBS || _blobHashes.length != MAX_NUMBER_OF_BLOBS) { - revert InvalidNumberOfBlobs(MAX_NUMBER_OF_BLOBS, _blobCommitments.length, _blobHashes.length); + if (_blobCommitments.length != TOTAL_BLOBS_IN_COMMITMENT || _blobHashes.length != TOTAL_BLOBS_IN_COMMITMENT) { + revert InvalidNumberOfBlobs(TOTAL_BLOBS_IN_COMMITMENT, _blobCommitments.length, _blobHashes.length); } // for each blob we have: @@ -657,7 +676,7 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { blobAuxOutputWords = new bytes32[](2 * TOTAL_BLOBS_IN_COMMITMENT); - for (uint256 i = 0; i < MAX_NUMBER_OF_BLOBS; ++i) { + for (uint256 i = 0; i < TOTAL_BLOBS_IN_COMMITMENT; ++i) { blobAuxOutputWords[i * 2] = _blobHashes[i]; blobAuxOutputWords[i * 2 + 1] = _blobCommitments[i]; } @@ -677,102 +696,4 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { function _setBit(uint256 _bitMap, uint8 _index) internal pure returns (uint256) { return _bitMap | (1 << _index); } - - /// @notice Calls the point evaluation precompile and verifies the output - /// Verify p(z) = y given commitment that corresponds to the polynomial p(x) and a KZG proof. - /// Also verify that the provided commitment matches the provided versioned_hash. - /// - function _pointEvaluationPrecompile( - bytes32 _versionedHash, - bytes32 _openingPoint, - bytes calldata _openingValueCommitmentProof - ) internal view { - bytes memory precompileInput = abi.encodePacked(_versionedHash, _openingPoint, _openingValueCommitmentProof); - - (bool success, bytes memory data) = POINT_EVALUATION_PRECOMPILE_ADDR.staticcall(precompileInput); - - // We verify that the point evaluation precompile call was successful by testing the latter 32 bytes of the - // response is equal to BLS_MODULUS as defined in https://eips.ethereum.org/EIPS/eip-4844#point-evaluation-precompile - if (!success) { - revert PointEvalCallFailed(precompileInput); - } - (, uint256 result) = abi.decode(data, (uint256, uint256)); - if (result != BLS_MODULUS) { - revert PointEvalFailed(abi.encode(result)); - } - } - - /// @dev Verifies that the blobs contain the correct data by calling the point evaluation precompile. For the precompile we need: - /// versioned hash || opening point || opening value || commitment || proof - /// the _pubdataCommitments will contain the last 4 values, the versioned hash is pulled from the BLOBHASH opcode - /// pubdataCommitments is a list of: opening point (16 bytes) || claimed value (32 bytes) || commitment (48 bytes) || proof (48 bytes)) = 144 bytes - function _verifyBlobInformation( - bytes calldata _pubdataCommitments, - bytes32[] memory _blobHashes - ) internal view returns (bytes32[] memory blobCommitments) { - uint256 versionedHashIndex = 0; - - if (_pubdataCommitments.length == 0) { - revert PubdataCommitmentsEmpty(); - } - if (_pubdataCommitments.length > PUBDATA_COMMITMENT_SIZE * MAX_NUMBER_OF_BLOBS) { - revert PubdataCommitmentsTooBig(); - } - if (_pubdataCommitments.length % PUBDATA_COMMITMENT_SIZE != 0) { - revert InvalidPubdataCommitmentsSize(); - } - blobCommitments = new bytes32[](MAX_NUMBER_OF_BLOBS); - - // We disable this check because calldata array length is cheap. - // solhint-disable-next-line gas-length-in-loops - for (uint256 i = 0; i < _pubdataCommitments.length; i += PUBDATA_COMMITMENT_SIZE) { - bytes32 blobVersionedHash = _getBlobVersionedHash(versionedHashIndex); - - if (blobVersionedHash == bytes32(0)) { - revert EmptyBlobVersionHash(versionedHashIndex); - } - - // First 16 bytes is the opening point. While we get the point as 16 bytes, the point evaluation precompile - // requires it to be 32 bytes. The blob commitment must use the opening point as 16 bytes though. - bytes32 openingPoint = bytes32( - uint256(uint128(bytes16(_pubdataCommitments[i:i + PUBDATA_COMMITMENT_CLAIMED_VALUE_OFFSET]))) - ); - - _pointEvaluationPrecompile( - blobVersionedHash, - openingPoint, - _pubdataCommitments[i + PUBDATA_COMMITMENT_CLAIMED_VALUE_OFFSET:i + PUBDATA_COMMITMENT_SIZE] - ); - - // Take the hash of the versioned hash || opening point || claimed value - blobCommitments[versionedHashIndex] = keccak256( - abi.encodePacked(blobVersionedHash, _pubdataCommitments[i:i + PUBDATA_COMMITMENT_COMMITMENT_OFFSET]) - ); - ++versionedHashIndex; - } - - // This check is required because we want to ensure that there aren't any extra blobs trying to be published. - // Calling the BLOBHASH opcode with an index > # blobs - 1 yields bytes32(0) - bytes32 versionedHash = _getBlobVersionedHash(versionedHashIndex); - if (versionedHash != bytes32(0)) { - revert NonEmptyBlobVersionHash(versionedHashIndex); - } - - // We verify that for each set of blobHash/blobCommitment are either both empty - // or there are values for both. - for (uint256 i = 0; i < MAX_NUMBER_OF_BLOBS; ++i) { - if ( - (_blobHashes[i] == bytes32(0) && blobCommitments[i] != bytes32(0)) || - (_blobHashes[i] != bytes32(0) && blobCommitments[i] == bytes32(0)) - ) { - revert BlobHashCommitmentError(i, _blobHashes[i] == bytes32(0), blobCommitments[i] == bytes32(0)); - } - } - } - - function _getBlobVersionedHash(uint256 _index) internal view virtual returns (bytes32 versionedHash) { - assembly { - versionedHash := blobhash(_index) - } - } } diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Getters.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Getters.sol index 9cb2a2da8..fec91b0f5 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Getters.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Getters.sol @@ -4,28 +4,30 @@ pragma solidity 0.8.24; import {SafeCast} from "@openzeppelin/contracts-v4/utils/math/SafeCast.sol"; -import {ZkSyncHyperchainBase} from "./ZkSyncHyperchainBase.sol"; -import {PubdataPricingMode} from "../ZkSyncHyperchainStorage.sol"; +import {ZKChainBase} from "./ZKChainBase.sol"; +import {PubdataPricingMode} from "../ZKChainStorage.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 {PriorityQueue} from "../../../state-transition/libraries/PriorityQueue.sol"; +import {PriorityTree} from "../../../state-transition/libraries/PriorityTree.sol"; +import {IBridgehub} from "../../../bridgehub/IBridgehub.sol"; import {UncheckedMath} from "../../../common/libraries/UncheckedMath.sol"; import {IGetters} from "../../chain-interfaces/IGetters.sol"; import {ILegacyGetters} from "../../chain-interfaces/ILegacyGetters.sol"; -import {InvalidSelector} from "../../../common/L1ContractErrors.sol"; import {SemVer} from "../../../common/libraries/SemVer.sol"; // While formally the following import is not used, it is needed to inherit documentation from it -import {IZkSyncHyperchainBase} from "../../chain-interfaces/IZkSyncHyperchainBase.sol"; +import {IZKChainBase} from "../../chain-interfaces/IZKChainBase.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 ZkSyncHyperchainBase, IGetters, ILegacyGetters { +contract GettersFacet is ZKChainBase, IGetters, ILegacyGetters { using UncheckedMath for uint256; using PriorityQueue for PriorityQueue.Queue; + using PriorityTree for PriorityTree.Tree; - /// @inheritdoc IZkSyncHyperchainBase + /// @inheritdoc IZKChainBase string public constant override getName = "GettersFacet"; /*////////////////////////////////////////////////////////////// @@ -53,18 +55,23 @@ contract GettersFacet is ZkSyncHyperchainBase, IGetters, ILegacyGetters { } /// @inheritdoc IGetters - function getStateTransitionManager() external view returns (address) { - return s.stateTransitionManager; + function getChainTypeManager() external view returns (address) { + return s.chainTypeManager; + } + + /// @inheritdoc IGetters + function getChainId() external view returns (uint256) { + return s.chainId; } /// @inheritdoc IGetters function getBaseToken() external view returns (address) { - return s.baseToken; + return IBridgehub(s.bridgehub).baseToken(s.chainId); } /// @inheritdoc IGetters - function getBaseTokenBridge() external view returns (address) { - return s.baseTokenBridge; + function getBaseTokenAssetId() external view returns (bytes32) { + return s.baseTokenAssetId; } /// @inheritdoc IGetters @@ -92,24 +99,47 @@ contract GettersFacet is ZkSyncHyperchainBase, IGetters, ILegacyGetters { return s.totalBatchesExecuted; } + /// @inheritdoc IGetters + function getTransactionFilterer() external view returns (address) { + return s.transactionFilterer; + } + /// @inheritdoc IGetters function getTotalPriorityTxs() external view returns (uint256) { - return s.priorityQueue.getTotalPriorityTxs(); + return _getTotalPriorityTxs(); + } + + /// @inheritdoc IGetters + function getPriorityTreeStartIndex() external view returns (uint256) { + return s.priorityTree.startIndex; } /// @inheritdoc IGetters function getFirstUnprocessedPriorityTx() external view returns (uint256) { - return s.priorityQueue.getFirstUnprocessedPriorityTx(); + if (_isPriorityQueueActive()) { + return s.priorityQueue.getFirstUnprocessedPriorityTx(); + } else { + return s.priorityTree.getFirstUnprocessedPriorityTx(); + } + } + + /// @inheritdoc IGetters + function getPriorityTreeRoot() external view returns (bytes32) { + return s.priorityTree.getRoot(); } /// @inheritdoc IGetters function getPriorityQueueSize() external view returns (uint256) { - return s.priorityQueue.getSize(); + if (_isPriorityQueueActive()) { + return s.priorityQueue.getSize(); + } else { + return s.priorityTree.getSize(); + } } /// @inheritdoc IGetters - function priorityQueueFrontOperation() external view returns (PriorityOperation memory) { - return s.priorityQueue.front(); + function isPriorityQueueActive() external view returns (bool) { + return _isPriorityQueueActive(); } /// @inheritdoc IGetters @@ -191,7 +221,8 @@ contract GettersFacet is ZkSyncHyperchainBase, IGetters, ILegacyGetters { function isFunctionFreezable(bytes4 _selector) external view returns (bool) { Diamond.DiamondStorage storage ds = Diamond.getDiamondStorage(); if (ds.selectorToFacet[_selector].facetAddress == address(0)) { - revert InvalidSelector(_selector); + // The function does not exist + return false; } return ds.selectorToFacet[_selector].isFreezable; } @@ -206,6 +237,15 @@ contract GettersFacet is ZkSyncHyperchainBase, IGetters, ILegacyGetters { return s.feeParams.pubdataPricingMode; } + /// @inheritdoc IGetters + function getSettlementLayer() external view returns (address) { + return s.settlementLayer; + } + + function getDAValidatorPair() external view returns (address, address) { + return (s.l1DAValidator, s.l2DAValidator); + } + /*////////////////////////////////////////////////////////////// DIAMOND LOUPE //////////////////////////////////////////////////////////////*/ diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol index 43f6b04e7..f27ca7005 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol @@ -5,55 +5,63 @@ pragma solidity 0.8.24; import {Math} from "@openzeppelin/contracts-v4/utils/math/Math.sol"; import {IMailbox} from "../../chain-interfaces/IMailbox.sol"; +import {IChainTypeManager} from "../../IChainTypeManager.sol"; +import {IBridgehub} from "../../../bridgehub/IBridgehub.sol"; + import {ITransactionFilterer} from "../../chain-interfaces/ITransactionFilterer.sol"; -import {Merkle} from "../../libraries/Merkle.sol"; +import {Merkle} from "../../../common/libraries/Merkle.sol"; import {PriorityQueue, PriorityOperation} from "../../libraries/PriorityQueue.sol"; +import {PriorityTree} from "../../libraries/PriorityTree.sol"; import {TransactionValidator} from "../../libraries/TransactionValidator.sol"; import {WritePriorityOpParams, L2CanonicalTransaction, L2Message, L2Log, TxStatus, BridgehubL2TransactionRequest} from "../../../common/Messaging.sol"; -import {FeeParams, PubdataPricingMode} from "../ZkSyncHyperchainStorage.sol"; +import {MessageHashing} from "../../../common/libraries/MessageHashing.sol"; +import {FeeParams, PubdataPricingMode} from "../ZKChainStorage.sol"; import {UncheckedMath} from "../../../common/libraries/UncheckedMath.sol"; import {L2ContractHelper} from "../../../common/libraries/L2ContractHelper.sol"; import {AddressAliasHelper} from "../../../vendor/AddressAliasHelper.sol"; -import {ZkSyncHyperchainBase} from "./ZkSyncHyperchainBase.sol"; -import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, ETH_TOKEN_ADDRESS, L1_GAS_PER_PUBDATA_BYTE, L2_L1_LOGS_TREE_DEFAULT_LEAF_HASH, PRIORITY_OPERATION_L2_TX_TYPE, PRIORITY_EXPIRATION, MAX_NEW_FACTORY_DEPS} from "../../../common/Config.sol"; -import {L2_BOOTLOADER_ADDRESS, L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR} from "../../../common/L2ContractAddresses.sol"; +import {ZKChainBase} from "./ZKChainBase.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, SETTLEMENT_LAYER_RELAY_SENDER, SUPPORTED_PROOF_METADATA_VERSION} from "../../../common/Config.sol"; +import {L2_BOOTLOADER_ADDRESS, L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, L2_BRIDGEHUB_ADDR} from "../../../common/L2ContractAddresses.sol"; -import {IL1SharedBridge} from "../../../bridge/interfaces/IL1SharedBridge.sol"; +import {IL1AssetRouter} from "../../../bridge/asset-router/IL1AssetRouter.sol"; -import {OnlyEraSupported, BatchNotExecuted, HashedLogIsDefault, BaseTokenGasPriceDenominatorNotSet, TransactionNotAllowed, GasPerPubdataMismatch, TooManyFactoryDeps, MsgValueTooLow} from "../../../common/L1ContractErrors.sol"; +import {MerklePathEmpty, OnlyEraSupported, BatchNotExecuted, HashedLogIsDefault, BaseTokenGasPriceDenominatorNotSet, TransactionNotAllowed, GasPerPubdataMismatch, TooManyFactoryDeps, MsgValueTooLow, InvalidProofLengthForFinalNode} from "../../../common/L1ContractErrors.sol"; +import {NotL1, UnsupportedProofMetadataVersion, LocalRootIsZero, LocalRootMustBeZero, NotSettlementLayer, NotHyperchain} from "../../L1StateTransitionErrors.sol"; // While formally the following import is not used, it is needed to inherit documentation from it -import {IZkSyncHyperchainBase} from "../../chain-interfaces/IZkSyncHyperchainBase.sol"; +import {IZKChainBase} from "../../chain-interfaces/IZKChainBase.sol"; /// @title ZKsync Mailbox contract providing interfaces for L1 <-> L2 interaction. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { +contract MailboxFacet is ZKChainBase, IMailbox { using UncheckedMath for uint256; using PriorityQueue for PriorityQueue.Queue; + using PriorityTree for PriorityTree.Tree; - /// @inheritdoc IZkSyncHyperchainBase + /// @inheritdoc IZKChainBase string public constant override getName = "MailboxFacet"; /// @dev Era's chainID uint256 internal immutable ERA_CHAIN_ID; - constructor(uint256 _eraChainId) { - ERA_CHAIN_ID = _eraChainId; - } + /// @notice The chain id of L1. This contract can be deployed on multiple layers, but this value is still equal to the + /// L1 that is at the most base layer. + uint256 internal immutable L1_CHAIN_ID; - /// @inheritdoc IMailbox - function transferEthToSharedBridge() external onlyBaseTokenBridge { - if (s.chainId != ERA_CHAIN_ID) { - revert OnlyEraSupported(); + modifier onlyL1() { + if (block.chainid != L1_CHAIN_ID) { + revert NotL1(block.chainid); } + _; + } - uint256 amount = address(this).balance; - address baseTokenBridgeAddress = s.baseTokenBridge; - IL1SharedBridge(baseTokenBridgeAddress).receiveEth{value: amount}(ERA_CHAIN_ID); + constructor(uint256 _eraChainId, uint256 _l1ChainId) { + ERA_CHAIN_ID = _eraChainId; + L1_CHAIN_ID = _l1ChainId; } - /// @notice when requesting transactions through the bridgehub + /// @inheritdoc IMailbox function bridgehubRequestL2Transaction( BridgehubL2TransactionRequest calldata _request ) external onlyBridgehub returns (bytes32 canonicalTxHash) { @@ -110,6 +118,185 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { return _proveL2LogInclusion(_l2BatchNumber, _l2MessageIndex, l2Log, _merkleProof); } + function _parseProofMetadata( + bytes32[] calldata _proof + ) + internal + pure + returns (uint256 proofStartIndex, uint256 logLeafProofLen, uint256 batchLeafProofLen, bool finalProofNode) + { + bytes32 proofMetadata = _proof[0]; + + // We support two formats of the proofs: + // 1. The old format, where `_proof` is just a plain Merkle proof. + // 2. The new format, where the first element of the `_proof` is encoded metadata, which consists of the following: + // - first byte: metadata version (0x01). + // - second byte: length of the log leaf proof (the proof that the log belongs to a batch). + // - third byte: length of the batch leaf proof (the proof that the batch belongs to another settlement layer, if any). + // - fourth byte: whether the current proof is the last in the links of recursive proofs for settlement layers. + // - the rest of the bytes are zeroes. + // + // In the future the old version will be disabled, and only the new version will be supported. + // For now, we need to support both for backwards compatibility. We distinguish between those based on whether the last 28 bytes are zeroes. + // It is safe, since the elements of the proof are hashes and are unlikely to have 28 zero bytes in them. + + // We shift left by 4 bytes = 32 bits to remove the top 32 bits of the metadata. + uint256 metadataAsUint256 = (uint256(proofMetadata) << 32); + + if (metadataAsUint256 == 0) { + // It is the new version + bytes1 metadataVersion = bytes1(proofMetadata); + if (uint256(uint8(metadataVersion)) != SUPPORTED_PROOF_METADATA_VERSION) { + revert UnsupportedProofMetadataVersion(uint256(uint8(metadataVersion))); + } + + proofStartIndex = 1; + logLeafProofLen = uint256(uint8(proofMetadata[1])); + batchLeafProofLen = uint256(uint8(proofMetadata[2])); + finalProofNode = uint256(uint8(proofMetadata[3])) != 0; + } else { + // It is the old version + + // The entire proof is a merkle path + proofStartIndex = 0; + logLeafProofLen = _proof.length; + batchLeafProofLen = 0; + finalProofNode = true; + } + + if (finalProofNode && batchLeafProofLen != 0) { + revert InvalidProofLengthForFinalNode(); + } + } + + function extractSlice( + bytes32[] calldata _proof, + uint256 _left, + uint256 _right + ) internal pure returns (bytes32[] memory slice) { + slice = new bytes32[](_right - _left); + for (uint256 i = _left; i < _right; i = i.uncheckedInc()) { + slice[i - _left] = _proof[i]; + } + } + + /// @notice Extracts slice until the end of the array. + /// @dev It is used in one place in order to circumvent the stack too deep error. + function extractSliceUntilEnd( + bytes32[] calldata _proof, + uint256 _start + ) internal pure returns (bytes32[] memory slice) { + slice = extractSlice(_proof, _start, _proof.length); + } + + /// @inheritdoc IMailbox + function proveL2LeafInclusion( + uint256 _batchNumber, + uint256 _leafProofMask, + bytes32 _leaf, + bytes32[] calldata _proof + ) external view override returns (bool) { + return _proveL2LeafInclusion(_batchNumber, _leafProofMask, _leaf, _proof); + } + + function _proveL2LeafInclusion( + uint256 _batchNumber, + uint256 _leafProofMask, + bytes32 _leaf, + bytes32[] calldata _proof + ) internal view returns (bool) { + if (_proof.length == 0) { + revert MerklePathEmpty(); + } + + uint256 ptr = 0; + bytes32 chainIdLeaf; + { + ( + uint256 proofStartIndex, + uint256 logLeafProofLen, + uint256 batchLeafProofLen, + bool finalProofNode + ) = _parseProofMetadata(_proof); + ptr = proofStartIndex; + + bytes32 batchSettlementRoot = Merkle.calculateRootMemory( + extractSlice(_proof, ptr, ptr + logLeafProofLen), + _leafProofMask, + _leaf + ); + ptr += logLeafProofLen; + + // If the `finalProofNode` is true, then we assume that this is L1 contract of the top-level + // in the aggregation, i.e. the batch root is stored here on L1. + if (finalProofNode) { + // Double checking that the batch has been executed. + if (_batchNumber > s.totalBatchesExecuted) { + revert BatchNotExecuted(_batchNumber); + } + + bytes32 correctBatchRoot = s.l2LogsRootHashes[_batchNumber]; + if (correctBatchRoot == bytes32(0)) { + revert LocalRootIsZero(); + } + return correctBatchRoot == batchSettlementRoot; + } + + if (s.l2LogsRootHashes[_batchNumber] != bytes32(0)) { + revert LocalRootMustBeZero(); + } + + // Now, we'll have to check that the Gateway included the message. + bytes32 batchLeafHash = MessageHashing.batchLeafHash(batchSettlementRoot, _batchNumber); + + uint256 batchLeafProofMask = uint256(bytes32(_proof[ptr])); + ++ptr; + + bytes32 chainIdRoot = Merkle.calculateRootMemory( + extractSlice(_proof, ptr, ptr + batchLeafProofLen), + batchLeafProofMask, + batchLeafHash + ); + ptr += batchLeafProofLen; + + chainIdLeaf = MessageHashing.chainIdLeafHash(chainIdRoot, s.chainId); + } + + uint256 settlementLayerBatchNumber; + uint256 settlementLayerBatchRootMask; + address settlementLayerAddress; + + // Preventing stack too deep error + { + // Now, we just need to double check whether this chainId leaf was present in the tree. + uint256 settlementLayerPackedBatchInfo = uint256(_proof[ptr]); + ++ptr; + settlementLayerBatchNumber = uint256(settlementLayerPackedBatchInfo >> 128); + settlementLayerBatchRootMask = uint256(settlementLayerPackedBatchInfo & ((1 << 128) - 1)); + + uint256 settlementLayerChainId = uint256(_proof[ptr]); + ++ptr; + + // Assuming that `settlementLayerChainId` is an honest chain, the `chainIdLeaf` should belong + // to a chain's message root only if the chain has indeed executed its batch on top of it. + // + // We trust all chains whitelisted by the Bridgehub governance. + if (!IBridgehub(s.bridgehub).whitelistedSettlementLayers(settlementLayerChainId)) { + revert NotSettlementLayer(); + } + + settlementLayerAddress = IBridgehub(s.bridgehub).getZKChain(settlementLayerChainId); + } + + return + IMailbox(settlementLayerAddress).proveL2LeafInclusion( + settlementLayerBatchNumber, + settlementLayerBatchRootMask, + chainIdLeaf, + extractSliceUntilEnd(_proof, ptr) + ); + } + /// @dev Prove that a specific L2 log was sent in a specific L2 batch number function _proveL2LogInclusion( uint256 _batchNumber, @@ -117,10 +304,6 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { L2Log memory _log, bytes32[] calldata _proof ) internal view returns (bool) { - if (_batchNumber > s.totalBatchesExecuted) { - revert BatchNotExecuted(_batchNumber); - } - bytes32 hashedLog = keccak256( // solhint-disable-next-line func-named-parameters abi.encodePacked(_log.l2ShardId, _log.isService, _log.txNumberInBatch, _log.sender, _log.key, _log.value) @@ -135,10 +318,9 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { // of leaf preimage (which is `L2_TO_L1_LOG_SERIALIZE_SIZE`) is not // equal to the length of other nodes preimages (which are `2 * 32`) - bytes32 calculatedRootHash = Merkle.calculateRoot(_proof, _index, hashedLog); - bytes32 actualRootHash = s.l2LogsRootHashes[_batchNumber]; + // We can use `index` as a mask, since the `localMessageRoot` is on the left part of the tree. - return actualRootHash == calculatedRootHash; + return _proveL2LeafInclusion(_batchNumber, _index, hashedLog, _proof); } /// @dev Convert arbitrary-length message to the raw l2 log @@ -194,58 +376,60 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { } /// @inheritdoc IMailbox - function finalizeEthWithdrawal( - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes calldata _message, - bytes32[] calldata _merkleProof - ) external nonReentrant { - if (s.chainId != ERA_CHAIN_ID) { - revert OnlyEraSupported(); + function requestL2TransactionToGatewayMailbox( + uint256 _chainId, + bytes32 _canonicalTxHash, + uint64 _expirationTimestamp + ) external override onlyL1 returns (bytes32 canonicalTxHash) { + if (!IBridgehub(s.bridgehub).whitelistedSettlementLayers(s.chainId)) { + revert NotSettlementLayer(); } - IL1SharedBridge(s.baseTokenBridge).finalizeWithdrawal({ - _chainId: ERA_CHAIN_ID, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _message: _message, - _merkleProof: _merkleProof + if (IChainTypeManager(s.chainTypeManager).getZKChain(_chainId) != msg.sender) { + revert NotHyperchain(); + } + + BridgehubL2TransactionRequest memory wrappedRequest = _wrapRequest({ + _chainId: _chainId, + _canonicalTxHash: _canonicalTxHash, + _expirationTimestamp: _expirationTimestamp }); + canonicalTxHash = _requestL2TransactionToGatewayFree(wrappedRequest); } - /// @inheritdoc IMailbox - function requestL2Transaction( - address _contractL2, - uint256 _l2Value, - bytes calldata _calldata, - uint256 _l2GasLimit, - uint256 _l2GasPerPubdataByteLimit, - bytes[] calldata _factoryDeps, - address _refundRecipient - ) external payable returns (bytes32 canonicalTxHash) { - if (s.chainId != ERA_CHAIN_ID) { - revert OnlyEraSupported(); - } - 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 + /// @inheritdoc IMailbox + function bridgehubRequestL2TransactionOnGateway( + bytes32 _canonicalTxHash, + uint64 _expirationTimestamp + ) external override onlyBridgehub { + _writePriorityOpHash(_canonicalTxHash, _expirationTimestamp); + emit NewRelayedPriorityTransaction(_getTotalPriorityTxs(), _canonicalTxHash, _expirationTimestamp); + } + + function _wrapRequest( + uint256 _chainId, + bytes32 _canonicalTxHash, + uint64 _expirationTimestamp + ) internal view returns (BridgehubL2TransactionRequest memory) { + // solhint-disable-next-line func-named-parameters + bytes memory data = abi.encodeCall( + IBridgehub.forwardTransactionOnGateway, + (_chainId, _canonicalTxHash, _expirationTimestamp) ); + return + BridgehubL2TransactionRequest({ + /// There is no sender for the wrapping, we use a virtual address. + sender: SETTLEMENT_LAYER_RELAY_SENDER, + contractL2: L2_BRIDGEHUB_ADDR, + mintValue: 0, + l2Value: 0, + // Very large amount + l2GasLimit: 72_000_000, + l2Calldata: data, + l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + factoryDeps: new bytes[](0), + // Tx is free, no so refund recipient needed + refundRecipient: address(0) + }); } function _requestL2TransactionSender( @@ -288,7 +472,7 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { if (request.factoryDeps.length > MAX_NEW_FACTORY_DEPS) { revert TooManyFactoryDeps(); } - _params.txId = s.priorityQueue.getTotalPriorityTxs(); + _params.txId = _nextPriorityTxId(); // Checking that the user provided enough ether to pay for the transaction. _params.l2GasPrice = _deriveL2GasPrice(tx.gasprice, request.l2GasPerPubdataByteLimit); @@ -305,12 +489,45 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { if (request.sender != tx.origin) { request.sender = AddressAliasHelper.applyL1ToL2Alias(request.sender); } - // solhint-enable avoid-tx-origin // populate missing fields _params.expirationTimestamp = uint64(block.timestamp + PRIORITY_EXPIRATION); // Safe to cast - canonicalTxHash = _writePriorityOp(_params); + L2CanonicalTransaction memory transaction; + (transaction, canonicalTxHash) = _validateTx(_params); + + _writePriorityOp(transaction, _params.request.factoryDeps, canonicalTxHash, _params.expirationTimestamp); + if (s.settlementLayer != address(0)) { + // slither-disable-next-line unused-return + IMailbox(s.settlementLayer).requestL2TransactionToGatewayMailbox({ + _chainId: s.chainId, + _canonicalTxHash: canonicalTxHash, + _expirationTimestamp: _params.expirationTimestamp + }); + } + } + + function _nextPriorityTxId() internal view returns (uint256) { + if (_isPriorityQueueActive()) { + return s.priorityQueue.getTotalPriorityTxs(); + } else { + return s.priorityTree.getTotalPriorityTxs(); + } + } + + function _requestL2TransactionToGatewayFree( + BridgehubL2TransactionRequest memory _request + ) internal nonReentrant returns (bytes32 canonicalTxHash) { + WritePriorityOpParams memory params = WritePriorityOpParams({ + request: _request, + txId: _nextPriorityTxId(), + l2GasPrice: 0, + expirationTimestamp: uint64(block.timestamp + PRIORITY_EXPIRATION) + }); + + L2CanonicalTransaction memory transaction; + (transaction, canonicalTxHash) = _validateTx(params); + _writePriorityOp(transaction, params.request.factoryDeps, canonicalTxHash, params.expirationTimestamp); } function _serializeL2Transaction( @@ -338,40 +555,45 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { }); } - /// @notice Stores a transaction record in storage & send event about that - function _writePriorityOp( + function _validateTx( WritePriorityOpParams memory _priorityOpParams - ) internal returns (bytes32 canonicalTxHash) { - L2CanonicalTransaction memory transaction = _serializeL2Transaction(_priorityOpParams); - + ) internal view returns (L2CanonicalTransaction memory transaction, bytes32 canonicalTxHash) { + transaction = _serializeL2Transaction(_priorityOpParams); bytes memory transactionEncoding = abi.encode(transaction); - TransactionValidator.validateL1ToL2Transaction( transaction, transactionEncoding, s.priorityTxMaxGasLimit, s.feeParams.priorityTxMaxPubdata ); - canonicalTxHash = keccak256(transactionEncoding); + } - s.priorityQueue.pushBack( - PriorityOperation({ - canonicalTxHash: canonicalTxHash, - expirationTimestamp: _priorityOpParams.expirationTimestamp, - layer2Tip: uint192(0) // TODO: Restore after fee modeling will be stable. (SMA-1230) - }) - ); + /// @notice Stores a transaction record in storage & send event about that + function _writePriorityOp( + L2CanonicalTransaction memory _transaction, + bytes[] memory _factoryDeps, + bytes32 _canonicalTxHash, + uint64 _expirationTimestamp + ) internal { + _writePriorityOpHash(_canonicalTxHash, _expirationTimestamp); // Data that is needed for the operator to simulate priority queue offchain // solhint-disable-next-line func-named-parameters - emit NewPriorityRequest( - _priorityOpParams.txId, - canonicalTxHash, - _priorityOpParams.expirationTimestamp, - transaction, - _priorityOpParams.request.factoryDeps - ); + emit NewPriorityRequest(_transaction.nonce, _canonicalTxHash, _expirationTimestamp, _transaction, _factoryDeps); + } + + function _writePriorityOpHash(bytes32 _canonicalTxHash, uint64 _expirationTimestamp) internal { + if (_isPriorityQueueActive()) { + s.priorityQueue.pushBack( + PriorityOperation({ + canonicalTxHash: _canonicalTxHash, + expirationTimestamp: _expirationTimestamp, + layer2Tip: uint192(0) // TODO: Restore after fee modeling will be stable. (SMA-1230) + }) + ); + } + s.priorityTree.push(_canonicalTxHash); } /// @notice Hashes the L2 bytecodes and returns them in the format in which they are processed by the bootloader @@ -387,4 +609,64 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { } } } + + /////////////////////////////////////////////////////// + //////// Legacy Era functions + + /// @inheritdoc IMailbox + function finalizeEthWithdrawal( + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes calldata _message, + bytes32[] calldata _merkleProof + ) external nonReentrant onlyL1 { + if (s.chainId != ERA_CHAIN_ID) { + revert OnlyEraSupported(); + } + address sharedBridge = IBridgehub(s.bridgehub).sharedBridge(); + IL1AssetRouter(sharedBridge).finalizeWithdrawal({ + _chainId: ERA_CHAIN_ID, + _l2BatchNumber: _l2BatchNumber, + _l2MessageIndex: _l2MessageIndex, + _l2TxNumberInBatch: _l2TxNumberInBatch, + _message: _message, + _merkleProof: _merkleProof + }); + } + + /// @inheritdoc IMailbox + function requestL2Transaction( + address _contractL2, + uint256 _l2Value, + bytes calldata _calldata, + uint256 _l2GasLimit, + uint256 _l2GasPerPubdataByteLimit, + bytes[] calldata _factoryDeps, + address _refundRecipient + ) external payable onlyL1 returns (bytes32 canonicalTxHash) { + if (s.chainId != ERA_CHAIN_ID) { + revert OnlyEraSupported(); + } + canonicalTxHash = _requestL2TransactionSender( + BridgehubL2TransactionRequest({ + sender: msg.sender, + contractL2: _contractL2, + mintValue: msg.value, + l2Value: _l2Value, + l2GasLimit: _l2GasLimit, + l2Calldata: _calldata, + l2GasPerPubdataByteLimit: _l2GasPerPubdataByteLimit, + factoryDeps: _factoryDeps, + refundRecipient: _refundRecipient + }) + ); + address sharedBridge = IBridgehub(s.bridgehub).sharedBridge(); + IL1AssetRouter(sharedBridge).bridgehubDepositBaseToken{value: msg.value}( + s.chainId, + s.baseTokenAssetId, + msg.sender, + msg.value + ); + } } diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/ZKChainBase.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/ZKChainBase.sol new file mode 100644 index 000000000..0211f9a8b --- /dev/null +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/ZKChainBase.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {ZKChainStorage} from "../ZKChainStorage.sol"; +import {ReentrancyGuard} from "../../../common/ReentrancyGuard.sol"; +import {PriorityQueue} from "../../libraries/PriorityQueue.sol"; +import {PriorityTree} from "../../libraries/PriorityTree.sol"; +import {Unauthorized, NotSettlementLayer} from "../../../common/L1ContractErrors.sol"; + +/// @title Base contract containing functions accessible to the other facets. +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +contract ZKChainBase is ReentrancyGuard { + using PriorityQueue for PriorityQueue.Queue; + using PriorityTree for PriorityTree.Tree; + + // slither-disable-next-line uninitialized-state + ZKChainStorage internal s; + + /// @notice Checks that the message sender is an active admin + modifier onlyAdmin() { + if (msg.sender != s.admin) { + revert Unauthorized(msg.sender); + } + _; + } + + /// @notice Checks if validator is active + modifier onlyValidator() { + if (!s.validators[msg.sender]) { + revert Unauthorized(msg.sender); + } + _; + } + + modifier onlyChainTypeManager() { + if (msg.sender != s.chainTypeManager) { + revert Unauthorized(msg.sender); + } + _; + } + + modifier onlyBridgehub() { + if (msg.sender != s.bridgehub) { + revert Unauthorized(msg.sender); + } + _; + } + + modifier onlyAdminOrChainTypeManager() { + if (msg.sender != s.admin && msg.sender != s.chainTypeManager) { + revert Unauthorized(msg.sender); + } + _; + } + + modifier onlyValidatorOrChainTypeManager() { + if (!s.validators[msg.sender] && msg.sender != s.chainTypeManager) { + revert Unauthorized(msg.sender); + } + _; + } + + modifier onlySettlementLayer() { + if (s.settlementLayer != address(0)) { + revert NotSettlementLayer(); + } + _; + } + + /// @notice Returns whether the priority queue is still active, i.e. + /// the chain has not processed all transactions from it + function _isPriorityQueueActive() internal view returns (bool) { + return s.priorityQueue.getFirstUnprocessedPriorityTx() < s.priorityTree.startIndex; + } + + /// @notice Ensures that the queue is deactivated. Should be invoked + /// whenever the chain migrates to another settlement layer. + function _forceDeactivateQueue() internal { + // We double check whether it is still active mainly to prevent + // overriding `tail`/`head` on L1 deployment. + if (_isPriorityQueueActive()) { + uint256 startIndex = s.priorityTree.startIndex; + s.priorityQueue.head = startIndex; + s.priorityQueue.tail = startIndex; + } + } + + function _getTotalPriorityTxs() internal view returns (uint256) { + if (_isPriorityQueueActive()) { + return s.priorityQueue.getTotalPriorityTxs(); + } else { + return s.priorityTree.getTotalPriorityTxs(); + } + } +} diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/ZkSyncHyperchainBase.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/ZkSyncHyperchainBase.sol deleted file mode 100644 index 0910fcab3..000000000 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/ZkSyncHyperchainBase.sol +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -import {ZkSyncHyperchainStorage} from "../ZkSyncHyperchainStorage.sol"; -import {ReentrancyGuard} from "../../../common/ReentrancyGuard.sol"; - -import {Unauthorized} from "../../../common/L1ContractErrors.sol"; - -/// @title Base contract containing functions accessible to the other facets. -/// @author Matter Labs -/// @custom:security-contact security@matterlabs.dev -contract ZkSyncHyperchainBase is ReentrancyGuard { - // slither-disable-next-line uninitialized-state - ZkSyncHyperchainStorage internal s; - - /// @notice Checks that the message sender is an active admin - modifier onlyAdmin() { - if (msg.sender != s.admin) { - revert Unauthorized(msg.sender); - } - _; - } - - /// @notice Checks if validator is active - modifier onlyValidator() { - if (!s.validators[msg.sender]) { - revert Unauthorized(msg.sender); - } - _; - } - - modifier onlyStateTransitionManager() { - if (msg.sender != s.stateTransitionManager) { - revert Unauthorized(msg.sender); - } - _; - } - - modifier onlyBridgehub() { - if (msg.sender != s.bridgehub) { - revert Unauthorized(msg.sender); - } - _; - } - - modifier onlyAdminOrStateTransitionManager() { - if (msg.sender != s.admin && msg.sender != s.stateTransitionManager) { - revert Unauthorized(msg.sender); - } - _; - } - - modifier onlyValidatorOrStateTransitionManager() { - if (!s.validators[msg.sender] && msg.sender != s.stateTransitionManager) { - revert Unauthorized(msg.sender); - } - _; - } - - modifier onlyBaseTokenBridge() { - if (msg.sender != s.baseTokenBridge) { - revert Unauthorized(msg.sender); - } - _; - } -} diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol index 643c6114d..4ca0810fc 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol @@ -2,15 +2,16 @@ // We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. pragma solidity ^0.8.21; -import {IZkSyncHyperchainBase} from "../chain-interfaces/IZkSyncHyperchainBase.sol"; +import {IZKChainBase} from "../chain-interfaces/IZKChainBase.sol"; import {Diamond} from "../libraries/Diamond.sol"; -import {FeeParams, PubdataPricingMode} from "../chain-deps/ZkSyncHyperchainStorage.sol"; +import {FeeParams, PubdataPricingMode} from "../chain-deps/ZKChainStorage.sol"; +import {ZKChainCommitment} from "../../common/Config.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 IZkSyncHyperchainBase { +interface IAdmin is IZKChainBase { /// @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 @@ -52,7 +53,7 @@ interface IAdmin is IZkSyncHyperchainBase { function upgradeChainFromVersion(uint256 _protocolVersion, Diamond.DiamondCutData calldata _cutData) external; /// @notice Executes a proposed governor upgrade - /// @dev Only the current admin can execute the upgrade + /// @dev Only the ChainTypeManager contract can execute the upgrade /// @param _diamondCut The diamond cut parameters to be executed function executeUpgrade(Diamond.DiamondCutData calldata _diamondCut) external; @@ -61,9 +62,31 @@ interface IAdmin is IZkSyncHyperchainBase { function freezeDiamond() external; /// @notice Unpause the functionality of all freezable facets & their selectors - /// @dev Both the admin and the STM can unfreeze Diamond Proxy + /// @dev Only the CTM can unfreeze Diamond Proxy function unfreezeDiamond() external; + function genesisUpgrade( + address _l1GenesisUpgrade, + address _ctmDeployer, + bytes calldata _forceDeploymentData, + bytes[] calldata _factoryDeps + ) external; + + /// @notice Set the L1 DA validator address as well as the L2 DA validator address. + /// @dev While in principle it is possible that updating only one of the addresses is needed, + /// usually these should work in pair and L1 validator typically expects a specific input from the L2 Validator. + /// That's why we change those together to prevent admins of chains from shooting themselves in the foot. + /// @param _l1DAValidator The address of the L1 DA validator + /// @param _l2DAValidator The address of the L2 DA validator + function setDAValidatorPair(address _l1DAValidator, address _l2DAValidator) external; + + /// @notice Makes the chain as permanent rollup. + /// @dev This is a security feature needed for chains that should be + /// trusted to keep their data available even if the chain admin becomes malicious + /// and tries to set the DA validator pair to something which does not publish DA to Ethereum. + /// @dev DANGEROUS: once activated, there is no way back! + function makePermanentRollup() external; + /// @notice Porter availability status changes event IsPorterAvailableStatusUpdate(bool isPorterAvailable); @@ -84,7 +107,7 @@ interface IAdmin is IZkSyncHyperchainBase { event NewFeeParams(FeeParams oldFeeParams, FeeParams newFeeParams); /// @notice Validium mode status changed - event ValidiumModeStatusUpdate(PubdataPricingMode validiumMode); + event PubdataPricingModeUpdate(PubdataPricingMode validiumMode); /// @notice The transaction filterer has been updated event NewTransactionFilterer(address oldTransactionFilterer, address newTransactionFilterer); @@ -100,9 +123,38 @@ interface IAdmin is IZkSyncHyperchainBase { /// @notice Emitted when an upgrade is executed. event ExecuteUpgrade(Diamond.DiamondCutData diamondCut); + /// @notice Emitted when the migration to the new settlement layer is complete. + event MigrationComplete(); + /// @notice Emitted when the contract is frozen. event Freeze(); /// @notice Emitted when the contract is unfrozen. event Unfreeze(); + + /// @notice New pair of DA validators set + event NewL2DAValidator(address indexed oldL2DAValidator, address indexed newL2DAValidator); + event NewL1DAValidator(address indexed oldL1DAValidator, address indexed newL1DAValidator); + + event BridgeMint(address indexed _account, uint256 _amount); + + /// @dev Similar to IL1AssetHandler interface, used to send chains. + function forwardedBridgeBurn( + address _settlementLayer, + address _originalCaller, + bytes calldata _data + ) external payable returns (bytes memory _bridgeMintData); + + /// @dev Similar to IL1AssetHandler interface, used to claim failed chain transfers. + function forwardedBridgeRecoverFailedTransfer( + uint256 _chainId, + bytes32 _assetInfo, + address _originalCaller, + bytes calldata _chainData + ) external payable; + + /// @dev Similar to IL1AssetHandler interface, used to receive chains. + function forwardedBridgeMint(bytes calldata _data, bool _contractAlreadyDeployed) external payable; + + function prepareChainCommitment() external view returns (ZKChainCommitment memory commitment); } diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IDiamondInit.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IDiamondInit.sol index 189ae69fa..e175ac91f 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IDiamondInit.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IDiamondInit.sol @@ -3,16 +3,15 @@ pragma solidity ^0.8.21; import {IVerifier, VerifierParams} from "./IVerifier.sol"; -import {FeeParams} from "../chain-deps/ZkSyncHyperchainStorage.sol"; +import {FeeParams} from "../chain-deps/ZKChainStorage.sol"; /// @param chainId the id of the chain /// @param bridgehub the address of the bridgehub contract -/// @param stateTransitionManager contract's address +/// @param chainTypeManager 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 baseTokenAssetId asset id of the base token of the chain /// @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 @@ -25,12 +24,11 @@ import {FeeParams} from "../chain-deps/ZkSyncHyperchainStorage.sol"; struct InitializeData { uint256 chainId; address bridgehub; - address stateTransitionManager; + address chainTypeManager; uint256 protocolVersion; address admin; address validatorTimelock; - address baseToken; - address baseTokenBridge; + bytes32 baseTokenAssetId; bytes32 storedBatchZero; IVerifier verifier; VerifierParams verifierParams; diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IExecutor.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IExecutor.sol index d74e4288b..8ecee04b2 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IExecutor.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IExecutor.sol @@ -2,32 +2,23 @@ // We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. pragma solidity ^0.8.21; -import {IZkSyncHyperchainBase} from "./IZkSyncHyperchainBase.sol"; +import {IZKChainBase} from "./IZKChainBase.sol"; /// @dev Enum used by L2 System Contracts to differentiate logs. enum SystemLogKey { L2_TO_L1_LOGS_TREE_ROOT_KEY, - TOTAL_L2_TO_L1_PUBDATA_KEY, - STATE_DIFF_HASH_KEY, PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY, - PREV_BATCH_HASH_KEY, CHAINED_PRIORITY_TXN_HASH_KEY, NUMBER_OF_LAYER_1_TXS_KEY, - BLOB_ONE_HASH_KEY, - BLOB_TWO_HASH_KEY, - BLOB_THREE_HASH_KEY, - BLOB_FOUR_HASH_KEY, - BLOB_FIVE_HASH_KEY, - BLOB_SIX_HASH_KEY, + // Note, that it is important that `PREV_BATCH_HASH_KEY` has position + // `4` since it is the same as it was in the previous protocol version and + // it is the only one that is emitted before the system contracts are upgraded. + PREV_BATCH_HASH_KEY, + L2_DA_VALIDATOR_OUTPUT_HASH_KEY, + USED_L2_DA_VALIDATOR_ADDRESS_KEY, EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY } -/// @dev Enum used to determine the source of pubdata. At first we will support calldata and blobs but this can be extended. -enum PubdataSource { - Calldata, - Blob -} - struct LogProcessingOutput { uint256 numberOfLayer1Txs; bytes32 chainedPriorityTxsHash; @@ -36,14 +27,9 @@ struct LogProcessingOutput { bytes32 stateDiffHash; bytes32 l2LogsTreeRoot; uint256 packedBatchAndL2BlockTimestamp; - bytes32[] blobHashes; + bytes32 l2DAValidatorOutputHash; } -/// @dev Total number of bytes in a blob. Blob = 4096 field elements * 31 bytes per field element -/// @dev EIP-4844 defines it as 131_072 but we use 4096 * 31 within our circuits to always fit within a field element -/// @dev Our circuits will prove that a EIP-4844 blob and our internal blob are the same. -uint256 constant BLOB_SIZE_BYTES = 126_976; - /// @dev Offset used to pull Address From Log. Equal to 4 (bytes for isService) uint256 constant L2_LOG_ADDRESS_OFFSET = 4; @@ -53,20 +39,6 @@ uint256 constant L2_LOG_KEY_OFFSET = 24; /// @dev Offset used to pull Value From Log. Equal to 4 (bytes for isService) + 20 (bytes for address) + 32 (bytes for key) uint256 constant L2_LOG_VALUE_OFFSET = 56; -/// @dev BLS Modulus value defined in EIP-4844 and the magic value returned from a successful call to the -/// point evaluation precompile -uint256 constant BLS_MODULUS = 52435875175126190479447740508185965837690552500527637822603658699938581184513; - -/// @dev Packed pubdata commitments. -/// @dev Format: list of: opening point (16 bytes) || claimed value (32 bytes) || commitment (48 bytes) || proof (48 bytes)) = 144 bytes -uint256 constant PUBDATA_COMMITMENT_SIZE = 144; - -/// @dev Offset in pubdata commitment of blobs for claimed value -uint256 constant PUBDATA_COMMITMENT_CLAIMED_VALUE_OFFSET = 16; - -/// @dev Offset in pubdata commitment of blobs for kzg commitment -uint256 constant PUBDATA_COMMITMENT_COMMITMENT_OFFSET = 48; - /// @dev Max number of blobs currently supported uint256 constant MAX_NUMBER_OF_BLOBS = 6; @@ -78,7 +50,7 @@ uint256 constant TOTAL_BLOBS_IN_COMMITMENT = 16; /// @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 IZkSyncHyperchainBase { +interface IExecutor is IZKChainBase { /// @notice Rollup batch stored data /// @param batchNumber Rollup batch number /// @param batchHash Hash of L2 batch @@ -110,7 +82,7 @@ interface IExecutor is IZkSyncHyperchainBase { /// @param bootloaderHeapInitialContentsHash Hash of the initial contents of the bootloader heap. In practice it serves as the commitment to the transactions in the batch. /// @param eventsQueueStateHash Hash of the events queue state. In practice it serves as the commitment to the events in the batch. /// @param systemLogs concatenation of all L2 -> L1 system logs in the batch - /// @param pubdataCommitments Packed pubdata commitments/data. + /// @param operatorDAInput Packed pubdata commitments/data. /// @dev pubdataCommitments format: This will always start with a 1 byte pubdataSource flag. Current allowed values are 0 (calldata) or 1 (blobs) /// kzg: list of: opening point (16 bytes) || claimed value (32 bytes) || commitment (48 bytes) || proof (48 bytes) = 144 bytes /// calldata: pubdataCommitments.length - 1 - 32 bytes of pubdata @@ -128,68 +100,56 @@ interface IExecutor is IZkSyncHyperchainBase { bytes32 bootloaderHeapInitialContentsHash; bytes32 eventsQueueStateHash; bytes systemLogs; - bytes pubdataCommitments; - } - - /// @notice Recursive proof input data (individual commitments are constructed onchain) - struct ProofInput { - uint256[] recursiveAggregationInput; - uint256[] serializedProof; + bytes operatorDAInput; } /// @notice Function called by the operator to commit new batches. It is responsible for: /// - Verifying the correctness of their timestamps. /// - Processing their L2->L1 logs. /// - Storing batch commitments. - /// @param _lastCommittedBatchData Stored data of the last committed batch. - /// @param _newBatchesData Data of the new batches to be committed. - function commitBatches( - StoredBatchInfo calldata _lastCommittedBatchData, - CommitBatchInfo[] calldata _newBatchesData - ) external; - - /// @notice same as `commitBatches` but with the chainId so ValidatorTimelock can sort the inputs. + /// @param _chainId Chain ID of the chain. + /// @param _processFrom The batch number from which the processing starts. + /// @param _processTo The batch number at which the processing ends. + /// @param _commitData The encoded data of the new batches to be committed. function commitBatchesSharedBridge( uint256 _chainId, - StoredBatchInfo calldata _lastCommittedBatchData, - CommitBatchInfo[] calldata _newBatchesData + uint256 _processFrom, + uint256 _processTo, + bytes calldata _commitData ) external; /// @notice Batches commitment verification. /// @dev Only verifies batch commitments without any other processing. - /// @param _prevBatch Stored data of the last committed batch. - /// @param _committedBatches Stored data of the committed batches. - /// @param _proof The zero knowledge proof. - function proveBatches( - StoredBatchInfo calldata _prevBatch, - StoredBatchInfo[] calldata _committedBatches, - ProofInput calldata _proof - ) external; - - /// @notice same as `proveBatches` but with the chainId so ValidatorTimelock can sort the inputs. + /// @param _chainId Chain ID of the chain. + /// @param _processBatchFrom The batch number from which the verification starts. + /// @param _processBatchTo The batch number at which the verification ends. + /// @param _proofData The encoded data of the new batches to be verified. function proveBatchesSharedBridge( uint256 _chainId, - StoredBatchInfo calldata _prevBatch, - StoredBatchInfo[] calldata _committedBatches, - ProofInput calldata _proof + uint256 _processBatchFrom, + uint256 _processBatchTo, + bytes calldata _proofData ) 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; + /// @param _chainId Chain ID of the chain. + /// @param _processFrom The batch number from which the execution starts. + /// @param _processTo The batch number at which the execution ends. + /// @param _executeData The encoded data of the new batches to be executed. + function executeBatchesSharedBridge( + uint256 _chainId, + uint256 _processFrom, + uint256 _processTo, + bytes calldata _executeData + ) external; /// @notice Reverts unexecuted batches + /// @param _chainId Chain ID of the chain /// @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 diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol index 4d06f9e8e..0d6cb4775 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol @@ -2,15 +2,17 @@ // We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. pragma solidity ^0.8.21; -import {PriorityOperation} from "../libraries/PriorityQueue.sol"; import {VerifierParams} from "../chain-interfaces/IVerifier.sol"; -import {PubdataPricingMode} from "../chain-deps/ZkSyncHyperchainStorage.sol"; -import {IZkSyncHyperchainBase} from "./IZkSyncHyperchainBase.sol"; +import {PubdataPricingMode} from "../chain-deps/ZKChainStorage.sol"; +import {IZKChainBase} from "./IZKChainBase.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 IZkSyncHyperchainBase { +/// @dev Most of the methods simply return the values that correspond to the current diamond proxy and possibly +/// not to the ZK Chain as a whole. For example, if the chain is migrated to another settlement layer, the values returned +/// by this facet will correspond to the values stored on this chain and possilbly not the canonical state of the chain. +interface IGetters is IZKChainBase { /*////////////////////////////////////////////////////////////// CUSTOM GETTERS //////////////////////////////////////////////////////////////*/ @@ -28,13 +30,16 @@ interface IGetters is IZkSyncHyperchainBase { function getBridgehub() external view returns (address); /// @return The address of the state transition - function getStateTransitionManager() external view returns (address); + function getChainTypeManager() external view returns (address); + + /// @return The chain ID + function getChainId() external view returns (uint256); /// @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 address of the base token + function getBaseTokenAssetId() external view returns (bytes32); /// @return The total number of batches that were committed function getTotalBatchesCommitted() external view returns (uint256); @@ -45,9 +50,24 @@ interface IGetters is IZkSyncHyperchainBase { /// @return The total number of batches that were committed & verified & executed function getTotalBatchesExecuted() external view returns (uint256); + // @return Address of transaction filterer + function getTransactionFilterer() external view returns (address); + /// @return The total number of priority operations that were added to the priority queue, including all processed ones function getTotalPriorityTxs() external view returns (uint256); + /// @return The start index of the priority tree, i.e. the index of the first priority operation that + /// was included into the priority tree. + function getPriorityTreeStartIndex() external view returns (uint256); + + /// @return The root hash of the priority tree + function getPriorityTreeRoot() external view returns (bytes32); + + /// @return Whether the priority queue is active, i.e. whether new priority operations are appended to it. + /// Once the chain processes all the transaction that were present in the priority queue, all the L1->L2 related + /// operations will start to get done using the priority tree. + function isPriorityQueueActive() external view returns (bool); + /// @notice The function that returns the first unprocessed priority transaction. /// @dev Returns zero if and only if no operations were processed from the queue. /// @dev If all the transactions were processed, it will return the last processed index, so @@ -58,9 +78,6 @@ interface IGetters is IZkSyncHyperchainBase { /// @return The number of priority operations currently in the queue function getPriorityQueueSize() external view returns (uint256); - /// @return The first unprocessed priority operation from the queue - function priorityQueueFrontOperation() external view returns (PriorityOperation memory); - /// @return Whether the address has a validator access function isValidator(address _address) external view returns (bool); @@ -147,4 +164,7 @@ interface IGetters is IZkSyncHyperchainBase { /// @return isFreezable Whether the facet can be frozen by the admin or always accessible function isFacetFreezable(address _facet) external view returns (bool isFreezable); + + /// @return The address of the current settlement layer. + function getSettlementLayer() external view returns (address); } diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IL1DAValidator.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IL1DAValidator.sol new file mode 100644 index 000000000..b5ea1b85c --- /dev/null +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IL1DAValidator.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +/// @dev Enum used to determine the source of pubdata. At first we will support calldata and blobs but this can be extended. +enum PubdataSource { + Calldata, + Blob +} + +struct L1DAValidatorOutput { + /// @dev The hash of the uncompressed state diff. + bytes32 stateDiffHash; + /// @dev The hashes of the blobs on L1. The array is dynamic to account for forward compatibility. + /// The length of it must be equal to `maxBlobsSupported`. + bytes32[] blobsLinearHashes; + /// @dev The commitments to the blobs on L1. The array is dynamic to account for forward compatibility. + /// Its length must be equal to the length of blobsLinearHashes. + /// @dev If the system supports more blobs than returned, the rest of the array should be filled with zeros. + bytes32[] blobsOpeningCommitments; +} + +interface IL1DAValidator { + /// @notice The function that checks the data availability for the given batch input. + /// @param _chainId The chain id of the chain that is being committed. + /// @param _batchNumber The batch number for which the data availability is being checked. + /// @param _l2DAValidatorOutputHash The hash of that was returned by the l2DAValidator. + /// @param _operatorDAInput The DA input by the operator provided on L1. + /// @param _maxBlobsSupported The maximal number of blobs supported by the chain. + /// We provide this value for future compatibility. + /// This is needed because the corresponding `blobsLinearHashes`/`blobsOpeningCommitments` + /// in the `L1DAValidatorOutput` struct will have to have this length as it is required + /// to be static by the circuits. + function checkDA( + uint256 _chainId, + uint256 _batchNumber, + bytes32 _l2DAValidatorOutputHash, + bytes calldata _operatorDAInput, + uint256 _maxBlobsSupported + ) external returns (L1DAValidatorOutput memory output); +} diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/ILegacyGetters.sol b/l1-contracts/contracts/state-transition/chain-interfaces/ILegacyGetters.sol index cb62f5087..9c143d93e 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/ILegacyGetters.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/ILegacyGetters.sol @@ -2,13 +2,13 @@ // We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. pragma solidity ^0.8.21; -import {IZkSyncHyperchainBase} from "./IZkSyncHyperchainBase.sol"; +import {IZKChainBase} from "./IZKChainBase.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 IZkSyncHyperchainBase { +interface ILegacyGetters is IZKChainBase { /// @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/state-transition/chain-interfaces/IMailbox.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IMailbox.sol index 9daffebcf..e63832aa7 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IMailbox.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IMailbox.sol @@ -2,13 +2,13 @@ // We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. pragma solidity ^0.8.21; -import {IZkSyncHyperchainBase} from "./IZkSyncHyperchainBase.sol"; +import {IZKChainBase} from "./IZKChainBase.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 IZkSyncHyperchainBase { +interface IMailbox is IZKChainBase { /// @notice Prove that a specific arbitrary-length message was sent in a specific L2 batch number /// @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 @@ -95,10 +95,24 @@ interface IMailbox is IZkSyncHyperchainBase { address _refundRecipient ) external payable returns (bytes32 canonicalTxHash); + /// @notice when requesting transactions through the bridgehub function bridgehubRequestL2Transaction( BridgehubL2TransactionRequest calldata _request ) external returns (bytes32 canonicalTxHash); + /// @dev On the Gateway the chain's mailbox receives the tx from the bridgehub. + function bridgehubRequestL2TransactionOnGateway(bytes32 _canonicalTxHash, uint64 _expirationTimestamp) external; + + /// @dev On L1 we have to forward to the Gateway's mailbox which sends to the Bridgehub on the Gw + /// @param _chainId the chainId of the chain + /// @param _canonicalTxHash the canonical transaction hash + /// @param _expirationTimestamp the expiration timestamp + function requestL2TransactionToGatewayMailbox( + uint256 _chainId, + bytes32 _canonicalTxHash, + uint64 _expirationTimestamp + ) external 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 @@ -110,8 +124,33 @@ interface IMailbox is IZkSyncHyperchainBase { uint256 _l2GasPerPubdataByteLimit ) external view returns (uint256); + /// Proves that a certain leaf was included as part of the log merkle tree. + function proveL2LeafInclusion( + uint256 _batchNumber, + uint256 _batchRootMask, + bytes32 _leaf, + bytes32[] calldata _proof + ) external view returns (bool); + /// @notice transfer Eth to shared bridge as part of migration process - function transferEthToSharedBridge() external; + // function transferEthToSharedBridge() external; + + // function relayTxSL( + // address _to, + // L2CanonicalTransaction memory _transaction, + // bytes[] memory _factoryDeps, + // bytes32 _canonicalTxHash, + // uint64 _expirationTimestamp + // ) external; + + // function freeAcceptTx( + // L2CanonicalTransaction memory _transaction, + // bytes[] memory _factoryDeps, + // bytes32 _canonicalTxHash, + // uint64 _expirationTimestamp + // ) external; + + // function acceptFreeRequestFromBridgehub(BridgehubL2TransactionRequest calldata _request) external; /// @notice New priority request event. Emitted when a request is placed into the priority queue /// @param txId Serial number of the priority operation @@ -127,4 +166,13 @@ interface IMailbox is IZkSyncHyperchainBase { L2CanonicalTransaction transaction, bytes[] factoryDeps ); + + /// @notice New relayed priority request event. It is emitted on a chain that is deployed + /// on top of the gateway when it receives a request relayed via the Bridgehub. + /// @dev IMPORTANT: this event most likely will be removed in the future, so + /// no one should rely on it for indexing purposes. + /// @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 + event NewRelayedPriorityTransaction(uint256 txId, bytes32 txHash, uint64 expirationTimestamp); } diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IVerifier.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IVerifier.sol index 97872c370..fe5e2af2c 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IVerifier.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IVerifier.sol @@ -16,11 +16,7 @@ interface IVerifier { /// @dev Verifies a zk-SNARK proof. /// @return A boolean value indicating whether the zk-SNARK proof is valid. /// Note: The function may revert execution instead of returning false in some cases. - function verify( - uint256[] calldata _publicInputs, - uint256[] calldata _proof, - uint256[] calldata _recursiveAggregationInput - ) external view returns (bool); + function verify(uint256[] calldata _publicInputs, uint256[] calldata _proof) external view returns (bool); /// @notice Calculates a keccak256 hash of the runtime loaded verification keys. /// @return vkHash The keccak256 hash of the loaded verification keys. diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncHyperchain.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IZKChain.sol similarity index 89% rename from l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncHyperchain.sol rename to l1-contracts/contracts/state-transition/chain-interfaces/IZKChain.sol index 14aa123b0..31d14009b 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncHyperchain.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IZKChain.sol @@ -9,7 +9,7 @@ import {IMailbox} from "./IMailbox.sol"; import {Diamond} from "../libraries/Diamond.sol"; -interface IZkSyncHyperchain is IAdmin, IExecutor, IGetters, IMailbox { +interface IZKChain is IAdmin, IExecutor, IGetters, IMailbox { // We need this structure for the server for now event ProposeTransparentUpgrade( Diamond.DiamondCutData diamondCut, diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncHyperchainBase.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IZKChainBase.sol similarity index 81% rename from l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncHyperchainBase.sol rename to l1-contracts/contracts/state-transition/chain-interfaces/IZKChainBase.sol index 3cd646cc9..06f0c9784 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncHyperchainBase.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IZKChainBase.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT -// We use a floating point pragma here so it can be used within other projects that interact with the zkSync ecosystem without using our exact pragma version. +// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. pragma solidity ^0.8.21; /// @title The interface of the ZKsync contract, responsible for the main ZKsync logic. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -interface IZkSyncHyperchainBase { +interface IZKChainBase { /// @return Returns facet name. function getName() external view returns (string memory); } diff --git a/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol b/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol new file mode 100644 index 000000000..fd3d91bd0 --- /dev/null +++ b/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {OperatorDAInputTooSmall, InvalidNumberOfBlobs, InvalidL2DAOutputHash, OnlyOneBlobWithCalldataAllowed, PubdataInputTooSmall, PubdataLengthTooBig, InvalidPubdataHash} from "../L1StateTransitionErrors.sol"; + +/// @dev Total number of bytes in a blob. Blob = 4096 field elements * 31 bytes per field element +/// @dev EIP-4844 defines it as 131_072 but we use 4096 * 31 within our circuits to always fit within a field element +/// @dev Our circuits will prove that a EIP-4844 blob and our internal blob are the same. +uint256 constant BLOB_SIZE_BYTES = 126_976; + +/// @dev The state diff hash, hash of pubdata + the number of blobs. +uint256 constant BLOB_DATA_OFFSET = 65; + +/// @dev The size of the commitment for a single blob. +uint256 constant BLOB_COMMITMENT_SIZE = 32; + +/// @notice Contract that contains the functionality for process the calldata DA. +/// @dev The expected l2DAValidator that should be used with it `RollupL2DAValidator`. +abstract contract CalldataDA { + /// @notice Parses the input that the L2 DA validator has provided to the contract. + /// @param _l2DAValidatorOutputHash The hash of the output of the L2 DA validator. + /// @param _maxBlobsSupported The maximal number of blobs supported by the chain. + /// @param _operatorDAInput The DA input by the operator provided on L1. + function _processL2RollupDAValidatorOutputHash( + bytes32 _l2DAValidatorOutputHash, + uint256 _maxBlobsSupported, + bytes calldata _operatorDAInput + ) + internal + pure + returns ( + bytes32 stateDiffHash, + bytes32 fullPubdataHash, + bytes32[] memory blobsLinearHashes, + uint256 blobsProvided, + bytes calldata l1DaInput + ) + { + // The preimage under the hash `_l2DAValidatorOutputHash` is expected to be in the following format: + // - First 32 bytes are the hash of the uncompressed state diff. + // - Then, there is a 32-byte hash of the full pubdata. + // - Then, there is the 1-byte number of blobs published. + // - Then, there are linear hashes of the published blobs, 32 bytes each. + + // Check that it accommodates enough pubdata for the state diff hash, hash of pubdata + the number of blobs. + if (_operatorDAInput.length < BLOB_DATA_OFFSET) { + revert OperatorDAInputTooSmall(_operatorDAInput.length, BLOB_DATA_OFFSET); + } + + stateDiffHash = bytes32(_operatorDAInput[:32]); + fullPubdataHash = bytes32(_operatorDAInput[32:64]); + blobsProvided = uint256(uint8(_operatorDAInput[64])); + + if (blobsProvided > _maxBlobsSupported) { + revert InvalidNumberOfBlobs(blobsProvided, _maxBlobsSupported); + } + + // Note that the API of the contract requires that the returned blobs linear hashes have length of + // the `_maxBlobsSupported` + blobsLinearHashes = new bytes32[](_maxBlobsSupported); + + if (_operatorDAInput.length < BLOB_DATA_OFFSET + 32 * blobsProvided) { + revert OperatorDAInputTooSmall(_operatorDAInput.length, BLOB_DATA_OFFSET + 32 * blobsProvided); + } + + _cloneCalldata(blobsLinearHashes, _operatorDAInput[BLOB_DATA_OFFSET:], blobsProvided); + + uint256 ptr = BLOB_DATA_OFFSET + 32 * blobsProvided; + + // Now, we need to double check that the provided input was indeed returned by the L2 DA validator. + if (keccak256(_operatorDAInput[:ptr]) != _l2DAValidatorOutputHash) { + revert InvalidL2DAOutputHash(_l2DAValidatorOutputHash); + } + + // The rest of the output was provided specifically by the operator + l1DaInput = _operatorDAInput[ptr:]; + } + + /// @notice Verify that the calldata DA was correctly provided. + /// @param _blobsProvided The number of blobs provided. + /// @param _fullPubdataHash Hash of the pubdata preimage. + /// @param _maxBlobsSupported Maximum number of blobs supported. + /// @param _pubdataInput Full pubdata + an additional 32 bytes containing the blob commitment for the pubdata. + /// @dev We supply the blob commitment as part of the pubdata because even with calldata the prover will check these values. + function _processCalldataDA( + uint256 _blobsProvided, + bytes32 _fullPubdataHash, + uint256 _maxBlobsSupported, + bytes calldata _pubdataInput + ) internal pure virtual returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { + if (_blobsProvided != 1) { + revert OnlyOneBlobWithCalldataAllowed(); + } + if (_pubdataInput.length < BLOB_COMMITMENT_SIZE) { + revert PubdataInputTooSmall(_pubdataInput.length, BLOB_COMMITMENT_SIZE); + } + + // We typically do not know whether we'll use calldata or blobs at the time when + // we start proving the batch. That's why the blob commitment for a single blob is still present in the case of calldata. + + blobCommitments = new bytes32[](_maxBlobsSupported); + + _pubdata = _pubdataInput[:_pubdataInput.length - BLOB_COMMITMENT_SIZE]; + + if (_pubdata.length > BLOB_SIZE_BYTES) { + revert PubdataLengthTooBig(_pubdata.length, BLOB_SIZE_BYTES); + } + if (_fullPubdataHash != keccak256(_pubdata)) { + revert InvalidPubdataHash(_fullPubdataHash, keccak256(_pubdata)); + } + blobCommitments[0] = bytes32(_pubdataInput[_pubdataInput.length - BLOB_COMMITMENT_SIZE:_pubdataInput.length]); + } + + /// @notice Method that clones a slice of calldata into a bytes32[] memory array. + /// @param _dst The destination array. + /// @param _input The input calldata. + /// @param _len The length of the slice in 32-byte words to clone. + function _cloneCalldata(bytes32[] memory _dst, bytes calldata _input, uint256 _len) internal pure { + assembly { + // The pointer to the allocated memory above. We skip 32 bytes to avoid overwriting the length. + let dstPtr := add(_dst, 0x20) + let inputPtr := _input.offset + calldatacopy(dstPtr, inputPtr, mul(_len, 32)) + } + } +} diff --git a/l1-contracts/contracts/state-transition/data-availability/CalldataDAGateway.sol b/l1-contracts/contracts/state-transition/data-availability/CalldataDAGateway.sol new file mode 100644 index 000000000..ac9bb34e9 --- /dev/null +++ b/l1-contracts/contracts/state-transition/data-availability/CalldataDAGateway.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {CalldataDA, BLOB_COMMITMENT_SIZE, BLOB_SIZE_BYTES} from "./CalldataDA.sol"; +import {PubdataInputTooSmall, PubdataLengthTooBig, InvalidPubdataHash} from "../L1StateTransitionErrors.sol"; + +/// @notice Contract that contains the functionality for processing the calldata DA. +/// @dev The expected l2DAValidator that should be used with it `RollupL2DAValidator`. +abstract contract CalldataDAGateway is CalldataDA { + /// @inheritdoc CalldataDA + function _processCalldataDA( + uint256 _blobsProvided, + bytes32 _fullPubdataHash, + uint256 _maxBlobsSupported, + bytes calldata _pubdataInput + ) internal pure override returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { + if (_pubdataInput.length < _blobsProvided * BLOB_COMMITMENT_SIZE) { + revert PubdataInputTooSmall(_pubdataInput.length, _blobsProvided * BLOB_COMMITMENT_SIZE); + } + + // We typically do not know whether we'll use calldata or blobs at the time when + // we start proving the batch. That's why the blob commitment for a single blob is still present in the case of calldata. + blobCommitments = new bytes32[](_maxBlobsSupported); + + _pubdata = _pubdataInput[:_pubdataInput.length - _blobsProvided * BLOB_COMMITMENT_SIZE]; + + if (_pubdata.length > _blobsProvided * BLOB_SIZE_BYTES) { + revert PubdataLengthTooBig(_pubdata.length, _blobsProvided * BLOB_SIZE_BYTES); + } + if (_fullPubdataHash != keccak256(_pubdata)) { + revert InvalidPubdataHash(_fullPubdataHash, keccak256(_pubdata)); + } + + bytes calldata providedCommitments = _pubdataInput[_pubdataInput.length - + _blobsProvided * + BLOB_COMMITMENT_SIZE:]; + + _cloneCalldata(blobCommitments, providedCommitments, _blobsProvided); + } +} diff --git a/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol b/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol new file mode 100644 index 000000000..0a37e8986 --- /dev/null +++ b/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IL1DAValidator, L1DAValidatorOutput, PubdataSource} from "../chain-interfaces/IL1DAValidator.sol"; +import {IL1Messenger} from "../../common/interfaces/IL1Messenger.sol"; + +import {CalldataDAGateway} from "./CalldataDAGateway.sol"; + +import {IBridgehub} from "../../bridgehub/IBridgehub.sol"; +import {L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, L2_BRIDGEHUB_ADDR} from "../../common/L2ContractAddresses.sol"; +import {BlobHashBlobCommitmentMismatchValue, L1DAValidatorInvalidSender, InvalidPubdataSource} from "../L1StateTransitionErrors.sol"; + +/// @notice The DA validator intended to be used in Era-environment. +/// @dev For compatibility reasons it accepts calldata in the same format as the `RollupL1DAValidator`, but unlike the latter it +/// does not support blobs. +/// @dev Note that it does not provide any compression whatsoever. +contract RelayedSLDAValidator is IL1DAValidator, CalldataDAGateway { + /// @dev Ensures that the sender is the chain that is supposed to send the message. + /// @param _chainId The chain id of the chain that is supposed to send the message. + function _ensureOnlyChainSender(uint256 _chainId) internal view { + // Note that this contract is only supposed to be deployed on L2, where the + // bridgehub is predeployed at `L2_BRIDGEHUB_ADDR` address. + if (IBridgehub(L2_BRIDGEHUB_ADDR).getZKChain(_chainId) != msg.sender) { + revert L1DAValidatorInvalidSender(msg.sender); + } + } + + /// @dev Relays the calldata to L1. + /// @param _chainId The chain id of the chain that is supposed to send the message. + /// @param _batchNumber The batch number for which the data availability is being checked. + /// @param _pubdata The pubdata to be relayed to L1. + function _relayCalldata(uint256 _chainId, uint256 _batchNumber, bytes calldata _pubdata) internal { + // Re-sending all the pubdata in pure form to L1. + // slither-disable-next-line unused-return + IL1Messenger(L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR).sendToL1(abi.encode(_chainId, _batchNumber, _pubdata)); + } + + /// @inheritdoc IL1DAValidator + function checkDA( + uint256 _chainId, + uint256 _batchNumber, + bytes32 _l2DAValidatorOutputHash, + bytes calldata _operatorDAInput, + uint256 _maxBlobsSupported + ) external returns (L1DAValidatorOutput memory output) { + // Unfortunately we have to use a method call instead of a modifier + // because of the stack-too-deep error caused by it. + _ensureOnlyChainSender(_chainId); + + // Preventing "stack too deep" error + uint256 blobsProvided; + bytes32 fullPubdataHash; + bytes calldata l1DaInput; + { + bytes32 stateDiffHash; + bytes32[] memory blobsLinearHashes; + ( + stateDiffHash, + fullPubdataHash, + blobsLinearHashes, + blobsProvided, + l1DaInput + ) = _processL2RollupDAValidatorOutputHash(_l2DAValidatorOutputHash, _maxBlobsSupported, _operatorDAInput); + + output.stateDiffHash = stateDiffHash; + output.blobsLinearHashes = blobsLinearHashes; + } + + uint8 pubdataSource = uint8(l1DaInput[0]); + + // Note, that the blobs are not supported in the RelayedSLDAValidator. + if (pubdataSource == uint8(PubdataSource.Calldata)) { + bytes calldata pubdata; + bytes32[] memory blobCommitments; + + (blobCommitments, pubdata) = _processCalldataDA( + blobsProvided, + fullPubdataHash, + _maxBlobsSupported, + l1DaInput[1:] + ); + + _relayCalldata(_chainId, _batchNumber, pubdata); + + output.blobsOpeningCommitments = blobCommitments; + } else { + revert InvalidPubdataSource(pubdataSource); + } + + // We verify that for each set of blobHash/blobCommitment are either both empty + // or there are values for both. + // This is mostly a sanity check and it is not strictly required. + for (uint256 i = 0; i < _maxBlobsSupported; ++i) { + if ( + (output.blobsLinearHashes[i] != bytes32(0) || output.blobsOpeningCommitments[i] != bytes32(0)) && + (output.blobsLinearHashes[i] == bytes32(0) || output.blobsOpeningCommitments[i] == bytes32(0)) + ) { + revert BlobHashBlobCommitmentMismatchValue(); + } + } + } +} diff --git a/l1-contracts/contracts/state-transition/data-availability/RollupDAManager.sol b/l1-contracts/contracts/state-transition/data-availability/RollupDAManager.sol new file mode 100644 index 000000000..83987a020 --- /dev/null +++ b/l1-contracts/contracts/state-transition/data-availability/RollupDAManager.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import {Ownable2Step} from "@openzeppelin/contracts-v4/access/Ownable2Step.sol"; +import {ZeroAddress} from "../../common/L1ContractErrors.sol"; + +/// @title The RollupManager contract +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice Responsible for determining which DA pairs are allowed to be used +/// for permanent rollups. +contract RollupDAManager is Ownable2Step { + /// @dev Mapping to track the status (enabled/disabled) of each DAPair. + mapping(address l1DAValidator => mapping(address l2DAValidator => bool)) public allowedDAPairs; + + /// @dev Emitted when a DAPair is added or updated. + /// @param l1DAValidator Address of the L1 data availability validator. + /// @param l2DAValidator Address of the L2 data availability validator. + /// @param status Boolean representing the state of the DAPair. + event DAPairUpdated(address indexed l1DAValidator, address indexed l2DAValidator, bool status); + + /// @dev Modifier to ensure addresses in DAPair are not zero addresses. + /// @param l1DAValidator Address of the L1 data availability validator. + /// @param l2DAValidator Address of the L2 data availability validator. + modifier validAddresses(address l1DAValidator, address l2DAValidator) { + if (l1DAValidator == address(0) || l2DAValidator == address(0)) { + revert ZeroAddress(); + } + _; + } + + /// @dev Adds or updates a DAPair in the `allowedDAPairs` mapping. Only callable by the contract owner. + /// + /// Emits a {DAPairUpdated} event. + /// + /// @param _l1DAValidator Address of the L1 data availability validator. + /// @param _l2DAValidator Address of the L2 data availability validator. + /// @param _status Boolean representing whether the DAPair is active or not. + /// + /// Requirements: + /// + /// - The `l1DAValidator` and `l2DAValidator` must be valid addresses (non-zero). + /// - Only the owner of the contract can call this function. + function updateDAPair( + address _l1DAValidator, + address _l2DAValidator, + bool _status + ) external onlyOwner validAddresses(_l1DAValidator, _l2DAValidator) { + allowedDAPairs[_l1DAValidator][_l2DAValidator] = _status; + + emit DAPairUpdated(_l1DAValidator, _l2DAValidator, _status); + } + + /// @notice Returns whether the DA pair is allowed. + /// + /// @param _l1DAValidator Address of the L1 data availability validator. + /// @param _l2DAValidator Address of the L2 data availability validator. + /// @return bool indicating if the DA pair is allowed. + function isPairAllowed(address _l1DAValidator, address _l2DAValidator) external view returns (bool) { + return allowedDAPairs[_l1DAValidator][_l2DAValidator]; + } +} diff --git a/l1-contracts/contracts/state-transition/data-availability/ValidiumL1DAValidator.sol b/l1-contracts/contracts/state-transition/data-availability/ValidiumL1DAValidator.sol new file mode 100644 index 000000000..24f0919e3 --- /dev/null +++ b/l1-contracts/contracts/state-transition/data-availability/ValidiumL1DAValidator.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IL1DAValidator, L1DAValidatorOutput} from "../chain-interfaces/IL1DAValidator.sol"; +import {ValL1DAWrongInputLength} from "../L1StateTransitionErrors.sol"; + +contract ValidiumL1DAValidator is IL1DAValidator { + function checkDA( + uint256, // _chainId + uint256, // _batchNumber + bytes32, // _l2DAValidatorOutputHash + bytes calldata _operatorDAInput, + uint256 maxBlobsSupported + ) external override returns (L1DAValidatorOutput memory output) { + // For Validiums, we expect the operator to just provide the data for us. + // We don't need to do any checks with regard to the l2DAValidatorOutputHash. + if (_operatorDAInput.length != 32) { + revert ValL1DAWrongInputLength(_operatorDAInput.length, 32); + } + bytes32 stateDiffHash = abi.decode(_operatorDAInput, (bytes32)); + + // The rest of the fields that relate to blobs are empty. + output.stateDiffHash = stateDiffHash; + + output.blobsLinearHashes = new bytes32[](maxBlobsSupported); + output.blobsOpeningCommitments = new bytes32[](maxBlobsSupported); + } +} diff --git a/l1-contracts/contracts/state-transition/l2-deps/IComplexUpgrader.sol b/l1-contracts/contracts/state-transition/l2-deps/IComplexUpgrader.sol new file mode 100644 index 000000000..f07b879e7 --- /dev/null +++ b/l1-contracts/contracts/state-transition/l2-deps/IComplexUpgrader.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +interface IComplexUpgrader { + function upgrade(address _delegateTo, bytes calldata _calldata) external payable; +} diff --git a/l1-contracts/contracts/state-transition/l2-deps/IL2GatewayUpgrade.sol b/l1-contracts/contracts/state-transition/l2-deps/IL2GatewayUpgrade.sol new file mode 100644 index 000000000..fdafe2807 --- /dev/null +++ b/l1-contracts/contracts/state-transition/l2-deps/IL2GatewayUpgrade.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {IL2ContractDeployer} from "../../common/interfaces/IL2ContractDeployer.sol"; + +interface IL2GatewayUpgrade { + function upgrade( + IL2ContractDeployer.ForceDeployment[] calldata _forceDeployments, + address _ctmDeployer, + bytes calldata _fixedForceDeploymentsData, + bytes calldata _additionalForceDeploymentsData + ) external payable; +} diff --git a/l1-contracts/contracts/state-transition/l2-deps/IL2GenesisUpgrade.sol b/l1-contracts/contracts/state-transition/l2-deps/IL2GenesisUpgrade.sol new file mode 100644 index 000000000..418e68512 --- /dev/null +++ b/l1-contracts/contracts/state-transition/l2-deps/IL2GenesisUpgrade.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @notice A struct that describes a forced deployment on an address +struct ForceDeployment { + // The bytecode hash to put on an address + bytes32 bytecodeHash; + // The address on which to deploy the bytecodehash to + address newAddress; + // Whether to run the constructor on the force deployment + bool callConstructor; + // The value with which to initialize a contract + uint256 value; + // The constructor calldata + bytes input; +} + +// solhint-disable-next-line gas-struct-packing +struct ZKChainSpecificForceDeploymentsData { + bytes32 baseTokenAssetId; + address l2LegacySharedBridge; + address predeployedL2WethAddress; + address baseTokenL1Address; + /// @dev Some info about the base token, it is + /// needed to deploy weth token in case it is not present + string baseTokenName; + string baseTokenSymbol; +} + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +interface IL2GenesisUpgrade { + event UpgradeComplete(uint256 _chainId); + + function genesisUpgrade( + uint256 _chainId, + address _ctmDeployer, + bytes calldata _fixedForceDeploymentsData, + bytes calldata _additionalForceDeploymentsData + ) external payable; +} diff --git a/l1-contracts/contracts/state-transition/l2-deps/ISystemContext.sol b/l1-contracts/contracts/state-transition/l2-deps/ISystemContext.sol index d3244b74b..8448cb4e4 100644 --- a/l1-contracts/contracts/state-transition/l2-deps/ISystemContext.sol +++ b/l1-contracts/contracts/state-transition/l2-deps/ISystemContext.sol @@ -1,7 +1,9 @@ // SPDX-License-Identifier: MIT -// We use a floating point pragma here so it can be used within other projects that interact with the zkSync ecosystem without using our exact pragma version. +// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. pragma solidity ^0.8.21; +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface ISystemContext { function setChainId(uint256 _newChainId) external; } diff --git a/l1-contracts/contracts/state-transition/libraries/BatchDecoder.sol b/l1-contracts/contracts/state-transition/libraries/BatchDecoder.sol new file mode 100644 index 000000000..bca183f0d --- /dev/null +++ b/l1-contracts/contracts/state-transition/libraries/BatchDecoder.sol @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: MIT +// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. +pragma solidity ^0.8.21; + +import {IExecutor} from "../chain-interfaces/IExecutor.sol"; +import {PriorityOpsBatchInfo} from "./PriorityTree.sol"; +import {IncorrectBatchBounds, EmptyData, UnsupportedCommitBatchEncoding, UnsupportedProofBatchEncoding, UnsupportedExecuteBatchEncoding} from "../../common/L1ContractErrors.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice Utility library for decoding and validating batch data. +/// @dev This library decodes commit, proof, and execution batch data and verifies batch number bounds. +/// It reverts with custom errors when the data is invalid or unsupported encoding is used. +library BatchDecoder { + /// @notice The currently supported encoding version. + uint8 internal constant SUPPORTED_ENCODING_VERSION = 0; + + /// @notice Decodes commit data from a calldata bytes into the last committed batch data and an array of new batch data. + /// @param _commitData The calldata byte array containing the data for committing batches. + /// @return lastCommittedBatchData The data for the batch before newly committed batches. + /// @return newBatchesData An array containing the newly committed batches. + function _decodeCommitData( + bytes calldata _commitData + ) + private + pure + returns ( + IExecutor.StoredBatchInfo memory lastCommittedBatchData, + IExecutor.CommitBatchInfo[] memory newBatchesData + ) + { + if (_commitData.length == 0) { + revert EmptyData(); + } + + uint8 encodingVersion = uint8(_commitData[0]); + if (encodingVersion == SUPPORTED_ENCODING_VERSION) { + (lastCommittedBatchData, newBatchesData) = abi.decode( + _commitData[1:], + (IExecutor.StoredBatchInfo, IExecutor.CommitBatchInfo[]) + ); + } else { + revert UnsupportedCommitBatchEncoding(encodingVersion); + } + } + + /// @notice Decodes the commit data and checks that the provided batch bounds are correct. + /// @dev Note that it only checks that the last and the first batches in the array correspond to the provided bounds. + /// The fact that the batches inside the array are provided in the correct order should be checked by the caller. + /// @param _commitData The calldata byte array containing the data for committing batches. + /// @param _processBatchFrom The expected batch number of the first commit batch in the array. + /// @param _processBatchTo The expected batch number of the last commit batch in the array. + /// @return lastCommittedBatchData The data for the batch before newly committed batches. + /// @return newBatchesData An array containing the newly committed batches. + function decodeAndCheckCommitData( + bytes calldata _commitData, + uint256 _processBatchFrom, + uint256 _processBatchTo + ) + internal + pure + returns ( + IExecutor.StoredBatchInfo memory lastCommittedBatchData, + IExecutor.CommitBatchInfo[] memory newBatchesData + ) + { + (lastCommittedBatchData, newBatchesData) = _decodeCommitData(_commitData); + + if (newBatchesData.length == 0) { + revert EmptyData(); + } + + if ( + newBatchesData[0].batchNumber != _processBatchFrom || + newBatchesData[newBatchesData.length - 1].batchNumber != _processBatchTo + ) { + revert IncorrectBatchBounds( + _processBatchFrom, + _processBatchTo, + newBatchesData[0].batchNumber, + newBatchesData[newBatchesData.length - 1].batchNumber + ); + } + } + + /// @notice Decodes proof data from a calldata byte array into the previous batch, an array of proved batches, and a proof array. + /// @param _proofData The calldata byte array containing the data for proving batches. + /// @return prevBatch The batch information before the batches to be verified. + /// @return provedBatches An array containing the the batches to be verified. + /// @return proof An array containing the proof for the verifier. + function _decodeProofData( + bytes calldata _proofData + ) + private + pure + returns ( + IExecutor.StoredBatchInfo memory prevBatch, + IExecutor.StoredBatchInfo[] memory provedBatches, + uint256[] memory proof + ) + { + uint8 encodingVersion = uint8(_proofData[0]); + if (encodingVersion == SUPPORTED_ENCODING_VERSION) { + (prevBatch, provedBatches, proof) = abi.decode( + _proofData[1:], + (IExecutor.StoredBatchInfo, IExecutor.StoredBatchInfo[], uint256[]) + ); + } else { + revert UnsupportedProofBatchEncoding(encodingVersion); + } + } + + /// @notice Decodes the commit data and checks that the provided batch bounds are correct. + /// @dev Note that it only checks that the last and the first batches in the array correspond to the provided bounds. + /// The fact that the batches inside the array are provided in the correct order should be checked by the caller. + /// @param _proofData The commit data to decode. + /// @param _processBatchFrom The expected batch number of the first batch in the array. + /// @param _processBatchTo The expected batch number of the last batch in the array. + /// @return prevBatch The batch information before the batches to be verified. + /// @return provedBatches An array containing the the batches to be verified. + /// @return proof An array containing the proof for the verifier. + function decodeAndCheckProofData( + bytes calldata _proofData, + uint256 _processBatchFrom, + uint256 _processBatchTo + ) + internal + pure + returns ( + IExecutor.StoredBatchInfo memory prevBatch, + IExecutor.StoredBatchInfo[] memory provedBatches, + uint256[] memory proof + ) + { + (prevBatch, provedBatches, proof) = _decodeProofData(_proofData); + + if (provedBatches.length == 0) { + revert EmptyData(); + } + + if ( + provedBatches[0].batchNumber != _processBatchFrom || + provedBatches[provedBatches.length - 1].batchNumber != _processBatchTo + ) { + revert IncorrectBatchBounds( + _processBatchFrom, + _processBatchTo, + provedBatches[0].batchNumber, + provedBatches[provedBatches.length - 1].batchNumber + ); + } + } + + /// @notice Decodes execution data from a calldata byte array into an array of stored batch information. + /// @param _executeData The calldata byte array containing the execution data to decode. + /// @return executeData An array containing the stored batch information for execution. + /// @return priorityOpsData Merkle proofs of the priority operations for each batch. + function _decodeExecuteData( + bytes calldata _executeData + ) + private + pure + returns (IExecutor.StoredBatchInfo[] memory executeData, PriorityOpsBatchInfo[] memory priorityOpsData) + { + if (_executeData.length == 0) { + revert EmptyData(); + } + + uint8 encodingVersion = uint8(_executeData[0]); + if (encodingVersion == SUPPORTED_ENCODING_VERSION) { + (executeData, priorityOpsData) = abi.decode( + _executeData[1:], + (IExecutor.StoredBatchInfo[], PriorityOpsBatchInfo[]) + ); + } else { + revert UnsupportedExecuteBatchEncoding(encodingVersion); + } + } + + /// @notice Decodes the execute data and checks that the provided batch bounds are correct. + /// @dev Note that it only checks that the last and the first batches in the array correspond to the provided bounds. + /// The fact that the batches inside the array are provided in the correct order should be checked by the caller. + /// @param _executeData The calldata byte array containing the execution data to decode. + /// @param _processBatchFrom The expected batch number of the first batch in the array. + /// @param _processBatchTo The expected batch number of the last batch in the array. + /// @return executeData An array containing the stored batch information for execution. + /// @return priorityOpsData Merkle proofs of the priority operations for each batch. + function decodeAndCheckExecuteData( + bytes calldata _executeData, + uint256 _processBatchFrom, + uint256 _processBatchTo + ) + internal + pure + returns (IExecutor.StoredBatchInfo[] memory executeData, PriorityOpsBatchInfo[] memory priorityOpsData) + { + (executeData, priorityOpsData) = _decodeExecuteData(_executeData); + + if (executeData.length == 0) { + revert EmptyData(); + } + + if ( + executeData[0].batchNumber != _processBatchFrom || + executeData[executeData.length - 1].batchNumber != _processBatchTo + ) { + revert IncorrectBatchBounds( + _processBatchFrom, + _processBatchTo, + executeData[0].batchNumber, + executeData[executeData.length - 1].batchNumber + ); + } + } +} diff --git a/l1-contracts/contracts/state-transition/libraries/Merkle.sol b/l1-contracts/contracts/state-transition/libraries/Merkle.sol deleted file mode 100644 index 57701f338..000000000 --- a/l1-contracts/contracts/state-transition/libraries/Merkle.sol +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: MIT -// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. -pragma solidity ^0.8.21; - -import {UncheckedMath} from "../../common/libraries/UncheckedMath.sol"; -import {MerklePathEmpty, MerklePathOutOfBounds, MerkleIndexOutOfBounds} from "../../common/L1ContractErrors.sol"; - -/// @author Matter Labs -/// @custom:security-contact security@matterlabs.dev -library Merkle { - using UncheckedMath for uint256; - - /// @dev Calculate Merkle root by the provided Merkle proof. - /// NOTE: When using this function, check that the _path length is equal to the tree height to prevent shorter/longer paths attack - /// @param _path Merkle path from the leaf to the root - /// @param _index Leaf index in the tree - /// @param _itemHash Hash of leaf content - /// @return The Merkle root - function calculateRoot( - bytes32[] calldata _path, - uint256 _index, - bytes32 _itemHash - ) internal pure returns (bytes32) { - uint256 pathLength = _path.length; - if (pathLength == 0) { - revert MerklePathEmpty(); - } - if (pathLength >= 256) { - revert MerklePathOutOfBounds(); - } - if (_index >= (1 << pathLength)) { - revert MerkleIndexOutOfBounds(); - } - - bytes32 currentHash = _itemHash; - for (uint256 i; i < pathLength; i = i.uncheckedInc()) { - currentHash = (_index % 2 == 0) - ? _efficientHash(currentHash, _path[i]) - : _efficientHash(_path[i], currentHash); - _index /= 2; - } - - return currentHash; - } - - /// @dev Keccak hash of the concatenation of two 32-byte words - function _efficientHash(bytes32 _lhs, bytes32 _rhs) private pure returns (bytes32 result) { - assembly { - mstore(0x00, _lhs) - mstore(0x20, _rhs) - result := keccak256(0x00, 0x40) - } - } -} diff --git a/l1-contracts/contracts/state-transition/libraries/PriorityTree.sol b/l1-contracts/contracts/state-transition/libraries/PriorityTree.sol new file mode 100644 index 000000000..0032fef9a --- /dev/null +++ b/l1-contracts/contracts/state-transition/libraries/PriorityTree.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MIT +// We use a floating point pragma here so it can be used within other projects that interact with the zkSync ecosystem without using our exact pragma version. +pragma solidity ^0.8.21; + +import {DynamicIncrementalMerkle} from "../../common/libraries/DynamicIncrementalMerkle.sol"; +import {Merkle} from "../../common/libraries/Merkle.sol"; +import {PriorityTreeCommitment} from "../../common/Config.sol"; +import {NotHistoricalRoot, InvalidCommitment, InvalidStartIndex, InvalidUnprocessedIndex, InvalidNextLeafIndex} from "../L1StateTransitionErrors.sol"; + +struct PriorityOpsBatchInfo { + bytes32[] leftPath; + bytes32[] rightPath; + bytes32[] itemHashes; +} + +bytes32 constant ZERO_LEAF_HASH = keccak256(""); + +library PriorityTree { + using PriorityTree for Tree; + using DynamicIncrementalMerkle for DynamicIncrementalMerkle.Bytes32PushTree; + + struct Tree { + uint256 startIndex; // priority tree started accepting priority ops from this index + uint256 unprocessedIndex; // relative to `startIndex` + mapping(bytes32 => bool) historicalRoots; + DynamicIncrementalMerkle.Bytes32PushTree tree; + } + + /// @notice Returns zero if and only if no operations were processed from the tree + /// @return Index of the oldest priority operation that wasn't processed yet + function getFirstUnprocessedPriorityTx(Tree storage _tree) internal view returns (uint256) { + return _tree.startIndex + _tree.unprocessedIndex; + } + + /// @return The total number of priority operations that were added to the priority queue, including all processed ones + function getTotalPriorityTxs(Tree storage _tree) internal view returns (uint256) { + return _tree.startIndex + _tree.tree._nextLeafIndex; + } + + /// @return The total number of unprocessed priority operations in a priority queue + function getSize(Tree storage _tree) internal view returns (uint256) { + return _tree.tree._nextLeafIndex - _tree.unprocessedIndex; + } + + /// @notice Add the priority operation to the end of the priority queue + function push(Tree storage _tree, bytes32 _hash) internal { + (, bytes32 newRoot) = _tree.tree.push(_hash); + _tree.historicalRoots[newRoot] = true; + } + + /// @notice Set up the tree + function setup(Tree storage _tree, uint256 _startIndex) internal { + bytes32 initialRoot = _tree.tree.setup(ZERO_LEAF_HASH); + _tree.historicalRoots[initialRoot] = true; + _tree.startIndex = _startIndex; + } + + /// @return Returns the tree root. + function getRoot(Tree storage _tree) internal view returns (bytes32) { + return _tree.tree.root(); + } + + /// @param _root The root to check. + /// @return Returns true if the root is a historical root. + function isHistoricalRoot(Tree storage _tree, bytes32 _root) internal view returns (bool) { + return _tree.historicalRoots[_root]; + } + + /// @notice Process the priority operations of a batch. + /// @dev Note, that the function below only checks that a certain segment of items is present in the tree. + /// It does not check that e.g. there are no zero items inside the provided `itemHashes`, so in theory proofs + /// that include non-existing priority operations could be created. This function relies on the fact + /// that the `itemHashes` of `_priorityOpsData` are hashes of valid priority transactions. + /// This fact is ensures by the fact the rolling hash of those is sent to the Executor by the bootloader + /// and so assuming that zero knowledge proofs are correct, so is the structure of the `itemHashes`. + function processBatch(Tree storage _tree, PriorityOpsBatchInfo memory _priorityOpsData) internal { + if (_priorityOpsData.itemHashes.length > 0) { + bytes32 expectedRoot = Merkle.calculateRootPaths( + _priorityOpsData.leftPath, + _priorityOpsData.rightPath, + _tree.unprocessedIndex, + _priorityOpsData.itemHashes + ); + if (!_tree.historicalRoots[expectedRoot]) { + revert NotHistoricalRoot(); + } + _tree.unprocessedIndex += _priorityOpsData.itemHashes.length; + } + } + + /// @notice Allows to skip a certain number of operations. + /// @param _lastUnprocessed The new expected id of the unprocessed transaction. + /// @dev It is used when the corresponding transactions have been processed by priority queue. + function skipUntil(Tree storage _tree, uint256 _lastUnprocessed) internal { + if (_tree.startIndex > _lastUnprocessed) { + // Nothing to do, return + return; + } + uint256 newUnprocessedIndex = _lastUnprocessed - _tree.startIndex; + if (newUnprocessedIndex <= _tree.unprocessedIndex) { + // These transactions were already processed, skip. + return; + } + + _tree.unprocessedIndex = newUnprocessedIndex; + } + + /// @notice Initialize a chain from a commitment. + function initFromCommitment(Tree storage _tree, PriorityTreeCommitment memory _commitment) internal { + uint256 height = _commitment.sides.length; // Height, including the root node. + if (height == 0) { + revert InvalidCommitment(); + } + _tree.startIndex = _commitment.startIndex; + _tree.unprocessedIndex = _commitment.unprocessedIndex; + _tree.tree._nextLeafIndex = _commitment.nextLeafIndex; + _tree.tree._sides = _commitment.sides; + bytes32 zero = ZERO_LEAF_HASH; + _tree.tree._zeros = new bytes32[](height); + for (uint256 i; i < height; ++i) { + _tree.tree._zeros[i] = zero; + zero = Merkle.efficientHash(zero, zero); + } + _tree.historicalRoots[_tree.tree.root()] = true; + } + + /// @notice Reinitialize the tree from a commitment on L1. + function l1Reinit(Tree storage _tree, PriorityTreeCommitment memory _commitment) internal { + if (_tree.startIndex != _commitment.startIndex) { + revert InvalidStartIndex(_tree.startIndex, _commitment.startIndex); + } + if (_tree.unprocessedIndex > _commitment.unprocessedIndex) { + revert InvalidUnprocessedIndex(_tree.unprocessedIndex, _commitment.unprocessedIndex); + } + if (_tree.tree._nextLeafIndex < _commitment.nextLeafIndex) { + revert InvalidNextLeafIndex(_tree.tree._nextLeafIndex, _commitment.nextLeafIndex); + } + + _tree.unprocessedIndex = _commitment.unprocessedIndex; + } + + /// @notice Reinitialize the tree from a commitment on GW. + function checkGWReinit(Tree storage _tree, PriorityTreeCommitment memory _commitment) internal view { + if (_tree.startIndex != _commitment.startIndex) { + revert InvalidStartIndex(_tree.startIndex, _commitment.startIndex); + } + if (_tree.unprocessedIndex > _commitment.unprocessedIndex) { + revert InvalidUnprocessedIndex(_tree.unprocessedIndex, _commitment.unprocessedIndex); + } + if (_tree.tree._nextLeafIndex > _commitment.nextLeafIndex) { + revert InvalidNextLeafIndex(_tree.tree._nextLeafIndex, _commitment.nextLeafIndex); + } + } + + /// @notice Returns the commitment to the priority tree. + function getCommitment(Tree storage _tree) internal view returns (PriorityTreeCommitment memory commitment) { + commitment.nextLeafIndex = _tree.tree._nextLeafIndex; + commitment.startIndex = _tree.startIndex; + commitment.unprocessedIndex = _tree.unprocessedIndex; + commitment.sides = _tree.tree._sides; + } +} diff --git a/l1-contracts/contracts/transactionFilterer/GatewayTransactionFilterer.sol b/l1-contracts/contracts/transactionFilterer/GatewayTransactionFilterer.sol new file mode 100644 index 000000000..39bd69d1d --- /dev/null +++ b/l1-contracts/contracts/transactionFilterer/GatewayTransactionFilterer.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; + +import {AlreadyWhitelisted, InvalidSelector, NotWhitelisted, ZeroAddress} from "../common/L1ContractErrors.sol"; +import {ITransactionFilterer} from "../state-transition/chain-interfaces/ITransactionFilterer.sol"; +import {IBridgehub} from "../bridgehub/IBridgehub.sol"; +import {IAssetRouterBase} from "../bridge/asset-router/IAssetRouterBase.sol"; +import {IL2AssetRouter} from "../bridge/asset-router/IL2AssetRouter.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @dev Filters transactions received by the Mailbox +/// @dev Only allows whitelisted senders to deposit to Gateway +contract GatewayTransactionFilterer is ITransactionFilterer, Ownable2StepUpgradeable { + /// @notice Event emitted when sender is whitelisted + event WhitelistGranted(address indexed sender); + + /// @notice Event emitted when sender is removed from whitelist + event WhitelistRevoked(address indexed sender); + + /// @notice The ecosystem's Bridgehub + IBridgehub public immutable BRIDGE_HUB; + + /// @notice The L1 asset router + address public immutable L1_ASSET_ROUTER; + + /// @notice Indicates whether the sender is whitelisted to deposit to Gateway + mapping(address sender => bool whitelisted) public whitelistedSenders; + + /// @dev Contract is expected to be used as proxy implementation. + /// @dev Initialize the implementation to prevent Parity hack. + constructor(IBridgehub _bridgeHub, address _assetRouter) { + BRIDGE_HUB = _bridgeHub; + L1_ASSET_ROUTER = _assetRouter; + _disableInitializers(); + } + + /// @notice Initializes a contract filterer for later use. Expected to be used in the proxy. + /// @param _owner The address which can upgrade the implementation. + function initialize(address _owner) external initializer { + if (_owner == address(0)) { + revert ZeroAddress(); + } + _transferOwnership(_owner); + } + + /// @notice Whitelists the sender. + /// @param sender Address of the tx sender. + function grantWhitelist(address sender) external onlyOwner { + if (whitelistedSenders[sender]) { + revert AlreadyWhitelisted(sender); + } + whitelistedSenders[sender] = true; + emit WhitelistGranted(sender); + } + + /// @notice Revoke the sender from whitelist. + /// @param sender Address of the tx sender. + function revokeWhitelist(address sender) external onlyOwner { + if (!whitelistedSenders[sender]) { + revert NotWhitelisted(sender); + } + whitelistedSenders[sender] = false; + emit WhitelistRevoked(sender); + } + + /// @notice Checks if the transaction is allowed + /// @param sender The sender of the transaction + /// @param l2Calldata The calldata of the L2 transaction + /// @return Whether the transaction is allowed + function isTransactionAllowed( + address sender, + address, + uint256, + uint256, + bytes calldata l2Calldata, + address + ) external view returns (bool) { + if (sender == L1_ASSET_ROUTER) { + bytes4 l2TxSelector = bytes4(l2Calldata[:4]); + + if (IL2AssetRouter.setAssetHandlerAddress.selector == l2TxSelector) { + (, bytes32 decodedAssetId, ) = abi.decode(l2Calldata[4:], (uint256, bytes32, address)); + return _checkCTMAssetId(decodedAssetId); + } + + if (IAssetRouterBase.finalizeDeposit.selector != l2TxSelector) { + revert InvalidSelector(l2TxSelector); + } + + (, bytes32 decodedAssetId, ) = abi.decode(l2Calldata[4:], (uint256, bytes32, bytes)); + return _checkCTMAssetId(decodedAssetId); + } + + return whitelistedSenders[sender]; + } + + function _checkCTMAssetId(bytes32 assetId) internal view returns (bool) { + address ctmAddress = BRIDGE_HUB.ctmAssetIdToAddress(assetId); + return ctmAddress != address(0); + } +} diff --git a/l1-contracts/contracts/upgrades/BaseZkSyncUpgrade.sol b/l1-contracts/contracts/upgrades/BaseZkSyncUpgrade.sol index 4534884d5..a619e5311 100644 --- a/l1-contracts/contracts/upgrades/BaseZkSyncUpgrade.sol +++ b/l1-contracts/contracts/upgrades/BaseZkSyncUpgrade.sol @@ -4,19 +4,18 @@ pragma solidity 0.8.24; import {SafeCast} from "@openzeppelin/contracts-v4/utils/math/SafeCast.sol"; -import {ZkSyncHyperchainBase} from "../state-transition/chain-deps/facets/ZkSyncHyperchainBase.sol"; +import {ZKChainBase} from "../state-transition/chain-deps/facets/ZKChainBase.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 "../state-transition/libraries/TransactionValidator.sol"; import {MAX_NEW_FACTORY_DEPS, SYSTEM_UPGRADE_L2_TX_TYPE, MAX_ALLOWED_MINOR_VERSION_DELTA} from "../common/Config.sol"; import {L2CanonicalTransaction} from "../common/Messaging.sol"; -import {ProtocolVersionMinorDeltaTooBig, TimeNotReached, InvalidTxType, L2UpgradeNonceNotEqualToNewProtocolVersion, TooManyFactoryDeps, UnexpectedNumberOfFactoryDeps, ProtocolVersionTooSmall, PreviousUpgradeNotFinalized, PreviousUpgradeNotCleaned, L2BytecodeHashMismatch, PatchCantSetUpgradeTxn, PreviousProtocolMajorVersionNotZero, NewProtocolMajorVersionNotZero, PatchUpgradeCantSetDefaultAccount, PatchUpgradeCantSetBootloader} from "./ZkSyncUpgradeErrors.sol"; +import {ProtocolVersionMinorDeltaTooBig, TimeNotReached, InvalidTxType, L2UpgradeNonceNotEqualToNewProtocolVersion, TooManyFactoryDeps, ProtocolVersionTooSmall, PreviousUpgradeNotFinalized, PreviousUpgradeNotCleaned, PatchCantSetUpgradeTxn, PreviousProtocolMajorVersionNotZero, NewProtocolMajorVersionNotZero, PatchUpgradeCantSetDefaultAccount, PatchUpgradeCantSetBootloader} from "./ZkSyncUpgradeErrors.sol"; import {SemVer} from "../common/libraries/SemVer.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. @@ -30,7 +29,6 @@ import {SemVer} from "../common/libraries/SemVer.sol"; /// the previous protocol version. struct ProposedUpgrade { L2CanonicalTransaction l2ProtocolUpgradeTx; - bytes[] factoryDeps; bytes32 bootloaderHash; bytes32 defaultAccountHash; address verifier; @@ -44,7 +42,7 @@ struct ProposedUpgrade { /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev /// @notice Interface to which all the upgrade implementations should adhere -abstract contract BaseZkSyncUpgrade is ZkSyncHyperchainBase { +abstract contract BaseZkSyncUpgrade is ZKChainBase { /// @notice Changes the protocol version event NewProtocolVersion(uint256 indexed previousProtocolVersion, uint256 indexed newProtocolVersion); @@ -63,7 +61,7 @@ abstract contract BaseZkSyncUpgrade is ZkSyncHyperchainBase { /// @notice Notifies about complete upgrade event UpgradeComplete(uint256 indexed newProtocolVersion, bytes32 indexed l2UpgradeTxHash, ProposedUpgrade upgrade); - /// @notice The main function that will be provided by the upgrade proxy + /// @notice The main function that will be delegate-called by the chain. /// @dev This is a virtual function and should be overridden by custom upgrade implementations. /// @param _proposedUpgrade The upgrade to be executed. /// @return txHash The hash of the L2 system contract upgrade transaction. @@ -81,12 +79,7 @@ abstract contract BaseZkSyncUpgrade is ZkSyncHyperchainBase { _upgradeVerifier(_proposedUpgrade.verifier, _proposedUpgrade.verifierParams); _setBaseSystemContracts(_proposedUpgrade.bootloaderHash, _proposedUpgrade.defaultAccountHash, isPatchOnly); - txHash = _setL2SystemContractUpgrade( - _proposedUpgrade.l2ProtocolUpgradeTx, - _proposedUpgrade.factoryDeps, - newMinorVersion, - isPatchOnly - ); + txHash = _setL2SystemContractUpgrade(_proposedUpgrade.l2ProtocolUpgradeTx, newMinorVersion, isPatchOnly); _postUpgrade(_proposedUpgrade.postUpgradeCalldata); @@ -193,14 +186,12 @@ abstract contract BaseZkSyncUpgrade is ZkSyncHyperchainBase { /// @notice Sets the hash of the L2 system contract upgrade transaction for the next batch to be committed /// @dev If the transaction is noop (i.e. its type is 0) it does nothing and returns 0. /// @param _l2ProtocolUpgradeTx The L2 system contract upgrade transaction. - /// @param _factoryDeps The factory dependencies that are used by the transaction. /// @param _newMinorProtocolVersion The new minor protocol version. It must be used as the `nonce` field /// of the `_l2ProtocolUpgradeTx`. /// @param _patchOnly Whether only the patch part of the protocol version semver has changed. /// @return System contracts upgrade transaction hash. Zero if no upgrade transaction is set. function _setL2SystemContractUpgrade( L2CanonicalTransaction calldata _l2ProtocolUpgradeTx, - bytes[] calldata _factoryDeps, uint32 _newMinorProtocolVersion, bool _patchOnly ) internal returns (bytes32) { @@ -233,7 +224,7 @@ abstract contract BaseZkSyncUpgrade is ZkSyncHyperchainBase { revert L2UpgradeNonceNotEqualToNewProtocolVersion(_l2ProtocolUpgradeTx.nonce, _newMinorProtocolVersion); } - _verifyFactoryDeps(_factoryDeps, _l2ProtocolUpgradeTx.factoryDeps); + _verifyFactoryDeps(_l2ProtocolUpgradeTx.factoryDeps); bytes32 l2ProtocolUpgradeTxHash = keccak256(encodedTransaction); @@ -242,24 +233,15 @@ abstract contract BaseZkSyncUpgrade is ZkSyncHyperchainBase { return l2ProtocolUpgradeTxHash; } - /// @notice Verifies that the factory deps correspond to the proper hashes - /// @param _factoryDeps The list of factory deps - /// @param _expectedHashes The list of expected bytecode hashes - function _verifyFactoryDeps(bytes[] calldata _factoryDeps, uint256[] calldata _expectedHashes) private pure { - if (_factoryDeps.length != _expectedHashes.length) { - revert UnexpectedNumberOfFactoryDeps(); - } - if (_factoryDeps.length > MAX_NEW_FACTORY_DEPS) { + /// @notice Verifies that the factory deps provided are in the correct format + /// @param _hashes The list of hashes of factory deps + /// @dev Note, that unlike normal L1->L2 transactions, factory dependencies for + /// an upgrade transaction should be made available prior to the upgrade via publishing those + /// to the `BytecodesSupplier` contract. + function _verifyFactoryDeps(uint256[] calldata _hashes) private pure { + if (_hashes.length > MAX_NEW_FACTORY_DEPS) { revert TooManyFactoryDeps(); } - uint256 length = _factoryDeps.length; - - for (uint256 i = 0; i < length; ++i) { - bytes32 bytecodeHash = L2ContractHelper.hashL2Bytecode(_factoryDeps[i]); - if (bytecodeHash != bytes32(_expectedHashes[i])) { - revert L2BytecodeHashMismatch(bytecodeHash, bytes32(_expectedHashes[i])); - } - } } /// @notice Changes the protocol version @@ -304,7 +286,7 @@ abstract contract BaseZkSyncUpgrade is ZkSyncHyperchainBase { // must be ensured in the other parts of the upgrade that the upgrade transaction is not overridden. if (!patchOnly) { // 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 + // Note it is important to keep this check, as otherwise ZK chains might skip upgrades by overwriting if (s.l2SystemContractsUpgradeTxHash != bytes32(0)) { revert PreviousUpgradeNotFinalized(s.l2SystemContractsUpgradeTxHash); } diff --git a/l1-contracts/contracts/upgrades/BaseZkSyncUpgradeGenesis.sol b/l1-contracts/contracts/upgrades/BaseZkSyncUpgradeGenesis.sol index 561f25d23..49237ccfd 100644 --- a/l1-contracts/contracts/upgrades/BaseZkSyncUpgradeGenesis.sol +++ b/l1-contracts/contracts/upgrades/BaseZkSyncUpgradeGenesis.sol @@ -58,7 +58,7 @@ abstract contract BaseZkSyncUpgradeGenesis is BaseZkSyncUpgrade { // must be ensured in the other parts of the upgrade that the upgrade transaction is not overridden. if (!patchOnly) { // 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 + // Note it is important to keep this check, as otherwise ZK chains might skip upgrades by overwriting if (s.l2SystemContractsUpgradeTxHash != bytes32(0)) { revert PreviousUpgradeNotFinalized(s.l2SystemContractsUpgradeTxHash); } diff --git a/l1-contracts/contracts/upgrades/BytecodesSupplier.sol b/l1-contracts/contracts/upgrades/BytecodesSupplier.sol new file mode 100644 index 000000000..4415ad5e0 --- /dev/null +++ b/l1-contracts/contracts/upgrades/BytecodesSupplier.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {L2ContractHelper} from "../common/libraries/L2ContractHelper.sol"; +import {BytecodeAlreadyPublished} from "../common/L1ContractErrors.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice Contract that is used to track published L2 bytecodes. +/// It will be the contract to which the preimages for the factory dependencies protocol upgrade transaction +/// will be submitted to. +/// @dev The contract has no access control as anyone is allowed to publish any bytecode. +contract BytecodesSupplier { + /// @notice Event emitted when a bytecode is published. + event BytecodePublished(bytes32 indexed bytecodeHash, bytes bytecode); + + /// @notice Mapping of bytecode hashes to the block number when they were published. + mapping(bytes32 bytecodeHash => uint256 blockNumber) public publishingBlock; + + /// @notice Publishes the bytecode hash and the bytecode itself. + /// @param _bytecode Bytecode to be published. + function publishBytecode(bytes calldata _bytecode) public { + bytes32 bytecodeHash = L2ContractHelper.hashL2BytecodeCalldata(_bytecode); + + if (publishingBlock[bytecodeHash] != 0) { + revert BytecodeAlreadyPublished(bytecodeHash); + } + + publishingBlock[bytecodeHash] = block.number; + + emit BytecodePublished(bytecodeHash, _bytecode); + } + + /// @notice Publishes multiple bytecodes. + /// @param _bytecodes Array of bytecodes to be published. + function publishBytecodes(bytes[] calldata _bytecodes) external { + // solhint-disable-next-line gas-length-in-loops + for (uint256 i = 0; i < _bytecodes.length; ++i) { + publishBytecode(_bytecodes[i]); + } + } +} diff --git a/l1-contracts/contracts/upgrades/DefaultUpgrade.sol b/l1-contracts/contracts/upgrades/DefaultUpgrade.sol index c6ebb18dc..87c6dd220 100644 --- a/l1-contracts/contracts/upgrades/DefaultUpgrade.sol +++ b/l1-contracts/contracts/upgrades/DefaultUpgrade.sol @@ -8,7 +8,7 @@ import {BaseZkSyncUpgrade, ProposedUpgrade} from "./BaseZkSyncUpgrade.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev contract DefaultUpgrade is BaseZkSyncUpgrade { - /// @notice The main function that will be called by the upgrade proxy. + /// @notice The main function that will be delegate-called by the chain. /// @param _proposedUpgrade The upgrade to be executed. function upgrade(ProposedUpgrade calldata _proposedUpgrade) public override returns (bytes32) { super.upgrade(_proposedUpgrade); diff --git a/l1-contracts/contracts/upgrades/GatewayUpgrade.sol b/l1-contracts/contracts/upgrades/GatewayUpgrade.sol new file mode 100644 index 000000000..b1a327eb3 --- /dev/null +++ b/l1-contracts/contracts/upgrades/GatewayUpgrade.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {BaseZkSyncUpgrade, ProposedUpgrade} from "./BaseZkSyncUpgrade.sol"; + +import {DataEncoding} from "../common/libraries/DataEncoding.sol"; + +import {Diamond} from "../state-transition/libraries/Diamond.sol"; +import {PriorityQueue} from "../state-transition/libraries/PriorityQueue.sol"; +import {PriorityTree} from "../state-transition/libraries/PriorityTree.sol"; +import {GatewayUpgradeFailed} from "./ZkSyncUpgradeErrors.sol"; + +import {IGatewayUpgrade} from "./IGatewayUpgrade.sol"; +import {IL2ContractDeployer} from "../common/interfaces/IL2ContractDeployer.sol"; +import {L1GatewayBase} from "./L1GatewayBase.sol"; + +// solhint-disable-next-line gas-struct-packing +struct GatewayUpgradeEncodedInput { + IL2ContractDeployer.ForceDeployment[] forceDeployments; + uint256 l2GatewayUpgradePosition; + bytes fixedForceDeploymentsData; + address ctmDeployer; + address oldValidatorTimelock; + address newValidatorTimelock; + address wrappedBaseTokenStore; +} + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice This upgrade will be used to migrate Era to be part of the ZK chain ecosystem contracts. +contract GatewayUpgrade is BaseZkSyncUpgrade, L1GatewayBase, IGatewayUpgrade { + using PriorityQueue for PriorityQueue.Queue; + using PriorityTree for PriorityTree.Tree; + + /// @notice The address of this contract. + /// @dev needed as this address is delegateCalled, and we delegateCall it again. + address public immutable THIS_ADDRESS; + + constructor() { + THIS_ADDRESS = address(this); + } + + /// @notice The main function that will be delegate-called by the chain. + /// @param _proposedUpgrade The upgrade to be executed. + /// @dev Doesn't require any access-control restrictions as the contract is used in the delegate call. + function upgrade(ProposedUpgrade calldata _proposedUpgrade) public override returns (bytes32) { + GatewayUpgradeEncodedInput memory encodedInput = abi.decode( + _proposedUpgrade.postUpgradeCalldata, + (GatewayUpgradeEncodedInput) + ); + + bytes32 baseTokenAssetId = DataEncoding.encodeNTVAssetId(block.chainid, s.__DEPRECATED_baseToken); + + s.baseTokenAssetId = baseTokenAssetId; + s.priorityTree.setup(s.priorityQueue.getTotalPriorityTxs()); + s.validators[encodedInput.oldValidatorTimelock] = false; + s.validators[encodedInput.newValidatorTimelock] = true; + ProposedUpgrade memory proposedUpgrade = _proposedUpgrade; + + bytes memory gatewayUpgradeCalldata = abi.encode( + encodedInput.ctmDeployer, + encodedInput.fixedForceDeploymentsData, + getZKChainSpecificForceDeploymentsData(s, encodedInput.wrappedBaseTokenStore, s.__DEPRECATED_baseToken) + ); + encodedInput.forceDeployments[encodedInput.l2GatewayUpgradePosition].input = gatewayUpgradeCalldata; + + proposedUpgrade.l2ProtocolUpgradeTx.data = abi.encodeCall( + IL2ContractDeployer.forceDeployOnAddresses, + (encodedInput.forceDeployments) + ); + + // slither-disable-next-line controlled-delegatecall + (bool success, ) = THIS_ADDRESS.delegatecall(abi.encodeCall(IGatewayUpgrade.upgradeExternal, proposedUpgrade)); + if (!success) { + revert GatewayUpgradeFailed(); + } + return Diamond.DIAMOND_INIT_SUCCESS_RETURN_VALUE; + } + + /// @notice The function that will be called from this same contract, we need an external call to be able to modify _proposedUpgrade (memory/calldata). + /// @dev Doesn't require any access-control restrictions as the contract is used in the delegate call. + function upgradeExternal(ProposedUpgrade calldata _proposedUpgrade) external override { + super.upgrade(_proposedUpgrade); + } +} diff --git a/l1-contracts/contracts/upgrades/GenesisUpgrade.sol b/l1-contracts/contracts/upgrades/GenesisUpgrade.sol deleted file mode 100644 index 5e0ee280a..000000000 --- a/l1-contracts/contracts/upgrades/GenesisUpgrade.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -import {Diamond} from "../state-transition/libraries/Diamond.sol"; -import {BaseZkSyncUpgradeGenesis} from "./BaseZkSyncUpgradeGenesis.sol"; -import {ProposedUpgrade} from "./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/GovernanceUpgradeTimer.sol b/l1-contracts/contracts/upgrades/GovernanceUpgradeTimer.sol new file mode 100644 index 000000000..f4b237da1 --- /dev/null +++ b/l1-contracts/contracts/upgrades/GovernanceUpgradeTimer.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import {Ownable2Step} from "@openzeppelin/contracts-v4/access/Ownable2Step.sol"; +import {ZeroAddress, TimerAlreadyStarted, CallerNotTimerAdmin, DeadlineNotYetPassed, NewDeadlineNotGreaterThanCurrent, NewDeadlineExceedsMaxDeadline} from "../common/L1ContractErrors.sol"; + +/// @title Governance Upgrade Timer +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice This contract will be used by the governance to ensure that the chains have enough time +/// to upgrade their implementation before finalizing the upgrade on L1. +/// @notice The `startTimer` function should be called once the new version is published. It starts the +/// timer and gives at least `INITIAL_DELAY` for the chains to upgrade. In case for any reason the timeline has to +/// be extended, the owner of this contract can increase the timeline, but only the maximum of `MAX_ADDITIONAL_DELAY` +/// is allowed. +contract GovernanceUpgradeTimer is Ownable2Step { + /// @notice The initial delay to be used. + uint256 public immutable INITIAL_DELAY; + /// @notice The maximal delay for the upgrade. + uint256 public immutable MAX_ADDITIONAL_DELAY; + /// @notice The address that can start the timer. + address public immutable TIMER_GOVERNANCE; + + /// @notice The deadline which we should wait. + uint256 public deadline; + /// @notice The maximal deadline to which the owner of this contract can + /// increase the deadline. + uint256 public maxDeadline; + + /// @dev Emitted when the timer is started, logging the initial `deadline` and `maxDeadline`. + /// @param deadline The initial deadline set for the timer. + /// @param maxDeadline The maximum deadline the timer can be extended to. + event TimerStarted(uint256 deadline, uint256 maxDeadline); + + /// @dev Emitted when the owner changes the deadline. + /// @param newDeadline The new deadline set by the owner. + event DeadlineChanged(uint256 newDeadline); + + /// @dev Initializes the contract with immutable values for `INITIAL_DELAY`, `MAX_ADDITIONAL_DELAY`, and `TIMER_GOVERNANCE`. + /// @param _initialDelay The initial delay in seconds to be added to the current block timestamp to set the deadline. + /// @param _maxAdditionalDelay The maximum number of seconds that can be added to the initial delay to set `maxDeadline`. + /// @param _timerGovernance The address of the timer administrator, who is allowed to start the timer. + /// @param _initialOwner The initial owner of the contract. + constructor(uint256 _initialDelay, uint256 _maxAdditionalDelay, address _timerGovernance, address _initialOwner) { + if (_timerGovernance == address(0)) { + revert ZeroAddress(); + } + + INITIAL_DELAY = _initialDelay; + MAX_ADDITIONAL_DELAY = _maxAdditionalDelay; + TIMER_GOVERNANCE = _timerGovernance; + + _transferOwnership(_initialOwner); + } + + /// @dev Modifier that restricts function access to the `TIMER_GOVERNANCE` address. + /// Reverts with a custom error if the caller is not `TIMER_GOVERNANCE`. + modifier onlyTimerAdmin() { + if (msg.sender != TIMER_GOVERNANCE) { + revert CallerNotTimerAdmin(); + } + _; + } + + /// @dev Starts the timer by setting the `deadline` and `maxDeadline`. Only callable by the `TIMER_GOVERNANCE`. + /// + /// Emits a {TimerStarted} event. + function startTimer() external onlyTimerAdmin { + if (deadline != 0) { + revert TimerAlreadyStarted(); + } + + deadline = block.timestamp + INITIAL_DELAY; + maxDeadline = deadline + MAX_ADDITIONAL_DELAY; + + emit TimerStarted(deadline, maxDeadline); + } + + /// @dev Checks if the current `deadline` has passed. Reverts if the deadline has already passed. + /// + /// Reverts with {DeadlineNotYetPassed} error if the current block timestamp is less than `deadline`. + function checkDeadline() external view { + if (block.timestamp < deadline) { + revert DeadlineNotYetPassed(); + } + } + + /// @dev Allows the owner to change the current `deadline` to a new value. + /// + /// The new deadline must be greater than the current deadline and must not exceed `maxDeadline`. + /// + /// Emits a {DeadlineChanged} event. + /// + /// @param newDeadline The new deadline to be set. + /// + /// Reverts with {NewDeadlineNotGreaterThanCurrent} if the new deadline is not greater than the current one. + /// Reverts with {NewDeadlineExceedsMaxDeadline} if the new deadline exceeds `maxDeadline`. + function changeDeadline(uint256 newDeadline) external onlyOwner { + if (newDeadline <= deadline) { + revert NewDeadlineNotGreaterThanCurrent(); + } + if (newDeadline > maxDeadline) { + revert NewDeadlineExceedsMaxDeadline(); + } + + deadline = newDeadline; + + emit DeadlineChanged(newDeadline); + } +} diff --git a/l1-contracts/contracts/upgrades/IGatewayUpgrade.sol b/l1-contracts/contracts/upgrades/IGatewayUpgrade.sol new file mode 100644 index 000000000..bc4a3873c --- /dev/null +++ b/l1-contracts/contracts/upgrades/IGatewayUpgrade.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {ProposedUpgrade} from "./BaseZkSyncUpgrade.sol"; + +/** + * @author Matter Labs + * @custom:security-contact security@matterlabs.dev + * @notice Gateway upgrade interface. Used for the protocol upgrade that introduces the Gateway. + */ +interface IGatewayUpgrade { + /// @notice The upgrade function called from within this same contract + /// @dev This is needed for memory -> calldata conversion of the _upgrade arg. + /// @param _upgrade The upgrade to be executed. + function upgradeExternal(ProposedUpgrade calldata _upgrade) external; +} diff --git a/l1-contracts/contracts/upgrades/IL1GenesisUpgrade.sol b/l1-contracts/contracts/upgrades/IL1GenesisUpgrade.sol new file mode 100644 index 000000000..c217e3be1 --- /dev/null +++ b/l1-contracts/contracts/upgrades/IL1GenesisUpgrade.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {L2CanonicalTransaction} from "../common/Messaging.sol"; + +/** + * @author Matter Labs + * @custom:security-contact security@matterlabs.dev + * @notice L1 genesis upgrade interface. Every chain has to process an upgrade txs at its genesis. + * @notice This is needed to set system params like the chainId and to deploy some system contracts. + */ +interface IL1GenesisUpgrade { + /// @dev emitted when a chain registers and a GenesisUpgrade happens + /// @param _zkChain the address of the zk chain + /// @param _l2Transaction the l2 genesis upgrade transaction + /// @param _protocolVersion the current protocol version + /// @param _factoryDeps the factory dependencies needed for the upgrade + event GenesisUpgrade( + address indexed _zkChain, + L2CanonicalTransaction _l2Transaction, + uint256 indexed _protocolVersion, + bytes[] _factoryDeps + ); + + /// @notice The main function that will be called by the Admin facet at genesis. + /// @param _l1GenesisUpgrade the address of the l1 genesis upgrade + /// @param _chainId the chain id + /// @param _protocolVersion the current protocol version + /// @param _l1CtmDeployerAddress the address of the l1 ctm deployer + /// @param _forceDeployments the force deployments + /// @param _factoryDeps the factory dependencies + function genesisUpgrade( + address _l1GenesisUpgrade, + uint256 _chainId, + uint256 _protocolVersion, + address _l1CtmDeployerAddress, + bytes calldata _forceDeployments, + bytes[] calldata _factoryDeps + ) external returns (bytes32); +} diff --git a/l1-contracts/contracts/upgrades/L1GatewayBase.sol b/l1-contracts/contracts/upgrades/L1GatewayBase.sol new file mode 100644 index 000000000..16a56253a --- /dev/null +++ b/l1-contracts/contracts/upgrades/L1GatewayBase.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IL1SharedBridgeLegacy} from "../bridge/interfaces/IL1SharedBridgeLegacy.sol"; +import {IBridgehub} from "../bridgehub/IBridgehub.sol"; +import {ETH_TOKEN_ADDRESS} from "../common/Config.sol"; +import {ZKChainSpecificForceDeploymentsData} from "../state-transition/l2-deps/IL2GenesisUpgrade.sol"; + +import {ZKChainStorage} from "../state-transition/chain-deps/ZKChainStorage.sol"; + +import {L2WrappedBaseTokenStore} from "../bridge/L2WrappedBaseTokenStore.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts-v4/token/ERC20/extensions/IERC20Metadata.sol"; + +/// @title L1GatewayBase +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +abstract contract L1GatewayBase { + /// @notice The function to retrieve the chain-specific upgrade data. + /// @param s The pointer to the storage of the chain. + /// @param _wrappedBaseTokenStore The address of the `L2WrappedBaseTokenStore` contract. + /// It is expected to be zero during creation of new chains and non-zero during upgrades. + /// @param _baseTokenAddress The L1 address of the base token of the chain. Note, that for + /// chains whose token originates from an L2, this address will be the address of its bridged + /// representation on L1. + function getZKChainSpecificForceDeploymentsData( + ZKChainStorage storage s, + address _wrappedBaseTokenStore, + address _baseTokenAddress + ) internal view returns (bytes memory) { + address sharedBridge = IBridgehub(s.bridgehub).sharedBridge(); + address legacySharedBridge = IL1SharedBridgeLegacy(sharedBridge).l2BridgeAddress(s.chainId); + + address l2WBaseToken; + if (_wrappedBaseTokenStore != address(0)) { + l2WBaseToken = L2WrappedBaseTokenStore(_wrappedBaseTokenStore).l2WBaseTokenAddress(s.chainId); + } + + // It is required for a base token to implement the following methods + string memory baseTokenName; + string memory baseTokenSymbol; + if (_baseTokenAddress == ETH_TOKEN_ADDRESS) { + baseTokenName = string("Ether"); + baseTokenSymbol = string("ETH"); + } else { + try this.getTokenName(_baseTokenAddress) returns (string memory name) { + baseTokenName = name; + } catch { + baseTokenName = string("Base Token"); + } + + try this.getTokenSymbol(_baseTokenAddress) returns (string memory symbol) { + baseTokenSymbol = symbol; + } catch { + // "BT" is an acronym for "Base Token" + baseTokenSymbol = string("BT"); + } + } + + ZKChainSpecificForceDeploymentsData + memory additionalForceDeploymentsData = ZKChainSpecificForceDeploymentsData({ + baseTokenAssetId: s.baseTokenAssetId, + l2LegacySharedBridge: legacySharedBridge, + predeployedL2WethAddress: l2WBaseToken, + baseTokenL1Address: _baseTokenAddress, + baseTokenName: baseTokenName, + baseTokenSymbol: baseTokenSymbol + }); + return abi.encode(additionalForceDeploymentsData); + } + + function getTokenName(address _token) external view returns (string memory) { + return IERC20Metadata(_token).name(); + } + + function getTokenSymbol(address _token) external view returns (string memory) { + return IERC20Metadata(_token).symbol(); + } +} diff --git a/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol b/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol new file mode 100644 index 000000000..d4a177d0f --- /dev/null +++ b/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {SafeCast} from "@openzeppelin/contracts-v4/utils/math/SafeCast.sol"; + +import {Diamond} from "../state-transition/libraries/Diamond.sol"; +import {BaseZkSyncUpgradeGenesis} from "./BaseZkSyncUpgradeGenesis.sol"; +import {ProposedUpgrade} from "./IDefaultUpgrade.sol"; +import {L2CanonicalTransaction} from "../common/Messaging.sol"; +import {IL2GenesisUpgrade} from "../state-transition/l2-deps/IL2GenesisUpgrade.sol"; +import {IL1GenesisUpgrade} from "./IL1GenesisUpgrade.sol"; +import {IComplexUpgrader} from "../state-transition/l2-deps/IComplexUpgrader.sol"; +import {L2_FORCE_DEPLOYER_ADDR, L2_COMPLEX_UPGRADER_ADDR, L2_GENESIS_UPGRADE_ADDR} from "../common/L2ContractAddresses.sol"; //, COMPLEX_UPGRADER_ADDR, GENESIS_UPGRADE_ADDR +import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, SYSTEM_UPGRADE_L2_TX_TYPE, PRIORITY_TX_MAX_GAS_LIMIT} from "../common/Config.sol"; +import {SemVer} from "../common/libraries/SemVer.sol"; + +import {IBridgehub} from "../bridgehub/IBridgehub.sol"; + +import {VerifierParams} from "../state-transition/chain-interfaces/IVerifier.sol"; +import {L2ContractHelper} from "../common/libraries/L2ContractHelper.sol"; +import {L1GatewayBase} from "./L1GatewayBase.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +contract L1GenesisUpgrade is IL1GenesisUpgrade, BaseZkSyncUpgradeGenesis, L1GatewayBase { + /// @notice The main function that will be called by the Admin facet. + /// @param _l1GenesisUpgrade the address of the l1 genesis upgrade + /// @param _chainId the chain id + /// @param _protocolVersion the current protocol version + /// @param _l1CtmDeployerAddress the address of the l1 ctm deployer + /// @param _fixedForceDeploymentsData the force deployments data + /// @param _factoryDeps the factory dependencies + function genesisUpgrade( + address _l1GenesisUpgrade, + uint256 _chainId, + uint256 _protocolVersion, + address _l1CtmDeployerAddress, + bytes calldata _fixedForceDeploymentsData, + bytes[] calldata _factoryDeps + ) public override returns (bytes32) { + address baseTokenAddress = IBridgehub(s.bridgehub).baseToken(_chainId); + + L2CanonicalTransaction memory l2ProtocolUpgradeTx; + + { + bytes memory complexUpgraderCalldata; + { + bytes memory additionalForceDeploymentsData = getZKChainSpecificForceDeploymentsData( + s, + address(0), + baseTokenAddress + ); + bytes memory l2GenesisUpgradeCalldata = abi.encodeCall( + IL2GenesisUpgrade.genesisUpgrade, + (_chainId, _l1CtmDeployerAddress, _fixedForceDeploymentsData, additionalForceDeploymentsData) + ); + complexUpgraderCalldata = abi.encodeCall( + IComplexUpgrader.upgrade, + (L2_GENESIS_UPGRADE_ADDR, l2GenesisUpgradeCalldata) + ); + } + + // slither-disable-next-line unused-return + (, uint32 minorVersion, ) = SemVer.unpackSemVer(SafeCast.toUint96(_protocolVersion)); + l2ProtocolUpgradeTx = L2CanonicalTransaction({ + txType: SYSTEM_UPGRADE_L2_TX_TYPE, + from: uint256(uint160(L2_FORCE_DEPLOYER_ADDR)), + to: uint256(uint160(L2_COMPLEX_UPGRADER_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 protocol version is used as "nonce" for system upgrade transactions + nonce: minorVersion, + value: 0, + reserved: [uint256(0), 0, 0, 0], + data: complexUpgraderCalldata, + signature: new bytes(0), + factoryDeps: L2ContractHelper.hashFactoryDeps(_factoryDeps), + paymasterInput: new bytes(0), + reservedDynamic: new bytes(0) + }); + } + ProposedUpgrade memory proposedUpgrade = ProposedUpgrade({ + l2ProtocolUpgradeTx: l2ProtocolUpgradeTx, + 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: _l1GenesisUpgrade, + initCalldata: abi.encodeCall(this.upgrade, (proposedUpgrade)) + }); + Diamond.diamondCut(cutData); + + emit GenesisUpgrade(address(this), l2ProtocolUpgradeTx, _protocolVersion, _factoryDeps); + return Diamond.DIAMOND_INIT_SUCCESS_RETURN_VALUE; + } + + /// @notice the upgrade function. + 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/ZkSyncUpgradeErrors.sol b/l1-contracts/contracts/upgrades/ZkSyncUpgradeErrors.sol index b30c882e7..816a8e753 100644 --- a/l1-contracts/contracts/upgrades/ZkSyncUpgradeErrors.sol +++ b/l1-contracts/contracts/upgrades/ZkSyncUpgradeErrors.sol @@ -2,8 +2,6 @@ pragma solidity ^0.8.21; -// 0x7a47c9a2 -error InvalidChainId(); // 0xd7f8c13e error PreviousUpgradeBatchNotCleared(); // 0x3c43ccce @@ -12,12 +10,8 @@ error ProtocolMajorVersionNotZero(); error PatchCantSetUpgradeTxn(); // 0xd2c011d6 error L2UpgradeNonceNotEqualToNewProtocolVersion(uint256 nonce, uint256 protocolVersion); -// 0xcb5e4247 -error L2BytecodeHashMismatch(bytes32 expected, bytes32 provided); // 0x88d7b498 error ProtocolVersionTooSmall(); -// 0x56d45b12 -error ProtocolVersionTooBig(); // 0x5c598b60 error PreviousProtocolMajorVersionNotZero(); // 0x72ea85ad @@ -26,8 +20,6 @@ error NewProtocolMajorVersionNotZero(); error ProtocolVersionMinorDeltaTooBig(uint256 limit, uint256 proposed); // 0xe1a9736b error ProtocolVersionDeltaTooLarge(uint256 _proposedDelta, uint256 _maxDelta); -// 0x6d172ab2 -error ProtocolVersionShouldBeGreater(uint256 _oldProtocolVersion, uint256 _newProtocolVersion); // 0x559cc34e error PatchUpgradeCantSetDefaultAccount(); // 0x962fd7d0 @@ -36,8 +28,6 @@ error PatchUpgradeCantSetBootloader(); error PreviousUpgradeNotFinalized(bytes32 txHash); // 0xa0f47245 error PreviousUpgradeNotCleaned(); -// 0x07218375 -error UnexpectedNumberOfFactoryDeps(); // 0x76da24b9 error TooManyFactoryDeps(); // 0x5cb29523 @@ -46,3 +36,6 @@ error InvalidTxType(uint256 txType); error TimeNotReached(uint256 expectedTimestamp, uint256 actualTimestamp); // 0xd92e233d error ZeroAddress(); + +// 0x388b6f68 +error GatewayUpgradeFailed(); diff --git a/l1-contracts/contracts/vendor/AddressAliasHelper.sol b/l1-contracts/contracts/vendor/AddressAliasHelper.sol index ad80f3483..b604e9d24 100644 --- a/l1-contracts/contracts/vendor/AddressAliasHelper.sol +++ b/l1-contracts/contracts/vendor/AddressAliasHelper.sol @@ -43,19 +43,19 @@ library AddressAliasHelper { /// @notice Utility function used to calculate the correct refund recipient /// @param _refundRecipient the address that should receive the refund - /// @param _prevMsgSender the address that triggered the tx to L2 + /// @param _originalCaller the address that triggered the tx to L2 /// @return _recipient the corrected address that should receive the refund function actualRefundRecipient( address _refundRecipient, - address _prevMsgSender + address _originalCaller ) internal view returns (address _recipient) { if (_refundRecipient == address(0)) { - // If the `_refundRecipient` is not provided, we use the `_prevMsgSender` as the recipient. + // If the `_refundRecipient` is not provided, we use the `_originalCaller` as the recipient. // solhint-disable avoid-tx-origin // slither-disable-next-line tx-origin - _recipient = _prevMsgSender == tx.origin - ? _prevMsgSender - : AddressAliasHelper.applyL1ToL2Alias(_prevMsgSender); + _recipient = _originalCaller == tx.origin + ? _originalCaller + : AddressAliasHelper.applyL1ToL2Alias(_originalCaller); // solhint-enable avoid-tx-origin } else if (_refundRecipient.code.length > 0) { // If the `_refundRecipient` is a smart contract, we apply the L1 to L2 alias to prevent foot guns. diff --git a/l1-contracts/deploy-script-config-template/config-deploy-l1.toml b/l1-contracts/deploy-script-config-template/config-deploy-l1.toml index ad8982ffc..1802c8e0e 100644 --- a/l1-contracts/deploy-script-config-template/config-deploy-l1.toml +++ b/l1-contracts/deploy-script-config-template/config-deploy-l1.toml @@ -1,6 +1,7 @@ era_chain_id = 9 owner_address = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" testnet_verifier = true +support_l2_legacy_shared_bridge_test = false [contracts] governance_security_council_address = "0x0000000000000000000000000000000000000000" @@ -25,6 +26,7 @@ diamond_init_priority_tx_max_pubdata = 99000 diamond_init_minimal_l2_gas_price = 250000000 bootloader_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" default_aa_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +force_deployments_data = "0x" [tokens] token_weth_address = "0x0000000000000000000000000000000000000000" diff --git a/l1-contracts/deploy-script-config-template/register-hyperchain.toml b/l1-contracts/deploy-script-config-template/register-hyperchain.toml index dd93f34a4..bc27ace6d 100644 --- a/l1-contracts/deploy-script-config-template/register-hyperchain.toml +++ b/l1-contracts/deploy-script-config-template/register-hyperchain.toml @@ -10,3 +10,4 @@ base_token_gas_price_multiplier_nominator = 1 base_token_gas_price_multiplier_denominator = 1 governance_min_delay = 0 governance_security_council_address = "0x0000000000000000000000000000000000000000" +force_deployments_data = "0x" diff --git a/l1-contracts/deploy-scripts/AcceptAdmin.s.sol b/l1-contracts/deploy-scripts/AcceptAdmin.s.sol index 3a40b4d78..043b293cd 100644 --- a/l1-contracts/deploy-scripts/AcceptAdmin.s.sol +++ b/l1-contracts/deploy-scripts/AcceptAdmin.s.sol @@ -4,11 +4,20 @@ pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {Ownable2Step} from "@openzeppelin/contracts-v4/access/Ownable2Step.sol"; -import {IZkSyncHyperchain} from "contracts/state-transition/chain-interfaces/IZkSyncHyperchain.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; import {ChainAdmin} from "contracts/governance/ChainAdmin.sol"; +import {AccessControlRestriction} from "contracts/governance/AccessControlRestriction.sol"; import {IChainAdmin} from "contracts/governance/IChainAdmin.sol"; +import {IChainAdminSingleOwner} from "contracts/governance/IChainAdminSingleOwner.sol"; +import {Call} from "contracts/governance/Common.sol"; import {Utils} from "./Utils.sol"; +import {IGovernance} from "contracts/governance/IGovernance.sol"; import {stdToml} from "forge-std/StdToml.sol"; +import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; +import {ValidatorTimelock} from "contracts/state-transition/ValidatorTimelock.sol"; + +bytes32 constant SET_TOKEN_MULTIPLIER_SETTER_ROLE = keccak256("SET_TOKEN_MULTIPLIER_SETTER_ROLE"); contract AcceptAdmin is Script { using stdToml for string; @@ -43,7 +52,7 @@ contract AcceptAdmin is Script { // This function should be called by the owner to accept the admin role function governanceAcceptAdmin(address governor, address target) public { - IZkSyncHyperchain adminContract = IZkSyncHyperchain(target); + IZKChain adminContract = IZKChain(target); Utils.executeUpgrade({ _governor: governor, _salt: bytes32(0), @@ -56,10 +65,10 @@ contract AcceptAdmin is Script { // This function should be called by the owner to accept the admin role function chainAdminAcceptAdmin(ChainAdmin chainAdmin, address target) public { - IZkSyncHyperchain adminContract = IZkSyncHyperchain(target); + IZKChain adminContract = IZKChain(target); - IChainAdmin.Call[] memory calls = new IChainAdmin.Call[](1); - calls[0] = IChainAdmin.Call({target: target, value: 0, data: abi.encodeCall(adminContract.acceptAdmin, ())}); + Call[] memory calls = new Call[](1); + calls[0] = Call({target: target, value: 0, data: abi.encodeCall(adminContract.acceptAdmin, ())}); vm.startBroadcast(); chainAdmin.multicall(calls, true); @@ -67,11 +76,154 @@ contract AcceptAdmin is Script { } // This function should be called by the owner to update token multiplier setter role - function chainSetTokenMultiplierSetter(address chainAdmin, address target) public { - IChainAdmin admin = IChainAdmin(chainAdmin); + function chainSetTokenMultiplierSetter( + address chainAdmin, + address accessControlRestriction, + address diamondProxyAddress, + address setter + ) public { + if (accessControlRestriction == address(0)) { + _chainSetTokenMultiplierSetterSingleOwner(chainAdmin, setter); + } else { + _chainSetTokenMultiplierSetterLatestChainAdmin(accessControlRestriction, diamondProxyAddress, setter); + } + } + + function _chainSetTokenMultiplierSetterSingleOwner(address chainAdmin, address setter) internal { + IChainAdminSingleOwner admin = IChainAdminSingleOwner(chainAdmin); vm.startBroadcast(); - admin.setTokenMultiplierSetter(target); + admin.setTokenMultiplierSetter(setter); vm.stopBroadcast(); } + + function _chainSetTokenMultiplierSetterLatestChainAdmin( + address accessControlRestriction, + address diamondProxyAddress, + address setter + ) internal { + AccessControlRestriction restriction = AccessControlRestriction(accessControlRestriction); + + if ( + restriction.requiredRoles(diamondProxyAddress, IAdmin.setTokenMultiplier.selector) != + SET_TOKEN_MULTIPLIER_SETTER_ROLE + ) { + vm.startBroadcast(); + restriction.setRequiredRoleForCall( + diamondProxyAddress, + IAdmin.setTokenMultiplier.selector, + SET_TOKEN_MULTIPLIER_SETTER_ROLE + ); + vm.stopBroadcast(); + } + + if (!restriction.hasRole(SET_TOKEN_MULTIPLIER_SETTER_ROLE, setter)) { + vm.startBroadcast(); + restriction.grantRole(SET_TOKEN_MULTIPLIER_SETTER_ROLE, setter); + vm.stopBroadcast(); + } + } + + function governanceExecuteCalls(bytes memory callsToExecute, address governanceAddr) public { + IGovernance governance = IGovernance(governanceAddr); + Ownable2Step ownable = Ownable2Step(governanceAddr); + + Call[] memory calls = abi.decode(callsToExecute, (Call[])); + + IGovernance.Operation memory operation = IGovernance.Operation({ + calls: calls, + predecessor: bytes32(0), + salt: bytes32(0) + }); + + vm.startBroadcast(ownable.owner()); + governance.scheduleTransparent(operation, 0); + // We assume that the total value is 0 + governance.execute{value: 0}(operation); + vm.stopBroadcast(); + } + + function adminExecuteUpgrade( + bytes memory diamondCut, + address adminAddr, + address accessControlRestriction, + address chainDiamondProxy + ) public { + uint256 oldProtocolVersion = IZKChain(chainDiamondProxy).getProtocolVersion(); + Diamond.DiamondCutData memory upgradeCutData = abi.decode(diamondCut, (Diamond.DiamondCutData)); + + Utils.adminExecute( + adminAddr, + accessControlRestriction, + chainDiamondProxy, + abi.encodeCall(IAdmin.upgradeChainFromVersion, (oldProtocolVersion, upgradeCutData)), + 0 + ); + } + + function adminScheduleUpgrade( + address adminAddr, + address accessControlRestriction, + uint256 newProtocolVersion, + uint256 timestamp + ) public { + Utils.adminExecute( + adminAddr, + accessControlRestriction, + adminAddr, + // We do instant upgrades, but obviously it should be different in prod + abi.encodeCall(ChainAdmin.setUpgradeTimestamp, (newProtocolVersion, timestamp)), + 0 + ); + } + + function setDAValidatorPair( + ChainAdmin chainAdmin, + address target, + address l1DaValidator, + address l2DaValidator + ) public { + IZKChain adminContract = IZKChain(target); + + Call[] memory calls = new Call[](1); + calls[0] = Call({ + target: target, + value: 0, + data: abi.encodeCall(adminContract.setDAValidatorPair, (l1DaValidator, l2DaValidator)) + }); + + vm.startBroadcast(); + chainAdmin.multicall(calls, true); + vm.stopBroadcast(); + } + + function makePermanentRollup(ChainAdmin chainAdmin, address target) public { + IZKChain adminContract = IZKChain(target); + + Call[] memory calls = new Call[](1); + calls[0] = Call({target: target, value: 0, data: abi.encodeCall(adminContract.makePermanentRollup, ())}); + + vm.startBroadcast(); + chainAdmin.multicall(calls, true); + vm.stopBroadcast(); + } + + function updateValidator( + address adminAddr, + address accessControlRestriction, + address validatorTimelock, + uint256 chainId, + address validatorAddress, + bool addValidator + ) public { + bytes memory data; + // The interface should be compatible with both the new and the old ValidatorTimelock + if (addValidator) { + data = abi.encodeCall(ValidatorTimelock.addValidator, (chainId, validatorAddress)); + } else { + data = abi.encodeCall(ValidatorTimelock.removeValidator, (chainId, validatorAddress)); + } + + Utils.adminExecute(adminAddr, accessControlRestriction, validatorTimelock, data, 0); + } } diff --git a/l1-contracts/deploy-scripts/CreateAndTransfer.sol b/l1-contracts/deploy-scripts/CreateAndTransfer.sol new file mode 100644 index 000000000..a429d354f --- /dev/null +++ b/l1-contracts/deploy-scripts/CreateAndTransfer.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +import {ProxyAdmin} from "@openzeppelin/contracts-v4/proxy/transparent/ProxyAdmin.sol"; + +pragma solidity 0.8.24; + +contract CreateAndTransfer { + constructor(bytes memory bytecode, bytes32 salt, address owner) { + address addr; + assembly { + addr := create2(0x0, add(bytecode, 0x20), mload(bytecode), salt) + } + + require(addr != address(0), "Create2: Failed on deploy"); + ProxyAdmin proxy = ProxyAdmin(addr); + proxy.transferOwnership(owner); + } +} diff --git a/l1-contracts/deploy-scripts/DecentralizeGovernanceUpgradeScript.s.sol b/l1-contracts/deploy-scripts/DecentralizeGovernanceUpgradeScript.s.sol index 1e35d3fe4..86efb1292 100644 --- a/l1-contracts/deploy-scripts/DecentralizeGovernanceUpgradeScript.s.sol +++ b/l1-contracts/deploy-scripts/DecentralizeGovernanceUpgradeScript.s.sol @@ -1,5 +1,4 @@ // SPDX-License-Identifier: MIT -// solhint-disable reason-string, gas-custom-errors pragma solidity 0.8.24; import {Script} from "forge-std/Script.sol"; @@ -8,20 +7,25 @@ import {ProxyAdmin} from "@openzeppelin/contracts-v4/proxy/transparent/ProxyAdmi import {ITransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; import {Governance} from "contracts/governance/Governance.sol"; -import {IStateTransitionManager} from "contracts/state-transition/IStateTransitionManager.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; import {Utils} from "./Utils.sol"; +import {ProxyAdminIncorrect, ProxyAdminIncorrectOwner} from "./ZkSyncScriptErrors.sol"; contract DecentralizeGovernanceUpgradeScript is Script { - function upgradeSTM( + function upgradeCTM( ProxyAdmin _proxyAdmin, - ITransparentUpgradeableProxy _stmProxy, + ITransparentUpgradeableProxy _ctmProxy, Governance _governance, - address _newStmImpl + address _newCtmImpl ) public { - require(_proxyAdmin.getProxyAdmin(_stmProxy) == address(_proxyAdmin)); - require(_proxyAdmin.owner() == address(_governance)); + if (_proxyAdmin.getProxyAdmin(_ctmProxy) != address(_proxyAdmin)) { + revert ProxyAdminIncorrect(_proxyAdmin.getProxyAdmin(_ctmProxy), address(_proxyAdmin)); + } + if (_proxyAdmin.owner() != address(_governance)) { + revert ProxyAdminIncorrectOwner(_proxyAdmin.owner(), address(_governance)); + } - bytes memory proxyAdminUpgradeData = abi.encodeCall(ProxyAdmin.upgrade, (_stmProxy, _newStmImpl)); + bytes memory proxyAdminUpgradeData = abi.encodeCall(ProxyAdmin.upgrade, (_ctmProxy, _newCtmImpl)); Utils.executeUpgrade({ _governor: address(_governance), @@ -34,7 +38,7 @@ contract DecentralizeGovernanceUpgradeScript is Script { } function setPendingAdmin(address _target, Governance _governance, address _pendingAdmin) public { - bytes memory upgradeData = abi.encodeCall(IStateTransitionManager.setPendingAdmin, (_pendingAdmin)); + bytes memory upgradeData = abi.encodeCall(IChainTypeManager.setPendingAdmin, (_pendingAdmin)); Utils.executeUpgrade({ _governor: address(_governance), _salt: bytes32(0), diff --git a/l1-contracts/deploy-scripts/DeployErc20.s.sol b/l1-contracts/deploy-scripts/DeployErc20.s.sol index 9a7c9cfa9..400c1ff1f 100644 --- a/l1-contracts/deploy-scripts/DeployErc20.s.sol +++ b/l1-contracts/deploy-scripts/DeployErc20.s.sol @@ -45,13 +45,22 @@ contract DeployErc20Script is Script { saveOutput(); } + function getTokensAddresses() public view returns (address[] memory) { + uint256 tokensLength = config.tokens.length; + address[] memory addresses = new address[](tokensLength); + for (uint256 i = 0; i < tokensLength; ++i) { + addresses[i] = config.tokens[i].addr; + } + return addresses; + } + function initializeConfig() internal { config.deployerAddress = msg.sender; string memory root = vm.projectRoot(); // Grab config from output of l1 deployment - string memory path = string.concat(root, "/script-out/output-deploy-l1.toml"); + string memory path = string.concat(root, vm.envString("L1_OUTPUT")); string memory toml = vm.readFile(path); // Config file must be parsed key by key, otherwise values returned @@ -61,7 +70,7 @@ contract DeployErc20Script is Script { config.create2FactorySalt = vm.parseTomlBytes32(toml, "$.create2_factory_salt"); // Grab config from custom config file - path = string.concat(root, "/script-config/config-deploy-erc20.toml"); + path = string.concat(root, vm.envString("TOKENS_CONFIG")); toml = vm.readFile(path); config.additionalAddressesForMinting = vm.parseTomlAddressArray(toml, "$.additional_addresses_for_minting"); @@ -128,6 +137,9 @@ contract DeployErc20Script is Script { revert MintFailed(); } console.log("Minting to:", additionalAddressesForMinting[i]); + if (!success) { + revert MintFailed(); + } } } @@ -158,4 +170,7 @@ contract DeployErc20Script is Script { function deployViaCreate2(bytes memory _bytecode) internal returns (address) { return Utils.deployViaCreate2(_bytecode, config.create2FactorySalt, config.create2FactoryAddr); } + + // add this to be excluded from coverage report + function test() internal {} } diff --git a/l1-contracts/deploy-scripts/DeployL1.s.sol b/l1-contracts/deploy-scripts/DeployL1.s.sol index 416ffd6a3..b430791fb 100644 --- a/l1-contracts/deploy-scripts/DeployL1.s.sol +++ b/l1-contracts/deploy-scripts/DeployL1.s.sol @@ -1,250 +1,147 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -// solhint-disable no-console +// solhint-disable no-console, gas-custom-errors import {Script, console2 as console} from "forge-std/Script.sol"; import {stdToml} from "forge-std/StdToml.sol"; import {ProxyAdmin} from "@openzeppelin/contracts-v4/proxy/transparent/ProxyAdmin.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; - -import {Utils} from "./Utils.sol"; +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; +import {StateTransitionDeployedAddresses, Utils, L2_BRIDGEHUB_ADDRESS, L2_ASSET_ROUTER_ADDRESS, L2_NATIVE_TOKEN_VAULT_ADDRESS, L2_MESSAGE_ROOT_ADDRESS} from "./Utils.sol"; import {Multicall3} from "contracts/dev-contracts/Multicall3.sol"; import {Verifier} from "contracts/state-transition/Verifier.sol"; import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; import {VerifierParams, IVerifier} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; import {DefaultUpgrade} from "contracts/upgrades/DefaultUpgrade.sol"; import {Governance} from "contracts/governance/Governance.sol"; +import {L1GenesisUpgrade} from "contracts/upgrades/L1GenesisUpgrade.sol"; import {ChainAdmin} from "contracts/governance/ChainAdmin.sol"; -import {GenesisUpgrade} from "contracts/upgrades/GenesisUpgrade.sol"; +import {AccessControlRestriction} from "contracts/governance/AccessControlRestriction.sol"; import {ValidatorTimelock} from "contracts/state-transition/ValidatorTimelock.sol"; import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; +import {MessageRoot} from "contracts/bridgehub/MessageRoot.sol"; +import {CTMDeploymentTracker} from "contracts/bridgehub/CTMDeploymentTracker.sol"; +import {L1NativeTokenVault} from "contracts/bridge/ntv/L1NativeTokenVault.sol"; import {ExecutorFacet} from "contracts/state-transition/chain-deps/facets/Executor.sol"; import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol"; import {MailboxFacet} from "contracts/state-transition/chain-deps/facets/Mailbox.sol"; import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; import {DiamondInit} from "contracts/state-transition/chain-deps/DiamondInit.sol"; -import {StateTransitionManager} from "contracts/state-transition/StateTransitionManager.sol"; -import {StateTransitionManagerInitializeData, ChainCreationParams} from "contracts/state-transition/IStateTransitionManager.sol"; -import {IStateTransitionManager} from "contracts/state-transition/IStateTransitionManager.sol"; +import {ChainTypeManager} from "contracts/state-transition/ChainTypeManager.sol"; +import {ChainTypeManagerInitializeData, ChainCreationParams} from "contracts/state-transition/IChainTypeManager.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {InitializeDataNewChain as DiamondInitializeDataNewChain} from "contracts/state-transition/chain-interfaces/IDiamondInit.sol"; -import {FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol"; -import {L1SharedBridge} from "contracts/bridge/L1SharedBridge.sol"; +import {FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZKChainStorage.sol"; +import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol"; import {L1ERC20Bridge} from "contracts/bridge/L1ERC20Bridge.sol"; +import {L1Nullifier} from "contracts/bridge/L1Nullifier.sol"; import {DiamondProxy} from "contracts/state-transition/chain-deps/DiamondProxy.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {INativeTokenVault} from "contracts/bridge/ntv/INativeTokenVault.sol"; +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; import {AddressHasNoCode} from "./ZkSyncScriptErrors.sol"; - -contract DeployL1Script is Script { +import {ICTMDeploymentTracker} from "contracts/bridgehub/ICTMDeploymentTracker.sol"; +import {IMessageRoot} from "contracts/bridgehub/IMessageRoot.sol"; +import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {IL1Nullifier} from "contracts/bridge/L1Nullifier.sol"; +import {IL1NativeTokenVault} from "contracts/bridge/ntv/IL1NativeTokenVault.sol"; +import {L1NullifierDev} from "contracts/dev-contracts/L1NullifierDev.sol"; +import {AccessControlRestriction} from "contracts/governance/AccessControlRestriction.sol"; +import {ICTMDeploymentTracker} from "contracts/bridgehub/ICTMDeploymentTracker.sol"; +import {IMessageRoot} from "contracts/bridgehub/IMessageRoot.sol"; +import {IAssetRouterBase} from "contracts/bridge/asset-router/IAssetRouterBase.sol"; +import {L2ContractsBytecodesLib} from "./L2ContractsBytecodesLib.sol"; +import {ValidiumL1DAValidator} from "contracts/state-transition/data-availability/ValidiumL1DAValidator.sol"; +import {RollupDAManager} from "contracts/state-transition/data-availability/RollupDAManager.sol"; +import {BytecodesSupplier} from "contracts/upgrades/BytecodesSupplier.sol"; +import {L2LegacySharedBridgeTestHelper} from "./L2LegacySharedBridgeTestHelper.sol"; + +import {DeployUtils, GeneratedData, Config, DeployedAddresses, FixedForceDeploymentsData} from "./DeployUtils.s.sol"; + +contract DeployL1Script is Script, DeployUtils { using stdToml for string; address internal constant ADDRESS_ONE = 0x0000000000000000000000000000000000000001; - address internal constant DETERMINISTIC_CREATE2_ADDRESS = 0x4e59b44847b379578588920cA78FbF26c0B4956C; - - // solhint-disable-next-line gas-struct-packing - struct DeployedAddresses { - BridgehubDeployedAddresses bridgehub; - StateTransitionDeployedAddresses stateTransition; - BridgesDeployedAddresses bridges; - address transparentProxyAdmin; - address governance; - address chainAdmin; - address blobVersionedHashRetriever; - address validatorTimelock; - address create2Factory; - } - - // solhint-disable-next-line gas-struct-packing - struct BridgehubDeployedAddresses { - address bridgehubImplementation; - address bridgehubProxy; - } - - // solhint-disable-next-line gas-struct-packing - struct StateTransitionDeployedAddresses { - address stateTransitionProxy; - address stateTransitionImplementation; - address verifier; - address adminFacet; - address mailboxFacet; - address executorFacet; - address gettersFacet; - address diamondInit; - address genesisUpgrade; - address defaultUpgrade; - address diamondProxy; - } - - // solhint-disable-next-line gas-struct-packing - struct BridgesDeployedAddresses { - address erc20BridgeImplementation; - address erc20BridgeProxy; - address sharedBridgeImplementation; - address sharedBridgeProxy; - } - - // solhint-disable-next-line gas-struct-packing - struct Config { - uint256 l1ChainId; - uint256 eraChainId; - address deployerAddress; - address ownerAddress; - bool testnetVerifier; - ContractsConfig contracts; - TokensConfig tokens; - } - - // solhint-disable-next-line gas-struct-packing - struct ContractsConfig { - bytes32 create2FactorySalt; - address create2FactoryAddr; - address multicall3Addr; - uint256 validatorTimelockExecutionDelay; - bytes32 genesisRoot; - uint256 genesisRollupLeafIndex; - bytes32 genesisBatchCommitment; - uint256 latestProtocolVersion; - bytes32 recursionNodeLevelVkHash; - bytes32 recursionLeafLevelVkHash; - bytes32 recursionCircuitsSetVksHash; - uint256 priorityTxMaxGasLimit; - PubdataPricingMode diamondInitPubdataPricingMode; - uint256 diamondInitBatchOverheadL1Gas; - uint256 diamondInitMaxPubdataPerBatch; - uint256 diamondInitMaxL2GasPerBatch; - uint256 diamondInitPriorityTxMaxPubdata; - uint256 diamondInitMinimalL2GasPrice; - address governanceSecurityCouncilAddress; - uint256 governanceMinDelay; - uint256 maxNumberOfChains; - bytes diamondCutData; - bytes32 bootloaderHash; - bytes32 defaultAAHash; - } - - struct TokensConfig { - address tokenWethAddress; - } - - Config internal config; - DeployedAddresses internal addresses; function run() public { console.log("Deploying L1 contracts"); - initializeConfig(); + runInner("/script-config/config-deploy-l1.toml", "/script-out/output-deploy-l1.toml"); + } + + function runForTest() public { + runInner(vm.envString("L1_CONFIG"), vm.envString("L1_OUTPUT")); + } + + function getAddresses() public view returns (DeployedAddresses memory) { + return addresses; + } + + function getConfig() public view returns (Config memory) { + return config; + } + + function runInner(string memory inputPath, string memory outputPath) internal { + string memory root = vm.projectRoot(); + inputPath = string.concat(root, inputPath); + outputPath = string.concat(root, outputPath); + + saveDiamondSelectors(); + initializeConfig(inputPath); instantiateCreate2Factory(); deployIfNeededMulticall3(); + deployBytecodesSupplier(); + deployVerifier(); deployDefaultUpgrade(); deployGenesisUpgrade(); + deployDAValidators(); deployValidatorTimelock(); deployGovernance(); deployChainAdmin(); deployTransparentProxyAdmin(); deployBridgehubContract(); - deployBlobVersionedHashRetriever(); - deployStateTransitionManagerContract(); - setStateTransitionManagerInValidatorTimelock(); - - deployDiamondProxy(); + deployMessageRootContract(); + deployL1NullifierContracts(); deploySharedBridgeContracts(); + deployBridgedStandardERC20Implementation(); + deployBridgedTokenBeacon(); + deployL1NativeTokenVaultImplementation(); + deployL1NativeTokenVaultProxy(); deployErc20BridgeImplementation(); deployErc20BridgeProxy(); updateSharedBridge(); + deployCTMDeploymentTracker(); + setBridgehubParams(); + + initializeGeneratedData(); + + deployBlobVersionedHashRetriever(); + deployChainTypeManagerContract(); + registerChainTypeManager(); + setChainTypeManagerInValidatorTimelock(); updateOwners(); - saveOutput(); + saveOutput(outputPath); } - function initializeConfig() internal { - string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/script-config/config-deploy-l1.toml"); - string memory toml = vm.readFile(path); - - config.l1ChainId = block.chainid; - config.deployerAddress = msg.sender; - - // Config file must be parsed key by key, otherwise values returned - // are parsed alfabetically and not by key. - // https://book.getfoundry.sh/cheatcodes/parse-toml - config.eraChainId = toml.readUint("$.era_chain_id"); - config.ownerAddress = toml.readAddress("$.owner_address"); - config.testnetVerifier = toml.readBool("$.testnet_verifier"); - - config.contracts.governanceSecurityCouncilAddress = toml.readAddress( - "$.contracts.governance_security_council_address" - ); - config.contracts.governanceMinDelay = toml.readUint("$.contracts.governance_min_delay"); - config.contracts.maxNumberOfChains = toml.readUint("$.contracts.max_number_of_chains"); - config.contracts.create2FactorySalt = toml.readBytes32("$.contracts.create2_factory_salt"); - if (vm.keyExistsToml(toml, "$.contracts.create2_factory_addr")) { - config.contracts.create2FactoryAddr = toml.readAddress("$.contracts.create2_factory_addr"); - } - config.contracts.validatorTimelockExecutionDelay = toml.readUint( - "$.contracts.validator_timelock_execution_delay" - ); - config.contracts.genesisRoot = toml.readBytes32("$.contracts.genesis_root"); - config.contracts.genesisRollupLeafIndex = toml.readUint("$.contracts.genesis_rollup_leaf_index"); - config.contracts.genesisBatchCommitment = toml.readBytes32("$.contracts.genesis_batch_commitment"); - config.contracts.latestProtocolVersion = toml.readUint("$.contracts.latest_protocol_version"); - config.contracts.recursionNodeLevelVkHash = toml.readBytes32("$.contracts.recursion_node_level_vk_hash"); - config.contracts.recursionLeafLevelVkHash = toml.readBytes32("$.contracts.recursion_leaf_level_vk_hash"); - config.contracts.recursionCircuitsSetVksHash = toml.readBytes32("$.contracts.recursion_circuits_set_vks_hash"); - config.contracts.priorityTxMaxGasLimit = toml.readUint("$.contracts.priority_tx_max_gas_limit"); - config.contracts.diamondInitPubdataPricingMode = PubdataPricingMode( - toml.readUint("$.contracts.diamond_init_pubdata_pricing_mode") - ); - config.contracts.diamondInitBatchOverheadL1Gas = toml.readUint( - "$.contracts.diamond_init_batch_overhead_l1_gas" - ); - config.contracts.diamondInitMaxPubdataPerBatch = toml.readUint( - "$.contracts.diamond_init_max_pubdata_per_batch" - ); - config.contracts.diamondInitMaxL2GasPerBatch = toml.readUint("$.contracts.diamond_init_max_l2_gas_per_batch"); - config.contracts.diamondInitPriorityTxMaxPubdata = toml.readUint( - "$.contracts.diamond_init_priority_tx_max_pubdata" - ); - config.contracts.diamondInitMinimalL2GasPrice = toml.readUint("$.contracts.diamond_init_minimal_l2_gas_price"); - config.contracts.defaultAAHash = toml.readBytes32("$.contracts.default_aa_hash"); - config.contracts.bootloaderHash = toml.readBytes32("$.contracts.bootloader_hash"); - - config.tokens.tokenWethAddress = toml.readAddress("$.tokens.token_weth_address"); - } - - function instantiateCreate2Factory() internal { - address contractAddress; - - bool isDeterministicDeployed = DETERMINISTIC_CREATE2_ADDRESS.code.length > 0; - bool isConfigured = config.contracts.create2FactoryAddr != address(0); - - if (isConfigured) { - if (config.contracts.create2FactoryAddr.code.length == 0) { - revert AddressHasNoCode(config.contracts.create2FactoryAddr); - } - contractAddress = config.contracts.create2FactoryAddr; - console.log("Using configured Create2Factory address:", contractAddress); - } else if (isDeterministicDeployed) { - contractAddress = DETERMINISTIC_CREATE2_ADDRESS; - console.log("Using deterministic Create2Factory address:", contractAddress); - } else { - contractAddress = Utils.deployCreate2Factory(); - console.log("Create2Factory deployed at:", contractAddress); - } - - addresses.create2Factory = contractAddress; + function initializeGeneratedData() internal { + generatedData.forceDeploymentsData = prepareForceDeploymentsData(); } function deployIfNeededMulticall3() internal { // Multicall3 is already deployed on public networks if (MULTICALL3_ADDRESS.code.length == 0) { - address contractAddress = deployViaCreate2(type(Multicall3).creationCode); + address contractAddress = deployViaCreate2(type(Multicall3).creationCode, ""); console.log("Multicall3 deployed at:", contractAddress); config.contracts.multicall3Addr = contractAddress; } else { @@ -252,80 +149,73 @@ contract DeployL1Script is Script { } } - function deployVerifier() internal { - bytes memory code; - if (config.testnetVerifier) { - code = type(TestnetVerifier).creationCode; - } else { - code = type(Verifier).creationCode; - } - address contractAddress = deployViaCreate2(code); - console.log("Verifier deployed at:", contractAddress); - addresses.stateTransition.verifier = contractAddress; + function getRollupL2ValidatorAddress() internal returns (address) { + return + Utils.getL2AddressViaCreate2Factory( + bytes32(0), + L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readRollupL2DAValidatorBytecode()), + hex"" + ); } - function deployDefaultUpgrade() internal { - address contractAddress = deployViaCreate2(type(DefaultUpgrade).creationCode); - console.log("DefaultUpgrade deployed at:", contractAddress); - addresses.stateTransition.defaultUpgrade = contractAddress; + function getNoDAValidiumL2ValidatorAddress() internal returns (address) { + return + Utils.getL2AddressViaCreate2Factory( + bytes32(0), + L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readNoDAL2DAValidatorBytecode()), + hex"" + ); } - function deployGenesisUpgrade() internal { - address contractAddress = deployViaCreate2(type(GenesisUpgrade).creationCode); - console.log("GenesisUpgrade deployed at:", contractAddress); - addresses.stateTransition.genesisUpgrade = contractAddress; + function getAvailL2ValidatorAddress() internal returns (address) { + return + Utils.getL2AddressViaCreate2Factory( + bytes32(0), + L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readAvailL2DAValidatorBytecode()), + hex"" + ); } - function deployValidatorTimelock() internal { - uint32 executionDelay = uint32(config.contracts.validatorTimelockExecutionDelay); - bytes memory bytecode = abi.encodePacked( - type(ValidatorTimelock).creationCode, - abi.encode(config.deployerAddress, executionDelay, config.eraChainId) - ); - address contractAddress = deployViaCreate2(bytecode); - console.log("ValidatorTimelock deployed at:", contractAddress); - addresses.validatorTimelock = contractAddress; - } + function deployDAValidators() internal { + vm.broadcast(msg.sender); + address rollupDAManager = address(new RollupDAManager()); + addresses.daAddresses.rollupDAManager = rollupDAManager; - function deployGovernance() internal { - bytes memory bytecode = abi.encodePacked( - type(Governance).creationCode, - abi.encode( - config.ownerAddress, - config.contracts.governanceSecurityCouncilAddress, - config.contracts.governanceMinDelay - ) - ); - address contractAddress = deployViaCreate2(bytecode); - console.log("Governance deployed at:", contractAddress); - addresses.governance = contractAddress; - } + address rollupDAValidator = deployViaCreate2(Utils.readRollupDAValidatorBytecode(), ""); + console.log("L1RollupDAValidator deployed at:", rollupDAValidator); + addresses.daAddresses.l1RollupDAValidator = rollupDAValidator; - function deployChainAdmin() internal { - bytes memory bytecode = abi.encodePacked( - type(ChainAdmin).creationCode, - abi.encode(config.ownerAddress, address(0)) + addresses.daAddresses.noDAValidiumL1DAValidator = deployViaCreate2( + type(ValidiumL1DAValidator).creationCode, + "" ); - address contractAddress = deployViaCreate2(bytecode); - console.log("ChainAdmin deployed at:", contractAddress); - addresses.chainAdmin = contractAddress; - } + console.log("L1NoDAValidiumDAValidator deployed at:", addresses.daAddresses.noDAValidiumL1DAValidator); + + if (config.contracts.availL1DAValidator == address(0)) { + address availBridge = deployViaCreate2(Utils.readDummyAvailBridgeBytecode(), ""); + addresses.daAddresses.availL1DAValidator = deployViaCreate2( + Utils.readAvailL1DAValidatorBytecode(), + abi.encode(availBridge) + ); + console.log("AvailL1DAValidator deployed at:", addresses.daAddresses.availL1DAValidator); + } else { + addresses.daAddresses.availL1DAValidator = config.contracts.availL1DAValidator; + } - function deployTransparentProxyAdmin() internal { - vm.startBroadcast(); - ProxyAdmin proxyAdmin = new ProxyAdmin(); - proxyAdmin.transferOwnership(addresses.governance); + vm.startBroadcast(msg.sender); + RollupDAManager(rollupDAManager).updateDAPair(address(rollupDAValidator), getRollupL2ValidatorAddress(), true); vm.stopBroadcast(); - console.log("Transparent Proxy Admin deployed at:", address(proxyAdmin)); - addresses.transparentProxyAdmin = address(proxyAdmin); } function deployBridgehubContract() internal { - address bridgehubImplementation = deployViaCreate2(type(Bridgehub).creationCode); + address bridgehubImplementation = deployViaCreate2( + type(Bridgehub).creationCode, + abi.encode(config.l1ChainId, config.ownerAddress, (config.contracts.maxNumberOfChains)) + ); console.log("Bridgehub Implementation deployed at:", bridgehubImplementation); addresses.bridgehub.bridgehubImplementation = bridgehubImplementation; - bytes memory bytecode = abi.encodePacked( + address bridgehubProxy = deployViaCreate2( type(TransparentUpgradeableProxy).creationCode, abi.encode( bridgehubImplementation, @@ -333,164 +223,88 @@ contract DeployL1Script is Script { abi.encodeCall(Bridgehub.initialize, (config.deployerAddress)) ) ); - address bridgehubProxy = deployViaCreate2(bytecode); console.log("Bridgehub Proxy deployed at:", bridgehubProxy); addresses.bridgehub.bridgehubProxy = bridgehubProxy; } - function deployBlobVersionedHashRetriever() internal { - // solc contracts/state-transition/utils/blobVersionedHashRetriever.yul --strict-assembly --bin - bytes memory bytecode = hex"600b600b5f39600b5ff3fe5f358049805f5260205ff3"; - address contractAddress = deployViaCreate2(bytecode); - console.log("BlobVersionedHashRetriever deployed at:", contractAddress); - addresses.blobVersionedHashRetriever = contractAddress; - } - - function deployStateTransitionManagerContract() internal { - deployStateTransitionDiamondFacets(); - deployStateTransitionManagerImplementation(); - deployStateTransitionManagerProxy(); - registerStateTransitionManager(); - } - - function deployStateTransitionDiamondFacets() internal { - address executorFacet = deployViaCreate2(type(ExecutorFacet).creationCode); - console.log("ExecutorFacet deployed at:", executorFacet); - addresses.stateTransition.executorFacet = executorFacet; - - address adminFacet = deployViaCreate2(type(AdminFacet).creationCode); - console.log("AdminFacet deployed at:", adminFacet); - addresses.stateTransition.adminFacet = adminFacet; - - address mailboxFacet = deployViaCreate2( - abi.encodePacked(type(MailboxFacet).creationCode, abi.encode(config.eraChainId)) + function deployMessageRootContract() internal { + address messageRootImplementation = deployViaCreate2( + type(MessageRoot).creationCode, + abi.encode(addresses.bridgehub.bridgehubProxy) ); - console.log("MailboxFacet deployed at:", mailboxFacet); - addresses.stateTransition.mailboxFacet = mailboxFacet; - - address gettersFacet = deployViaCreate2(type(GettersFacet).creationCode); - console.log("GettersFacet deployed at:", gettersFacet); - addresses.stateTransition.gettersFacet = gettersFacet; + console.log("MessageRoot Implementation deployed at:", messageRootImplementation); + addresses.bridgehub.messageRootImplementation = messageRootImplementation; - address diamondInit = deployViaCreate2(type(DiamondInit).creationCode); - console.log("DiamondInit deployed at:", diamondInit); - addresses.stateTransition.diamondInit = diamondInit; - } - - function deployStateTransitionManagerImplementation() internal { - bytes memory bytecode = abi.encodePacked( - type(StateTransitionManager).creationCode, - abi.encode(addresses.bridgehub.bridgehubProxy), - abi.encode(config.contracts.maxNumberOfChains) + address messageRootProxy = deployViaCreate2( + type(TransparentUpgradeableProxy).creationCode, + abi.encode( + messageRootImplementation, + addresses.transparentProxyAdmin, + abi.encodeCall(MessageRoot.initialize, ()) + ) ); - address contractAddress = deployViaCreate2(bytecode); - console.log("StateTransitionManagerImplementation deployed at:", contractAddress); - addresses.stateTransition.stateTransitionImplementation = contractAddress; + console.log("Message Root Proxy deployed at:", messageRootProxy); + addresses.bridgehub.messageRootProxy = messageRootProxy; } - function deployStateTransitionManagerProxy() internal { - Diamond.FacetCut[] memory facetCuts = new Diamond.FacetCut[](4); - facetCuts[0] = Diamond.FacetCut({ - facet: addresses.stateTransition.adminFacet, - action: Diamond.Action.Add, - isFreezable: false, - selectors: Utils.getAllSelectors(addresses.stateTransition.adminFacet.code) - }); - facetCuts[1] = Diamond.FacetCut({ - facet: addresses.stateTransition.gettersFacet, - action: Diamond.Action.Add, - isFreezable: false, - selectors: Utils.getAllSelectors(addresses.stateTransition.gettersFacet.code) - }); - facetCuts[2] = Diamond.FacetCut({ - facet: addresses.stateTransition.mailboxFacet, - action: Diamond.Action.Add, - isFreezable: true, - selectors: Utils.getAllSelectors(addresses.stateTransition.mailboxFacet.code) - }); - facetCuts[3] = Diamond.FacetCut({ - facet: addresses.stateTransition.executorFacet, - action: Diamond.Action.Add, - isFreezable: true, - selectors: Utils.getAllSelectors(addresses.stateTransition.executorFacet.code) - }); - - VerifierParams memory verifierParams = VerifierParams({ - recursionNodeLevelVkHash: config.contracts.recursionNodeLevelVkHash, - recursionLeafLevelVkHash: config.contracts.recursionLeafLevelVkHash, - recursionCircuitsSetVksHash: config.contracts.recursionCircuitsSetVksHash - }); - - FeeParams memory feeParams = FeeParams({ - pubdataPricingMode: config.contracts.diamondInitPubdataPricingMode, - batchOverheadL1Gas: uint32(config.contracts.diamondInitBatchOverheadL1Gas), - maxPubdataPerBatch: uint32(config.contracts.diamondInitMaxPubdataPerBatch), - maxL2GasPerBatch: uint32(config.contracts.diamondInitMaxL2GasPerBatch), - priorityTxMaxPubdata: uint32(config.contracts.diamondInitPriorityTxMaxPubdata), - minimalL2GasPrice: uint64(config.contracts.diamondInitMinimalL2GasPrice) - }); - - DiamondInitializeDataNewChain memory initializeData = DiamondInitializeDataNewChain({ - verifier: IVerifier(addresses.stateTransition.verifier), - verifierParams: verifierParams, - l2BootloaderBytecodeHash: config.contracts.bootloaderHash, - l2DefaultAccountBytecodeHash: config.contracts.defaultAAHash, - priorityTxMaxGasLimit: config.contracts.priorityTxMaxGasLimit, - feeParams: feeParams, - blobVersionedHashRetriever: addresses.blobVersionedHashRetriever - }); - - Diamond.DiamondCutData memory diamondCut = Diamond.DiamondCutData({ - facetCuts: facetCuts, - initAddress: addresses.stateTransition.diamondInit, - initCalldata: abi.encode(initializeData) - }); - - config.contracts.diamondCutData = abi.encode(diamondCut); - - ChainCreationParams memory chainCreationParams = ChainCreationParams({ - genesisUpgrade: addresses.stateTransition.genesisUpgrade, - genesisBatchHash: config.contracts.genesisRoot, - genesisIndexRepeatedStorageChanges: uint64(config.contracts.genesisRollupLeafIndex), - genesisBatchCommitment: config.contracts.genesisBatchCommitment, - diamondCut: diamondCut - }); - - StateTransitionManagerInitializeData memory diamondInitData = StateTransitionManagerInitializeData({ - owner: msg.sender, - validatorTimelock: addresses.validatorTimelock, - chainCreationParams: chainCreationParams, - protocolVersion: config.contracts.latestProtocolVersion - }); + function deployCTMDeploymentTracker() internal { + address ctmDTImplementation = deployViaCreate2( + type(CTMDeploymentTracker).creationCode, + abi.encode(addresses.bridgehub.bridgehubProxy, addresses.bridges.sharedBridgeProxy) + ); + console.log("CTM Deployment Tracker Implementation deployed at:", ctmDTImplementation); + addresses.bridgehub.ctmDeploymentTrackerImplementation = ctmDTImplementation; - address contractAddress = deployViaCreate2( - abi.encodePacked( - type(TransparentUpgradeableProxy).creationCode, - abi.encode( - addresses.stateTransition.stateTransitionImplementation, - addresses.transparentProxyAdmin, - abi.encodeCall(StateTransitionManager.initialize, (diamondInitData)) - ) + address ctmDTProxy = deployViaCreate2( + type(TransparentUpgradeableProxy).creationCode, + abi.encode( + ctmDTImplementation, + addresses.transparentProxyAdmin, + abi.encodeCall(CTMDeploymentTracker.initialize, (config.deployerAddress)) ) ); - console.log("StateTransitionManagerProxy deployed at:", contractAddress); - addresses.stateTransition.stateTransitionProxy = contractAddress; + console.log("CTM Deployment Tracker Proxy deployed at:", ctmDTProxy); + addresses.bridgehub.ctmDeploymentTrackerProxy = ctmDTProxy; + } + + function deployBlobVersionedHashRetriever() internal { + // solc contracts/state-transition/utils/blobVersionedHashRetriever.yul --strict-assembly --bin + bytes memory bytecode = hex"600b600b5f39600b5ff3fe5f358049805f5260205ff3"; + address contractAddress = deployViaCreate2(bytecode, ""); + console.log("BlobVersionedHashRetriever deployed at:", contractAddress); + addresses.blobVersionedHashRetriever = contractAddress; } - function registerStateTransitionManager() internal { + function registerChainTypeManager() internal { Bridgehub bridgehub = Bridgehub(addresses.bridgehub.bridgehubProxy); - vm.broadcast(); - bridgehub.addStateTransitionManager(addresses.stateTransition.stateTransitionProxy); - console.log("StateTransitionManager registered"); + vm.startBroadcast(msg.sender); + bridgehub.addChainTypeManager(addresses.stateTransition.chainTypeManagerProxy); + console.log("ChainTypeManager registered"); + CTMDeploymentTracker ctmDT = CTMDeploymentTracker(addresses.bridgehub.ctmDeploymentTrackerProxy); + L1AssetRouter sharedBridge = L1AssetRouter(addresses.bridges.sharedBridgeProxy); + sharedBridge.setAssetDeploymentTracker( + bytes32(uint256(uint160(addresses.stateTransition.chainTypeManagerProxy))), + address(ctmDT) + ); + console.log("CTM DT whitelisted"); + + ctmDT.registerCTMAssetOnL1(addresses.stateTransition.chainTypeManagerProxy); + vm.stopBroadcast(); + console.log("CTM registered in CTMDeploymentTracker"); + + bytes32 assetId = bridgehub.ctmAssetIdFromAddress(addresses.stateTransition.chainTypeManagerProxy); + console.log( + "CTM in router 1", + sharedBridge.assetHandlerAddress(assetId), + bridgehub.ctmAssetIdToAddress(assetId) + ); } - function setStateTransitionManagerInValidatorTimelock() internal { + function setChainTypeManagerInValidatorTimelock() internal { ValidatorTimelock validatorTimelock = ValidatorTimelock(addresses.validatorTimelock); - vm.broadcast(); - validatorTimelock.setStateTransitionManager( - IStateTransitionManager(addresses.stateTransition.stateTransitionProxy) - ); - console.log("StateTransitionManager set in ValidatorTimelock"); + vm.broadcast(msg.sender); + validatorTimelock.setChainTypeManager(IChainTypeManager(addresses.stateTransition.chainTypeManagerProxy)); + console.log("ChainTypeManager set in ValidatorTimelock"); } function deployDiamondProxy() internal { @@ -506,11 +320,10 @@ contract DeployL1Script is Script { initAddress: address(0), initCalldata: "" }); - bytes memory bytecode = abi.encodePacked( + address contractAddress = deployViaCreate2( type(DiamondProxy).creationCode, abi.encode(config.l1ChainId, diamondCut) ); - address contractAddress = deployViaCreate2(bytecode); console.log("DiamondProxy deployed at:", contractAddress); addresses.stateTransition.diamondProxy = contractAddress; } @@ -518,75 +331,174 @@ contract DeployL1Script is Script { function deploySharedBridgeContracts() internal { deploySharedBridgeImplementation(); deploySharedBridgeProxy(); - registerSharedBridge(); + } + + function deployL1NullifierContracts() internal { + deployL1NullifierImplementation(); + deployL1NullifierProxy(); + } + + function deployL1NullifierImplementation() internal { + bytes memory bytecode; + if (config.supportL2LegacySharedBridgeTest) { + bytecode = type(L1NullifierDev).creationCode; + } else { + bytecode = type(L1Nullifier).creationCode; + } + + address contractAddress = deployViaCreate2( + bytecode, + // solhint-disable-next-line func-named-parameters + abi.encode(addresses.bridgehub.bridgehubProxy, config.eraChainId, addresses.stateTransition.diamondProxy) + ); + console.log("L1NullifierImplementation deployed at:", contractAddress); + addresses.bridges.l1NullifierImplementation = contractAddress; + } + + function deployL1NullifierProxy() internal { + bytes memory initCalldata = abi.encodeCall(L1Nullifier.initialize, (config.deployerAddress, 1, 1, 1, 0)); + address contractAddress = deployViaCreate2( + type(TransparentUpgradeableProxy).creationCode, + abi.encode(addresses.bridges.l1NullifierImplementation, addresses.transparentProxyAdmin, initCalldata) + ); + console.log("L1NullifierProxy deployed at:", contractAddress); + addresses.bridges.l1NullifierProxy = contractAddress; } function deploySharedBridgeImplementation() internal { - bytes memory bytecode = abi.encodePacked( - type(L1SharedBridge).creationCode, + address contractAddress = deployViaCreate2( + type(L1AssetRouter).creationCode, // solhint-disable-next-line func-named-parameters abi.encode( config.tokens.tokenWethAddress, addresses.bridgehub.bridgehubProxy, + addresses.bridges.l1NullifierProxy, config.eraChainId, addresses.stateTransition.diamondProxy ) ); - address contractAddress = deployViaCreate2(bytecode); console.log("SharedBridgeImplementation deployed at:", contractAddress); addresses.bridges.sharedBridgeImplementation = contractAddress; } function deploySharedBridgeProxy() internal { - bytes memory initCalldata = abi.encodeCall(L1SharedBridge.initialize, (config.deployerAddress)); - bytes memory bytecode = abi.encodePacked( + bytes memory initCalldata = abi.encodeCall(L1AssetRouter.initialize, (config.deployerAddress)); + address contractAddress = deployViaCreate2( type(TransparentUpgradeableProxy).creationCode, abi.encode(addresses.bridges.sharedBridgeImplementation, addresses.transparentProxyAdmin, initCalldata) ); - address contractAddress = deployViaCreate2(bytecode); console.log("SharedBridgeProxy deployed at:", contractAddress); addresses.bridges.sharedBridgeProxy = contractAddress; } - function registerSharedBridge() internal { + function setBridgehubParams() internal { Bridgehub bridgehub = Bridgehub(addresses.bridgehub.bridgehubProxy); - vm.startBroadcast(); - bridgehub.addToken(ADDRESS_ONE); - bridgehub.setSharedBridge(addresses.bridges.sharedBridgeProxy); + vm.startBroadcast(msg.sender); + bridgehub.addTokenAssetId(bridgehub.baseTokenAssetId(config.eraChainId)); + bridgehub.setAddresses( + addresses.bridges.sharedBridgeProxy, + ICTMDeploymentTracker(addresses.bridgehub.ctmDeploymentTrackerProxy), + IMessageRoot(addresses.bridgehub.messageRootProxy) + ); vm.stopBroadcast(); console.log("SharedBridge registered"); } function deployErc20BridgeImplementation() internal { - bytes memory bytecode = abi.encodePacked( + address contractAddress = deployViaCreate2( type(L1ERC20Bridge).creationCode, - abi.encode(addresses.bridges.sharedBridgeProxy) + abi.encode( + addresses.bridges.l1NullifierProxy, + addresses.bridges.sharedBridgeProxy, + addresses.vaults.l1NativeTokenVaultProxy, + config.eraChainId + ) ); - address contractAddress = deployViaCreate2(bytecode); console.log("Erc20BridgeImplementation deployed at:", contractAddress); addresses.bridges.erc20BridgeImplementation = contractAddress; } function deployErc20BridgeProxy() internal { bytes memory initCalldata = abi.encodeCall(L1ERC20Bridge.initialize, ()); - bytes memory bytecode = abi.encodePacked( + address contractAddress = deployViaCreate2( type(TransparentUpgradeableProxy).creationCode, abi.encode(addresses.bridges.erc20BridgeImplementation, addresses.transparentProxyAdmin, initCalldata) ); - address contractAddress = deployViaCreate2(bytecode); console.log("Erc20BridgeProxy deployed at:", contractAddress); addresses.bridges.erc20BridgeProxy = contractAddress; } function updateSharedBridge() internal { - L1SharedBridge sharedBridge = L1SharedBridge(addresses.bridges.sharedBridgeProxy); - vm.broadcast(); - sharedBridge.setL1Erc20Bridge(addresses.bridges.erc20BridgeProxy); + L1AssetRouter sharedBridge = L1AssetRouter(addresses.bridges.sharedBridgeProxy); + vm.broadcast(msg.sender); + sharedBridge.setL1Erc20Bridge(L1ERC20Bridge(addresses.bridges.erc20BridgeProxy)); console.log("SharedBridge updated with ERC20Bridge address"); } + function deployBridgedStandardERC20Implementation() internal { + address contractAddress = deployViaCreate2( + type(BridgedStandardERC20).creationCode, + // solhint-disable-next-line func-named-parameters + abi.encode() + ); + console.log("BridgedStandardERC20Implementation deployed at:", contractAddress); + addresses.bridges.bridgedStandardERC20Implementation = contractAddress; + } + + function deployBridgedTokenBeacon() internal { + /// Note we cannot use create2 as the deployer is the owner. + vm.broadcast(); + UpgradeableBeacon beacon = new UpgradeableBeacon(addresses.bridges.bridgedStandardERC20Implementation); + address contractAddress = address(beacon); + vm.broadcast(); + beacon.transferOwnership(config.ownerAddress); + console.log("BridgedTokenBeacon deployed at:", contractAddress); + addresses.bridges.bridgedTokenBeacon = contractAddress; + } + + function deployL1NativeTokenVaultImplementation() internal { + address contractAddress = deployViaCreate2( + type(L1NativeTokenVault).creationCode, + // solhint-disable-next-line func-named-parameters + abi.encode( + config.tokens.tokenWethAddress, + addresses.bridges.sharedBridgeProxy, + addresses.bridges.l1NullifierProxy + ) + ); + console.log("L1NativeTokenVaultImplementation deployed at:", contractAddress); + addresses.vaults.l1NativeTokenVaultImplementation = contractAddress; + } + + function deployL1NativeTokenVaultProxy() internal { + bytes memory initCalldata = abi.encodeCall( + L1NativeTokenVault.initialize, + (config.ownerAddress, addresses.bridges.bridgedTokenBeacon) + ); + address contractAddress = deployViaCreate2( + type(TransparentUpgradeableProxy).creationCode, + abi.encode(addresses.vaults.l1NativeTokenVaultImplementation, addresses.transparentProxyAdmin, initCalldata) + ); + console.log("L1NativeTokenVaultProxy deployed at:", contractAddress); + addresses.vaults.l1NativeTokenVaultProxy = contractAddress; + + IL1AssetRouter sharedBridge = IL1AssetRouter(addresses.bridges.sharedBridgeProxy); + IL1Nullifier l1Nullifier = IL1Nullifier(addresses.bridges.l1NullifierProxy); + // Ownable ownable = Ownable(addresses.bridges.sharedBridgeProxy); + + vm.broadcast(msg.sender); + sharedBridge.setNativeTokenVault(INativeTokenVault(addresses.vaults.l1NativeTokenVaultProxy)); + vm.broadcast(msg.sender); + l1Nullifier.setL1NativeTokenVault(IL1NativeTokenVault(addresses.vaults.l1NativeTokenVaultProxy)); + vm.broadcast(msg.sender); + l1Nullifier.setL1AssetRouter(addresses.bridges.sharedBridgeProxy); + + vm.broadcast(msg.sender); + IL1NativeTokenVault(addresses.vaults.l1NativeTokenVaultProxy).registerEthToken(); + } + function updateOwners() internal { - vm.startBroadcast(); + vm.startBroadcast(msg.sender); ValidatorTimelock validatorTimelock = ValidatorTimelock(addresses.validatorTimelock); validatorTimelock.transferOwnership(config.ownerAddress); @@ -595,35 +507,82 @@ contract DeployL1Script is Script { bridgehub.transferOwnership(addresses.governance); bridgehub.setPendingAdmin(addresses.chainAdmin); - L1SharedBridge sharedBridge = L1SharedBridge(addresses.bridges.sharedBridgeProxy); + L1AssetRouter sharedBridge = L1AssetRouter(addresses.bridges.sharedBridgeProxy); sharedBridge.transferOwnership(addresses.governance); - sharedBridge.setPendingAdmin(addresses.chainAdmin); - StateTransitionManager stm = StateTransitionManager(addresses.stateTransition.stateTransitionProxy); - stm.transferOwnership(addresses.governance); - stm.setPendingAdmin(addresses.chainAdmin); + ChainTypeManager ctm = ChainTypeManager(addresses.stateTransition.chainTypeManagerProxy); + ctm.transferOwnership(addresses.governance); + ctm.setPendingAdmin(addresses.chainAdmin); + + CTMDeploymentTracker ctmDeploymentTracker = CTMDeploymentTracker(addresses.bridgehub.ctmDeploymentTrackerProxy); + ctmDeploymentTracker.transferOwnership(addresses.governance); + + RollupDAManager(addresses.daAddresses.rollupDAManager).transferOwnership(addresses.governance); vm.stopBroadcast(); console.log("Owners updated"); } - function saveOutput() internal { + function saveDiamondSelectors() public { + AdminFacet adminFacet = new AdminFacet(1, RollupDAManager(address(0))); + GettersFacet gettersFacet = new GettersFacet(); + MailboxFacet mailboxFacet = new MailboxFacet(1, 1); + ExecutorFacet executorFacet = new ExecutorFacet(1); + bytes4[] memory adminFacetSelectors = Utils.getAllSelectors(address(adminFacet).code); + bytes4[] memory gettersFacetSelectors = Utils.getAllSelectors(address(gettersFacet).code); + bytes4[] memory mailboxFacetSelectors = Utils.getAllSelectors(address(mailboxFacet).code); + bytes4[] memory executorFacetSelectors = Utils.getAllSelectors(address(executorFacet).code); + + string memory root = vm.projectRoot(); + string memory outputPath = string.concat(root, "/script-out/diamond-selectors.toml"); + + bytes memory adminFacetSelectorsBytes = abi.encode(adminFacetSelectors); + bytes memory gettersFacetSelectorsBytes = abi.encode(gettersFacetSelectors); + bytes memory mailboxFacetSelectorsBytes = abi.encode(mailboxFacetSelectors); + bytes memory executorFacetSelectorsBytes = abi.encode(executorFacetSelectors); + + vm.serializeBytes("diamond_selectors", "admin_facet_selectors", adminFacetSelectorsBytes); + vm.serializeBytes("diamond_selectors", "getters_facet_selectors", gettersFacetSelectorsBytes); + vm.serializeBytes("diamond_selectors", "mailbox_facet_selectors", mailboxFacetSelectorsBytes); + string memory toml = vm.serializeBytes( + "diamond_selectors", + "executor_facet_selectors", + executorFacetSelectorsBytes + ); + + vm.writeToml(toml, outputPath); + } + + function saveOutput(string memory outputPath) internal { vm.serializeAddress("bridgehub", "bridgehub_proxy_addr", addresses.bridgehub.bridgehubProxy); + vm.serializeAddress("bridgehub", "bridgehub_implementation_addr", addresses.bridgehub.bridgehubImplementation); + vm.serializeAddress( + "bridgehub", + "ctm_deployment_tracker_proxy_addr", + addresses.bridgehub.ctmDeploymentTrackerProxy + ); + vm.serializeAddress( + "bridgehub", + "ctm_deployment_tracker_implementation_addr", + addresses.bridgehub.ctmDeploymentTrackerImplementation + ); + vm.serializeAddress("bridgehub", "message_root_proxy_addr", addresses.bridgehub.messageRootProxy); string memory bridgehub = vm.serializeAddress( "bridgehub", - "bridgehub_implementation_addr", - addresses.bridgehub.bridgehubImplementation + "message_root_implementation_addr", + addresses.bridgehub.messageRootImplementation ); + // TODO(EVM-744): this has to be renamed to chain type manager vm.serializeAddress( "state_transition", "state_transition_proxy_addr", - addresses.stateTransition.stateTransitionProxy + addresses.stateTransition.chainTypeManagerProxy ); vm.serializeAddress( "state_transition", "state_transition_implementation_addr", - addresses.stateTransition.stateTransitionImplementation + addresses.stateTransition.chainTypeManagerImplementation ); vm.serializeAddress("state_transition", "verifier_addr", addresses.stateTransition.verifier); vm.serializeAddress("state_transition", "admin_facet_addr", addresses.stateTransition.adminFacet); @@ -633,6 +592,7 @@ contract DeployL1Script is Script { vm.serializeAddress("state_transition", "diamond_init_addr", addresses.stateTransition.diamondInit); vm.serializeAddress("state_transition", "genesis_upgrade_addr", addresses.stateTransition.genesisUpgrade); vm.serializeAddress("state_transition", "default_upgrade_addr", addresses.stateTransition.defaultUpgrade); + vm.serializeAddress("state_transition", "bytecodes_supplier_addr", addresses.stateTransition.bytecodesSupplier); string memory stateTransition = vm.serializeAddress( "state_transition", "diamond_proxy_addr", @@ -641,6 +601,8 @@ contract DeployL1Script is Script { vm.serializeAddress("bridges", "erc20_bridge_implementation_addr", addresses.bridges.erc20BridgeImplementation); vm.serializeAddress("bridges", "erc20_bridge_proxy_addr", addresses.bridges.erc20BridgeProxy); + vm.serializeAddress("bridges", "l1_nullifier_implementation_addr", addresses.bridges.l1NullifierImplementation); + vm.serializeAddress("bridges", "l1_nullifier_proxy_addr", addresses.bridges.l1NullifierProxy); vm.serializeAddress( "bridges", "shared_bridge_implementation_addr", @@ -654,8 +616,8 @@ contract DeployL1Script is Script { vm.serializeUint( "contracts_config", - "diamond_init_pubdata_pricing_mode", - uint256(config.contracts.diamondInitPubdataPricingMode) + "diamond_init_max_l2_gas_per_batch", + config.contracts.diamondInitMaxL2GasPerBatch ); vm.serializeUint( "contracts_config", @@ -669,8 +631,8 @@ contract DeployL1Script is Script { ); vm.serializeUint( "contracts_config", - "diamond_init_max_l2_gas_per_batch", - config.contracts.diamondInitMaxL2GasPerBatch + "diamond_init_minimal_l2_gas_price", + config.contracts.diamondInitMinimalL2GasPrice ); vm.serializeUint( "contracts_config", @@ -679,13 +641,14 @@ contract DeployL1Script is Script { ); vm.serializeUint( "contracts_config", - "diamond_init_minimal_l2_gas_price", - config.contracts.diamondInitMinimalL2GasPrice + "diamond_init_pubdata_pricing_mode", + uint256(config.contracts.diamondInitPubdataPricingMode) ); + vm.serializeUint("contracts_config", "priority_tx_max_gas_limit", config.contracts.priorityTxMaxGasLimit); vm.serializeBytes32( "contracts_config", - "recursion_node_level_vk_hash", - config.contracts.recursionNodeLevelVkHash + "recursion_circuits_set_vks_hash", + config.contracts.recursionCircuitsSetVksHash ); vm.serializeBytes32( "contracts_config", @@ -694,28 +657,58 @@ contract DeployL1Script is Script { ); vm.serializeBytes32( "contracts_config", - "recursion_circuits_set_vks_hash", - config.contracts.recursionCircuitsSetVksHash + "recursion_node_level_vk_hash", + config.contracts.recursionNodeLevelVkHash ); - vm.serializeUint("contracts_config", "priority_tx_max_gas_limit", config.contracts.priorityTxMaxGasLimit); + vm.serializeBytes("contracts_config", "diamond_cut_data", config.contracts.diamondCutData); + string memory contractsConfig = vm.serializeBytes( "contracts_config", - "diamond_cut_data", - config.contracts.diamondCutData + "force_deployments_data", + generatedData.forceDeploymentsData ); - vm.serializeAddress("deployed_addresses", "transparent_proxy_admin_addr", addresses.transparentProxyAdmin); - vm.serializeAddress("deployed_addresses", "governance_addr", addresses.governance); vm.serializeAddress( "deployed_addresses", "blob_versioned_hash_retriever_addr", addresses.blobVersionedHashRetriever ); + vm.serializeAddress("deployed_addresses", "governance_addr", addresses.governance); + vm.serializeAddress("deployed_addresses", "transparent_proxy_admin_addr", addresses.transparentProxyAdmin); + vm.serializeAddress("deployed_addresses", "validator_timelock_addr", addresses.validatorTimelock); vm.serializeAddress("deployed_addresses", "chain_admin", addresses.chainAdmin); + vm.serializeAddress( + "deployed_addresses", + "access_control_restriction_addr", + addresses.accessControlRestrictionAddress + ); vm.serializeString("deployed_addresses", "bridgehub", bridgehub); + vm.serializeString("deployed_addresses", "bridges", bridges); vm.serializeString("deployed_addresses", "state_transition", stateTransition); - string memory deployedAddresses = vm.serializeString("deployed_addresses", "bridges", bridges); + + vm.serializeAddress("deployed_addresses", "l1_rollup_da_manager", addresses.daAddresses.rollupDAManager); + vm.serializeAddress( + "deployed_addresses", + "rollup_l1_da_validator_addr", + addresses.daAddresses.l1RollupDAValidator + ); + vm.serializeAddress( + "deployed_addresses", + "no_da_validium_l1_validator_addr", + addresses.daAddresses.noDAValidiumL1DAValidator + ); + vm.serializeAddress( + "deployed_addresses", + "avail_l1_da_validator_addr", + addresses.daAddresses.availL1DAValidator + ); + + string memory deployedAddresses = vm.serializeAddress( + "deployed_addresses", + "native_token_vault_addr", + addresses.vaults.l1NativeTokenVaultProxy + ); vm.serializeAddress("root", "create2_factory_addr", addresses.create2Factory); vm.serializeBytes32("root", "create2_factory_salt", config.contracts.create2FactorySalt); @@ -725,13 +718,53 @@ contract DeployL1Script is Script { vm.serializeAddress("root", "deployer_addr", config.deployerAddress); vm.serializeString("root", "deployed_addresses", deployedAddresses); vm.serializeString("root", "contracts_config", contractsConfig); - string memory toml = vm.serializeAddress("root", "owner_addr", config.ownerAddress); + vm.serializeAddress("root", "expected_rollup_l2_da_validator_addr", getRollupL2ValidatorAddress()); + vm.serializeAddress("root", "expected_no_da_validium_l2_validator_addr", getNoDAValidiumL2ValidatorAddress()); + vm.serializeAddress("root", "expected_avail_l2_da_validator_addr", getAvailL2ValidatorAddress()); + string memory toml = vm.serializeAddress("root", "owner_address", config.ownerAddress); - string memory path = string.concat(vm.projectRoot(), "/script-out/output-deploy-l1.toml"); - vm.writeToml(toml, path); + vm.writeToml(toml, outputPath); } - function deployViaCreate2(bytes memory _bytecode) internal returns (address) { - return Utils.deployViaCreate2(_bytecode, config.contracts.create2FactorySalt, addresses.create2Factory); + function prepareForceDeploymentsData() internal view returns (bytes memory) { + require(addresses.governance != address(0), "Governance address is not set"); + + address dangerousTestOnlyForcedBeacon; + if (config.supportL2LegacySharedBridgeTest) { + (dangerousTestOnlyForcedBeacon, ) = L2LegacySharedBridgeTestHelper.calculateTestL2TokenBeaconAddress( + addresses.bridges.erc20BridgeProxy, + addresses.bridges.l1NullifierProxy, + addresses.governance + ); + } + + FixedForceDeploymentsData memory data = FixedForceDeploymentsData({ + l1ChainId: config.l1ChainId, + eraChainId: config.eraChainId, + l1AssetRouter: addresses.bridges.sharedBridgeProxy, + l2TokenProxyBytecodeHash: L2ContractHelper.hashL2Bytecode( + L2ContractsBytecodesLib.readBeaconProxyBytecode() + ), + aliasedL1Governance: AddressAliasHelper.applyL1ToL2Alias(addresses.governance), + maxNumberOfZKChains: config.contracts.maxNumberOfChains, + bridgehubBytecodeHash: L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readBridgehubBytecode()), + l2AssetRouterBytecodeHash: L2ContractHelper.hashL2Bytecode( + L2ContractsBytecodesLib.readL2AssetRouterBytecode() + ), + l2NtvBytecodeHash: L2ContractHelper.hashL2Bytecode( + L2ContractsBytecodesLib.readL2NativeTokenVaultBytecode() + ), + messageRootBytecodeHash: L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readMessageRootBytecode()), + // For newly created chains it it is expected that the following bridges are not present at the moment + // of creation of the chain + l2SharedBridgeLegacyImpl: address(0), + l2BridgedStandardERC20Impl: address(0), + dangerousTestOnlyForcedBeacon: dangerousTestOnlyForcedBeacon + }); + + return abi.encode(data); } + + // add this to be excluded from coverage report + function test() internal virtual override {} } diff --git a/l1-contracts/deploy-scripts/DeployL2Contracts.sol b/l1-contracts/deploy-scripts/DeployL2Contracts.sol index 9ce7583fd..a6bc5cb5a 100644 --- a/l1-contracts/deploy-scripts/DeployL2Contracts.sol +++ b/l1-contracts/deploy-scripts/DeployL2Contracts.sol @@ -8,63 +8,79 @@ import {stdToml} from "forge-std/StdToml.sol"; import {Utils} from "./Utils.sol"; import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; -import {L1SharedBridge} from "contracts/bridge/L1SharedBridge.sol"; +import {L2ContractsBytecodesLib} from "./L2ContractsBytecodesLib.sol"; +import {IGovernance} from "contracts/governance/IGovernance.sol"; +import {Ownable2Step} from "@openzeppelin/contracts-v4/access/Ownable2Step.sol"; +import {Call} from "contracts/governance/Common.sol"; +// import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol"; contract DeployL2Script is Script { using stdToml for string; Config internal config; - ContractsBytecodes internal contracts; + DeployedContrats internal deployed; + + enum DAValidatorType { + Rollup, + NoDA, + Avail + } // solhint-disable-next-line gas-struct-packing struct Config { - address bridgehubAddress; + uint256 eraChainId; + uint256 chainId; address l1SharedBridgeProxy; + address bridgehubAddress; address governance; address erc20BridgeProxy; - // The owner of the contract sets the validator/attester weights. - // Can be the developer multisig wallet on mainnet. + DAValidatorType validatorType; address consensusRegistryOwner; - uint256 chainId; - uint256 eraChainId; - address l2SharedBridgeImplementation; - address l2SharedBridgeProxy; + } + + struct DeployedContrats { + address l2DaValidatorAddress; + address forceDeployUpgraderAddress; address consensusRegistryImplementation; address consensusRegistryProxy; address multicall3; - address forceDeployUpgraderAddress; address timestampAsserter; } - struct ContractsBytecodes { - bytes l2StandardErc20FactoryBytecode; - bytes beaconProxy; - bytes l2StandardErc20Bytecode; - bytes l2SharedBridgeBytecode; - bytes l2SharedBridgeProxyBytecode; - bytes consensusRegistryBytecode; - bytes consensusRegistryProxyBytecode; - bytes multicall3Bytecode; - bytes forceDeployUpgrader; - bytes timestampAsserterBytecode; - } - function run() public { + initializeConfig(); + deploy(false); } + function governanceExecuteCalls(bytes memory callsToExecute, address governanceAddr) internal { + IGovernance governance = IGovernance(governanceAddr); + Ownable2Step ownable = Ownable2Step(governanceAddr); + + Call[] memory calls = abi.decode(callsToExecute, (Call[])); + + IGovernance.Operation memory operation = IGovernance.Operation({ + calls: calls, + predecessor: bytes32(0), + salt: bytes32(0) + }); + + vm.startPrank(ownable.owner()); + governance.scheduleTransparent(operation, 0); + // We assume that the total value is 0 + governance.execute{value: 0}(operation); + vm.stopPrank(); + } + function runWithLegacyBridge() public { + initializeConfig(); deploy(true); } function deploy(bool legacyBridge) public { - initializeConfig(); - loadContracts(legacyBridge); + // Note, that it is important that the first transaction is for setting the L2 DA validator + deployL2DaValidator(); - deployFactoryDeps(); - deploySharedBridge(); - deploySharedBridgeProxy(legacyBridge); - initializeChain(); deployForceDeployer(); deployConsensusRegistry(); deployConsensusRegistryProxy(); @@ -74,29 +90,8 @@ contract DeployL2Script is Script { saveOutput(); } - function runDeployLegacySharedBridge() public { - deploySharedBridge(true); - } - - function runDeploySharedBridge() public { - deploySharedBridge(false); - } - - function deploySharedBridge(bool legacyBridge) internal { - initializeConfig(); - loadContracts(legacyBridge); - - deployFactoryDeps(); - deploySharedBridge(); - deploySharedBridgeProxy(legacyBridge); - initializeChain(); - - saveOutput(); - } - function runDefaultUpgrader() public { initializeConfig(); - loadContracts(false); deployForceDeployer(); @@ -105,7 +100,6 @@ contract DeployL2Script is Script { function runDeployConsensusRegistry() public { initializeConfig(); - loadContracts(false); deployConsensusRegistry(); deployConsensusRegistryProxy(); @@ -115,57 +109,12 @@ contract DeployL2Script is Script { function runDeployMulticall3() public { initializeConfig(); - loadContracts(false); deployMulticall3(); saveOutput(); } - function loadContracts(bool legacyBridge) internal { - //HACK: Meanwhile we are not integrated foundry zksync we use contracts that has been built using hardhat - contracts.l2StandardErc20FactoryBytecode = Utils.readFoundryBytecode( - "/../l2-contracts/zkout/UpgradeableBeacon.sol/UpgradeableBeacon.json" - ); - contracts.beaconProxy = Utils.readFoundryBytecode("/../l2-contracts/zkout/BeaconProxy.sol/BeaconProxy.json"); - contracts.l2StandardErc20Bytecode = Utils.readFoundryBytecode( - "/../l2-contracts/zkout/L2StandardERC20.sol/L2StandardERC20.json" - ); - - if (legacyBridge) { - contracts.l2SharedBridgeBytecode = Utils.readFoundryBytecode( - "/../l2-contracts/zkout/DevL2SharedBridge.sol/DevL2SharedBridge.json" - ); - } else { - contracts.l2SharedBridgeBytecode = Utils.readFoundryBytecode( - "/../l2-contracts/zkout/L2SharedBridge.sol/L2SharedBridge.json" - ); - } - - contracts.l2SharedBridgeProxyBytecode = Utils.readFoundryBytecode( - "/../l2-contracts/zkout/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json" - ); - - contracts.consensusRegistryBytecode = Utils.readFoundryBytecode( - "/../l2-contracts/zkout/ConsensusRegistry.sol/ConsensusRegistry.json" - ); - contracts.consensusRegistryProxyBytecode = Utils.readFoundryBytecode( - "/../l2-contracts/zkout/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json" - ); - - contracts.multicall3Bytecode = Utils.readFoundryBytecode( - "/../l2-contracts/zkout/Multicall3.sol/Multicall3.json" - ); - - contracts.forceDeployUpgrader = Utils.readFoundryBytecode( - "/../l2-contracts/zkout/ForceDeployUpgrader.sol/ForceDeployUpgrader.json" - ); - - contracts.timestampAsserterBytecode = Utils.readFoundryBytecode( - "/../l2-contracts/zkout/TimestampAsserter.sol/TimestampAsserter.json" - ); - } - function initializeConfig() internal { string memory root = vm.projectRoot(); string memory path = string.concat(root, "/script-config/config-deploy-l2-contracts.toml"); @@ -177,41 +126,43 @@ contract DeployL2Script is Script { config.consensusRegistryOwner = toml.readAddress("$.consensus_registry_owner"); config.chainId = toml.readUint("$.chain_id"); config.eraChainId = toml.readUint("$.era_chain_id"); + + uint256 validatorTypeUint = toml.readUint("$.da_validator_type"); + require(validatorTypeUint < 3, "Invalid DA validator type"); + config.validatorType = DAValidatorType(validatorTypeUint); } function saveOutput() internal { - vm.serializeAddress("root", "l2_shared_bridge_implementation", config.l2SharedBridgeImplementation); - vm.serializeAddress("root", "l2_shared_bridge_proxy", config.l2SharedBridgeProxy); - vm.serializeAddress("root", "consensus_registry_implementation", config.consensusRegistryImplementation); - vm.serializeAddress("root", "consensus_registry_proxy", config.consensusRegistryProxy); - vm.serializeAddress("root", "multicall3", config.multicall3); - vm.serializeAddress("root", "timestamp_asserter", config.timestampAsserter); - string memory toml = vm.serializeAddress("root", "l2_default_upgrader", config.forceDeployUpgraderAddress); + vm.serializeAddress("root", "l2_da_validator_address", deployed.l2DaValidatorAddress); + vm.serializeAddress("root", "multicall3", deployed.multicall3); + vm.serializeAddress("root", "consensus_registry_implementation", deployed.consensusRegistryImplementation); + vm.serializeAddress("root", "consensus_registry_proxy", deployed.consensusRegistryProxy); + vm.serializeAddress("root", "timestamp_asserter", deployed.timestampAsserter); + string memory toml = vm.serializeAddress("root", "l2_default_upgrader", deployed.forceDeployUpgraderAddress); + string memory root = vm.projectRoot(); string memory path = string.concat(root, "/script-out/output-deploy-l2-contracts.toml"); vm.writeToml(toml, path); } - function deployFactoryDeps() internal { - bytes[] memory factoryDeps = new bytes[](3); - factoryDeps[0] = contracts.l2StandardErc20FactoryBytecode; - factoryDeps[1] = contracts.l2StandardErc20Bytecode; - factoryDeps[2] = contracts.beaconProxy; - Utils.publishBytecodes(factoryDeps, config.chainId, config.bridgehubAddress, config.l1SharedBridgeProxy); - } - - function deploySharedBridge() internal { - bytes[] memory factoryDeps = new bytes[](1); - factoryDeps[0] = contracts.beaconProxy; - - bytes memory constructorData = abi.encode(config.eraChainId); + function deployL2DaValidator() internal { + bytes memory bytecode; + if (config.validatorType == DAValidatorType.Rollup) { + bytecode = L2ContractsBytecodesLib.readRollupL2DAValidatorBytecode(); + } else if (config.validatorType == DAValidatorType.NoDA) { + bytecode = L2ContractsBytecodesLib.readNoDAL2DAValidatorBytecode(); + } else if (config.validatorType == DAValidatorType.Avail) { + bytecode = L2ContractsBytecodesLib.readAvailL2DAValidatorBytecode(); + } else { + revert("Invalid DA validator type"); + } - config.l2SharedBridgeImplementation = Utils.deployThroughL1({ - bytecode: contracts.l2SharedBridgeBytecode, - constructorargs: constructorData, + deployed.l2DaValidatorAddress = Utils.deployThroughL1Deterministic({ + bytecode: bytecode, + constructorargs: bytes(""), create2salt: "", l2GasLimit: Utils.MAX_PRIORITY_TX_GAS, - factoryDeps: factoryDeps, + factoryDeps: new bytes[](0), chainId: config.chainId, bridgehubAddress: config.bridgehubAddress, l1SharedBridgeProxy: config.l1SharedBridgeProxy @@ -220,8 +171,8 @@ contract DeployL2Script is Script { function deployForceDeployer() internal { bytes[] memory factoryDeps = new bytes[](0); - config.forceDeployUpgraderAddress = Utils.deployThroughL1({ - bytecode: contracts.forceDeployUpgrader, + deployed.forceDeployUpgraderAddress = Utils.deployThroughL1({ + bytecode: L2ContractsBytecodesLib.readForceDeployUpgraderBytecode(), constructorargs: "", create2salt: "", l2GasLimit: Utils.MAX_PRIORITY_TX_GAS, @@ -232,51 +183,13 @@ contract DeployL2Script is Script { }); } - function deploySharedBridgeProxy(bool legacyBridge) internal { - address l2GovernorAddress = AddressAliasHelper.applyL1ToL2Alias(config.governance); - bytes32 l2StandardErc20BytecodeHash = L2ContractHelper.hashL2Bytecode(contracts.beaconProxy); - - string memory functionSignature; - - if (legacyBridge) { - functionSignature = "initializeDevBridge(address,address,bytes32,address)"; - } else { - functionSignature = "initialize(address,address,bytes32,address)"; - } - // solhint-disable-next-line func-named-parameters - bytes memory proxyInitializationParams = abi.encodeWithSignature( - functionSignature, - config.l1SharedBridgeProxy, - config.erc20BridgeProxy, - l2StandardErc20BytecodeHash, - l2GovernorAddress - ); - - bytes memory l2SharedBridgeProxyConstructorData = abi.encode( - config.l2SharedBridgeImplementation, - l2GovernorAddress, - proxyInitializationParams - ); - - config.l2SharedBridgeProxy = Utils.deployThroughL1({ - bytecode: contracts.l2SharedBridgeProxyBytecode, - constructorargs: l2SharedBridgeProxyConstructorData, - create2salt: "", - l2GasLimit: Utils.MAX_PRIORITY_TX_GAS, - factoryDeps: new bytes[](0), - chainId: config.chainId, - bridgehubAddress: config.bridgehubAddress, - l1SharedBridgeProxy: config.l1SharedBridgeProxy - }); - } - // Deploy the ConsensusRegistry implementation and save its address into the config. function deployConsensusRegistry() internal { // ConsensusRegistry.sol doesn't have a constructor, just an initializer. bytes memory constructorData = ""; - config.consensusRegistryImplementation = Utils.deployThroughL1({ - bytecode: contracts.consensusRegistryBytecode, + deployed.consensusRegistryImplementation = Utils.deployThroughL1({ + bytecode: L2ContractsBytecodesLib.readConsensusRegistryBytecode(), constructorargs: constructorData, create2salt: "", l2GasLimit: Utils.MAX_PRIORITY_TX_GAS, @@ -291,8 +204,8 @@ contract DeployL2Script is Script { // Multicall3 doesn't have a constructor. bytes memory constructorData = ""; - config.multicall3 = Utils.deployThroughL1({ - bytecode: contracts.multicall3Bytecode, + deployed.multicall3 = Utils.deployThroughL1({ + bytecode: L2ContractsBytecodesLib.readMulticall3Bytecode(), constructorargs: constructorData, create2salt: "", l2GasLimit: Utils.MAX_PRIORITY_TX_GAS, @@ -304,9 +217,9 @@ contract DeployL2Script is Script { } function deployTimestampAsserter() internal { - config.timestampAsserter = Utils.deployThroughL1({ - bytecode: contracts.timestampAsserterBytecode, - constructorargs: hex"", + deployed.timestampAsserter = Utils.deployThroughL1({ + bytecode: L2ContractsBytecodesLib.readTimestampAsserterBytecode(), + constructorargs: "", create2salt: "", l2GasLimit: Utils.MAX_PRIORITY_TX_GAS, factoryDeps: new bytes[](0), @@ -330,13 +243,13 @@ contract DeployL2Script is Script { ); bytes memory consensusRegistryProxyConstructorData = abi.encode( - config.consensusRegistryImplementation, // _logic + deployed.consensusRegistryImplementation, // _logic l2GovernorAddress, // admin_ proxyInitializationParams // _data ); - config.consensusRegistryProxy = Utils.deployThroughL1({ - bytecode: contracts.consensusRegistryProxyBytecode, + deployed.consensusRegistryProxy = Utils.deployThroughL1({ + bytecode: L2ContractsBytecodesLib.readTransparentUpgradeableProxyBytecode(), constructorargs: consensusRegistryProxyConstructorData, create2salt: "", l2GasLimit: Utils.MAX_PRIORITY_TX_GAS, @@ -346,15 +259,4 @@ contract DeployL2Script is Script { l1SharedBridgeProxy: config.l1SharedBridgeProxy }); } - - function initializeChain() internal { - L1SharedBridge bridge = L1SharedBridge(config.l1SharedBridgeProxy); - - Utils.chainAdminMulticall({ - _chainAdmin: bridge.admin(), - _target: config.l1SharedBridgeProxy, - _data: abi.encodeCall(bridge.initializeChainGovernance, (config.chainId, config.l2SharedBridgeProxy)), - _value: 0 - }); - } } diff --git a/l1-contracts/deploy-scripts/DeployPaymaster.s.sol b/l1-contracts/deploy-scripts/DeployPaymaster.s.sol index eec87fbb0..a9f775a83 100644 --- a/l1-contracts/deploy-scripts/DeployPaymaster.s.sol +++ b/l1-contracts/deploy-scripts/DeployPaymaster.s.sol @@ -44,8 +44,9 @@ contract DeployPaymaster is Script { } function deploy() internal { - bytes memory testnetPaymasterBytecode = Utils.readFoundryBytecode( - "/../l2-contracts/zkout/TestnetPaymaster.sol/TestnetPaymaster.json" + bytes memory testnetPaymasterBytecode = Utils.readZKFoundryBytecodeL2( + "TestnetPaymaster.sol", + "TestnetPaymaster" ); config.paymaster = Utils.deployThroughL1({ diff --git a/l1-contracts/deploy-scripts/DeployUtils.s.sol b/l1-contracts/deploy-scripts/DeployUtils.s.sol new file mode 100644 index 000000000..a3d2ff825 --- /dev/null +++ b/l1-contracts/deploy-scripts/DeployUtils.s.sol @@ -0,0 +1,528 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +// solhint-disable no-console, gas-custom-errors + +import {Script, console2 as console} from "forge-std/Script.sol"; +import {stdToml} from "forge-std/StdToml.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts-v4/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; +import {StateTransitionDeployedAddresses, Utils, L2_BRIDGEHUB_ADDRESS, L2_ASSET_ROUTER_ADDRESS, L2_NATIVE_TOKEN_VAULT_ADDRESS, L2_MESSAGE_ROOT_ADDRESS} from "./Utils.sol"; +import {Multicall3} from "contracts/dev-contracts/Multicall3.sol"; +import {Verifier} from "contracts/state-transition/Verifier.sol"; +import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; +import {VerifierParams, IVerifier} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; +import {DefaultUpgrade} from "contracts/upgrades/DefaultUpgrade.sol"; +import {Governance} from "contracts/governance/Governance.sol"; +import {L1GenesisUpgrade} from "contracts/upgrades/L1GenesisUpgrade.sol"; +import {ChainAdmin} from "contracts/governance/ChainAdmin.sol"; +import {ValidatorTimelock} from "contracts/state-transition/ValidatorTimelock.sol"; +import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; +import {MessageRoot} from "contracts/bridgehub/MessageRoot.sol"; +import {CTMDeploymentTracker} from "contracts/bridgehub/CTMDeploymentTracker.sol"; +import {L1NativeTokenVault} from "contracts/bridge/ntv/L1NativeTokenVault.sol"; +import {ExecutorFacet} from "contracts/state-transition/chain-deps/facets/Executor.sol"; +import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol"; +import {MailboxFacet} from "contracts/state-transition/chain-deps/facets/Mailbox.sol"; +import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; +import {DiamondInit} from "contracts/state-transition/chain-deps/DiamondInit.sol"; +import {ChainTypeManager} from "contracts/state-transition/ChainTypeManager.sol"; +import {ChainTypeManagerInitializeData, ChainCreationParams} from "contracts/state-transition/IChainTypeManager.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; +import {InitializeDataNewChain as DiamondInitializeDataNewChain} from "contracts/state-transition/chain-interfaces/IDiamondInit.sol"; +import {FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZKChainStorage.sol"; +import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol"; +import {L1ERC20Bridge} from "contracts/bridge/L1ERC20Bridge.sol"; +import {L1Nullifier} from "contracts/bridge/L1Nullifier.sol"; +import {DiamondProxy} from "contracts/state-transition/chain-deps/DiamondProxy.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {INativeTokenVault} from "contracts/bridge/ntv/INativeTokenVault.sol"; +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; +import {AddressHasNoCode} from "./ZkSyncScriptErrors.sol"; +import {ICTMDeploymentTracker} from "contracts/bridgehub/ICTMDeploymentTracker.sol"; +import {IMessageRoot} from "contracts/bridgehub/IMessageRoot.sol"; +import {IL2ContractDeployer} from "contracts/common/interfaces/IL2ContractDeployer.sol"; +import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {IL1Nullifier} from "contracts/bridge/L1Nullifier.sol"; +import {IL1NativeTokenVault} from "contracts/bridge/ntv/IL1NativeTokenVault.sol"; +import {L1NullifierDev} from "contracts/dev-contracts/L1NullifierDev.sol"; +import {AccessControlRestriction} from "contracts/governance/AccessControlRestriction.sol"; +import {ICTMDeploymentTracker} from "contracts/bridgehub/ICTMDeploymentTracker.sol"; +import {IMessageRoot} from "contracts/bridgehub/IMessageRoot.sol"; +import {IAssetRouterBase} from "contracts/bridge/asset-router/IAssetRouterBase.sol"; +import {L2ContractsBytecodesLib} from "./L2ContractsBytecodesLib.sol"; +import {BytecodesSupplier} from "contracts/upgrades/BytecodesSupplier.sol"; +import {ChainAdminSingleOwner} from "contracts/governance/ChainAdminSingleOwner.sol"; + +struct FixedForceDeploymentsData { + uint256 l1ChainId; + uint256 eraChainId; + address l1AssetRouter; + bytes32 l2TokenProxyBytecodeHash; + address aliasedL1Governance; + uint256 maxNumberOfZKChains; + bytes32 bridgehubBytecodeHash; + bytes32 l2AssetRouterBytecodeHash; + bytes32 l2NtvBytecodeHash; + bytes32 messageRootBytecodeHash; + address l2SharedBridgeLegacyImpl; + address l2BridgedStandardERC20Impl; + // The forced beacon address. It is needed only for internal testing. + // MUST be equal to 0 in production. + // It will be the job of the governance to ensure that this value is set correctly. + address dangerousTestOnlyForcedBeacon; +} + +// solhint-disable-next-line gas-struct-packing +struct DeployedAddresses { + BridgehubDeployedAddresses bridgehub; + StateTransitionDeployedAddresses stateTransition; + BridgesDeployedAddresses bridges; + L1NativeTokenVaultAddresses vaults; + DataAvailabilityDeployedAddresses daAddresses; + address transparentProxyAdmin; + address governance; + address chainAdmin; + address accessControlRestrictionAddress; + address blobVersionedHashRetriever; + address validatorTimelock; + address create2Factory; +} + +// solhint-disable-next-line gas-struct-packing +struct L1NativeTokenVaultAddresses { + address l1NativeTokenVaultImplementation; + address l1NativeTokenVaultProxy; +} + +struct DataAvailabilityDeployedAddresses { + address rollupDAManager; + address l1RollupDAValidator; + address noDAValidiumL1DAValidator; + address availL1DAValidator; +} + +// solhint-disable-next-line gas-struct-packing +struct BridgehubDeployedAddresses { + address bridgehubImplementation; + address bridgehubProxy; + address ctmDeploymentTrackerImplementation; + address ctmDeploymentTrackerProxy; + address messageRootImplementation; + address messageRootProxy; +} + +// solhint-disable-next-line gas-struct-packing +struct BridgesDeployedAddresses { + address erc20BridgeImplementation; + address erc20BridgeProxy; + address sharedBridgeImplementation; + address sharedBridgeProxy; + address l1NullifierImplementation; + address l1NullifierProxy; + address bridgedStandardERC20Implementation; + address bridgedTokenBeacon; +} + +// solhint-disable-next-line gas-struct-packing +struct Config { + uint256 l1ChainId; + address deployerAddress; + uint256 eraChainId; + address ownerAddress; + bool testnetVerifier; + bool supportL2LegacySharedBridgeTest; + ContractsConfig contracts; + TokensConfig tokens; +} + +// solhint-disable-next-line gas-struct-packing +struct ContractsConfig { + bytes32 create2FactorySalt; + address create2FactoryAddr; + address multicall3Addr; + uint256 validatorTimelockExecutionDelay; + bytes32 genesisRoot; + uint256 genesisRollupLeafIndex; + bytes32 genesisBatchCommitment; + uint256 latestProtocolVersion; + bytes32 recursionNodeLevelVkHash; + bytes32 recursionLeafLevelVkHash; + bytes32 recursionCircuitsSetVksHash; + uint256 priorityTxMaxGasLimit; + PubdataPricingMode diamondInitPubdataPricingMode; + uint256 diamondInitBatchOverheadL1Gas; + uint256 diamondInitMaxPubdataPerBatch; + uint256 diamondInitMaxL2GasPerBatch; + uint256 diamondInitPriorityTxMaxPubdata; + uint256 diamondInitMinimalL2GasPrice; + address governanceSecurityCouncilAddress; + uint256 governanceMinDelay; + uint256 maxNumberOfChains; + bytes diamondCutData; + bytes32 bootloaderHash; + bytes32 defaultAAHash; + address availL1DAValidator; +} + +struct TokensConfig { + address tokenWethAddress; +} + +// solhint-disable-next-line gas-struct-packing +struct GeneratedData { + bytes forceDeploymentsData; +} + +contract DeployUtils is Script { + using stdToml for string; + + address internal constant DETERMINISTIC_CREATE2_ADDRESS = 0x4e59b44847b379578588920cA78FbF26c0B4956C; + + Config public config; + GeneratedData internal generatedData; + DeployedAddresses internal addresses; + + function initializeConfig(string memory configPath) internal { + string memory toml = vm.readFile(configPath); + + config.l1ChainId = block.chainid; + config.deployerAddress = msg.sender; + + // Config file must be parsed key by key, otherwise values returned + // are parsed alfabetically and not by key. + // https://book.getfoundry.sh/cheatcodes/parse-toml + config.eraChainId = toml.readUint("$.era_chain_id"); + config.ownerAddress = toml.readAddress("$.owner_address"); + config.testnetVerifier = toml.readBool("$.testnet_verifier"); + config.supportL2LegacySharedBridgeTest = toml.readBool("$.support_l2_legacy_shared_bridge_test"); + + config.contracts.governanceSecurityCouncilAddress = toml.readAddress( + "$.contracts.governance_security_council_address" + ); + config.contracts.governanceMinDelay = toml.readUint("$.contracts.governance_min_delay"); + config.contracts.maxNumberOfChains = toml.readUint("$.contracts.max_number_of_chains"); + config.contracts.create2FactorySalt = toml.readBytes32("$.contracts.create2_factory_salt"); + if (vm.keyExistsToml(toml, "$.contracts.create2_factory_addr")) { + config.contracts.create2FactoryAddr = toml.readAddress("$.contracts.create2_factory_addr"); + } + config.contracts.validatorTimelockExecutionDelay = toml.readUint( + "$.contracts.validator_timelock_execution_delay" + ); + config.contracts.genesisRoot = toml.readBytes32("$.contracts.genesis_root"); + config.contracts.genesisRollupLeafIndex = toml.readUint("$.contracts.genesis_rollup_leaf_index"); + config.contracts.genesisBatchCommitment = toml.readBytes32("$.contracts.genesis_batch_commitment"); + config.contracts.latestProtocolVersion = toml.readUint("$.contracts.latest_protocol_version"); + config.contracts.recursionNodeLevelVkHash = toml.readBytes32("$.contracts.recursion_node_level_vk_hash"); + config.contracts.recursionLeafLevelVkHash = toml.readBytes32("$.contracts.recursion_leaf_level_vk_hash"); + config.contracts.recursionCircuitsSetVksHash = toml.readBytes32("$.contracts.recursion_circuits_set_vks_hash"); + config.contracts.priorityTxMaxGasLimit = toml.readUint("$.contracts.priority_tx_max_gas_limit"); + config.contracts.diamondInitPubdataPricingMode = PubdataPricingMode( + toml.readUint("$.contracts.diamond_init_pubdata_pricing_mode") + ); + config.contracts.diamondInitBatchOverheadL1Gas = toml.readUint( + "$.contracts.diamond_init_batch_overhead_l1_gas" + ); + config.contracts.diamondInitMaxPubdataPerBatch = toml.readUint( + "$.contracts.diamond_init_max_pubdata_per_batch" + ); + config.contracts.diamondInitMaxL2GasPerBatch = toml.readUint("$.contracts.diamond_init_max_l2_gas_per_batch"); + config.contracts.diamondInitPriorityTxMaxPubdata = toml.readUint( + "$.contracts.diamond_init_priority_tx_max_pubdata" + ); + config.contracts.diamondInitMinimalL2GasPrice = toml.readUint("$.contracts.diamond_init_minimal_l2_gas_price"); + config.contracts.defaultAAHash = toml.readBytes32("$.contracts.default_aa_hash"); + config.contracts.bootloaderHash = toml.readBytes32("$.contracts.bootloader_hash"); + + if (vm.keyExistsToml(toml, "$.contracts.avail_l1_da_validator")) { + config.contracts.availL1DAValidator = toml.readAddress("$.contracts.avail_l1_da_validator"); + } + + config.tokens.tokenWethAddress = toml.readAddress("$.tokens.token_weth_address"); + } + + function instantiateCreate2Factory() internal { + address contractAddress; + + bool isDeterministicDeployed = DETERMINISTIC_CREATE2_ADDRESS.code.length > 0; + bool isConfigured = config.contracts.create2FactoryAddr != address(0); + + if (isConfigured) { + if (config.contracts.create2FactoryAddr.code.length == 0) { + revert AddressHasNoCode(config.contracts.create2FactoryAddr); + } + contractAddress = config.contracts.create2FactoryAddr; + console.log("Using configured Create2Factory address:", contractAddress); + } else if (isDeterministicDeployed) { + contractAddress = DETERMINISTIC_CREATE2_ADDRESS; + console.log("Using deterministic Create2Factory address:", contractAddress); + } else { + contractAddress = Utils.deployCreate2Factory(); + console.log("Create2Factory deployed at:", contractAddress); + } + + addresses.create2Factory = contractAddress; + } + + function deployViaCreate2( + bytes memory creationCode, + bytes memory constructorArgs + ) internal virtual returns (address) { + return + Utils.deployViaCreate2( + abi.encodePacked(creationCode, constructorArgs), + config.contracts.create2FactorySalt, + addresses.create2Factory + ); + } + + function deployBytecodesSupplier() internal { + address contractAddress = deployViaCreate2(type(BytecodesSupplier).creationCode, ""); + console.log("BytecodesSupplier deployed at:", contractAddress); + addresses.stateTransition.bytecodesSupplier = contractAddress; + } + + function deployVerifier() internal { + bytes memory code; + if (config.testnetVerifier) { + code = type(TestnetVerifier).creationCode; + } else { + code = type(Verifier).creationCode; + } + address contractAddress = deployViaCreate2(code, ""); + console.log("Verifier deployed at:", contractAddress); + addresses.stateTransition.verifier = contractAddress; + } + + function deployDefaultUpgrade() internal { + address contractAddress = deployViaCreate2(type(DefaultUpgrade).creationCode, ""); + console.log("DefaultUpgrade deployed at:", contractAddress); + addresses.stateTransition.defaultUpgrade = contractAddress; + } + + function deployGenesisUpgrade() internal { + address contractAddress = deployViaCreate2(type(L1GenesisUpgrade).creationCode, ""); + console.log("GenesisUpgrade deployed at:", contractAddress); + addresses.stateTransition.genesisUpgrade = contractAddress; + } + + function deployValidatorTimelock() internal { + uint32 executionDelay = uint32(config.contracts.validatorTimelockExecutionDelay); + address contractAddress = deployViaCreate2( + type(ValidatorTimelock).creationCode, + abi.encode(config.deployerAddress, executionDelay, config.eraChainId) + ); + console.log("ValidatorTimelock deployed at:", contractAddress); + addresses.validatorTimelock = contractAddress; + } + + function deployGovernance() internal { + address contractAddress = deployViaCreate2( + type(Governance).creationCode, + abi.encode( + config.ownerAddress, + config.contracts.governanceSecurityCouncilAddress, + config.contracts.governanceMinDelay + ) + ); + console.log("Governance deployed at:", contractAddress); + addresses.governance = contractAddress; + } + + function deployChainAdmin() internal { + // TODO(EVM-924): provide an option to deploy a non-single owner ChainAdmin. + (address chainAdmin, address accessControlRestriction) = deployChainAdminSingleOwner(); + + addresses.accessControlRestrictionAddress = accessControlRestriction; + addresses.chainAdmin = chainAdmin; + } + + function deployChainAdminSingleOwner() internal returns (address chainAdmin, address accessControlRestriction) { + chainAdmin = deployViaCreate2( + type(ChainAdminSingleOwner).creationCode, + abi.encode(config.ownerAddress, address(0)) + ); + // The single owner chainAdmin does not have a separate control restriction contract. + // We set to it to zero explicitly so that it is clear to the reader. + accessControlRestriction = address(0); + + console.log("ChainAdminSingleOwner deployed at:", accessControlRestriction); + } + + // TODO(EVM-924): this function is unused + function deployChainAdminWithRestrictions() + internal + returns (address chainAdmin, address accessControlRestriction) + { + accessControlRestriction = deployViaCreate2( + type(AccessControlRestriction).creationCode, + abi.encode(uint256(0), config.ownerAddress) + ); + + console.log("Access control restriction deployed at:", accessControlRestriction); + address[] memory restrictions = new address[](1); + restrictions[0] = accessControlRestriction; + addresses.accessControlRestrictionAddress = accessControlRestriction; + + chainAdmin = deployViaCreate2(type(ChainAdmin).creationCode, abi.encode(restrictions)); + console.log("ChainAdmin deployed at:", chainAdmin); + } + + function deployTransparentProxyAdmin() internal { + vm.startBroadcast(); + ProxyAdmin proxyAdmin = new ProxyAdmin(); + proxyAdmin.transferOwnership(addresses.governance); + vm.stopBroadcast(); + console.log("Transparent Proxy Admin deployed at:", address(proxyAdmin)); + addresses.transparentProxyAdmin = address(proxyAdmin); + } + + function deployChainTypeManagerContract() internal { + deployStateTransitionDiamondFacets(); + deployChainTypeManagerImplementation(); + deployChainTypeManagerProxy(); + } + + function deployStateTransitionDiamondFacets() internal { + address executorFacet = deployViaCreate2(type(ExecutorFacet).creationCode, abi.encode(config.l1ChainId)); + console.log("ExecutorFacet deployed at:", executorFacet); + addresses.stateTransition.executorFacet = executorFacet; + + address adminFacet = deployViaCreate2( + type(AdminFacet).creationCode, + abi.encode(config.l1ChainId, addresses.daAddresses.rollupDAManager) + ); + console.log("AdminFacet deployed at:", adminFacet); + addresses.stateTransition.adminFacet = adminFacet; + + address mailboxFacet = deployViaCreate2( + type(MailboxFacet).creationCode, + abi.encode(config.eraChainId, config.l1ChainId) + ); + console.log("MailboxFacet deployed at:", mailboxFacet); + addresses.stateTransition.mailboxFacet = mailboxFacet; + + address gettersFacet = deployViaCreate2(type(GettersFacet).creationCode, ""); + console.log("GettersFacet deployed at:", gettersFacet); + addresses.stateTransition.gettersFacet = gettersFacet; + + address diamondInit = deployViaCreate2(type(DiamondInit).creationCode, ""); + console.log("DiamondInit deployed at:", diamondInit); + addresses.stateTransition.diamondInit = diamondInit; + } + + function deployChainTypeManagerImplementation() internal { + bytes memory bytecode = type(ChainTypeManager).creationCode; + bytes memory constructorArgs = abi.encode(addresses.bridgehub.bridgehubProxy); + address contractAddress = deployViaCreate2(bytecode, constructorArgs); + console.log("ChainTypeManagerImplementation deployed at:", contractAddress); + addresses.stateTransition.chainTypeManagerImplementation = contractAddress; + } + + function deployChainTypeManagerProxy() internal { + string memory root = vm.projectRoot(); + string memory inputPath = string.concat(root, "/script-out/diamond-selectors.toml"); + string memory toml = vm.readFile(inputPath); + + bytes memory adminFacetSelectors = toml.readBytes("$.admin_facet_selectors"); + bytes memory gettersFacetSelectors = toml.readBytes("$.getters_facet_selectors"); + bytes memory mailboxFacetSelectors = toml.readBytes("$.mailbox_facet_selectors"); + bytes memory executorFacetSelectors = toml.readBytes("$.executor_facet_selectors"); + + bytes4[] memory adminFacetSelectorsArray = abi.decode(adminFacetSelectors, (bytes4[])); + bytes4[] memory gettersFacetSelectorsArray = abi.decode(gettersFacetSelectors, (bytes4[])); + bytes4[] memory mailboxFacetSelectorsArray = abi.decode(mailboxFacetSelectors, (bytes4[])); + bytes4[] memory executorFacetSelectorsArray = abi.decode(executorFacetSelectors, (bytes4[])); + + Diamond.FacetCut[] memory facetCuts = new Diamond.FacetCut[](4); + facetCuts[0] = Diamond.FacetCut({ + facet: addresses.stateTransition.adminFacet, + action: Diamond.Action.Add, + isFreezable: false, + selectors: adminFacetSelectorsArray + }); + facetCuts[1] = Diamond.FacetCut({ + facet: addresses.stateTransition.gettersFacet, + action: Diamond.Action.Add, + isFreezable: false, + selectors: gettersFacetSelectorsArray + }); + facetCuts[2] = Diamond.FacetCut({ + facet: addresses.stateTransition.mailboxFacet, + action: Diamond.Action.Add, + isFreezable: true, + selectors: mailboxFacetSelectorsArray + }); + facetCuts[3] = Diamond.FacetCut({ + facet: addresses.stateTransition.executorFacet, + action: Diamond.Action.Add, + isFreezable: true, + selectors: executorFacetSelectorsArray + }); + + VerifierParams memory verifierParams = VerifierParams({ + recursionNodeLevelVkHash: config.contracts.recursionNodeLevelVkHash, + recursionLeafLevelVkHash: config.contracts.recursionLeafLevelVkHash, + recursionCircuitsSetVksHash: config.contracts.recursionCircuitsSetVksHash + }); + + FeeParams memory feeParams = FeeParams({ + pubdataPricingMode: config.contracts.diamondInitPubdataPricingMode, + batchOverheadL1Gas: uint32(config.contracts.diamondInitBatchOverheadL1Gas), + maxPubdataPerBatch: uint32(config.contracts.diamondInitMaxPubdataPerBatch), + maxL2GasPerBatch: uint32(config.contracts.diamondInitMaxL2GasPerBatch), + priorityTxMaxPubdata: uint32(config.contracts.diamondInitPriorityTxMaxPubdata), + minimalL2GasPrice: uint64(config.contracts.diamondInitMinimalL2GasPrice) + }); + + DiamondInitializeDataNewChain memory initializeData = DiamondInitializeDataNewChain({ + verifier: IVerifier(addresses.stateTransition.verifier), + verifierParams: verifierParams, + l2BootloaderBytecodeHash: config.contracts.bootloaderHash, + l2DefaultAccountBytecodeHash: config.contracts.defaultAAHash, + priorityTxMaxGasLimit: config.contracts.priorityTxMaxGasLimit, + feeParams: feeParams, + blobVersionedHashRetriever: addresses.blobVersionedHashRetriever + }); + + Diamond.DiamondCutData memory diamondCut = Diamond.DiamondCutData({ + facetCuts: facetCuts, + initAddress: addresses.stateTransition.diamondInit, + initCalldata: abi.encode(initializeData) + }); + + config.contracts.diamondCutData = abi.encode(diamondCut); + + ChainCreationParams memory chainCreationParams = ChainCreationParams({ + genesisUpgrade: addresses.stateTransition.genesisUpgrade, + genesisBatchHash: config.contracts.genesisRoot, + genesisIndexRepeatedStorageChanges: uint64(config.contracts.genesisRollupLeafIndex), + genesisBatchCommitment: config.contracts.genesisBatchCommitment, + diamondCut: diamondCut, + forceDeploymentsData: generatedData.forceDeploymentsData + }); + + ChainTypeManagerInitializeData memory diamondInitData = ChainTypeManagerInitializeData({ + owner: msg.sender, + validatorTimelock: addresses.validatorTimelock, + chainCreationParams: chainCreationParams, + protocolVersion: config.contracts.latestProtocolVersion + }); + + address contractAddress = deployViaCreate2( + type(TransparentUpgradeableProxy).creationCode, + abi.encode( + addresses.stateTransition.chainTypeManagerImplementation, + addresses.transparentProxyAdmin, + abi.encodeCall(ChainTypeManager.initialize, (diamondInitData)) + ) + ); + console.log("ChainTypeManagerProxy deployed at:", contractAddress); + addresses.stateTransition.chainTypeManagerProxy = contractAddress; + } + + function test() internal virtual {} +} diff --git a/l1-contracts/deploy-scripts/DeployZKAndBridgeToL1.s.sol b/l1-contracts/deploy-scripts/DeployZKAndBridgeToL1.s.sol new file mode 100644 index 000000000..5e0a87681 --- /dev/null +++ b/l1-contracts/deploy-scripts/DeployZKAndBridgeToL1.s.sol @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +// solhint-disable no-console + +import {Vm} from "forge-std/Vm.sol"; +import {Script, console2 as console} from "forge-std/Script.sol"; +import {stdToml} from "forge-std/StdToml.sol"; + +// It's required to disable lints to force the compiler to compile the contracts +// solhint-disable no-unused-import +import {TestnetERC20Token} from "contracts/dev-contracts/TestnetERC20Token.sol"; +// solhint-disable no-unused-import +import {WETH9} from "contracts/dev-contracts/WETH9.sol"; + +import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR} from "contracts/common/L2ContractAddresses.sol"; + +import {FinalizeL1DepositParams} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol"; +import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {L1Nullifier} from "contracts/bridge/L1Nullifier.sol"; +import {L2NativeTokenVault} from "contracts/bridge/ntv/L2NativeTokenVault.sol"; +import {IL1NativeTokenVault} from "contracts/bridge/ntv/IL1NativeTokenVault.sol"; +import {Utils} from "./Utils.sol"; +import {MintFailed} from "./ZkSyncScriptErrors.sol"; + +contract DeployZKScript is Script { + using stdToml for string; + + struct Config { + TokenDescription zkToken; + address deployerAddress; + address[] additionalAddressesForMinting; + address create2FactoryAddr; + bytes32 create2FactorySalt; + uint256 chainId; + address l1SharedBridge; + address bridgehub; + address l1Nullifier; + address chainAdmin; + address governance; + address deployer; + address owner; + address anotherOwner; + address chainGovernor; + } + + struct TokenDescription { + address addr; + string name; + string symbol; + uint256 decimals; + string implementation; + uint256 mint; + bytes32 assetId; + } + + Config internal config; + + function run() public { + initializeConfig(); + deployZkToken(); + saveOutput(); + } + + function getTokenAddress() public view returns (address) { + return config.zkToken.addr; + } + + function initializeConfig() internal { + config.deployerAddress = msg.sender; + + string memory root = vm.projectRoot(); + + // Grab config from output of l1 deployment + string memory path = string.concat(root, vm.envString("TOKENS_CONFIG")); + string memory toml = vm.readFile(path); + + config.additionalAddressesForMinting = vm.parseTomlAddressArray(toml, "$.additional_addresses_for_minting"); + + // Parse the ZK token configuration + string memory key = "$.tokens.ZK"; + config.zkToken.name = toml.readString(string.concat(key, ".name")); + config.zkToken.symbol = toml.readString(string.concat(key, ".symbol")); + config.zkToken.decimals = toml.readUint(string.concat(key, ".decimals")); + config.zkToken.implementation = toml.readString(string.concat(key, ".implementation")); + config.zkToken.mint = toml.readUint(string.concat(key, ".mint")); + + // Grab config from custom config file + path = string.concat(root, vm.envString("ZK_CHAIN_CONFIG")); + toml = vm.readFile(path); + + config.bridgehub = toml.readAddress("$.deployed_addresses.bridgehub.bridgehub_proxy_addr"); + config.l1SharedBridge = toml.readAddress("$.deployed_addresses.bridges.shared_bridge_proxy_addr"); + config.l1Nullifier = toml.readAddress("$.deployed_addresses.bridges.l1_nullifier_proxy_addr"); + config.chainId = toml.readUint("$.chain.chain_chain_id"); + config.chainGovernor = toml.readAddress("$.owner_address"); + } + + function initializeAdditionalConfig() internal { + string memory root = vm.projectRoot(); + string memory path = string.concat(root, vm.envString("L1_OUTPUT")); + string memory toml = vm.readFile(path); + + config.owner = toml.readAddress("$.owner_address"); + } + + function deployZkToken() internal { + uint256 someBigAmount = 100000000000000000000000000000000; + TokenDescription storage token = config.zkToken; + console.log("Deploying token:", token.name); + + vm.startBroadcast(); + address zkTokenAddress = deployErc20({ + name: token.name, + symbol: token.symbol, + decimals: token.decimals, + mint: token.mint, + additionalAddressesForMinting: config.additionalAddressesForMinting + }); + console.log("Token deployed at:", zkTokenAddress); + token.addr = zkTokenAddress; + address deployer = msg.sender; + TestnetERC20Token zkToken = TestnetERC20Token(zkTokenAddress); + zkToken.mint(deployer, someBigAmount); + uint256 deployerBalance = zkToken.balanceOf(deployer); + console.log("Deployer balance:", deployerBalance); + L2AssetRouter l2AR = L2AssetRouter(L2_ASSET_ROUTER_ADDR); + L2NativeTokenVault l2NTV = L2NativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR); + l2NTV.registerToken(zkTokenAddress); + bytes32 zkTokenAssetId = l2NTV.assetId(zkTokenAddress); + config.zkToken.assetId = zkTokenAssetId; + console.log("zkTokenAssetId:", uint256(zkTokenAssetId)); + zkToken.approve(L2_NATIVE_TOKEN_VAULT_ADDR, someBigAmount); + vm.stopBroadcast(); + + vm.broadcast(); + l2AR.withdraw(zkTokenAssetId, abi.encode(someBigAmount, deployer)); + uint256 deployerBalanceAfterWithdraw = zkToken.balanceOf(deployer); + console.log("Deployed balance after withdraw:", deployerBalanceAfterWithdraw); + } + + /// TODO(EVM-748): make that function support non-ETH based chains + function supplyEraWallet(address addr, uint256 amount) public { + initializeConfig(); + + Utils.runL1L2Transaction( + hex"", + Utils.MAX_PRIORITY_TX_GAS, + amount, + new bytes[](0), + addr, + config.chainId, + config.bridgehub, + config.l1SharedBridge + ); + } + + function finalizeZkTokenWithdrawal( + uint256 _chainId, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes memory _message, + bytes32[] memory _merkleProof + ) public { + initializeConfig(); + + L1Nullifier l1Nullifier = L1Nullifier(config.l1Nullifier); + + vm.broadcast(); + l1Nullifier.finalizeDeposit( + FinalizeL1DepositParams({ + chainId: _chainId, + l2BatchNumber: _l2BatchNumber, + l2MessageIndex: _l2MessageIndex, + l2Sender: L2_ASSET_ROUTER_ADDR, + l2TxNumberInBatch: _l2TxNumberInBatch, + message: _message, + merkleProof: _merkleProof + }) + ); + } + + function saveL1Address() public { + initializeConfig(); + initializeAdditionalConfig(); + + string memory root = vm.projectRoot(); + string memory path = string.concat(root, vm.envString("ZK_TOKEN_OUTPUT")); + + string memory toml = vm.readFile(path); + + bytes32 zkTokenAssetId = toml.readBytes32("$.ZK.assetId"); + + L1AssetRouter l1AR = L1AssetRouter(config.l1SharedBridge); + console.log("L1 AR address", address(l1AR)); + IL1NativeTokenVault nativeTokenVault = IL1NativeTokenVault(address(l1AR.nativeTokenVault())); + address l1ZKAddress = nativeTokenVault.tokenAddress(zkTokenAssetId); + console.log("L1 ZK address", l1ZKAddress); + TestnetERC20Token l1ZK = TestnetERC20Token(l1ZKAddress); + + uint256 balance = l1ZK.balanceOf(config.deployerAddress); + vm.broadcast(); + l1ZK.transfer(config.owner, balance / 2); + string memory tokenInfo = vm.serializeAddress("ZK", "l1Address", l1ZKAddress); + vm.writeToml(tokenInfo, path, ".ZK.l1Address"); + } + + function fundChainGovernor() public { + initializeConfig(); + + string memory root = vm.projectRoot(); + string memory path = string.concat(root, vm.envString("ZK_TOKEN_OUTPUT")); + string memory toml = vm.readFile(path); + + address l1ZKAddress = toml.readAddress("$.ZK.l1Address.l1Address"); + console.log("L1 ZK address: ", l1ZKAddress); + console.log("Address of governor: ", config.chainGovernor); + TestnetERC20Token l1ZK = TestnetERC20Token(l1ZKAddress); + uint256 balance = l1ZK.balanceOf(config.deployerAddress); + vm.broadcast(); + l1ZK.transfer(config.chainGovernor, balance / 10); + } + + function deployErc20( + string memory name, + string memory symbol, + uint256 decimals, + uint256 mint, + address[] storage additionalAddressesForMinting + ) internal returns (address) { + address tokenAddress = address(new TestnetERC20Token(name, symbol, uint8(decimals))); // No salt for testing + + if (mint > 0) { + additionalAddressesForMinting.push(config.deployerAddress); + uint256 addressMintListLength = additionalAddressesForMinting.length; + for (uint256 i = 0; i < addressMintListLength; ++i) { + (bool success, ) = tokenAddress.call( + abi.encodeWithSignature("mint(address,uint256)", additionalAddressesForMinting[i], mint) + ); + if (!success) { + revert MintFailed(); + } + console.log("Minting to:", additionalAddressesForMinting[i]); + if (!success) { + revert MintFailed(); + } + } + } + + return tokenAddress; + } + + function saveOutput() internal { + TokenDescription memory token = config.zkToken; + string memory section = token.symbol; + + // Serialize each attribute directly under the token's symbol (e.g., [ZK]) + vm.serializeString(section, "name", token.name); + vm.serializeString(section, "symbol", token.symbol); + vm.serializeUint(section, "decimals", token.decimals); + vm.serializeString(section, "implementation", token.implementation); + vm.serializeUintToHex(section, "mint", token.mint); + vm.serializeBytes32(section, "assetId", token.assetId); + vm.serializeAddress(token.symbol, "l1Address", address(0)); + + string memory tokenInfo = vm.serializeAddress(token.symbol, "address", token.addr); + string memory toml = vm.serializeString("root", "ZK", tokenInfo); + string memory root = vm.projectRoot(); + string memory path = string.concat(root, vm.envString("ZK_TOKEN_OUTPUT")); + vm.writeToml(toml, path); + } + + // add this to be excluded from coverage report + function test() internal {} +} diff --git a/l1-contracts/deploy-scripts/GatewayCTMDeployerHelper.sol b/l1-contracts/deploy-scripts/GatewayCTMDeployerHelper.sol new file mode 100644 index 000000000..918c399a8 --- /dev/null +++ b/l1-contracts/deploy-scripts/GatewayCTMDeployerHelper.sol @@ -0,0 +1,330 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; + +import {ChainTypeManager} from "contracts/state-transition/ChainTypeManager.sol"; + +import {L2_BRIDGEHUB_ADDR} from "contracts/common/L2ContractAddresses.sol"; + +import {VerifierParams, IVerifier} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts-v4/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {InitializeDataNewChain as DiamondInitializeDataNewChain} from "contracts/state-transition/chain-interfaces/IDiamondInit.sol"; +import {ChainTypeManagerInitializeData, ChainCreationParams, IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; + +import {Utils} from "./Utils.sol"; + +import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; + +import {GatewayCTMDeployerConfig, DeployedContracts, BLOB_HASH_RETRIEVER_ADDR} from "contracts/state-transition/chain-deps/GatewayCTMDeployer.sol"; + +// solhint-disable gas-custom-errors + +struct InnerDeployConfig { + address deployerAddr; + bytes32 salt; +} + +library GatewayCTMDeployerHelper { + function calculateAddresses( + bytes32 _create2Salt, + GatewayCTMDeployerConfig memory config + ) internal returns (DeployedContracts memory contracts, bytes memory create2Calldata, address ctmDeployerAddress) { + (bytes32 bytecodeHash, bytes memory deployData) = Utils.getDeploymentCalldata( + _create2Salt, + Utils.readZKFoundryBytecodeL1("GatewayCTMDeployer.sol", "GatewayCTMDeployer"), + abi.encode(config) + ); + + // Create2Factory has the same interface as the usual deployer. + create2Calldata = deployData; + + ctmDeployerAddress = Utils.getL2AddressViaCreate2Factory(_create2Salt, bytecodeHash, abi.encode(config)); + + InnerDeployConfig memory innerConfig = InnerDeployConfig({deployerAddr: ctmDeployerAddress, salt: config.salt}); + + // Caching some values + bytes32 salt = config.salt; + uint256 eraChainId = config.eraChainId; + uint256 l1ChainId = config.l1ChainId; + + contracts.multicall3 = _deployInternal("Multicall3", "Multicall3.sol", hex"", innerConfig); + + contracts = _deployFacetsAndUpgrades( + salt, + eraChainId, + l1ChainId, + config.rollupL2DAValidatorAddress, + config.aliasedGovernanceAddress, + contracts, + innerConfig + ); + contracts = _deployVerifier(config.testnetVerifier, contracts, innerConfig); + + contracts.stateTransition.validatorTimelock = _deployInternal( + "ValidatorTimelock", + "ValidatorTimelock.sol", + abi.encode(ctmDeployerAddress, 0), + innerConfig + ); + + contracts = _deployCTM(salt, config, contracts, innerConfig); + } + + function _deployFacetsAndUpgrades( + bytes32 _salt, + uint256 _eraChainId, + uint256 _l1ChainId, + address _rollupL2DAValidatorAddress, + address _governanceAddress, + DeployedContracts memory _deployedContracts, + InnerDeployConfig memory innerConfig + ) internal returns (DeployedContracts memory) { + _deployedContracts.stateTransition.mailboxFacet = _deployInternal( + "MailboxFacet", + "Mailbox.sol", + abi.encode(_eraChainId, _l1ChainId), + innerConfig + ); + + _deployedContracts.stateTransition.executorFacet = _deployInternal( + "ExecutorFacet", + "Executor.sol", + abi.encode(_l1ChainId), + innerConfig + ); + + _deployedContracts.stateTransition.gettersFacet = _deployInternal( + "GettersFacet", + "Getters.sol", + hex"", + innerConfig + ); + + address rollupDAManager; + (_deployedContracts, rollupDAManager) = _deployRollupDAManager( + _salt, + _rollupL2DAValidatorAddress, + _governanceAddress, + _deployedContracts, + innerConfig + ); + _deployedContracts.stateTransition.adminFacet = _deployInternal( + "AdminFacet", + "Admin.sol", + abi.encode(_l1ChainId, rollupDAManager), + innerConfig + ); + + _deployedContracts.stateTransition.diamondInit = _deployInternal( + "DiamondInit", + "DiamondInit.sol", + hex"", + innerConfig + ); + _deployedContracts.stateTransition.genesisUpgrade = _deployInternal( + "L1GenesisUpgrade", + "L1GenesisUpgrade.sol", + hex"", + innerConfig + ); + + return _deployedContracts; + } + + function _deployVerifier( + bool _testnetVerifier, + DeployedContracts memory _deployedContracts, + InnerDeployConfig memory innerConfig + ) internal returns (DeployedContracts memory) { + if (_testnetVerifier) { + _deployedContracts.stateTransition.verifier = _deployInternal( + "TestnetVerifier", + "TestnetVerifier.sol", + hex"", + innerConfig + ); + } else { + _deployedContracts.stateTransition.verifier = _deployInternal( + "Verifier", + "Verifier.sol", + hex"", + innerConfig + ); + } + return _deployedContracts; + } + + function _deployRollupDAManager( + bytes32 _salt, + address _rollupL2DAValidatorAddress, + address _governanceAddress, + DeployedContracts memory _deployedContracts, + InnerDeployConfig memory innerConfig + ) internal returns (DeployedContracts memory, address) { + address daManager = _deployInternal("RollupDAManager", "RollupDAManager.sol", hex"", innerConfig); + + address validiumDAValidator = _deployInternal( + "ValidiumL1DAValidator", + "ValidiumL1DAValidator.sol", + hex"", + innerConfig + ); + + address relayedSLDAValidator = _deployInternal( + "RelayedSLDAValidator", + "RelayedSLDAValidator.sol", + hex"", + innerConfig + ); + + _deployedContracts.daContracts.rollupDAManager = daManager; + _deployedContracts.daContracts.relayedSLDAValidator = relayedSLDAValidator; + _deployedContracts.daContracts.validiumDAValidator = validiumDAValidator; + + return (_deployedContracts, daManager); + } + + function _deployCTM( + bytes32 _salt, + GatewayCTMDeployerConfig memory _config, + DeployedContracts memory _deployedContracts, + InnerDeployConfig memory innerConfig + ) internal returns (DeployedContracts memory) { + _deployedContracts.stateTransition.chainTypeManagerImplementation = _deployInternal( + "ChainTypeManager", + "ChainTypeManager.sol", + abi.encode(L2_BRIDGEHUB_ADDR), + innerConfig + ); + + address proxyAdmin = _deployInternal("ProxyAdmin", "ProxyAdmin.sol", hex"", innerConfig); + _deployedContracts.stateTransition.chainTypeManagerProxyAdmin = proxyAdmin; + + Diamond.FacetCut[] memory facetCuts = new Diamond.FacetCut[](4); + facetCuts[0] = Diamond.FacetCut({ + facet: _deployedContracts.stateTransition.adminFacet, + action: Diamond.Action.Add, + isFreezable: false, + selectors: _config.adminSelectors + }); + facetCuts[1] = Diamond.FacetCut({ + facet: _deployedContracts.stateTransition.gettersFacet, + action: Diamond.Action.Add, + isFreezable: false, + selectors: _config.gettersSelectors + }); + facetCuts[2] = Diamond.FacetCut({ + facet: _deployedContracts.stateTransition.mailboxFacet, + action: Diamond.Action.Add, + isFreezable: true, + selectors: _config.mailboxSelectors + }); + facetCuts[3] = Diamond.FacetCut({ + facet: _deployedContracts.stateTransition.executorFacet, + action: Diamond.Action.Add, + isFreezable: true, + selectors: _config.executorSelectors + }); + + DiamondInitializeDataNewChain memory initializeData = DiamondInitializeDataNewChain({ + verifier: IVerifier(_deployedContracts.stateTransition.verifier), + verifierParams: _config.verifierParams, + l2BootloaderBytecodeHash: _config.bootloaderHash, + l2DefaultAccountBytecodeHash: _config.defaultAccountHash, + priorityTxMaxGasLimit: _config.priorityTxMaxGasLimit, + feeParams: _config.feeParams, + blobVersionedHashRetriever: BLOB_HASH_RETRIEVER_ADDR + }); + + Diamond.DiamondCutData memory diamondCut = Diamond.DiamondCutData({ + facetCuts: facetCuts, + initAddress: _deployedContracts.stateTransition.diamondInit, + initCalldata: abi.encode(initializeData) + }); + + _deployedContracts.diamondCutData = abi.encode(diamondCut); + + ChainCreationParams memory chainCreationParams = ChainCreationParams({ + genesisUpgrade: _deployedContracts.stateTransition.genesisUpgrade, + genesisBatchHash: _config.genesisRoot, + genesisIndexRepeatedStorageChanges: uint64(_config.genesisRollupLeafIndex), + genesisBatchCommitment: _config.genesisBatchCommitment, + diamondCut: diamondCut, + forceDeploymentsData: _config.forceDeploymentsData + }); + + ChainTypeManagerInitializeData memory diamondInitData = ChainTypeManagerInitializeData({ + owner: _config.aliasedGovernanceAddress, + validatorTimelock: _deployedContracts.stateTransition.validatorTimelock, + chainCreationParams: chainCreationParams, + protocolVersion: _config.protocolVersion + }); + + _deployedContracts.stateTransition.chainTypeManagerProxy = _deployInternal( + "TransparentUpgradeableProxy", + "TransparentUpgradeableProxy.sol", + abi.encode( + _deployedContracts.stateTransition.chainTypeManagerImplementation, + proxyAdmin, + abi.encodeCall(ChainTypeManager.initialize, (diamondInitData)) + ), + innerConfig + ); + + return _deployedContracts; + } + + function _deployInternal( + string memory contractName, + string memory fileName, + bytes memory params, + InnerDeployConfig memory config + ) private returns (address) { + bytes memory bytecode = Utils.readZKFoundryBytecodeL1(fileName, contractName); + + return + L2ContractHelper.computeCreate2Address( + config.deployerAddr, + config.salt, + L2ContractHelper.hashL2Bytecode(bytecode), + keccak256(params) + ); + } + + /// @notice List of factory dependencies needed for the correct execution of + /// CTMDeployer and healthy functionaling of the system overall + function getListOfFactoryDeps() external returns (bytes[] memory dependencies) { + uint256 totalDependencies = 18; + dependencies = new bytes[](totalDependencies); + uint256 index = 0; + + dependencies[index++] = Utils.readZKFoundryBytecodeL1("GatewayCTMDeployer.sol", "GatewayCTMDeployer"); + dependencies[index++] = Utils.readZKFoundryBytecodeL1("Multicall3.sol", "Multicall3"); + dependencies[index++] = Utils.readZKFoundryBytecodeL1("Mailbox.sol", "MailboxFacet"); + dependencies[index++] = Utils.readZKFoundryBytecodeL1("Executor.sol", "ExecutorFacet"); + dependencies[index++] = Utils.readZKFoundryBytecodeL1("Getters.sol", "GettersFacet"); + dependencies[index++] = Utils.readZKFoundryBytecodeL1("RollupDAManager.sol", "RollupDAManager"); + dependencies[index++] = Utils.readZKFoundryBytecodeL1("ValidiumL1DAValidator.sol", "ValidiumL1DAValidator"); + dependencies[index++] = Utils.readZKFoundryBytecodeL1("RelayedSLDAValidator.sol", "RelayedSLDAValidator"); + dependencies[index++] = Utils.readZKFoundryBytecodeL1("Admin.sol", "AdminFacet"); + dependencies[index++] = Utils.readZKFoundryBytecodeL1("DiamondInit.sol", "DiamondInit"); + dependencies[index++] = Utils.readZKFoundryBytecodeL1("L1GenesisUpgrade.sol", "L1GenesisUpgrade"); + // Include both verifiers since we cannot determine which one will be used + dependencies[index++] = Utils.readZKFoundryBytecodeL1("TestnetVerifier.sol", "TestnetVerifier"); + dependencies[index++] = Utils.readZKFoundryBytecodeL1("Verifier.sol", "Verifier"); + dependencies[index++] = Utils.readZKFoundryBytecodeL1("ValidatorTimelock.sol", "ValidatorTimelock"); + dependencies[index++] = Utils.readZKFoundryBytecodeL1("ChainTypeManager.sol", "ChainTypeManager"); + dependencies[index++] = Utils.readZKFoundryBytecodeL1("ProxyAdmin.sol", "ProxyAdmin"); + dependencies[index++] = Utils.readZKFoundryBytecodeL1( + "TransparentUpgradeableProxy.sol", + "TransparentUpgradeableProxy" + ); + // Not used in scripts, but definitely needed for CTM to work + dependencies[index++] = Utils.readZKFoundryBytecodeL1("DiamondProxy.sol", "DiamondProxy"); + + return dependencies; + } +} diff --git a/l1-contracts/deploy-scripts/GatewayCTMFromL1.s.sol b/l1-contracts/deploy-scripts/GatewayCTMFromL1.s.sol new file mode 100644 index 000000000..023ccc0c9 --- /dev/null +++ b/l1-contracts/deploy-scripts/GatewayCTMFromL1.s.sol @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +// solhint-disable no-console, gas-custom-errors, reason-string + +import {Script, console2 as console} from "forge-std/Script.sol"; +// import {Vm} from "forge-std/Vm.sol"; +import {stdToml} from "forge-std/StdToml.sol"; + +// It's required to disable lints to force the compiler to compile the contracts +// solhint-disable no-unused-import +import {TestnetERC20Token} from "contracts/dev-contracts/TestnetERC20Token.sol"; + +import {Ownable} from "@openzeppelin/contracts-v4/access/Ownable.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA} from "contracts/common/Config.sol"; +import {L2TransactionRequestTwoBridgesOuter} from "contracts/bridgehub/IBridgehub.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {StateTransitionDeployedAddresses, Utils, L2_BRIDGEHUB_ADDRESS, L2_CREATE2_FACTORY_ADDRESS} from "./Utils.sol"; +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {L2ContractsBytecodesLib} from "./L2ContractsBytecodesLib.sol"; +import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol"; +import {IL1NativeTokenVault} from "contracts/bridge/ntv/IL1NativeTokenVault.sol"; + +import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol"; +import {ExecutorFacet} from "contracts/state-transition/chain-deps/facets/Executor.sol"; +import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; +import {MailboxFacet} from "contracts/state-transition/chain-deps/facets/Mailbox.sol"; +import {DiamondProxy} from "contracts/state-transition/chain-deps/DiamondProxy.sol"; +import {DiamondInit} from "contracts/state-transition/chain-deps/DiamondInit.sol"; + +import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; +import {Verifier} from "contracts/state-transition/Verifier.sol"; +import {VerifierParams, IVerifier} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; +import {ValidatorTimelock} from "contracts/state-transition/ValidatorTimelock.sol"; +import {L1GenesisUpgrade} from "contracts/upgrades/L1GenesisUpgrade.sol"; +import {DefaultUpgrade} from "contracts/upgrades/DefaultUpgrade.sol"; + +import {ChainTypeManager} from "contracts/state-transition/ChainTypeManager.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {InitializeDataNewChain as DiamondInitializeDataNewChain} from "contracts/state-transition/chain-interfaces/IDiamondInit.sol"; +import {FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZKChainStorage.sol"; +import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; +import {ChainTypeManagerInitializeData, ChainCreationParams, IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; + +import {DeployedContracts, GatewayCTMDeployerConfig} from "contracts/state-transition/chain-deps/GatewayCTMDeployer.sol"; +import {GatewayCTMDeployerHelper} from "./GatewayCTMDeployerHelper.sol"; + +/// @notice Scripts that is responsible for preparing the chain to become a gateway +/// @dev IMPORTANT: this script is not intended to be used in production. +/// TODO(EVM-925): support secure gateway deployment. +contract GatewayCTMFromL1 is Script { + using stdToml for string; + + address internal constant ADDRESS_ONE = 0x0000000000000000000000000000000000000001; + bytes32 internal constant STATE_TRANSITION_NEW_CHAIN_HASH = keccak256("NewHyperchain(uint256,address)"); + + address deployerAddress; + + // solhint-disable-next-line gas-struct-packing + struct Config { + address bridgehub; + address ctmDeploymentTracker; + address nativeTokenVault; + address chainTypeManagerProxy; + address sharedBridgeProxy; + address governanceAddr; + address baseToken; + uint256 chainChainId; + uint256 eraChainId; + uint256 l1ChainId; + bool testnetVerifier; + bytes32 recursionNodeLevelVkHash; + bytes32 recursionLeafLevelVkHash; + bytes32 recursionCircuitsSetVksHash; + PubdataPricingMode diamondInitPubdataPricingMode; + uint256 diamondInitBatchOverheadL1Gas; + uint256 diamondInitMaxPubdataPerBatch; + uint256 diamondInitMaxL2GasPerBatch; + uint256 diamondInitPriorityTxMaxPubdata; + uint256 diamondInitMinimalL2GasPrice; + bytes32 bootloaderHash; + bytes32 defaultAAHash; + uint256 priorityTxMaxGasLimit; + bytes32 genesisRoot; + uint256 genesisRollupLeafIndex; + bytes32 genesisBatchCommitment; + uint256 latestProtocolVersion; + address expectedRollupL2DAValidator; + bytes forceDeploymentsData; + } + + struct Output { + StateTransitionDeployedAddresses gatewayStateTransition; + address multicall3; + bytes diamondCutData; + address relayedSLDAValidator; + address validiumDAValidator; + } + + Config internal config; + GatewayCTMDeployerConfig internal gatewayCTMDeployerConfig; + Output internal output; + + function prepareAddresses() external { + initializeConfig(); + if (config.baseToken != ADDRESS_ONE) { + distributeBaseToken(); + } + + (DeployedContracts memory expectedGatewayContracts, bytes memory create2Calldata, ) = GatewayCTMDeployerHelper + .calculateAddresses(bytes32(0), gatewayCTMDeployerConfig); + + _saveExpectedGatewayContractsToOutput(expectedGatewayContracts); + saveOutput(); + } + + function deployCTM() external { + initializeConfig(); + + (DeployedContracts memory expectedGatewayContracts, bytes memory create2Calldata, ) = GatewayCTMDeployerHelper + .calculateAddresses(bytes32(0), gatewayCTMDeployerConfig); + + bytes[] memory deps = GatewayCTMDeployerHelper.getListOfFactoryDeps(); + + for (uint i = 0; i < deps.length; i++) { + bytes[] memory localDeps = new bytes[](1); + localDeps[0] = deps[i]; + Utils.runL1L2Transaction({ + l2Calldata: hex"", + l2GasLimit: 72_000_000, + l2Value: 0, + factoryDeps: localDeps, + dstAddress: address(0), + chainId: config.chainChainId, + bridgehubAddress: config.bridgehub, + l1SharedBridgeProxy: config.sharedBridgeProxy + }); + } + + Utils.runL1L2Transaction({ + l2Calldata: create2Calldata, + l2GasLimit: 72_000_000, + l2Value: 0, + factoryDeps: new bytes[](0), + dstAddress: L2_CREATE2_FACTORY_ADDRESS, + chainId: config.chainChainId, + bridgehubAddress: config.bridgehub, + l1SharedBridgeProxy: config.sharedBridgeProxy + }); + + _saveExpectedGatewayContractsToOutput(expectedGatewayContracts); + saveOutput(); + } + + function _saveExpectedGatewayContractsToOutput(DeployedContracts memory expectedGatewayContracts) internal { + output = Output({ + gatewayStateTransition: StateTransitionDeployedAddresses({ + chainTypeManagerProxy: expectedGatewayContracts.stateTransition.chainTypeManagerProxy, + chainTypeManagerImplementation: expectedGatewayContracts.stateTransition.chainTypeManagerImplementation, + verifier: expectedGatewayContracts.stateTransition.verifier, + adminFacet: expectedGatewayContracts.stateTransition.adminFacet, + mailboxFacet: expectedGatewayContracts.stateTransition.mailboxFacet, + executorFacet: expectedGatewayContracts.stateTransition.executorFacet, + gettersFacet: expectedGatewayContracts.stateTransition.gettersFacet, + diamondInit: expectedGatewayContracts.stateTransition.diamondInit, + genesisUpgrade: expectedGatewayContracts.stateTransition.genesisUpgrade, + // No need for default upgrade on gateway + defaultUpgrade: address(0), + validatorTimelock: expectedGatewayContracts.stateTransition.validatorTimelock, + diamondProxy: address(0), + bytecodesSupplier: address(0) + }), + multicall3: expectedGatewayContracts.multicall3, + diamondCutData: expectedGatewayContracts.diamondCutData, + relayedSLDAValidator: expectedGatewayContracts.daContracts.relayedSLDAValidator, + validiumDAValidator: expectedGatewayContracts.daContracts.validiumDAValidator + }); + } + + function initializeConfig() internal { + deployerAddress = msg.sender; + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/script-config/config-deploy-gateway-ctm.toml"); + string memory toml = vm.readFile(path); + + // Config file must be parsed key by key, otherwise values returned + // are parsed alfabetically and not by key. + // https://book.getfoundry.sh/cheatcodes/parse-toml + + // Initializing all values at once is preferableo ensure type safety of + // the fact that all values are initialized + config = Config({ + bridgehub: toml.readAddress("$.bridgehub_proxy_addr"), + ctmDeploymentTracker: toml.readAddress("$.ctm_deployment_tracker_proxy_addr"), + nativeTokenVault: toml.readAddress("$.native_token_vault_addr"), + chainTypeManagerProxy: toml.readAddress("$.chain_type_manager_proxy_addr"), + sharedBridgeProxy: toml.readAddress("$.shared_bridge_proxy_addr"), + chainChainId: toml.readUint("$.chain_chain_id"), + governanceAddr: toml.readAddress("$.governance"), + baseToken: toml.readAddress("$.base_token"), + l1ChainId: toml.readUint("$.l1_chain_id"), + eraChainId: toml.readUint("$.era_chain_id"), + testnetVerifier: toml.readBool("$.testnet_verifier"), + recursionNodeLevelVkHash: toml.readBytes32("$.recursion_node_level_vk_hash"), + recursionLeafLevelVkHash: toml.readBytes32("$.recursion_leaf_level_vk_hash"), + recursionCircuitsSetVksHash: toml.readBytes32("$.recursion_circuits_set_vks_hash"), + diamondInitPubdataPricingMode: PubdataPricingMode(toml.readUint("$.diamond_init_pubdata_pricing_mode")), + diamondInitBatchOverheadL1Gas: toml.readUint("$.diamond_init_batch_overhead_l1_gas"), + diamondInitMaxPubdataPerBatch: toml.readUint("$.diamond_init_max_pubdata_per_batch"), + diamondInitMaxL2GasPerBatch: toml.readUint("$.diamond_init_max_l2_gas_per_batch"), + diamondInitPriorityTxMaxPubdata: toml.readUint("$.diamond_init_priority_tx_max_pubdata"), + diamondInitMinimalL2GasPrice: toml.readUint("$.diamond_init_minimal_l2_gas_price"), + bootloaderHash: toml.readBytes32("$.bootloader_hash"), + defaultAAHash: toml.readBytes32("$.default_aa_hash"), + priorityTxMaxGasLimit: toml.readUint("$.priority_tx_max_gas_limit"), + genesisRoot: toml.readBytes32("$.genesis_root"), + genesisRollupLeafIndex: toml.readUint("$.genesis_rollup_leaf_index"), + genesisBatchCommitment: toml.readBytes32("$.genesis_batch_commitment"), + latestProtocolVersion: toml.readUint("$.latest_protocol_version"), + expectedRollupL2DAValidator: toml.readAddress("$.expected_rollup_l2_da_validator"), + forceDeploymentsData: toml.readBytes("$.force_deployments_data") + }); + + address aliasedGovernor = AddressAliasHelper.applyL1ToL2Alias(config.governanceAddr); + gatewayCTMDeployerConfig = GatewayCTMDeployerConfig({ + aliasedGovernanceAddress: aliasedGovernor, + salt: bytes32(0), + eraChainId: config.eraChainId, + l1ChainId: config.l1ChainId, + rollupL2DAValidatorAddress: config.expectedRollupL2DAValidator, + testnetVerifier: config.testnetVerifier, + adminSelectors: Utils.getAllSelectorsForFacet("Admin"), + executorSelectors: Utils.getAllSelectorsForFacet("Executor"), + mailboxSelectors: Utils.getAllSelectorsForFacet("Mailbox"), + gettersSelectors: Utils.getAllSelectorsForFacet("Getters"), + verifierParams: VerifierParams({ + recursionNodeLevelVkHash: config.recursionNodeLevelVkHash, + recursionLeafLevelVkHash: config.recursionLeafLevelVkHash, + recursionCircuitsSetVksHash: config.recursionCircuitsSetVksHash + }), + feeParams: FeeParams({ + pubdataPricingMode: config.diamondInitPubdataPricingMode, + batchOverheadL1Gas: uint32(config.diamondInitBatchOverheadL1Gas), + maxPubdataPerBatch: uint32(config.diamondInitMaxPubdataPerBatch), + maxL2GasPerBatch: uint32(config.diamondInitMaxL2GasPerBatch), + priorityTxMaxPubdata: uint32(config.diamondInitPriorityTxMaxPubdata), + minimalL2GasPrice: uint64(config.diamondInitMinimalL2GasPrice) + }), + bootloaderHash: config.bootloaderHash, + defaultAccountHash: config.defaultAAHash, + priorityTxMaxGasLimit: config.priorityTxMaxGasLimit, + genesisRoot: config.genesisRoot, + genesisRollupLeafIndex: uint64(config.genesisRollupLeafIndex), + genesisBatchCommitment: config.genesisBatchCommitment, + forceDeploymentsData: config.forceDeploymentsData, + protocolVersion: config.latestProtocolVersion + }); + } + + function distributeBaseToken() internal { + deployerAddress = msg.sender; + uint256 amountForDistribution = 100000000000000000000; + L1AssetRouter l1AR = L1AssetRouter(config.sharedBridgeProxy); + IL1NativeTokenVault nativeTokenVault = IL1NativeTokenVault(address(l1AR.nativeTokenVault())); + bytes32 baseTokenAssetID = nativeTokenVault.assetId(config.baseToken); + uint256 baseTokenOriginChainId = nativeTokenVault.originChainId(baseTokenAssetID); + TestnetERC20Token baseToken = TestnetERC20Token(config.baseToken); + + vm.startBroadcast(); + if (baseTokenOriginChainId == block.chainid) { + baseToken.mint(config.governanceAddr, amountForDistribution); + } else { + baseToken.transfer(config.governanceAddr, amountForDistribution); + } + vm.stopBroadcast(); + } + + function saveOutput() internal { + vm.serializeAddress( + "gateway_state_transition", + "chain_type_manager_proxy_addr", + output.gatewayStateTransition.chainTypeManagerProxy + ); + vm.serializeAddress( + "gateway_state_transition", + "chain_type_manager_implementation_addr", + output.gatewayStateTransition.chainTypeManagerImplementation + ); + vm.serializeAddress("gateway_state_transition", "verifier_addr", output.gatewayStateTransition.verifier); + vm.serializeAddress("gateway_state_transition", "admin_facet_addr", output.gatewayStateTransition.adminFacet); + vm.serializeAddress( + "gateway_state_transition", + "mailbox_facet_addr", + output.gatewayStateTransition.mailboxFacet + ); + vm.serializeAddress( + "gateway_state_transition", + "executor_facet_addr", + output.gatewayStateTransition.executorFacet + ); + vm.serializeAddress( + "gateway_state_transition", + "getters_facet_addr", + output.gatewayStateTransition.gettersFacet + ); + vm.serializeAddress("gateway_state_transition", "diamond_init_addr", output.gatewayStateTransition.diamondInit); + vm.serializeAddress( + "gateway_state_transition", + "genesis_upgrade_addr", + output.gatewayStateTransition.genesisUpgrade + ); + vm.serializeAddress( + "gateway_state_transition", + "default_upgrade_addr", + output.gatewayStateTransition.defaultUpgrade + ); + vm.serializeAddress( + "gateway_state_transition", + "validator_timelock_addr", + output.gatewayStateTransition.validatorTimelock + ); + string memory gatewayStateTransition = vm.serializeAddress( + "gateway_state_transition", + "diamond_proxy_addr", + output.gatewayStateTransition.diamondProxy + ); + vm.serializeString("root", "gateway_state_transition", gatewayStateTransition); + vm.serializeAddress("root", "multicall3_addr", output.multicall3); + vm.serializeAddress("root", "relayed_sl_da_validator", output.relayedSLDAValidator); + vm.serializeAddress("root", "validium_da_validator", output.validiumDAValidator); + + string memory toml = vm.serializeBytes("root", "diamond_cut_data", output.diamondCutData); + string memory path = string.concat(vm.projectRoot(), "/script-out/output-deploy-gateway-ctm.toml"); + vm.writeToml(toml, path); + } + + function _deployInternal(bytes memory bytecode, bytes memory constructorargs) internal returns (address) { + return + Utils.deployThroughL1({ + bytecode: bytecode, + constructorargs: constructorargs, + create2salt: bytes32(0), + l2GasLimit: Utils.MAX_PRIORITY_TX_GAS, + factoryDeps: new bytes[](0), + chainId: config.chainChainId, + bridgehubAddress: config.bridgehub, + l1SharedBridgeProxy: config.sharedBridgeProxy + }); + } + + function deployGatewayFacets() internal { + address adminFacet = address( + _deployInternal(L2ContractsBytecodesLib.readAdminFacetBytecode(), abi.encode(config.l1ChainId)) + ); + console.log("Admin facet deployed at", adminFacet); + + address mailboxFacet = address( + _deployInternal( + L2ContractsBytecodesLib.readMailboxFacetBytecode(), + abi.encode(config.l1ChainId, config.eraChainId) + ) + ); + console.log("Mailbox facet deployed at", mailboxFacet); + + address executorFacet = address( + _deployInternal(L2ContractsBytecodesLib.readExecutorFacetBytecode(), abi.encode(config.l1ChainId)) + ); + console.log("ExecutorFacet facet deployed at", executorFacet); + + address gettersFacet = address(_deployInternal(L2ContractsBytecodesLib.readGettersFacetBytecode(), hex"")); + console.log("Getters facet deployed at", gettersFacet); + + output.gatewayStateTransition.adminFacet = adminFacet; + output.gatewayStateTransition.mailboxFacet = mailboxFacet; + output.gatewayStateTransition.executorFacet = executorFacet; + output.gatewayStateTransition.gettersFacet = gettersFacet; + } + + function deployGatewayVerifier() internal returns (address verifier) { + if (config.testnetVerifier) { + verifier = address( + _deployInternal(L2ContractsBytecodesLib.readL2TestnetVerifierBytecode(), abi.encode(config.l1ChainId)) + ); + } else { + verifier = address(_deployInternal(L2ContractsBytecodesLib.readL2VerifierBytecode(), hex"")); + } + + console.log("Verifier deployed at", verifier); + } + + function deployValidatorTimelock() internal returns (address validatorTimelock) { + // address aliasedGovernor = AddressAliasHelper.applyL1ToL2Alias(config.governance); + // TODO(EVM-745): eventually the governance should be moved to the governance contract + // Note: we do not apply alias because the deployer is an EOA. + validatorTimelock = address( + _deployInternal( + L2ContractsBytecodesLib.readValidatorTimelockBytecode(), + abi.encode(deployerAddress, 0, config.eraChainId) + ) + ); + console.log("Validator timelock deployed at", validatorTimelock); + } + + function deployGatewayChainTypeManager() internal { + // We need to publish the bytecode of the diamdon proxy contract, + // we can only do it via deploying its dummy version. + // We could've published the dependency separately, but we just repeated the code that would be + // used for pure L2 execution. + address dp = address(_deployInternal(L2ContractsBytecodesLib.readDiamondProxyBytecode(), hex"")); + console.log("Dummy diamond proxy deployed at", dp); + + output.gatewayStateTransition.chainTypeManagerImplementation = address( + _deployInternal(L2ContractsBytecodesLib.readChainTypeManagerBytecode(), abi.encode(L2_BRIDGEHUB_ADDRESS)) + ); + console.log( + "StateTransitionImplementation deployed at", + output.gatewayStateTransition.chainTypeManagerImplementation + ); + + // TODO(EVM-745): eventually a proxy admin or something should be deplyoed here + Diamond.FacetCut[] memory facetCuts = new Diamond.FacetCut[](4); + facetCuts[0] = Diamond.FacetCut({ + facet: output.gatewayStateTransition.adminFacet, + action: Diamond.Action.Add, + isFreezable: false, + selectors: Utils.getAllSelectorsForFacet("Admin") + }); + facetCuts[1] = Diamond.FacetCut({ + facet: output.gatewayStateTransition.gettersFacet, + action: Diamond.Action.Add, + isFreezable: false, + selectors: Utils.getAllSelectorsForFacet("Getters") + }); + facetCuts[2] = Diamond.FacetCut({ + facet: output.gatewayStateTransition.mailboxFacet, + action: Diamond.Action.Add, + isFreezable: true, + selectors: Utils.getAllSelectorsForFacet("Mailbox") + }); + facetCuts[3] = Diamond.FacetCut({ + facet: output.gatewayStateTransition.executorFacet, + action: Diamond.Action.Add, + isFreezable: true, + selectors: Utils.getAllSelectorsForFacet("Executor") + }); + + VerifierParams memory verifierParams = VerifierParams({ + recursionNodeLevelVkHash: config.recursionNodeLevelVkHash, + recursionLeafLevelVkHash: config.recursionLeafLevelVkHash, + recursionCircuitsSetVksHash: config.recursionCircuitsSetVksHash + }); + + FeeParams memory feeParams = FeeParams({ + pubdataPricingMode: config.diamondInitPubdataPricingMode, + batchOverheadL1Gas: uint32(config.diamondInitBatchOverheadL1Gas), + maxPubdataPerBatch: uint32(config.diamondInitMaxPubdataPerBatch), + maxL2GasPerBatch: uint32(config.diamondInitMaxL2GasPerBatch), + priorityTxMaxPubdata: uint32(config.diamondInitPriorityTxMaxPubdata), + minimalL2GasPrice: uint64(config.diamondInitMinimalL2GasPrice) + }); + + DiamondInitializeDataNewChain memory initializeData = DiamondInitializeDataNewChain({ + verifier: IVerifier(output.gatewayStateTransition.verifier), + verifierParams: verifierParams, + l2BootloaderBytecodeHash: config.bootloaderHash, + l2DefaultAccountBytecodeHash: config.defaultAAHash, + priorityTxMaxGasLimit: config.priorityTxMaxGasLimit, + feeParams: feeParams, + // We can not provide zero value there. At the same time, there is no such contract on gateway + blobVersionedHashRetriever: ADDRESS_ONE + }); + + Diamond.DiamondCutData memory diamondCut = Diamond.DiamondCutData({ + facetCuts: facetCuts, + initAddress: output.gatewayStateTransition.diamondInit, + initCalldata: abi.encode(initializeData) + }); + + output.diamondCutData = abi.encode(diamondCut); + + ChainCreationParams memory chainCreationParams = ChainCreationParams({ + genesisUpgrade: output.gatewayStateTransition.genesisUpgrade, + genesisBatchHash: config.genesisRoot, + genesisIndexRepeatedStorageChanges: uint64(config.genesisRollupLeafIndex), + genesisBatchCommitment: config.genesisBatchCommitment, + diamondCut: diamondCut, + // Note, it is the same as for contracts that are based on L2 + forceDeploymentsData: config.forceDeploymentsData + }); + + ChainTypeManagerInitializeData memory diamondInitData = ChainTypeManagerInitializeData({ + owner: msg.sender, + validatorTimelock: output.gatewayStateTransition.validatorTimelock, + chainCreationParams: chainCreationParams, + protocolVersion: config.latestProtocolVersion + }); + + output.gatewayStateTransition.chainTypeManagerProxy = _deployInternal( + L2ContractsBytecodesLib.readTransparentUpgradeableProxyBytecode(), + abi.encode( + output.gatewayStateTransition.chainTypeManagerImplementation, + deployerAddress, + abi.encodeCall(ChainTypeManager.initialize, (diamondInitData)) + ) + ); + + console.log("ChainTypeManagerProxy deployed at:", output.gatewayStateTransition.chainTypeManagerProxy); + output.gatewayStateTransition.chainTypeManagerProxy = output.gatewayStateTransition.chainTypeManagerProxy; + } + + function setChainTypeManagerInValidatorTimelock() internal { + bytes memory data = abi.encodeCall( + ValidatorTimelock.setChainTypeManager, + (IChainTypeManager(output.gatewayStateTransition.chainTypeManagerProxy)) + ); + + Utils.runL1L2Transaction({ + l2Calldata: data, + l2GasLimit: Utils.MAX_PRIORITY_TX_GAS, + l2Value: 0, + factoryDeps: new bytes[](0), + dstAddress: output.gatewayStateTransition.validatorTimelock, + chainId: config.chainChainId, + bridgehubAddress: config.bridgehub, + l1SharedBridgeProxy: config.sharedBridgeProxy + }); + + console.log("ChainTypeManager set in ValidatorTimelock"); + } +} diff --git a/l1-contracts/deploy-scripts/GatewayPreparation.s.sol b/l1-contracts/deploy-scripts/GatewayPreparation.s.sol new file mode 100644 index 000000000..2701e30c6 --- /dev/null +++ b/l1-contracts/deploy-scripts/GatewayPreparation.s.sol @@ -0,0 +1,652 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +// solhint-disable no-console, gas-custom-errors, reason-string + +import {Script, console2 as console} from "forge-std/Script.sol"; +// import {Vm} from "forge-std/Vm.sol"; +import {stdToml} from "forge-std/StdToml.sol"; + +// It's required to disable lints to force the compiler to compile the contracts +// solhint-disable no-unused-import +import {TestnetERC20Token} from "contracts/dev-contracts/TestnetERC20Token.sol"; + +import {Ownable} from "@openzeppelin/contracts-v4/access/Ownable.sol"; +import {IBridgehub, BridgehubBurnCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA} from "contracts/common/Config.sol"; +import {L2TransactionRequestTwoBridgesOuter} from "contracts/bridgehub/IBridgehub.sol"; +import {L2_BRIDGEHUB_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {StateTransitionDeployedAddresses, Utils, L2_BRIDGEHUB_ADDRESS} from "./Utils.sol"; +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {ValidatorTimelock} from "contracts/state-transition/ValidatorTimelock.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {GatewayTransactionFilterer} from "contracts/transactionFilterer/GatewayTransactionFilterer.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {SET_ASSET_HANDLER_COUNTERPART_ENCODING_VERSION} from "contracts/bridge/asset-router/IAssetRouterBase.sol"; +import {CTM_DEPLOYMENT_TRACKER_ENCODING_VERSION} from "contracts/bridgehub/CTMDeploymentTracker.sol"; +import {L2AssetRouter, IL2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {L1Nullifier} from "contracts/bridge/L1Nullifier.sol"; +import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol"; +import {IL1NativeTokenVault} from "contracts/bridge/ntv/IL1NativeTokenVault.sol"; +import {BridgehubMintCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; +import {IAssetRouterBase} from "contracts/bridge/asset-router/IAssetRouterBase.sol"; +import {L2_ASSET_ROUTER_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {FinalizeL1DepositParams} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {AccessControlRestriction} from "contracts/governance/AccessControlRestriction.sol"; +import {L2ContractsBytecodesLib} from "./L2ContractsBytecodesLib.sol"; +import {ChainAdmin} from "contracts/governance/ChainAdmin.sol"; +import {Call} from "contracts/governance/Common.sol"; +import {IGovernance} from "contracts/governance/IGovernance.sol"; +import {Ownable2Step} from "@openzeppelin/contracts-v4/access/Ownable2Step.sol"; +import {ICTMDeploymentTracker} from "contracts/bridgehub/ICTMDeploymentTracker.sol"; + +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; + +// solhint-disable-next-line gas-struct-packing +struct Config { + address bridgehub; + address ctmDeploymentTracker; + address chainTypeManagerProxy; + address sharedBridgeProxy; + address governance; + uint256 gatewayChainId; + address gatewayChainAdmin; + address gatewayAccessControlRestriction; + address gatewayChainProxyAdmin; + address l1NullifierProxy; + bytes gatewayDiamondCutData; + bytes l1DiamondCutData; +} + +/// @notice Scripts that is responsible for preparing the chain to become a gateway +/// @dev IMPORTANT: this script is not intended to be used in production. +/// TODO(EVM-925): support secure gateway deployment. +contract GatewayPreparation is Script { + using stdToml for string; + + address internal constant ADDRESS_ONE = 0x0000000000000000000000000000000000000001; + bytes32 internal constant STATE_TRANSITION_NEW_CHAIN_HASH = keccak256("NewHyperchain(uint256,address)"); + + address deployerAddress; + uint256 l1ChainId; + + struct Output { + bytes32 governanceL2TxHash; + address l2ChainAdminAddress; + address gatewayTransactionFiltererImplementation; + address gatewayTransactionFiltererProxy; + } + + Config internal config; + + function run() public { + console.log("Setting up the Gateway script"); + + initializeConfig(); + } + + function _getL1GasPrice() internal virtual returns (uint256) { + return Utils.bytesToUint256(vm.rpc("eth_gasPrice", "[]")); + } + + function initializeConfig() internal virtual { + deployerAddress = msg.sender; + l1ChainId = block.chainid; + + string memory root = vm.projectRoot(); + string memory path = string.concat(root, vm.envString("GATEWAY_PREPARATION_L1_CONFIG")); + string memory toml = vm.readFile(path); + + // Config file must be parsed key by key, otherwise values returned + // are parsed alfabetically and not by key. + // https://book.getfoundry.sh/cheatcodes/parse-toml + + // Initializing all values at once is preferable to ensure type safety of + // the fact that all values are initialized + config = Config({ + bridgehub: toml.readAddress("$.bridgehub_proxy_addr"), + ctmDeploymentTracker: toml.readAddress("$.ctm_deployment_tracker_proxy_addr"), + chainTypeManagerProxy: toml.readAddress("$.chain_type_manager_proxy_addr"), + sharedBridgeProxy: toml.readAddress("$.shared_bridge_proxy_addr"), + gatewayChainId: toml.readUint("$.chain_chain_id"), + governance: toml.readAddress("$.governance"), + gatewayDiamondCutData: toml.readBytes("$.gateway_diamond_cut_data"), + l1DiamondCutData: toml.readBytes("$.l1_diamond_cut_data"), + gatewayChainAdmin: toml.readAddress("$.chain_admin"), + gatewayAccessControlRestriction: toml.readAddress("$.access_control_restriction"), + gatewayChainProxyAdmin: toml.readAddress("$.chain_proxy_admin"), + l1NullifierProxy: toml.readAddress("$.l1_nullifier_proxy_addr") + }); + } + + function saveOutput(Output memory output) internal { + vm.serializeAddress( + "root", + "gateway_transaction_filterer_implementation", + output.gatewayTransactionFiltererImplementation + ); + vm.serializeAddress("root", "gateway_transaction_filterer_proxy", output.gatewayTransactionFiltererProxy); + vm.serializeAddress("root", "l2_chain_admin_address", output.l2ChainAdminAddress); + string memory toml = vm.serializeBytes32("root", "governance_l2_tx_hash", output.governanceL2TxHash); + string memory path = string.concat(vm.projectRoot(), "/script-out/output-gateway-preparation-l1.toml"); + vm.writeToml(toml, path); + } + + function saveOutput(address l2ChainAdminAddress) internal { + Output memory output = Output({ + governanceL2TxHash: bytes32(0), + l2ChainAdminAddress: l2ChainAdminAddress, + gatewayTransactionFiltererImplementation: address(0), + gatewayTransactionFiltererProxy: address(0) + }); + + saveOutput(output); + } + + function saveOutput(bytes32 governanceL2TxHash) internal { + Output memory output = Output({ + governanceL2TxHash: governanceL2TxHash, + l2ChainAdminAddress: address(0), + gatewayTransactionFiltererImplementation: address(0), + gatewayTransactionFiltererProxy: address(0) + }); + + saveOutput(output); + } + + function saveOutput() internal { + Output memory output = Output({ + governanceL2TxHash: bytes32(0), + l2ChainAdminAddress: address(0), + gatewayTransactionFiltererImplementation: address(0), + gatewayTransactionFiltererProxy: address(0) + }); + + saveOutput(output); + } + + function saveOutput( + address gatewayTransactionFiltererImplementation, + address gatewayTransactionFiltererProxy + ) internal { + Output memory output = Output({ + governanceL2TxHash: bytes32(0), + l2ChainAdminAddress: address(0), + gatewayTransactionFiltererImplementation: gatewayTransactionFiltererImplementation, + gatewayTransactionFiltererProxy: gatewayTransactionFiltererProxy + }); + + saveOutput(output); + } + + /// @dev Requires the sender to be the owner of the contract + function governanceRegisterGateway() public { + initializeConfig(); + + IBridgehub bridgehub = IBridgehub(config.bridgehub); + + if (bridgehub.whitelistedSettlementLayers(config.gatewayChainId)) { + console.log("Chain already whitelisted as settlement layer"); + } else { + bytes memory data = abi.encodeCall(bridgehub.registerSettlementLayer, (config.gatewayChainId, true)); + Utils.executeUpgrade({ + _governor: config.governance, + _salt: bytes32(0), + _target: address(bridgehub), + _data: data, + _value: 0, + _delay: 0 + }); + console.log("Gateway whitelisted as settlement layer"); + } + // No tx has been executed, so we save an empty hash + saveOutput(bytes32(0)); + } + + /// @dev Requires the sender to be the owner of the contract + function governanceWhitelistGatewayCTM(address gatewayCTMAddress, bytes32 governanoceOperationSalt) public { + initializeConfig(); + + bytes memory data = abi.encodeCall(IBridgehub.addChainTypeManager, (gatewayCTMAddress)); + + bytes32 l2TxHash = Utils.runGovernanceL1L2DirectTransaction( + _getL1GasPrice(), + config.governance, + governanoceOperationSalt, + data, + Utils.MAX_PRIORITY_TX_GAS, + new bytes[](0), + L2_BRIDGEHUB_ADDRESS, + config.gatewayChainId, + config.bridgehub, + config.sharedBridgeProxy + ); + + saveOutput(l2TxHash); + } + + function governanceSetCTMAssetHandler(bytes32 governanoceOperationSalt) public { + initializeConfig(); + + L1AssetRouter sharedBridge = L1AssetRouter(config.sharedBridgeProxy); + bytes memory data = abi.encodeCall( + sharedBridge.setAssetDeploymentTracker, + (bytes32(uint256(uint160(config.chainTypeManagerProxy))), address(config.ctmDeploymentTracker)) + ); + Utils.executeUpgrade({ + _governor: config.governance, + _salt: bytes32(0), + _target: address(config.sharedBridgeProxy), + _data: data, + _value: 0, + _delay: 0 + }); + + ICTMDeploymentTracker tracker = ICTMDeploymentTracker(config.ctmDeploymentTracker); + data = abi.encodeCall(tracker.registerCTMAssetOnL1, (config.chainTypeManagerProxy)); + Utils.executeUpgrade({ + _governor: config.governance, + _salt: bytes32(0), + _target: address(config.ctmDeploymentTracker), + _data: data, + _value: 0, + _delay: 0 + }); + + bytes32 assetId = IBridgehub(config.bridgehub).ctmAssetIdFromAddress(config.chainTypeManagerProxy); + + // This should be equivalent to `config.chainTypeManagerProxy`, but we just double checking to ensure that + // bridgehub was initialized correctly + address ctmAddress = IBridgehub(config.bridgehub).ctmAssetIdToAddress(assetId); + require(ctmAddress == config.chainTypeManagerProxy, "CTM asset id does not match the expected CTM address"); + + bytes memory secondBridgeData = abi.encodePacked( + SET_ASSET_HANDLER_COUNTERPART_ENCODING_VERSION, + abi.encode(assetId, L2_BRIDGEHUB_ADDRESS) + ); + + bytes32 l2TxHash = Utils.runGovernanceL1L2TwoBridgesTransaction( + _getL1GasPrice(), + config.governance, + governanoceOperationSalt, + Utils.MAX_PRIORITY_TX_GAS, + config.gatewayChainId, + config.bridgehub, + config.sharedBridgeProxy, + config.sharedBridgeProxy, + 0, + secondBridgeData + ); + + saveOutput(l2TxHash); + } + + function registerAssetIdInBridgehub(address gatewayCTMAddress, bytes32 governanoceOperationSalt) public { + initializeConfig(); + + bytes memory secondBridgeData = abi.encodePacked( + bytes1(0x01), + abi.encode(config.chainTypeManagerProxy, gatewayCTMAddress) + ); + + bytes32 l2TxHash = Utils.runGovernanceL1L2TwoBridgesTransaction( + _getL1GasPrice(), + config.governance, + governanoceOperationSalt, + Utils.MAX_PRIORITY_TX_GAS, + config.gatewayChainId, + config.bridgehub, + config.sharedBridgeProxy, + config.ctmDeploymentTracker, + 0, + secondBridgeData + ); + + saveOutput(l2TxHash); + } + + function deployL2ChainAdmin() public { + initializeConfig(); + + // TODO(EVM-925): it is deployed without any restrictions. + address l2ChainAdminAddress = Utils.deployThroughL1({ + bytecode: L2ContractsBytecodesLib.readChainAdminBytecode(), + constructorargs: abi.encode(new address[](0)), + create2salt: bytes32(0), + l2GasLimit: Utils.MAX_PRIORITY_TX_GAS, + factoryDeps: new bytes[](0), + chainId: config.gatewayChainId, + bridgehubAddress: config.bridgehub, + l1SharedBridgeProxy: config.sharedBridgeProxy + }); + + saveOutput(l2ChainAdminAddress); + } + + /// @dev Calling this function requires private key to the admin of the chain + function migrateChainToGateway( + address chainAdmin, + address l2ChainAdmin, + address accessControlRestriction, + uint256 chainId + ) public { + initializeConfig(); + + IBridgehub bridgehubContract = IBridgehub(config.bridgehub); + bytes32 gatewayBaseTokenAssetId = bridgehubContract.baseTokenAssetId(config.gatewayChainId); + bytes32 ethTokenAssetId = DataEncoding.encodeNTVAssetId(block.chainid, ETH_TOKEN_ADDRESS); + + // Fund chain admin with tokens + if (gatewayBaseTokenAssetId != ethTokenAssetId) { + deployerAddress = msg.sender; + uint256 amountForDistribution = 100000000000000000000; + L1AssetRouter l1AR = L1AssetRouter(config.sharedBridgeProxy); + IL1NativeTokenVault nativeTokenVault = IL1NativeTokenVault(address(l1AR.nativeTokenVault())); + address baseTokenAddress = nativeTokenVault.tokenAddress(gatewayBaseTokenAssetId); + uint256 baseTokenOriginChainId = nativeTokenVault.originChainId(gatewayBaseTokenAssetId); + TestnetERC20Token baseToken = TestnetERC20Token(baseTokenAddress); + uint256 deployerBalance = baseToken.balanceOf(deployerAddress); + console.log("Base token origin id: ", baseTokenOriginChainId); + + vm.startBroadcast(); + if (baseTokenOriginChainId == block.chainid) { + baseToken.mint(chainAdmin, amountForDistribution); + } else { + baseToken.transfer(chainAdmin, amountForDistribution); + } + vm.stopBroadcast(); + } + + console.log("Chain Admin address:", chainAdmin); + + bytes32 chainAssetId = IBridgehub(config.bridgehub).ctmAssetIdFromChainId(chainId); + + uint256 currentSettlementLayer = IBridgehub(config.bridgehub).settlementLayer(chainId); + if (currentSettlementLayer == config.gatewayChainId) { + console.log("Chain already using gateway as its settlement layer"); + saveOutput(bytes32(0)); + return; + } + + bytes memory bridgehubData = abi.encode( + BridgehubBurnCTMAssetData({ + chainId: chainId, + ctmData: abi.encode(l2ChainAdmin, config.gatewayDiamondCutData), + chainData: abi.encode(IZKChain(IBridgehub(config.bridgehub).getZKChain(chainId)).getProtocolVersion()) + }) + ); + + // TODO: use constant for the 0x01 + bytes memory secondBridgeData = abi.encodePacked(bytes1(0x01), abi.encode(chainAssetId, bridgehubData)); + + bytes32 l2TxHash = Utils.runAdminL1L2TwoBridgesTransaction( + _getL1GasPrice(), + chainAdmin, + accessControlRestriction, + Utils.MAX_PRIORITY_TX_GAS, + config.gatewayChainId, + config.bridgehub, + config.sharedBridgeProxy, + config.sharedBridgeProxy, + 0, + secondBridgeData + ); + + saveOutput(l2TxHash); + } + + /// @dev Calling this function requires private key to the admin of the chain + function startMigrateChainFromGateway( + address chainAdmin, + address accessControlRestriction, + address l2ChainAdmin, + uint256 chainId + ) public { + initializeConfig(); + IBridgehub bridgehub = IBridgehub(config.bridgehub); + + uint256 currentSettlementLayer = bridgehub.settlementLayer(chainId); + if (currentSettlementLayer != config.gatewayChainId) { + console.log("Chain not using Gateway as settlement layer"); + saveOutput(bytes32(0)); + return; + } + + bytes memory bridgehubBurnData = abi.encode( + BridgehubBurnCTMAssetData({ + chainId: chainId, + ctmData: abi.encode(chainAdmin, config.l1DiamondCutData), + chainData: abi.encode(IChainTypeManager(config.chainTypeManagerProxy).getProtocolVersion(chainId)) + }) + ); + + bytes32 ctmAssetId = bridgehub.ctmAssetIdFromChainId(chainId); + L2AssetRouter l2AssetRouter = L2AssetRouter(L2_ASSET_ROUTER_ADDR); + + bytes memory l2Calldata; + + { + bytes memory data = abi.encodeCall(IL2AssetRouter.withdraw, (ctmAssetId, bridgehubBurnData)); + + Call[] memory calls = new Call[](1); + calls[0] = Call({target: L2_ASSET_ROUTER_ADDR, value: 0, data: data}); + + l2Calldata = abi.encodeCall(ChainAdmin.multicall, (calls, true)); + } + // TODO(EVM-925): this should migrate to use L2 transactions directly + bytes32 l2TxHash = Utils.runAdminL1L2DirectTransaction( + _getL1GasPrice(), + chainAdmin, + accessControlRestriction, + l2Calldata, + Utils.MAX_PRIORITY_TX_GAS, + new bytes[](0), + l2ChainAdmin, + config.gatewayChainId, + config.bridgehub, + config.sharedBridgeProxy + ); + + saveOutput(l2TxHash); + } + + function finishMigrateChainFromGateway( + uint256 migratingChainId, + uint256 gatewayChainId, + uint256 l2BatchNumber, + uint256 l2MessageIndex, + uint16 l2TxNumberInBatch, + bytes memory message, + bytes32[] memory merkleProof + ) public { + initializeConfig(); + + L1Nullifier l1Nullifier = L1Nullifier(config.l1NullifierProxy); + IBridgehub bridgehub = IBridgehub(config.bridgehub); + bytes32 assetId = bridgehub.ctmAssetIdFromChainId(migratingChainId); + vm.broadcast(); + l1Nullifier.finalizeDeposit( + FinalizeL1DepositParams({ + chainId: gatewayChainId, + l2BatchNumber: l2BatchNumber, + l2MessageIndex: l2MessageIndex, + l2Sender: L2_ASSET_ROUTER_ADDR, + l2TxNumberInBatch: l2TxNumberInBatch, + message: message, + merkleProof: merkleProof + }) + ); + } + + /// @dev Calling this function requires private key to the admin of the chain + function setDAValidatorPair( + address chainAdmin, + address accessControlRestriction, + uint256 chainId, + address l1DAValidator, + address l2DAValidator, + address chainDiamondProxyOnGateway, + address chainAdminOnGateway + ) public { + initializeConfig(); + + bytes memory data = abi.encodeCall(IAdmin.setDAValidatorPair, (l1DAValidator, l2DAValidator)); + + bytes32 l2TxHash = Utils.runAdminL1L2DirectTransaction( + _getL1GasPrice(), + chainAdmin, + accessControlRestriction, + _callL2AdminCalldata(data, chainDiamondProxyOnGateway), + Utils.MAX_PRIORITY_TX_GAS, + new bytes[](0), + chainAdminOnGateway, + config.gatewayChainId, + config.bridgehub, + config.sharedBridgeProxy + ); + + saveOutput(l2TxHash); + } + + function enableValidator( + address chainAdmin, + address accessControlRestriction, + uint256 chainId, + address validatorAddress, + address gatewayValidatorTimelock, + address chainAdminOnGateway + ) public { + initializeConfig(); + + bytes memory data = abi.encodeCall(ValidatorTimelock.addValidator, (chainId, validatorAddress)); + + bytes32 l2TxHash = Utils.runAdminL1L2DirectTransaction( + _getL1GasPrice(), + chainAdmin, + accessControlRestriction, + _callL2AdminCalldata(data, gatewayValidatorTimelock), + Utils.MAX_PRIORITY_TX_GAS, + new bytes[](0), + chainAdminOnGateway, + config.gatewayChainId, + config.bridgehub, + config.sharedBridgeProxy + ); + + saveOutput(l2TxHash); + } + + function _callL2AdminCalldata(bytes memory _data, address _target) private returns (bytes memory adminCalldata) { + Call[] memory calls = new Call[](1); + calls[0] = Call({target: _target, value: 0, data: _data}); + adminCalldata = abi.encodeCall(ChainAdmin.multicall, (calls, true)); + } + + /// TODO(EVM-748): make that function support non-ETH based chains + function supplyGatewayWallet(address addr, uint256 amount) public { + initializeConfig(); + + Utils.runL1L2Transaction( + hex"", + Utils.MAX_PRIORITY_TX_GAS, + amount, + new bytes[](0), + addr, + config.gatewayChainId, + config.bridgehub, + config.sharedBridgeProxy + ); + + // We record L2 tx hash only for governance operations + saveOutput(bytes32(0)); + } + + /// The caller of this function should have private key of the admin of the *gateway* + function deployAndSetGatewayTransactionFilterer() public { + initializeConfig(); + + vm.broadcast(); + GatewayTransactionFilterer impl = new GatewayTransactionFilterer( + IBridgehub(config.bridgehub), + config.sharedBridgeProxy + ); + + vm.broadcast(); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(impl), + config.gatewayChainProxyAdmin, + abi.encodeCall(GatewayTransactionFilterer.initialize, (config.gatewayChainAdmin)) + ); + + GatewayTransactionFilterer proxyAsFilterer = GatewayTransactionFilterer(address(proxy)); + + IZKChain chain = IZKChain(IBridgehub(config.bridgehub).getZKChain(config.gatewayChainId)); + + // Firstly, we set the filterer + Utils.adminExecute({ + _admin: config.gatewayChainAdmin, + _accessControlRestriction: config.gatewayAccessControlRestriction, + _target: address(chain), + _data: abi.encodeCall(IAdmin.setTransactionFilterer, (address(proxyAsFilterer))), + _value: 0 + }); + + _grantWhitelist(address(proxy), config.gatewayChainAdmin); + _grantWhitelist(address(proxy), config.sharedBridgeProxy); + _grantWhitelist(address(proxy), config.ctmDeploymentTracker); + + // Then, we grant the whitelist to a few addresses + + saveOutput(address(impl), address(proxy)); + } + + function grantWhitelist(address filtererProxy, address[] memory addresses) public { + initializeConfig(); + + for (uint256 i = 0; i < addresses.length; i++) { + if (GatewayTransactionFilterer(filtererProxy).whitelistedSenders(addresses[i])) { + console.log("Address already whitelisted: ", addresses[i]); + } else { + _grantWhitelist(filtererProxy, addresses[i]); + } + } + } + + function _grantWhitelist(address filtererProxy, address addr) internal { + Utils.adminExecute({ + _admin: config.gatewayChainAdmin, + _accessControlRestriction: config.gatewayAccessControlRestriction, + _target: address(filtererProxy), + _data: abi.encodeCall(GatewayTransactionFilterer.grantWhitelist, (addr)), + _value: 0 + }); + } + + function executeGovernanceTxs() public { + saveOutput(); + } + + function governanceExecuteCalls(bytes memory callsToExecute, address governanceAddr) internal { + IGovernance governance = IGovernance(governanceAddr); + Ownable2Step ownable = Ownable2Step(governanceAddr); + + Call[] memory calls = abi.decode(callsToExecute, (Call[])); + + IGovernance.Operation memory operation = IGovernance.Operation({ + calls: calls, + predecessor: bytes32(0), + salt: bytes32(0) + }); + + vm.startPrank(ownable.owner()); + governance.scheduleTransparent(operation, 0); + // We assume that the total value is 0 + governance.execute{value: 0}(operation); + vm.stopPrank(); + } +} diff --git a/l1-contracts/deploy-scripts/InitializeL2WethToken.s.sol b/l1-contracts/deploy-scripts/InitializeL2WethToken.s.sol index c9b1de8c6..57a63a176 100644 --- a/l1-contracts/deploy-scripts/InitializeL2WethToken.s.sol +++ b/l1-contracts/deploy-scripts/InitializeL2WethToken.s.sol @@ -44,7 +44,7 @@ contract InitializeL2WethTokenScript is Script { // Parse some config from output of l1 deployment string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/script-out/output-deploy-l1.toml"); + string memory path = string.concat(root, vm.envString("L1_OUTPUT")); string memory toml = vm.readFile(path); config.create2FactoryAddr = toml.readAddress("$.create2_factory_addr"); diff --git a/l1-contracts/deploy-scripts/L2ContractsBytecodesLib.sol b/l1-contracts/deploy-scripts/L2ContractsBytecodesLib.sol new file mode 100644 index 000000000..e0644d220 --- /dev/null +++ b/l1-contracts/deploy-scripts/L2ContractsBytecodesLib.sol @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./Utils.sol"; + +/// @title L2ContractsBytecodesLib +/// @notice Library providing functions to read bytecodes of L2 contracts individually. +library L2ContractsBytecodesLib { + /// @notice Reads the bytecode of the Bridgehub contract. + /// @return The bytecode of the Bridgehub contract. + function readBridgehubBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("Bridgehub.sol", "Bridgehub"); + } + + /// @notice Reads the bytecode of the L2NativeTokenVault contract. + /// @return The bytecode of the L2NativeTokenVault contract. + function readL2NativeTokenVaultBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("L2NativeTokenVault.sol", "L2NativeTokenVault"); + } + + /// @notice Reads the bytecode of the L2AssetRouter contract. + /// @return The bytecode of the L2AssetRouter contract. + function readL2AssetRouterBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("L2AssetRouter.sol", "L2AssetRouter"); + } + + /// @notice Reads the bytecode of the MessageRoot contract. + /// @return The bytecode of the MessageRoot contract. + function readMessageRootBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("MessageRoot.sol", "MessageRoot"); + } + + /// @notice Reads the bytecode of the UpgradeableBeacon contract. + /// @return The bytecode of the UpgradeableBeacon contract. + function readUpgradeableBeaconBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("UpgradeableBeacon.sol", "UpgradeableBeacon"); + } + + /// @notice Reads the bytecode of the BeaconProxy contract. + /// @return The bytecode of the BeaconProxy contract. + function readBeaconProxyBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("BeaconProxy.sol", "BeaconProxy"); + } + + /// @notice Reads the bytecode of the BridgedStandardERC20 contract. + /// @return The bytecode of the BridgedStandardERC20 contract. + function readStandardERC20Bytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("BridgedStandardERC20.sol", "BridgedStandardERC20"); + } + + /// @notice Reads the bytecode of the TransparentUpgradeableProxy contract. + /// @return The bytecode of the TransparentUpgradeableProxy contract. + function readTransparentUpgradeableProxyBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("TransparentUpgradeableProxy.sol", "TransparentUpgradeableProxy"); + } + + /// @notice Reads the bytecode of the TransparentUpgradeableProxy contract. + /// @return The bytecode of the TransparentUpgradeableProxy contract. + function readTransparentUpgradeableProxyBytecodeFromSystemContracts() internal view returns (bytes memory) { + return + Utils.readZKFoundryBytecodeSystemContracts( + "TransparentUpgradeableProxy.sol", + "TransparentUpgradeableProxy" + ); + } + + /// @notice Reads the bytecode of the ForceDeployUpgrader contract. + /// @return The bytecode of the ForceDeployUpgrader contract. + function readForceDeployUpgraderBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL2("ForceDeployUpgrader.sol", "ForceDeployUpgrader"); + } + + /// @notice Reads the bytecode of the RollupL2DAValidator contract. + /// @return The bytecode of the RollupL2DAValidator contract. + function readRollupL2DAValidatorBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL2("RollupL2DAValidator.sol", "RollupL2DAValidator"); + } + + /// @notice Reads the bytecode of the ValidiumL2DAValidator contract for Avail. + /// @return The bytecode of the ValidiumL2DAValidator contract. + function readAvailL2DAValidatorBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL2("AvailL2DAValidator.sol", "AvailL2DAValidator"); + } + + /// @notice Reads the bytecode of the ValidiumL2DAValidator contract for NoDA validium. + /// @return The bytecode of the ValidiumL2DAValidator contract. + function readNoDAL2DAValidatorBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL2("ValidiumL2DAValidator.sol", "ValidiumL2DAValidator"); + } + + /// @notice Reads the bytecode of the ChainTypeManager contract. + /// @return The bytecode of the ChainTypeManager contract. + function readChainTypeManagerBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("ChainTypeManager.sol", "ChainTypeManager"); + } + + /// @notice Reads the bytecode of the AdminFacet contract. + /// @return The bytecode of the AdminFacet contract. + function readAdminFacetBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("Admin.sol", "AdminFacet"); + } + + /// @notice Reads the bytecode of the MailboxFacet contract. + /// @return The bytecode of the MailboxFacet contract. + function readMailboxFacetBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("Mailbox.sol", "MailboxFacet"); + } + + /// @notice Reads the bytecode of the ExecutorFacet contract. + /// @return The bytecode of the ExecutorFacet contract. + function readExecutorFacetBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("Executor.sol", "ExecutorFacet"); + } + + /// @notice Reads the bytecode of the GettersFacet contract. + /// @return The bytecode of the GettersFacet contract. + function readGettersFacetBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("Getters.sol", "GettersFacet"); + } + + /// @notice Reads the bytecode of the Verifier contract. + /// @return The bytecode of the Verifier contract. + function readVerifierBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("Verifier.sol", "Verifier"); + } + + /// @notice Reads the bytecode of the L2 Verifier contract. + /// @return The bytecode of the Verifier contract. + function readL2VerifierBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL2("Verifier.sol", "Verifier"); + } + + /// @notice Reads the bytecode of the ConsensusRegistry contract. + /// @return The bytecode of the ConsensusRegistry contract. + function readConsensusRegistryBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL2("ConsensusRegistry.sol", "ConsensusRegistry"); + } + + /// @notice Reads the bytecode of the TestnetVerifier contract. + /// @return The bytecode of the TestnetVerifier contract. + function readL2TestnetVerifierBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL2("TestnetVerifier.sol", "TestnetVerifier"); + } + + /// @notice Reads the bytecode of the ValidatorTimelock contract. + /// @return The bytecode of the ValidatorTimelock contract. + function readValidatorTimelockBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("ValidatorTimelock.sol", "ValidatorTimelock"); + } + + /// @notice Reads the bytecode of the DiamondInit contract. + /// @return The bytecode of the DiamondInit contract. + function readDiamondInitBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("DiamondInit.sol", "DiamondInit"); + } + + /// @notice Reads the bytecode of the DiamondProxy contract. + /// @return The bytecode of the DiamondProxy contract. + function readDiamondProxyBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("DiamondProxy.sol", "DiamondProxy"); + } + + /// @notice Reads the bytecode of the L1GenesisUpgrade contract. + /// @return The bytecode of the L1GenesisUpgrade contract. + function readL1GenesisUpgradeBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("L1GenesisUpgrade.sol", "L1GenesisUpgrade"); + } + + /// @notice Reads the bytecode of the DefaultUpgrade contract. + /// @return The bytecode of the DefaultUpgrade contract. + function readDefaultUpgradeBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("DefaultUpgrade.sol", "DefaultUpgrade"); + } + + /// @notice Reads the bytecode of the Multicall3 contract. + /// @return The bytecode of the Multicall3 contract. + function readMulticall3Bytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("Multicall3.sol", "Multicall3"); + } + + /// @notice Reads the bytecode of the RelayedSLDAValidator contract. + /// @return The bytecode of the RelayedSLDAValidator contract. + function readRelayedSLDAValidatorBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("RelayedSLDAValidator.sol", "RelayedSLDAValidator"); + } + + /// @notice Reads the bytecode of the L2SharedBridgeLegacy contract. + /// @return The bytecode of the L2SharedBridgeLegacy contract. + function readL2LegacySharedBridgeBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("L2SharedBridgeLegacy.sol", "L2SharedBridgeLegacy"); + } + + /// @notice Reads the bytecode of the L2SharedBridgeLegacy contract. + /// @return The bytecode of the L2SharedBridgeLegacy contract. + function readL2LegacySharedBridgeDevBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("L2SharedBridgeLegacyDev.sol", "L2SharedBridgeLegacyDev"); + } + + /// @notice Reads the bytecode of the L2GatewayUpgrade contract. + /// @return The bytecode of the L2GatewayUpgrade contract. + function readGatewayUpgradeBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeSystemContracts("L2GatewayUpgrade.sol", "L2GatewayUpgrade"); + } + + /// @notice Reads the bytecode of the L2AdminFactory contract. + /// @return The bytecode of the L2AdminFactory contract. + function readL2AdminFactoryBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("L2AdminFactory.sol", "L2AdminFactory"); + } + + function readProxyAdminBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("ProxyAdmin.sol", "ProxyAdmin"); + } + + /// @notice Reads the bytecode of the PermanentRestriction contract. + /// @return The bytecode of the PermanentRestriction contract. + function readPermanentRestrictionBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("PermanentRestriction.sol", "PermanentRestriction"); + } + + /// @notice Reads the bytecode of the L2ProxyAdminDeployer contract. + /// @return The bytecode of the L2ProxyAdminDeployer contract. + function readProxyAdminDeployerBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("L2ProxyAdminDeployer.sol", "L2ProxyAdminDeployer"); + } + + /// @notice Reads the bytecode of the L2WrappedBaseToken contract. + /// @return The bytecode of the L2WrappedBaseToken contract. + function readL2WrappedBaseToken() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("L2WrappedBaseToken.sol", "L2WrappedBaseToken"); + } + + /// @notice Reads the bytecode of the TimestampAsserter contract. + /// @return The bytecode of the TimestampAsserter contract. + function readTimestampAsserterBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL2("TimestampAsserter.sol", "TimestampAsserter"); + } + + /// @notice Reads the bytecode of the ChainAdmin contract. + /// @return The bytecode of the ChainAdmin contract. + function readChainAdminBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL1("ChainAdmin.sol", "ChainAdmin"); + } +} diff --git a/l1-contracts/deploy-scripts/L2LegacySharedBridgeTestHelper.sol b/l1-contracts/deploy-scripts/L2LegacySharedBridgeTestHelper.sol new file mode 100644 index 000000000..14b1285f4 --- /dev/null +++ b/l1-contracts/deploy-scripts/L2LegacySharedBridgeTestHelper.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {L2ContractsBytecodesLib} from "./L2ContractsBytecodesLib.sol"; +import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; +import {Utils} from "./Utils.sol"; +import {L2SharedBridgeLegacyDev} from "contracts/dev-contracts/L2SharedBridgeLegacyDev.sol"; +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; + +library L2LegacySharedBridgeTestHelper { + function calculateL2LegacySharedBridgeProxyAddr( + address l1Erc20BridgeProxy, + address l1NullifierProxy, + address ecosystemL1Governance + ) internal view returns (address) { + // During local testing, we will deploy `L2SharedBridgeLegacyDev` to each chain + // that supports the legacy bridge. + + bytes32 implHash = L2ContractHelper.hashL2Bytecode( + L2ContractsBytecodesLib.readL2LegacySharedBridgeDevBytecode() + ); + address implAddress = Utils.getL2AddressViaCreate2Factory(bytes32(0), implHash, hex""); + + bytes32 proxyHash = L2ContractHelper.hashL2Bytecode( + L2ContractsBytecodesLib.readTransparentUpgradeableProxyBytecode() + ); + + return + Utils.getL2AddressViaCreate2Factory( + bytes32(0), + proxyHash, + getLegacySharedBridgeProxyConstructorParams( + implAddress, + l1Erc20BridgeProxy, + l1NullifierProxy, + ecosystemL1Governance + ) + ); + } + + function getLegacySharedBridgeProxyConstructorParams( + address _implAddress, + address _l1Erc20BridgeProxy, + address _l1NullifierProxy, + address _ecosystemL1Governance + ) internal view returns (bytes memory) { + bytes32 beaconProxyBytecodeHash = L2ContractHelper.hashL2Bytecode( + L2ContractsBytecodesLib.readBeaconProxyBytecode() + ); + + bytes memory initializeData = abi.encodeCall( + L2SharedBridgeLegacyDev.initializeDevBridge, + ( + _l1Erc20BridgeProxy, + // While the variable is named `sharedBridge`, in reality it will have the same + // address as the nullifier + _l1NullifierProxy, + beaconProxyBytecodeHash, + AddressAliasHelper.applyL1ToL2Alias(_ecosystemL1Governance) + ) + ); + + return abi.encode(_implAddress, AddressAliasHelper.applyL1ToL2Alias(_ecosystemL1Governance), initializeData); + } + + function calculateTestL2TokenBeaconAddress( + address l1Erc20BridgeProxy, + address l1NullifierProxy, + address ecosystemL1Governance + ) internal view returns (address tokenBeaconAddress, bytes32 tokenBeaconBytecodeHash) { + address l2SharedBridgeAddress = calculateL2LegacySharedBridgeProxyAddr( + l1Erc20BridgeProxy, + l1NullifierProxy, + ecosystemL1Governance + ); + + bytes32 bridgedL2ERC20Hash = L2ContractHelper.hashL2Bytecode( + L2ContractsBytecodesLib.readStandardERC20Bytecode() + ); + address bridgeL2ERC20ImplAddress = L2ContractHelper.computeCreate2Address( + l2SharedBridgeAddress, + bytes32(0), + bridgedL2ERC20Hash, + keccak256(hex"") + ); + + tokenBeaconBytecodeHash = L2ContractHelper.hashL2Bytecode( + L2ContractsBytecodesLib.readUpgradeableBeaconBytecode() + ); + tokenBeaconAddress = L2ContractHelper.computeCreate2Address( + l2SharedBridgeAddress, + bytes32(0), + tokenBeaconBytecodeHash, + keccak256(abi.encode(bridgeL2ERC20ImplAddress)) + ); + } +} diff --git a/l1-contracts/deploy-scripts/PrepareZKChainRegistrationCalldata.s.sol b/l1-contracts/deploy-scripts/PrepareZKChainRegistrationCalldata.s.sol index 0a7e20a53..618ee3c64 100644 --- a/l1-contracts/deploy-scripts/PrepareZKChainRegistrationCalldata.s.sol +++ b/l1-contracts/deploy-scripts/PrepareZKChainRegistrationCalldata.s.sol @@ -10,9 +10,10 @@ import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; -import {L1SharedBridge} from "contracts/bridge/L1SharedBridge.sol"; -import {IStateTransitionManager} from "contracts/state-transition/IStateTransitionManager.sol"; +import {L1AssetRouter} from "contracts/bridge/L1AssetRouter.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; import {IGovernance} from "contracts/governance/IGovernance.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; import {Utils} from "./Utils.sol"; /** @@ -58,7 +59,7 @@ contract PrepareZKChainRegistrationCalldataScript is Script { struct Config { // Admin of the yet-to-be-registered chain (L1-based address) address chainAdmin; - // STM proxy address + // CTM proxy address address stateTransitionProxy; // Chain ID of the new chain uint256 chainId; @@ -69,7 +70,7 @@ contract PrepareZKChainRegistrationCalldataScript is Script { // Address of the new chain's base token address baseToken; // Diamond cut data is a "configuration" for the Diamond proxy that will be created for a new chain. - // It can only be the one that's allowed by the STM. It can be generated by the other scripts or taken from the + // It can only be the one that's allowed by the CTM. It can be generated by the other scripts or taken from the // `etc/env/ecosystems/ENV.yaml` file in `zksync-era` repository bytes diamondCutData; // Address of the L1 ERC20 bridge proxy (required for the L2 bridge deployment) @@ -118,7 +119,7 @@ contract PrepareZKChainRegistrationCalldataScript is Script { calls = new IGovernance.Call[](1); } - IGovernance.Call memory registerChainCall = prepareRegisterHyperchainCall(); + IGovernance.Call memory registerChainCall = prepareRegisterZKChainCall(); calls[cnt] = registerChainCall; ++cnt; @@ -141,7 +142,7 @@ contract PrepareZKChainRegistrationCalldataScript is Script { config.stateTransitionProxy = toml.readAddress("$.deployed_addresses.state_transition_proxy_addr"); config.erc20BridgeProxy = toml.readAddress("$.deployed_addresses.erc20_bridge_proxy_addr"); - ecosystem.bridgehub = IStateTransitionManager(config.stateTransitionProxy).BRIDGE_HUB(); + ecosystem.bridgehub = IChainTypeManager(config.stateTransitionProxy).BRIDGE_HUB(); ecosystem.l1SharedBridgeProxy = address(Bridgehub(ecosystem.bridgehub).sharedBridge()); ecosystem.governance = Bridgehub(ecosystem.bridgehub).owner(); @@ -181,7 +182,10 @@ contract PrepareZKChainRegistrationCalldataScript is Script { function prepareRegisterBaseTokenCall() internal view returns (IGovernance.Call memory) { Bridgehub bridgehub = Bridgehub(ecosystem.bridgehub); - bytes memory data = abi.encodeCall(bridgehub.addToken, (config.baseToken)); + bytes memory data = abi.encodeCall( + bridgehub.addTokenAssetId, + (DataEncoding.encodeNTVAssetId(block.chainid, config.baseToken)) + ); return IGovernance.Call({target: ecosystem.bridgehub, value: 0, data: data}); } @@ -267,7 +271,7 @@ contract PrepareZKChainRegistrationCalldataScript is Script { return proxyContractAddress; } - function prepareRegisterHyperchainCall() internal view returns (IGovernance.Call memory) { + function prepareRegisterZKChainCall() internal view returns (IGovernance.Call memory) { Bridgehub bridgehub = Bridgehub(ecosystem.bridgehub); bytes memory data = abi.encodeCall( @@ -288,7 +292,7 @@ contract PrepareZKChainRegistrationCalldataScript is Script { function prepareInitializeChainGovernanceCall( address l2SharedBridgeProxy ) internal view returns (IGovernance.Call memory) { - L1SharedBridge bridge = L1SharedBridge(ecosystem.l1SharedBridgeProxy); + L1AssetRouter bridge = L1AssetRouter(ecosystem.l1SharedBridgeProxy); bytes memory data = abi.encodeCall(bridge.initializeChainGovernance, (config.chainId, l2SharedBridgeProxy)); diff --git a/l1-contracts/deploy-scripts/RegisterHyperchain.s.sol b/l1-contracts/deploy-scripts/RegisterHyperchain.s.sol deleted file mode 100644 index bbc01226d..000000000 --- a/l1-contracts/deploy-scripts/RegisterHyperchain.s.sol +++ /dev/null @@ -1,235 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -// solhint-disable no-console, gas-custom-errors, reason-string - -import {Script, console2 as console} from "forge-std/Script.sol"; -import {Vm} from "forge-std/Vm.sol"; -import {stdToml} from "forge-std/StdToml.sol"; - -import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; -import {IZkSyncHyperchain} from "contracts/state-transition/chain-interfaces/IZkSyncHyperchain.sol"; -import {ValidatorTimelock} from "contracts/state-transition/ValidatorTimelock.sol"; -import {Governance} from "contracts/governance/Governance.sol"; -import {ChainAdmin} from "contracts/governance/ChainAdmin.sol"; -import {Utils} from "./Utils.sol"; -import {PubdataPricingMode} from "contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol"; - -contract RegisterHyperchainScript is Script { - using stdToml for string; - - address internal constant ADDRESS_ONE = 0x0000000000000000000000000000000000000001; - bytes32 internal constant STATE_TRANSITION_NEW_CHAIN_HASH = keccak256("NewHyperchain(uint256,address)"); - - // solhint-disable-next-line gas-struct-packing - struct Config { - address deployerAddress; - address ownerAddress; - uint256 chainChainId; - bool validiumMode; - uint256 bridgehubCreateNewChainSalt; - address validatorSenderOperatorCommitEth; - address validatorSenderOperatorBlobsEth; - address baseToken; - uint128 baseTokenGasPriceMultiplierNominator; - uint128 baseTokenGasPriceMultiplierDenominator; - address bridgehub; - address stateTransitionProxy; - address validatorTimelock; - bytes diamondCutData; - address governanceSecurityCouncilAddress; - uint256 governanceMinDelay; - address newDiamondProxy; - address governance; - address chainAdmin; - } - - Config internal config; - - function run() public { - console.log("Deploying Hyperchain"); - - initializeConfig(); - - deployGovernance(); - deployChainAdmin(); - checkTokenAddress(); - registerTokenOnBridgehub(); - registerHyperchain(); - addValidators(); - configureZkSyncStateTransition(); - setPendingAdmin(); - - saveOutput(); - } - - function initializeConfig() internal { - // Grab config from output of l1 deployment - string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/script-config/register-hyperchain.toml"); - string memory toml = vm.readFile(path); - - config.deployerAddress = msg.sender; - - // Config file must be parsed key by key, otherwise values returned - // are parsed alfabetically and not by key. - // https://book.getfoundry.sh/cheatcodes/parse-toml - config.ownerAddress = toml.readAddress("$.owner_address"); - - config.bridgehub = toml.readAddress("$.deployed_addresses.bridgehub.bridgehub_proxy_addr"); - config.stateTransitionProxy = toml.readAddress( - "$.deployed_addresses.state_transition.state_transition_proxy_addr" - ); - config.validatorTimelock = toml.readAddress("$.deployed_addresses.validator_timelock_addr"); - - config.diamondCutData = toml.readBytes("$.contracts_config.diamond_cut_data"); - - config.chainChainId = toml.readUint("$.chain.chain_chain_id"); - config.bridgehubCreateNewChainSalt = toml.readUint("$.chain.bridgehub_create_new_chain_salt"); - config.baseToken = toml.readAddress("$.chain.base_token_addr"); - config.validiumMode = toml.readBool("$.chain.validium_mode"); - config.validatorSenderOperatorCommitEth = toml.readAddress("$.chain.validator_sender_operator_commit_eth"); - config.validatorSenderOperatorBlobsEth = toml.readAddress("$.chain.validator_sender_operator_blobs_eth"); - config.baseTokenGasPriceMultiplierNominator = uint128( - toml.readUint("$.chain.base_token_gas_price_multiplier_nominator") - ); - config.baseTokenGasPriceMultiplierDenominator = uint128( - toml.readUint("$.chain.base_token_gas_price_multiplier_denominator") - ); - config.governanceMinDelay = uint256(toml.readUint("$.chain.governance_min_delay")); - config.governanceSecurityCouncilAddress = toml.readAddress("$.chain.governance_security_council_address"); - } - - function checkTokenAddress() internal view { - if (config.baseToken == address(0)) { - revert("Token address is not set"); - } - - // Check if it's ethereum address - if (config.baseToken == ADDRESS_ONE) { - return; - } - - if (config.baseToken.code.length == 0) { - revert("Token address is not a contract address"); - } - - console.log("Using base token address:", config.baseToken); - } - - function registerTokenOnBridgehub() internal { - Bridgehub bridgehub = Bridgehub(config.bridgehub); - - if (bridgehub.tokenIsRegistered(config.baseToken)) { - console.log("Token already registered on Bridgehub"); - } else { - bytes memory data = abi.encodeCall(bridgehub.addToken, (config.baseToken)); - Utils.chainAdminMulticall({ - _chainAdmin: bridgehub.admin(), - _target: config.bridgehub, - _data: data, - _value: 0 - }); - console.log("Token registered on Bridgehub"); - } - } - - function deployGovernance() internal { - vm.broadcast(); - Governance governance = new Governance( - config.ownerAddress, - config.governanceSecurityCouncilAddress, - config.governanceMinDelay - ); - console.log("Governance deployed at:", address(governance)); - config.governance = address(governance); - } - - function deployChainAdmin() internal { - vm.broadcast(); - ChainAdmin chainAdmin = new ChainAdmin(config.ownerAddress, address(0)); - console.log("ChainAdmin deployed at:", address(chainAdmin)); - config.chainAdmin = address(chainAdmin); - } - - function registerHyperchain() internal { - Bridgehub bridgehub = Bridgehub(config.bridgehub); - - vm.recordLogs(); - bytes memory data = abi.encodeCall( - bridgehub.createNewChain, - ( - config.chainChainId, - config.stateTransitionProxy, - config.baseToken, - config.bridgehubCreateNewChainSalt, - msg.sender, - config.diamondCutData - ) - ); - - Utils.chainAdminMulticall({_chainAdmin: bridgehub.admin(), _target: config.bridgehub, _data: data, _value: 0}); - console.log("Hyperchain registered"); - - // Get new diamond proxy address from emitted events - Vm.Log[] memory logs = vm.getRecordedLogs(); - address diamondProxyAddress; - uint256 logsLength = logs.length; - for (uint256 i = 0; i < logsLength; ++i) { - if (logs[i].topics[0] == STATE_TRANSITION_NEW_CHAIN_HASH) { - diamondProxyAddress = address(uint160(uint256(logs[i].topics[2]))); - break; - } - } - if (diamondProxyAddress == address(0)) { - revert("Diamond proxy address not found"); - } - config.newDiamondProxy = diamondProxyAddress; - console.log("Hyperchain diamond proxy deployed at:", diamondProxyAddress); - } - - function addValidators() internal { - ValidatorTimelock validatorTimelock = ValidatorTimelock(config.validatorTimelock); - - vm.startBroadcast(); - validatorTimelock.addValidator(config.chainChainId, config.validatorSenderOperatorCommitEth); - validatorTimelock.addValidator(config.chainChainId, config.validatorSenderOperatorBlobsEth); - vm.stopBroadcast(); - - console.log("Validators added"); - } - - function configureZkSyncStateTransition() internal { - IZkSyncHyperchain hyperchain = IZkSyncHyperchain(config.newDiamondProxy); - - vm.startBroadcast(); - hyperchain.setTokenMultiplier( - config.baseTokenGasPriceMultiplierNominator, - config.baseTokenGasPriceMultiplierDenominator - ); - - if (config.validiumMode) { - hyperchain.setPubdataPricingMode(PubdataPricingMode.Validium); - } - - vm.stopBroadcast(); - console.log("ZkSync State Transition configured"); - } - - function setPendingAdmin() internal { - IZkSyncHyperchain hyperchain = IZkSyncHyperchain(config.newDiamondProxy); - - vm.broadcast(); - hyperchain.setPendingAdmin(config.chainAdmin); - console.log("Owner for ", config.newDiamondProxy, "set to", config.chainAdmin); - } - - function saveOutput() internal { - vm.serializeAddress("root", "diamond_proxy_addr", config.newDiamondProxy); - vm.serializeAddress("root", "chain_admin_addr", config.chainAdmin); - string memory toml = vm.serializeAddress("root", "governance_addr", config.governance); - string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/script-out/output-register-hyperchain.toml"); - vm.writeToml(toml, path); - } -} diff --git a/l1-contracts/deploy-scripts/RegisterZKChain.s.sol b/l1-contracts/deploy-scripts/RegisterZKChain.s.sol new file mode 100644 index 000000000..65675b806 --- /dev/null +++ b/l1-contracts/deploy-scripts/RegisterZKChain.s.sol @@ -0,0 +1,545 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +// solhint-disable no-console, gas-custom-errors, reason-string + +import {Script, console2 as console} from "forge-std/Script.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {stdToml} from "forge-std/StdToml.sol"; + +import {ProxyAdmin} from "@openzeppelin/contracts-v4/proxy/transparent/ProxyAdmin.sol"; +import {Ownable} from "@openzeppelin/contracts-v4/access/Ownable.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {ValidatorTimelock} from "contracts/state-transition/ValidatorTimelock.sol"; +import {Governance} from "contracts/governance/Governance.sol"; +import {ChainAdmin} from "contracts/governance/ChainAdmin.sol"; +import {AccessControlRestriction} from "contracts/governance/AccessControlRestriction.sol"; +import {Utils} from "./Utils.sol"; +import {L2ContractsBytecodesLib} from "./L2ContractsBytecodesLib.sol"; +import {PubdataPricingMode} from "contracts/state-transition/chain-deps/ZKChainStorage.sol"; +import {IL1NativeTokenVault} from "contracts/bridge/ntv/IL1NativeTokenVault.sol"; +import {INativeTokenVault} from "contracts/bridge/ntv/INativeTokenVault.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; +import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; +import {L1NullifierDev} from "contracts/dev-contracts/L1NullifierDev.sol"; +import {L2SharedBridgeLegacy} from "contracts/bridge/L2SharedBridgeLegacy.sol"; +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {L2LegacySharedBridgeTestHelper} from "./L2LegacySharedBridgeTestHelper.sol"; +import {IGovernance} from "contracts/governance/IGovernance.sol"; +import {Ownable2Step} from "@openzeppelin/contracts-v4/access/Ownable2Step.sol"; +import {Call} from "contracts/governance/Common.sol"; + +import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; +import {CreateAndTransfer} from "./CreateAndTransfer.sol"; +import {ChainAdminSingleOwner} from "contracts/governance/ChainAdminSingleOwner.sol"; + +// solhint-disable-next-line gas-struct-packing +struct Config { + address deployerAddress; + address ownerAddress; + uint256 chainChainId; + bool validiumMode; + uint256 bridgehubCreateNewChainSalt; + address validatorSenderOperatorCommitEth; + address validatorSenderOperatorBlobsEth; + address baseToken; + bytes32 baseTokenAssetId; + uint128 baseTokenGasPriceMultiplierNominator; + uint128 baseTokenGasPriceMultiplierDenominator; + address bridgehub; + // TODO(EVM-744): maybe rename to asset router + address sharedBridgeProxy; + address nativeTokenVault; + address chainTypeManagerProxy; + address validatorTimelock; + bytes diamondCutData; + bytes forceDeployments; + address governanceSecurityCouncilAddress; + uint256 governanceMinDelay; + address l1Nullifier; + address l1Erc20Bridge; + bool initializeLegacyBridge; + address governance; + address create2FactoryAddress; + bytes32 create2Salt; +} + +contract RegisterZKChainScript is Script { + using stdToml for string; + + address internal constant ADDRESS_ONE = 0x0000000000000000000000000000000000000001; + bytes32 internal constant STATE_TRANSITION_NEW_CHAIN_HASH = keccak256("NewZKChain(uint256,address)"); + + struct Output { + address governance; + address diamondProxy; + address chainAdmin; + address l2LegacySharedBridge; + address accessControlRestrictionAddress; + address chainProxyAdmin; + } + + struct LegacySharedBridgeParams { + bytes implementationConstructorParams; + address implementationAddress; + bytes proxyConstructorParams; + address proxyAddress; + } + + LegacySharedBridgeParams internal legacySharedBridgeParams; + + Config internal config; + Output internal output; + + function run() public { + console.log("Deploying ZKChain"); + + initializeConfig(); + // TODO: some chains may not want to have a legacy shared bridge + runInner("/script-out/output-register-zk-chain.toml"); + } + + function runForTest() public { + console.log("Deploying ZKChain"); + + initializeConfigTest(); + runInner(vm.envString("ZK_CHAIN_OUT")); + } + + function runInner(string memory outputPath) internal { + string memory root = vm.projectRoot(); + + outputPath = string.concat(root, outputPath); + + if (config.initializeLegacyBridge) { + // This must be run before the chain is deployed + setUpLegacySharedBridgeParams(); + } + + deployGovernance(); + deployChainAdmin(); + deployChainProxyAddress(); + checkTokenAddress(); + registerAssetIdOnBridgehub(); + registerTokenOnNTV(); + registerZKChain(); + addValidators(); + configureZkSyncStateTransition(); + setPendingAdmin(); + + if (config.initializeLegacyBridge) { + deployLegacySharedBridge(); + } + + saveOutput(outputPath); + } + + function initializeConfig() internal { + // Grab config from output of l1 deployment + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/script-config/register-zk-chain.toml"); + string memory toml = vm.readFile(path); + + config.deployerAddress = msg.sender; + + // Config file must be parsed key by key, otherwise values returned + // are parsed alfabetically and not by key. + // https://book.getfoundry.sh/cheatcodes/parse-toml + + config.bridgehub = toml.readAddress("$.deployed_addresses.bridgehub.bridgehub_proxy_addr"); + config.chainTypeManagerProxy = toml.readAddress( + "$.deployed_addresses.state_transition.chain_type_manager_proxy_addr" + ); + config.validatorTimelock = toml.readAddress("$.deployed_addresses.validator_timelock_addr"); + // config.bridgehubGovernance = toml.readAddress("$.deployed_addresses.governance_addr"); + config.nativeTokenVault = toml.readAddress("$.deployed_addresses.native_token_vault_addr"); + config.sharedBridgeProxy = toml.readAddress("$.deployed_addresses.bridges.shared_bridge_proxy_addr"); + config.l1Nullifier = toml.readAddress("$.deployed_addresses.bridges.l1_nullifier_proxy_addr"); + config.l1Erc20Bridge = toml.readAddress("$.deployed_addresses.bridges.erc20_bridge_proxy_addr"); + + config.diamondCutData = toml.readBytes("$.contracts_config.diamond_cut_data"); + config.forceDeployments = toml.readBytes("$.contracts_config.force_deployments_data"); + + config.ownerAddress = toml.readAddress("$.owner_address"); + + config.chainChainId = toml.readUint("$.chain.chain_chain_id"); + config.baseTokenGasPriceMultiplierNominator = uint128( + toml.readUint("$.chain.base_token_gas_price_multiplier_nominator") + ); + config.baseTokenGasPriceMultiplierDenominator = uint128( + toml.readUint("$.chain.base_token_gas_price_multiplier_denominator") + ); + config.baseToken = toml.readAddress("$.chain.base_token_addr"); + config.governanceSecurityCouncilAddress = toml.readAddress("$.chain.governance_security_council_address"); + config.governanceMinDelay = uint256(toml.readUint("$.chain.governance_min_delay")); + config.bridgehubCreateNewChainSalt = toml.readUint("$.chain.bridgehub_create_new_chain_salt"); + config.validiumMode = toml.readBool("$.chain.validium_mode"); + config.validatorSenderOperatorCommitEth = toml.readAddress("$.chain.validator_sender_operator_commit_eth"); + config.validatorSenderOperatorBlobsEth = toml.readAddress("$.chain.validator_sender_operator_blobs_eth"); + config.initializeLegacyBridge = toml.readBool("$.initialize_legacy_bridge"); + + config.governance = toml.readAddress("$.governance"); + config.create2FactoryAddress = toml.readAddress("$.create2_factory_address"); + config.create2Salt = toml.readBytes32("$.create2_salt"); + } + + function getConfig() public view returns (Config memory) { + return config; + } + + function initializeConfigTest() internal { + // Grab config from output of l1 deployment + string memory root = vm.projectRoot(); + string memory path = string.concat(root, vm.envString("L1_OUTPUT")); //"/script-config/register-zkChain.toml"); + string memory toml = vm.readFile(path); + + config.deployerAddress = msg.sender; + + // Config file must be parsed key by key, otherwise values returned + // are parsed alfabetically and not by key. + // https://book.getfoundry.sh/cheatcodes/parse-toml + + config.bridgehub = toml.readAddress("$.deployed_addresses.bridgehub.bridgehub_proxy_addr"); + // TODO(EVM-744): name of the key is a bit inconsistent + config.chainTypeManagerProxy = toml.readAddress( + "$.deployed_addresses.state_transition.state_transition_proxy_addr" + ); + config.validatorTimelock = toml.readAddress("$.deployed_addresses.validator_timelock_addr"); + // config.bridgehubGovernance = toml.readAddress("$.deployed_addresses.governance_addr"); + config.nativeTokenVault = toml.readAddress("$.deployed_addresses.native_token_vault_addr"); + config.sharedBridgeProxy = toml.readAddress("$.deployed_addresses.bridges.shared_bridge_proxy_addr"); + config.l1Nullifier = toml.readAddress("$.deployed_addresses.bridges.l1_nullifier_proxy_addr"); + + config.diamondCutData = toml.readBytes("$.contracts_config.diamond_cut_data"); + config.forceDeployments = toml.readBytes("$.contracts_config.force_deployments_data"); + + config.governance = toml.readAddress("$.deployed_addresses.governance_addr"); + config.create2FactoryAddress = toml.readAddress("$.create2_factory_addr"); + config.create2Salt = toml.readBytes32("$.create2_factory_salt"); + + path = string.concat(root, vm.envString("ZK_CHAIN_CONFIG")); + toml = vm.readFile(path); + + config.ownerAddress = toml.readAddress("$.owner_address"); + + config.chainChainId = toml.readUint("$.chain.chain_chain_id"); + config.bridgehubCreateNewChainSalt = toml.readUint("$.chain.bridgehub_create_new_chain_salt"); + config.baseToken = toml.readAddress("$.chain.base_token_addr"); + config.validiumMode = toml.readBool("$.chain.validium_mode"); + config.validatorSenderOperatorCommitEth = toml.readAddress("$.chain.validator_sender_operator_commit_eth"); + config.validatorSenderOperatorBlobsEth = toml.readAddress("$.chain.validator_sender_operator_blobs_eth"); + config.baseTokenGasPriceMultiplierNominator = uint128( + toml.readUint("$.chain.base_token_gas_price_multiplier_nominator") + ); + config.baseTokenGasPriceMultiplierDenominator = uint128( + toml.readUint("$.chain.base_token_gas_price_multiplier_denominator") + ); + config.governanceMinDelay = uint256(toml.readUint("$.chain.governance_min_delay")); + config.governanceSecurityCouncilAddress = toml.readAddress("$.chain.governance_security_council_address"); + } + + function getOwnerAddress() public view returns (address) { + return config.ownerAddress; + } + + function checkTokenAddress() internal view { + if (config.baseToken == address(0)) { + revert("Token address is not set"); + } + + // Check if it's ethereum address + if (config.baseToken == ADDRESS_ONE) { + return; + } + + if (config.baseToken.code.length == 0) { + revert("Token address is not a contract address"); + } + + console.log("Using base token address:", config.baseToken); + } + + function setUpLegacySharedBridgeParams() internal { + // Ecosystem governance is the owner of the L1Nullifier + address ecosystemGovernance = L1NullifierDev(config.l1Nullifier).owner(); + address bridgeAddress = L2LegacySharedBridgeTestHelper.calculateL2LegacySharedBridgeProxyAddr( + config.l1Erc20Bridge, + config.l1Nullifier, + ecosystemGovernance + ); + vm.broadcast(); + L1NullifierDev(config.l1Nullifier).setL2LegacySharedBridge(config.chainChainId, bridgeAddress); + } + + function registerAssetIdOnBridgehub() internal { + IBridgehub bridgehub = IBridgehub(config.bridgehub); + Ownable ownable = Ownable(config.bridgehub); + INativeTokenVault ntv = INativeTokenVault(config.nativeTokenVault); + bytes32 baseTokenAssetId = ntv.assetId(config.baseToken); + uint256 baseTokenOriginChain = ntv.originChainId(baseTokenAssetId); + + if (baseTokenAssetId == bytes32(0)) { + baseTokenAssetId = DataEncoding.encodeNTVAssetId(block.chainid, config.baseToken); + } + + if (bridgehub.assetIdIsRegistered(baseTokenAssetId)) { + console.log("Base token asset id already registered on Bridgehub"); + } else { + bytes memory data = abi.encodeCall(bridgehub.addTokenAssetId, (baseTokenAssetId)); + Utils.executeUpgrade({ + _governor: ownable.owner(), + _salt: bytes32(config.bridgehubCreateNewChainSalt), + _target: config.bridgehub, + _data: data, + _value: 0, + _delay: 0 + }); + console.log("Base token asset id registered on Bridgehub"); + } + } + + function registerTokenOnNTV() internal { + INativeTokenVault ntv = INativeTokenVault(config.nativeTokenVault); + bytes32 baseTokenAssetId = ntv.assetId(config.baseToken); + uint256 baseTokenOriginChain = ntv.originChainId(baseTokenAssetId); + + // If it hasn't been registered already with ntv + if (baseTokenAssetId == bytes32(0)) { + baseTokenAssetId = DataEncoding.encodeNTVAssetId(block.chainid, config.baseToken); + } + config.baseTokenAssetId = baseTokenAssetId; + if (ntv.tokenAddress(baseTokenAssetId) != address(0) || config.baseToken == ETH_TOKEN_ADDRESS) { + console.log("Token already registered on NTV"); + } else { + vm.broadcast(); + ntv.registerToken(config.baseToken); + console.log("Token registered on NTV"); + } + } + + function deployGovernance() internal { + bytes memory input = abi.encode( + config.ownerAddress, + config.governanceSecurityCouncilAddress, + config.governanceMinDelay + ); + address governance = Utils.deployViaCreate2( + abi.encodePacked(type(Governance).creationCode, input), + config.create2Salt, + config.create2FactoryAddress + ); + console.log("Governance deployed at:", governance); + output.governance = governance; + } + + function deployChainAdmin() internal { + // TODO(EVM-924): provide an option to deploy a non-single owner ChainAdmin. + (address chainAdmin, address accessControlRestriction) = deployChainAdminSingleOwner(); + + output.accessControlRestrictionAddress = accessControlRestriction; + output.chainAdmin = chainAdmin; + } + + function deployChainAdminSingleOwner() internal returns (address chainAdmin, address accessControlRestriction) { + chainAdmin = Utils.deployViaCreate2( + abi.encodePacked(type(ChainAdminSingleOwner).creationCode, abi.encode(config.ownerAddress, address(0))), + config.create2Salt, + config.create2FactoryAddress + ); + // The single owner chainAdmin does not have a separate control restriction contract. + // We set to it to zero explicitly so that it is clear to the reader. + accessControlRestriction = address(0); + + console.log("ChainAdminSingleOwner deployed at:", accessControlRestriction); + } + + // TODO(EVM-924): this function is unused + function deployChainAdminWithRestrictions() + internal + returns (address chainAdmin, address accessControlRestriction) + { + bytes memory input = abi.encode(0, config.ownerAddress); + accessControlRestriction = Utils.deployViaCreate2( + abi.encodePacked(type(AccessControlRestriction).creationCode, input), + config.create2Salt, + config.create2FactoryAddress + ); + + address[] memory restrictions = new address[](1); + restrictions[0] = accessControlRestriction; + + input = abi.encode(restrictions); + chainAdmin = Utils.deployViaCreate2( + abi.encodePacked(type(ChainAdmin).creationCode, input), + config.create2Salt, + config.create2FactoryAddress + ); + } + + function registerZKChain() internal { + IBridgehub bridgehub = IBridgehub(config.bridgehub); + Ownable ownable = Ownable(config.bridgehub); + + vm.recordLogs(); + bytes memory data = abi.encodeCall( + bridgehub.createNewChain, + ( + config.chainChainId, + config.chainTypeManagerProxy, + config.baseTokenAssetId, + config.bridgehubCreateNewChainSalt, + msg.sender, + abi.encode(config.diamondCutData, config.forceDeployments), + getFactoryDeps() + ) + ); + Utils.executeUpgrade({ + _governor: ownable.owner(), + _salt: bytes32(config.bridgehubCreateNewChainSalt), + _target: config.bridgehub, + _data: data, + _value: 0, + _delay: 0 + }); + console.log("ZK chain registered"); + + // Get new diamond proxy address from emitted events + Vm.Log[] memory logs = vm.getRecordedLogs(); + address diamondProxyAddress; + uint256 logsLength = logs.length; + for (uint256 i = 0; i < logsLength; ++i) { + if (logs[i].topics[0] == STATE_TRANSITION_NEW_CHAIN_HASH) { + diamondProxyAddress = address(uint160(uint256(logs[i].topics[2]))); + break; + } + } + if (diamondProxyAddress == address(0)) { + revert("Diamond proxy address not found"); + } + output.diamondProxy = diamondProxyAddress; + console.log("ZKChain diamond proxy deployed at:", diamondProxyAddress); + } + + function addValidators() internal { + ValidatorTimelock validatorTimelock = ValidatorTimelock(config.validatorTimelock); + + vm.startBroadcast(msg.sender); + validatorTimelock.addValidator(config.chainChainId, config.validatorSenderOperatorCommitEth); + validatorTimelock.addValidator(config.chainChainId, config.validatorSenderOperatorBlobsEth); + vm.stopBroadcast(); + + console.log("Validators added"); + } + + function configureZkSyncStateTransition() internal { + IZKChain zkChain = IZKChain(output.diamondProxy); + + vm.startBroadcast(msg.sender); + zkChain.setTokenMultiplier( + config.baseTokenGasPriceMultiplierNominator, + config.baseTokenGasPriceMultiplierDenominator + ); + + if (config.validiumMode) { + zkChain.setPubdataPricingMode(PubdataPricingMode.Validium); + } + + vm.stopBroadcast(); + console.log("ZkSync State Transition configured"); + } + + function setPendingAdmin() internal { + IZKChain zkChain = IZKChain(output.diamondProxy); + + vm.startBroadcast(msg.sender); + zkChain.setPendingAdmin(output.chainAdmin); + vm.stopBroadcast(); + console.log("Owner for ", output.diamondProxy, "set to", output.chainAdmin); + } + + function deployChainProxyAddress() internal { + bytes memory input = abi.encode(type(ProxyAdmin).creationCode, config.create2Salt, output.chainAdmin); + bytes memory encoded = abi.encodePacked(type(CreateAndTransfer).creationCode, input); + address createAndTransfer = Utils.deployViaCreate2(encoded, config.create2Salt, config.create2FactoryAddress); + + address proxyAdmin = vm.computeCreate2Address(config.create2Salt, keccak256(encoded), createAndTransfer); + + console.log("Transparent Proxy Admin deployed at:", address(proxyAdmin)); + output.chainProxyAdmin = address(proxyAdmin); + } + + function deployLegacySharedBridge() internal { + bytes[] memory emptyDeps = new bytes[](0); + address legacyBridgeImplAddr = Utils.deployThroughL1Deterministic({ + bytecode: L2ContractsBytecodesLib.readL2LegacySharedBridgeDevBytecode(), + constructorargs: hex"", + create2salt: "", + l2GasLimit: Utils.MAX_PRIORITY_TX_GAS, + factoryDeps: emptyDeps, + chainId: config.chainChainId, + bridgehubAddress: config.bridgehub, + l1SharedBridgeProxy: config.sharedBridgeProxy + }); + + output.l2LegacySharedBridge = Utils.deployThroughL1Deterministic({ + bytecode: L2ContractsBytecodesLib.readTransparentUpgradeableProxyBytecode(), + constructorargs: L2LegacySharedBridgeTestHelper.getLegacySharedBridgeProxyConstructorParams( + legacyBridgeImplAddr, + config.l1Erc20Bridge, + config.l1Nullifier, + // Ecosystem governance is the owner of the L1Nullifier + L1NullifierDev(config.l1Nullifier).owner() + ), + create2salt: "", + l2GasLimit: Utils.MAX_PRIORITY_TX_GAS, + factoryDeps: emptyDeps, + chainId: config.chainChainId, + bridgehubAddress: config.bridgehub, + l1SharedBridgeProxy: config.sharedBridgeProxy + }); + } + + function getFactoryDeps() internal view returns (bytes[] memory) { + bytes[] memory factoryDeps = new bytes[](4); + factoryDeps[0] = L2ContractsBytecodesLib.readBeaconProxyBytecode(); + factoryDeps[1] = L2ContractsBytecodesLib.readStandardERC20Bytecode(); + factoryDeps[2] = L2ContractsBytecodesLib.readUpgradeableBeaconBytecode(); + factoryDeps[3] = L2ContractsBytecodesLib.readTransparentUpgradeableProxyBytecodeFromSystemContracts(); + return factoryDeps; + } + + function saveOutput(string memory outputPath) internal { + vm.serializeAddress("root", "diamond_proxy_addr", output.diamondProxy); + vm.serializeAddress("root", "chain_admin_addr", output.chainAdmin); + if (output.l2LegacySharedBridge != address(0)) { + vm.serializeAddress("root", "l2_legacy_shared_bridge_addr", output.l2LegacySharedBridge); + } + vm.serializeAddress("root", "access_control_restriction_addr", output.accessControlRestrictionAddress); + vm.serializeAddress("root", "chain_proxy_admin_addr", output.chainProxyAdmin); + + string memory toml = vm.serializeAddress("root", "governance_addr", output.governance); + string memory root = vm.projectRoot(); + vm.writeToml(toml, outputPath); + console.log("Output saved at:", outputPath); + } + + function governanceExecuteCalls(bytes memory callsToExecute, address governanceAddr) internal { + IGovernance governance = IGovernance(governanceAddr); + Ownable2Step ownable = Ownable2Step(governanceAddr); + + Call[] memory calls = abi.decode(callsToExecute, (Call[])); + + IGovernance.Operation memory operation = IGovernance.Operation({ + calls: calls, + predecessor: bytes32(0), + salt: bytes32(0) + }); + + vm.startBroadcast(ownable.owner()); + governance.scheduleTransparent(operation, 0); + // We assume that the total value is 0 + governance.execute{value: 0}(operation); + vm.stopBroadcast(); + } +} diff --git a/l1-contracts/deploy-scripts/Utils.sol b/l1-contracts/deploy-scripts/Utils.sol index 5f509b0db..d19bafb08 100644 --- a/l1-contracts/deploy-scripts/Utils.sol +++ b/l1-contracts/deploy-scripts/Utils.sol @@ -4,11 +4,14 @@ pragma solidity 0.8.24; // solhint-disable gas-custom-errors, reason-string import {Vm} from "forge-std/Vm.sol"; +import {console2 as console} from "forge-std/Script.sol"; import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; -import {L2TransactionRequestDirect} from "contracts/bridgehub/IBridgehub.sol"; +import {L2TransactionRequestDirect, L2TransactionRequestTwoBridgesOuter} from "contracts/bridgehub/IBridgehub.sol"; import {IGovernance} from "contracts/governance/IGovernance.sol"; import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import {Ownable} from "@openzeppelin/contracts-v4/access/Ownable.sol"; +import {Call} from "contracts/governance/Common.sol"; import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA} from "contracts/common/Config.sol"; import {L2_DEPLOYER_SYSTEM_CONTRACT_ADDR} from "contracts/common/L2ContractAddresses.sol"; import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; @@ -18,6 +21,7 @@ import {IProtocolUpgradeHandler} from "./interfaces/IProtocolUpgradeHandler.sol" import {IEmergencyUpgrageBoard} from "./interfaces/IEmergencyUpgrageBoard.sol"; import {IMultisig} from "./interfaces/IMultisig.sol"; import {ISafe} from "./interfaces/ISafe.sol"; +import {AccessControlRestriction} from "contracts/governance/AccessControlRestriction.sol"; /// @dev EIP-712 TypeHash for the emergency protocol upgrade execution approved by the guardians. bytes32 constant EXECUTE_EMERGENCY_UPGRADE_GUARDIANS_TYPEHASH = keccak256( @@ -34,6 +38,48 @@ bytes32 constant EXECUTE_EMERGENCY_UPGRADE_ZK_FOUNDATION_TYPEHASH = keccak256( "ExecuteEmergencyUpgradeZKFoundation(bytes32 id)" ); +/// @dev The offset from which the built-in, but user space contracts are located. +uint160 constant USER_CONTRACTS_OFFSET = 0x10000; // 2^16 + +// address constant +address constant L2_BRIDGEHUB_ADDRESS = address(USER_CONTRACTS_OFFSET + 0x02); +address constant L2_ASSET_ROUTER_ADDRESS = address(USER_CONTRACTS_OFFSET + 0x03); +address constant L2_NATIVE_TOKEN_VAULT_ADDRESS = address(USER_CONTRACTS_OFFSET + 0x04); +address constant L2_MESSAGE_ROOT_ADDRESS = address(USER_CONTRACTS_OFFSET + 0x05); +address constant L2_WETH_IMPL_ADDRESS = address(USER_CONTRACTS_OFFSET + 0x07); + +address constant L2_CREATE2_FACTORY_ADDRESS = address(USER_CONTRACTS_OFFSET); + +// solhint-disable-next-line gas-struct-packing +struct StateTransitionDeployedAddresses { + address chainTypeManagerProxy; + address chainTypeManagerImplementation; + address verifier; + address adminFacet; + address mailboxFacet; + address executorFacet; + address gettersFacet; + address diamondInit; + address genesisUpgrade; + address defaultUpgrade; + address validatorTimelock; + address diamondProxy; + address bytecodesSupplier; +} + +/// @dev We need to use a struct instead of list of params to prevent stack too deep error +struct PrepareL1L2TransactionParams { + uint256 l1GasPrice; + bytes l2Calldata; + uint256 l2GasLimit; + uint256 l2Value; + bytes[] factoryDeps; + address dstAddress; + uint256 chainId; + address bridgehubAddress; + address l1SharedBridgeProxy; +} + library Utils { // Cheatcodes address, 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D. address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); @@ -97,6 +143,13 @@ library Utils { return selectors; } + function getAllSelectorsForFacet(string memory facetName) internal returns (bytes4[] memory) { + // TODO(EVM-746): use forge to read the bytecode + string memory path = string.concat("/../l1-contracts/out/", facetName, ".sol/", facetName, "Facet.json"); + bytes memory bytecode = readFoundryDeployedBytecode(path); + return getAllSelectors(bytecode); + } + /** * @dev Extract an address from bytes. */ @@ -129,25 +182,55 @@ library Utils { * @dev Returns the bytecode hash of the batch bootloader. */ function getBatchBootloaderBytecodeHash() internal view returns (bytes memory) { - return vm.readFileBinary("../system-contracts/bootloader/build/artifacts/proved_batch.yul.zbin"); + return + readZKFoundryBytecodeSystemContracts( + "proved_batch.yul/contracts-preprocessed/bootloader", + "proved_batch.yul" + ); + } + + /** + * @dev Read hardhat bytecodes + */ + function readHardhatBytecode(string memory artifactPath) internal view returns (bytes memory) { + string memory root = vm.projectRoot(); + string memory path = string.concat(root, artifactPath); + string memory json = vm.readFile(path); + bytes memory bytecode = vm.parseJsonBytes(json, ".bytecode"); + return bytecode; } /** * @dev Returns the bytecode of a given system contract. */ function readSystemContractsBytecode(string memory filename) internal view returns (bytes memory) { - string memory file = vm.readFile( - // solhint-disable-next-line func-named-parameters - string.concat( - "../system-contracts/artifacts-zk/contracts-preprocessed/", + return readZKFoundryBytecodeSystemContracts(string.concat(filename, ".sol"), filename); + } + + /** + * @dev Returns the bytecode of a given system contract. + */ + function readPrecompileBytecode(string memory filename) internal view returns (bytes memory) { + string memory path = string.concat( + "/../system-contracts/zkout/", + filename, + ".yul/contracts-preprocessed/precompiles/", + filename, + ".yul.json" + ); + + // It is the only exceptional case + if (keccak256(abi.encodePacked(filename)) == keccak256(abi.encodePacked("EventWriter"))) { + path = string.concat( + "/../system-contracts/zkout/", filename, - ".sol/", + ".yul/contracts-preprocessed/", filename, - ".json" - ) - ); - bytes memory bytecode = vm.parseJson(file, "$.bytecode"); - return bytecode; + ".yul.json" + ); + } + + return readFoundryBytecode(path); } /** @@ -161,8 +244,8 @@ library Utils { child := create(0, add(bytecode, 0x20), mload(bytecode)) } vm.stopBroadcast(); - require(child != address(0), "Failed to deploy Create2Factory"); - require(child.code.length > 0, "Failed to deploy Create2Factory"); + require(child != address(0), "Failed to deploy create2factory"); + require(child.code.length > 0, "Failed to deploy create2factory"); return child; } @@ -202,14 +285,7 @@ library Utils { address bridgehubAddress, address l1SharedBridgeProxy ) internal returns (address) { - bytes32 bytecodeHash = L2ContractHelper.hashL2Bytecode(bytecode); - - bytes memory deployData = abi.encodeWithSignature( - "create2(bytes32,bytes32,bytes)", - create2salt, - bytecodeHash, - constructorargs - ); + (bytes32 bytecodeHash, bytes memory deployData) = getDeploymentCalldata(create2salt, bytecode, constructorargs); address contractAddress = L2ContractHelper.computeCreate2Address( msg.sender, @@ -218,18 +294,12 @@ library Utils { keccak256(constructorargs) ); - uint256 factoryDepsLength = factoryDeps.length; - - bytes[] memory _factoryDeps = new bytes[](factoryDepsLength + 1); - - for (uint256 i = 0; i < factoryDepsLength; ++i) { - _factoryDeps[i] = factoryDeps[i]; - } - _factoryDeps[factoryDepsLength] = bytecode; + bytes[] memory _factoryDeps = appendArray(factoryDeps, bytecode); runL1L2Transaction({ l2Calldata: deployData, l2GasLimit: l2GasLimit, + l2Value: 0, factoryDeps: _factoryDeps, dstAddress: L2_DEPLOYER_SYSTEM_CONTRACT_ADDR, chainId: chainId, @@ -239,39 +309,161 @@ library Utils { return contractAddress; } + function getL2AddressViaCreate2Factory( + bytes32 create2Salt, + bytes32 bytecodeHash, + bytes memory constructorArgs + ) internal view returns (address) { + return + L2ContractHelper.computeCreate2Address( + L2_CREATE2_FACTORY_ADDRESS, + create2Salt, + bytecodeHash, + keccak256(constructorArgs) + ); + } + + function getDeploymentCalldata( + bytes32 create2Salt, + bytes memory bytecode, + bytes memory constructorArgs + ) internal view returns (bytes32 bytecodeHash, bytes memory data) { + bytecodeHash = L2ContractHelper.hashL2Bytecode(bytecode); + + data = abi.encodeWithSignature("create2(bytes32,bytes32,bytes)", create2Salt, bytecodeHash, constructorArgs); + } + + function appendArray(bytes[] memory array, bytes memory element) internal pure returns (bytes[] memory) { + uint256 arrayLength = array.length; + bytes[] memory newArray = new bytes[](arrayLength + 1); + for (uint256 i = 0; i < arrayLength; ++i) { + newArray[i] = array[i]; + } + newArray[arrayLength] = element; + return newArray; + } + /** - * @dev Run the l2 l1 transaction + * @dev Deploy l2 contracts through l1, while using built-in L2 Create2Factory contract. */ - function runL1L2Transaction( - bytes memory l2Calldata, + function deployThroughL1Deterministic( + bytes memory bytecode, + bytes memory constructorargs, + bytes32 create2salt, uint256 l2GasLimit, bytes[] memory factoryDeps, - address dstAddress, uint256 chainId, address bridgehubAddress, address l1SharedBridgeProxy - ) internal { + ) internal returns (address) { + (bytes32 bytecodeHash, bytes memory deployData) = getDeploymentCalldata(create2salt, bytecode, constructorargs); + + address contractAddress = getL2AddressViaCreate2Factory(create2salt, bytecodeHash, constructorargs); + + bytes[] memory _factoryDeps = appendArray(factoryDeps, bytecode); + + runL1L2Transaction({ + l2Calldata: deployData, + l2GasLimit: l2GasLimit, + l2Value: 0, + factoryDeps: _factoryDeps, + dstAddress: L2_CREATE2_FACTORY_ADDRESS, + chainId: chainId, + bridgehubAddress: bridgehubAddress, + l1SharedBridgeProxy: l1SharedBridgeProxy + }); + return contractAddress; + } + + function prepareL1L2Transaction( + PrepareL1L2TransactionParams memory params + ) internal returns (L2TransactionRequestDirect memory l2TransactionRequestDirect, uint256 requiredValueToDeploy) { + Bridgehub bridgehub = Bridgehub(params.bridgehubAddress); + + requiredValueToDeploy = + bridgehub.l2TransactionBaseCost( + params.chainId, + params.l1GasPrice, + params.l2GasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA + ) * + 2 + + params.l2Value; + + l2TransactionRequestDirect = L2TransactionRequestDirect({ + chainId: params.chainId, + mintValue: requiredValueToDeploy, + l2Contract: params.dstAddress, + l2Value: params.l2Value, + l2Calldata: params.l2Calldata, + l2GasLimit: params.l2GasLimit, + l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + factoryDeps: params.factoryDeps, + refundRecipient: msg.sender + }); + } + + function prepareL1L2TransactionTwoBridges( + uint256 l1GasPrice, + uint256 l2GasLimit, + uint256 chainId, + address bridgehubAddress, + address secondBridgeAddress, + uint256 secondBridgeValue, + bytes memory secondBridgeCalldata + ) + internal + returns (L2TransactionRequestTwoBridgesOuter memory l2TransactionRequest, uint256 requiredValueToDeploy) + { Bridgehub bridgehub = Bridgehub(bridgehubAddress); - uint256 gasPrice = bytesToUint256(vm.rpc("eth_gasPrice", "[]")); - uint256 requiredValueToDeploy = bridgehub.l2TransactionBaseCost( - chainId, - gasPrice, - l2GasLimit, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA - ) * 2; + requiredValueToDeploy = + bridgehub.l2TransactionBaseCost(chainId, l1GasPrice, l2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA) * + 2; - L2TransactionRequestDirect memory l2TransactionRequestDirect = L2TransactionRequestDirect({ + l2TransactionRequest = L2TransactionRequestTwoBridgesOuter({ chainId: chainId, mintValue: requiredValueToDeploy, - l2Contract: dstAddress, l2Value: 0, - l2Calldata: l2Calldata, l2GasLimit: l2GasLimit, l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - factoryDeps: factoryDeps, - refundRecipient: msg.sender + refundRecipient: msg.sender, + secondBridgeAddress: secondBridgeAddress, + secondBridgeValue: secondBridgeValue, + secondBridgeCalldata: secondBridgeCalldata }); + } + + /** + * @dev Run the l2 l1 transaction + */ + function runL1L2Transaction( + bytes memory l2Calldata, + uint256 l2GasLimit, + uint256 l2Value, + bytes[] memory factoryDeps, + address dstAddress, + uint256 chainId, + address bridgehubAddress, + address l1SharedBridgeProxy + ) internal { + Bridgehub bridgehub = Bridgehub(bridgehubAddress); + ( + L2TransactionRequestDirect memory l2TransactionRequestDirect, + uint256 requiredValueToDeploy + ) = prepareL1L2Transaction( + PrepareL1L2TransactionParams({ + l1GasPrice: bytesToUint256(vm.rpc("eth_gasPrice", "[]")), + l2Calldata: l2Calldata, + l2GasLimit: l2GasLimit, + l2Value: l2Value, + factoryDeps: factoryDeps, + dstAddress: dstAddress, + chainId: chainId, + bridgehubAddress: bridgehubAddress, + l1SharedBridgeProxy: l1SharedBridgeProxy + }) + ); address baseTokenAddress = bridgehub.baseToken(chainId); if (ADDRESS_ONE != baseTokenAddress) { @@ -285,6 +477,312 @@ library Utils { bridgehub.requestL2TransactionDirect{value: requiredValueToDeploy}(l2TransactionRequestDirect); } + function runGovernanceL1L2DirectTransaction( + uint256 l1GasPrice, + address governor, + bytes32 salt, + bytes memory l2Calldata, + uint256 l2GasLimit, + bytes[] memory factoryDeps, + address dstAddress, + uint256 chainId, + address bridgehubAddress, + address l1SharedBridgeProxy + ) internal returns (bytes32 txHash) { + ( + L2TransactionRequestDirect memory l2TransactionRequestDirect, + uint256 requiredValueToDeploy + ) = prepareL1L2Transaction( + PrepareL1L2TransactionParams({ + l1GasPrice: l1GasPrice, + l2Calldata: l2Calldata, + l2GasLimit: l2GasLimit, + l2Value: 0, + factoryDeps: factoryDeps, + dstAddress: dstAddress, + chainId: chainId, + bridgehubAddress: bridgehubAddress, + l1SharedBridgeProxy: l1SharedBridgeProxy + }) + ); + + requiredValueToDeploy = approveBaseTokenGovernance( + Bridgehub(bridgehubAddress), + l1SharedBridgeProxy, + governor, + salt, + chainId, + requiredValueToDeploy + ); + + bytes memory l2TransactionRequestDirectCalldata = abi.encodeCall( + Bridgehub.requestL2TransactionDirect, + (l2TransactionRequestDirect) + ); + + console.log("Executing transaction"); + vm.recordLogs(); + executeUpgrade(governor, salt, bridgehubAddress, l2TransactionRequestDirectCalldata, requiredValueToDeploy, 0); + Vm.Log[] memory logs = vm.getRecordedLogs(); + console.log("Transaction executed succeassfully! Extracting logs..."); + + address expectedDiamondProxyAddress = Bridgehub(bridgehubAddress).getHyperchain(chainId); + + txHash = extractPriorityOpFromLogs(expectedDiamondProxyAddress, logs); + + console.log("L2 Transaction hash is "); + console.logBytes32(txHash); + } + + function runGovernanceL1L2TwoBridgesTransaction( + uint256 l1GasPrice, + address governor, + bytes32 salt, + uint256 l2GasLimit, + uint256 chainId, + address bridgehubAddress, + address l1SharedBridgeProxy, + address secondBridgeAddress, + uint256 secondBridgeValue, + bytes memory secondBridgeCalldata + ) internal returns (bytes32 txHash) { + ( + L2TransactionRequestTwoBridgesOuter memory l2TransactionRequest, + uint256 requiredValueToDeploy + ) = prepareL1L2TransactionTwoBridges( + l1GasPrice, + l2GasLimit, + chainId, + bridgehubAddress, + secondBridgeAddress, + secondBridgeValue, + secondBridgeCalldata + ); + + requiredValueToDeploy = approveBaseTokenGovernance( + Bridgehub(bridgehubAddress), + l1SharedBridgeProxy, + governor, + salt, + chainId, + requiredValueToDeploy + ); + + bytes memory l2TransactionRequestCalldata = abi.encodeCall( + Bridgehub.requestL2TransactionTwoBridges, + (l2TransactionRequest) + ); + + console.log("Executing transaction"); + vm.recordLogs(); + executeUpgrade(governor, salt, bridgehubAddress, l2TransactionRequestCalldata, requiredValueToDeploy, 0); + Vm.Log[] memory logs = vm.getRecordedLogs(); + console.log("Transaction executed succeassfully! Extracting logs..."); + + address expectedDiamondProxyAddress = Bridgehub(bridgehubAddress).getHyperchain(chainId); + + txHash = extractPriorityOpFromLogs(expectedDiamondProxyAddress, logs); + + console.log("L2 Transaction hash is "); + console.logBytes32(txHash); + } + + function approveBaseTokenGovernance( + Bridgehub bridgehub, + address l1SharedBridgeProxy, + address governor, + bytes32 salt, + uint256 chainId, + uint256 amountToApprove + ) internal returns (uint256 ethAmountToPass) { + address baseTokenAddress = bridgehub.baseToken(chainId); + if (ADDRESS_ONE != baseTokenAddress) { + console.log("Base token not ETH, approving"); + IERC20 baseToken = IERC20(baseTokenAddress); + + bytes memory approvalCalldata = abi.encodeCall(baseToken.approve, (l1SharedBridgeProxy, amountToApprove)); + + executeUpgrade(governor, salt, address(baseToken), approvalCalldata, 0, 0); + + ethAmountToPass = 0; + } else { + console.log("Base token is ETH, no need to approve"); + ethAmountToPass = amountToApprove; + } + } + + function runAdminL1L2DirectTransaction( + uint256 gasPrice, + address admin, + address accessControlRestriction, + bytes memory l2Calldata, + uint256 l2GasLimit, + bytes[] memory factoryDeps, + address dstAddress, + uint256 chainId, + address bridgehubAddress, + address l1SharedBridgeProxy + ) internal returns (bytes32 txHash) { + ( + L2TransactionRequestDirect memory l2TransactionRequestDirect, + uint256 requiredValueToDeploy + ) = prepareL1L2Transaction( + PrepareL1L2TransactionParams({ + l1GasPrice: gasPrice, + l2Calldata: l2Calldata, + l2GasLimit: l2GasLimit, + l2Value: 0, + factoryDeps: factoryDeps, + dstAddress: dstAddress, + chainId: chainId, + bridgehubAddress: bridgehubAddress, + l1SharedBridgeProxy: l1SharedBridgeProxy + }) + ); + + requiredValueToDeploy = approveBaseTokenAdmin( + Bridgehub(bridgehubAddress), + l1SharedBridgeProxy, + admin, + accessControlRestriction, + chainId, + requiredValueToDeploy + ); + + bytes memory l2TransactionRequestDirectCalldata = abi.encodeCall( + Bridgehub.requestL2TransactionDirect, + (l2TransactionRequestDirect) + ); + + console.log("Executing transaction"); + vm.recordLogs(); + adminExecute( + admin, + accessControlRestriction, + bridgehubAddress, + l2TransactionRequestDirectCalldata, + requiredValueToDeploy + ); + Vm.Log[] memory logs = vm.getRecordedLogs(); + console.log("Transaction executed succeassfully! Extracting logs..."); + + address expectedDiamondProxyAddress = Bridgehub(bridgehubAddress).getHyperchain(chainId); + + txHash = extractPriorityOpFromLogs(expectedDiamondProxyAddress, logs); + + console.log("L2 Transaction hash is "); + console.logBytes32(txHash); + } + + function runAdminL1L2TwoBridgesTransaction( + uint256 l1GasPrice, + address admin, + address accessControlRestriction, + uint256 l2GasLimit, + uint256 chainId, + address bridgehubAddress, + address l1SharedBridgeProxy, + address secondBridgeAddress, + uint256 secondBridgeValue, + bytes memory secondBridgeCalldata + ) internal returns (bytes32 txHash) { + ( + L2TransactionRequestTwoBridgesOuter memory l2TransactionRequest, + uint256 requiredValueToDeploy + ) = prepareL1L2TransactionTwoBridges( + l1GasPrice, + l2GasLimit, + chainId, + bridgehubAddress, + secondBridgeAddress, + secondBridgeValue, + secondBridgeCalldata + ); + + requiredValueToDeploy = approveBaseTokenAdmin( + Bridgehub(bridgehubAddress), + l1SharedBridgeProxy, + admin, + accessControlRestriction, + chainId, + requiredValueToDeploy + ); + + bytes memory l2TransactionRequestCalldata = abi.encodeCall( + Bridgehub.requestL2TransactionTwoBridges, + (l2TransactionRequest) + ); + + console.log("Executing transaction"); + vm.recordLogs(); + adminExecute( + admin, + accessControlRestriction, + bridgehubAddress, + l2TransactionRequestCalldata, + requiredValueToDeploy + ); + Vm.Log[] memory logs = vm.getRecordedLogs(); + console.log("Transaction executed succeassfully! Extracting logs..."); + + address expectedDiamondProxyAddress = Bridgehub(bridgehubAddress).getHyperchain(chainId); + + txHash = extractPriorityOpFromLogs(expectedDiamondProxyAddress, logs); + + console.log("L2 Transaction hash is "); + console.logBytes32(txHash); + } + + function approveBaseTokenAdmin( + Bridgehub bridgehub, + address l1SharedBridgeProxy, + address admin, + address accessControlRestriction, + uint256 chainId, + uint256 amountToApprove + ) internal returns (uint256 ethAmountToPass) { + address baseTokenAddress = bridgehub.baseToken(chainId); + if (ADDRESS_ONE != baseTokenAddress) { + console.log("Base token not ETH, approving"); + IERC20 baseToken = IERC20(baseTokenAddress); + + bytes memory approvalCalldata = abi.encodeCall(baseToken.approve, (l1SharedBridgeProxy, amountToApprove)); + + adminExecute(admin, accessControlRestriction, address(baseToken), approvalCalldata, 0); + + ethAmountToPass = 0; + } else { + console.log("Base token is ETH, no need to approve"); + ethAmountToPass = amountToApprove; + } + } + + function extractPriorityOpFromLogs( + address expectedDiamondProxyAddress, + Vm.Log[] memory logs + ) internal pure returns (bytes32 txHash) { + // TODO(EVM-749): cleanup the constant and automate its derivation + bytes32 topic0 = bytes32(uint256(0x4531cd5795773d7101c17bdeb9f5ab7f47d7056017506f937083be5d6e77a382)); + + for (uint256 i = 0; i < logs.length; i++) { + if (logs[i].emitter == expectedDiamondProxyAddress && logs[i].topics[0] == topic0) { + if (txHash != bytes32(0)) { + revert("Multiple priority ops"); + } + + bytes memory data = logs[i].data; + assembly { + // Skip length + tx id + txHash := mload(add(data, 0x40)) + } + } + } + + if (txHash == bytes32(0)) { + revert("No priority op found"); + } + } + /** * @dev Publish bytecodes to l2 through l1 */ @@ -297,6 +795,7 @@ library Utils { runL1L2Transaction({ l2Calldata: "", l2GasLimit: MAX_PRIORITY_TX_GAS, + l2Value: 0, factoryDeps: factoryDeps, dstAddress: 0x0000000000000000000000000000000000000000, chainId: chainId, @@ -316,26 +815,52 @@ library Utils { return bytecode; } + function readFoundryBytecodeL1( + string memory fileName, + string memory contractName + ) internal view returns (bytes memory) { + string memory path = string.concat("/../l1-contracts/out/", fileName, "/", contractName, ".json"); + return readFoundryBytecode(path); + } + + function readZKFoundryBytecodeL1( + string memory fileName, + string memory contractName + ) internal view returns (bytes memory) { + string memory path = string.concat("/../l1-contracts/zkout/", fileName, "/", contractName, ".json"); + bytes memory bytecode = readFoundryBytecode(path); + return bytecode; + } + + function readZKFoundryBytecodeL2( + string memory fileName, + string memory contractName + ) internal view returns (bytes memory) { + string memory path = string.concat("/../l2-contracts/zkout/", fileName, "/", contractName, ".json"); + bytes memory bytecode = readFoundryBytecode(path); + return bytecode; + } + + function readZKFoundryBytecodeSystemContracts( + string memory fileName, + string memory contractName + ) internal view returns (bytes memory) { + string memory path = string.concat("/../system-contracts/zkout/", fileName, "/", contractName, ".json"); + bytes memory bytecode = readFoundryBytecode(path); + return bytecode; + } + /** * @dev Read hardhat bytecodes */ - function readHardhatBytecode(string memory artifactPath) internal view returns (bytes memory) { + function readFoundryDeployedBytecode(string memory artifactPath) internal view returns (bytes memory) { string memory root = vm.projectRoot(); string memory path = string.concat(root, artifactPath); string memory json = vm.readFile(path); - bytes memory bytecode = vm.parseJsonBytes(json, ".bytecode"); + bytes memory bytecode = vm.parseJsonBytes(json, ".deployedBytecode.object"); return bytecode; } - function chainAdminMulticall(address _chainAdmin, address _target, bytes memory _data, uint256 _value) internal { - IChainAdmin chainAdmin = IChainAdmin(_chainAdmin); - - IChainAdmin.Call[] memory calls = new IChainAdmin.Call[](1); - calls[0] = IChainAdmin.Call({target: _target, value: _value, data: _data}); - vm.broadcast(); - chainAdmin.multicall(calls, true); - } - function executeUpgrade( address _governor, bytes32 _salt, @@ -345,9 +870,10 @@ library Utils { uint256 _delay ) internal { IGovernance governance = IGovernance(_governor); + Ownable ownable = Ownable(_governor); - IGovernance.Call[] memory calls = new IGovernance.Call[](1); - calls[0] = IGovernance.Call({target: _target, value: _value, data: _data}); + Call[] memory calls = new Call[](1); + calls[0] = Call({target: _target, value: _value, data: _data}); IGovernance.Operation memory operation = IGovernance.Operation({ calls: calls, @@ -355,7 +881,7 @@ library Utils { salt: _salt }); - vm.startBroadcast(); + vm.startBroadcast(ownable.owner()); governance.scheduleTransparent(operation, _delay); if (_delay == 0) { governance.execute{value: _value}(operation); @@ -475,4 +1001,39 @@ library Utils { vm.stopBroadcast(); } } + + function adminExecute( + address _admin, + address _accessControlRestriction, + address _target, + bytes memory _data, + uint256 _value + ) internal { + // If `_accessControlRestriction` is not provided, we expect that this ChainAdmin is Ownable + address adminOwner = _accessControlRestriction == address(0) + ? Ownable(_admin).owner() + : AccessControlRestriction(_accessControlRestriction).defaultAdmin(); + + Call[] memory calls = new Call[](1); + calls[0] = Call({target: _target, value: _value, data: _data}); + + vm.startBroadcast(adminOwner); + IChainAdmin(_admin).multicall{value: _value}(calls, true); + vm.stopBroadcast(); + } + + function readRollupDAValidatorBytecode() internal view returns (bytes memory bytecode) { + bytecode = readFoundryBytecode("/../da-contracts/out/RollupL1DAValidator.sol/RollupL1DAValidator.json"); + } + + function readAvailL1DAValidatorBytecode() internal view returns (bytes memory bytecode) { + bytecode = readFoundryBytecode("/../da-contracts/out/AvailL1DAValidator.sol/AvailL1DAValidator.json"); + } + + function readDummyAvailBridgeBytecode() internal view returns (bytes memory bytecode) { + bytecode = readFoundryBytecode("/../da-contracts/out/DummyAvailBridge.sol/DummyAvailBridge.json"); + } + + // add this to be excluded from coverage report + function test() internal {} } diff --git a/l1-contracts/deploy-scripts/ZkSyncScriptErrors.sol b/l1-contracts/deploy-scripts/ZkSyncScriptErrors.sol index 76295d633..8b19da970 100644 --- a/l1-contracts/deploy-scripts/ZkSyncScriptErrors.sol +++ b/l1-contracts/deploy-scripts/ZkSyncScriptErrors.sol @@ -1,12 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -error FailedToDeploy(ZksyncContract); -error BytecodeNotSet(); -error FailedToDeployViaCreate2(); -error MissingAddress(ZksyncContract); +// 0x86bb51b8 error AddressHasNoCode(address); +// 0x07637bd8 error MintFailed(); +// 0xbd13da86 +error ProxyAdminIncorrect(address expectedProxyAdmin, address proxyAdmin); +// 0x565fae63 +error ProxyAdminIncorrectOwner(address proxyAdmin, address governance); enum ZksyncContract { Create2Factory, diff --git a/l1-contracts/deploy-scripts/dev/SetupLegacyBridge.s.sol b/l1-contracts/deploy-scripts/dev/SetupLegacyBridge.s.sol index 518005915..0e6652ce9 100644 --- a/l1-contracts/deploy-scripts/dev/SetupLegacyBridge.s.sol +++ b/l1-contracts/deploy-scripts/dev/SetupLegacyBridge.s.sol @@ -4,11 +4,15 @@ pragma solidity ^0.8.0; import {Script} from "forge-std/Script.sol"; import {stdToml} from "forge-std/StdToml.sol"; import {Utils} from "./../Utils.sol"; -import {L1SharedBridge} from "contracts/bridge/L1SharedBridge.sol"; +import {L2ContractsBytecodesLib} from "../L2ContractsBytecodesLib.sol"; +import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol"; import {DummyL1ERC20Bridge} from "contracts/dev-contracts/DummyL1ERC20Bridge.sol"; import {ProxyAdmin} from "@openzeppelin/contracts-v4/proxy/transparent/ProxyAdmin.sol"; import {ITransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; +import {L2LegacySharedBridgeTestHelper} from "../L2LegacySharedBridgeTestHelper.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; +import {L1NullifierDev} from "contracts/dev-contracts/L1NullifierDev.sol"; /// This scripts is only for developer contract SetupLegacyBridge is Script { @@ -26,13 +30,16 @@ contract SetupLegacyBridge is Script { struct Addresses { address create2FactoryAddr; address bridgehub; + address l1Nullifier; address diamondProxy; address sharedBridgeProxy; + address l1NativeTokenVault; address transparentProxyAdmin; address erc20BridgeProxy; address tokenWethAddress; address erc20BridgeProxyImpl; address sharedBridgeProxyImpl; + address l1NullifierProxyImpl; } function run() public { @@ -42,6 +49,8 @@ contract SetupLegacyBridge is Script { deployDummyErc20Bridge(); upgradeImplementation(addresses.erc20BridgeProxy, addresses.erc20BridgeProxyImpl); setParamsForDummyBridge(); + deployL1NullifierImplementation(); + upgradeImplementation(addresses.l1Nullifier, addresses.l1NullifierProxyImpl); } function initializeConfig() internal { @@ -51,7 +60,9 @@ contract SetupLegacyBridge is Script { addresses.bridgehub = toml.readAddress("$.bridgehub"); addresses.diamondProxy = toml.readAddress("$.diamond_proxy"); + addresses.l1Nullifier = toml.readAddress("$.l1_nullifier_proxy"); addresses.sharedBridgeProxy = toml.readAddress("$.shared_bridge_proxy"); + addresses.l1NativeTokenVault = toml.readAddress("$.l1_native_token_vault"); addresses.transparentProxyAdmin = toml.readAddress("$.transparent_proxy_admin"); addresses.erc20BridgeProxy = toml.readAddress("$.erc20bridge_proxy"); addresses.tokenWethAddress = toml.readAddress("$.token_weth_address"); @@ -64,9 +75,15 @@ contract SetupLegacyBridge is Script { // We need to deploy new shared bridge for changing chain id and diamond proxy address function deploySharedBridgeImplementation() internal { bytes memory bytecode = abi.encodePacked( - type(L1SharedBridge).creationCode, + type(L1AssetRouter).creationCode, // solhint-disable-next-line func-named-parameters - abi.encode(addresses.tokenWethAddress, addresses.bridgehub, config.chainId, addresses.diamondProxy) + abi.encode( + addresses.tokenWethAddress, + addresses.bridgehub, + addresses.l1Nullifier, + config.chainId, + addresses.diamondProxy + ) ); address contractAddress = deployViaCreate2(bytecode); @@ -77,12 +94,22 @@ contract SetupLegacyBridge is Script { bytes memory bytecode = abi.encodePacked( type(DummyL1ERC20Bridge).creationCode, // solhint-disable-next-line func-named-parameters - abi.encode(addresses.sharedBridgeProxy) + abi.encode(addresses.l1Nullifier, addresses.sharedBridgeProxy, addresses.l1NativeTokenVault, config.chainId) ); address contractAddress = deployViaCreate2(bytecode); addresses.erc20BridgeProxyImpl = contractAddress; } + function deployL1NullifierImplementation() internal { + bytes memory bytecode = abi.encodePacked( + type(L1NullifierDev).creationCode, + abi.encode(addresses.bridgehub, config.chainId, addresses.diamondProxy) + ); + address contractAddress = deployViaCreate2(bytecode); + + addresses.l1NullifierProxyImpl = contractAddress; + } + function upgradeImplementation(address proxy, address implementation) internal { bytes memory proxyAdminUpgradeData = abi.encodeCall( ProxyAdmin.upgrade, @@ -102,41 +129,18 @@ contract SetupLegacyBridge is Script { } function setParamsForDummyBridge() internal { - (address l2TokenBeacon, bytes32 l2TokenBeaconHash) = calculateTokenBeaconAddress(); + (address l2TokenBeacon, bytes32 l2TokenBeaconHash) = L2LegacySharedBridgeTestHelper + .calculateTestL2TokenBeaconAddress( + addresses.erc20BridgeProxy, + addresses.l1Nullifier, + Ownable2StepUpgradeable(addresses.l1Nullifier).owner() + ); + DummyL1ERC20Bridge bridge = DummyL1ERC20Bridge(addresses.erc20BridgeProxy); vm.broadcast(); bridge.setValues(config.l2SharedBridgeAddress, l2TokenBeacon, l2TokenBeaconHash); } - function calculateTokenBeaconAddress() - internal - returns (address tokenBeaconAddress, bytes32 tokenBeaconBytecodeHash) - { - bytes memory l2StandardTokenCode = Utils.readFoundryBytecode( - "/../l2-contracts/zkout/L2StandardERC20.sol/L2StandardERC20.json" - ); - (address l2StandardToken, ) = calculateL2Create2Address( - config.l2SharedBridgeAddress, - l2StandardTokenCode, - bytes32(0), - "" - ); - - bytes memory beaconProxy = Utils.readFoundryBytecode("/../l2-contracts/zkout/BeaconProxy.sol/BeaconProxy.json"); - tokenBeaconBytecodeHash = L2ContractHelper.hashL2Bytecode(beaconProxy); - - bytes memory upgradableBeacon = Utils.readFoundryBytecode( - "/../l2-contracts/zkout/UpgradeableBeacon.sol/UpgradeableBeacon.json" - ); - - (tokenBeaconAddress, ) = calculateL2Create2Address( - config.l2SharedBridgeAddress, - upgradableBeacon, - bytes32(0), - abi.encode(l2StandardToken) - ); - } - function calculateL2Create2Address( address sender, bytes memory bytecode, diff --git a/l1-contracts/deploy-scripts/upgrade/BytecodePublisher.s.sol b/l1-contracts/deploy-scripts/upgrade/BytecodePublisher.s.sol new file mode 100644 index 000000000..3981da95a --- /dev/null +++ b/l1-contracts/deploy-scripts/upgrade/BytecodePublisher.s.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +// solhint-disable gas-custom-errors, reason-string + +import {Vm} from "forge-std/Vm.sol"; +import {console2 as console} from "forge-std/Script.sol"; + +import {BytecodesSupplier} from "contracts/upgrades/BytecodesSupplier.sol"; +import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; + +library BytecodePublisher { + // Cheatcodes address, 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D. + address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); + Vm internal constant vm = Vm(VM_ADDRESS); + + /// @notice Maximal size of bytecodes' batch to be published at once + uint256 constant MAX_BATCH_SIZE = 126_000; + + /// @notice Publishes bytecodes in batches, each not exceeding `MAX_BATCH_SIZE` + /// @param bytecodes The array of bytecodes to publish + function publishBytecodesInBatches(BytecodesSupplier bytecodesSupplier, bytes[] memory bytecodes) internal { + uint256 totalBytecodes = bytecodes.length; + require(totalBytecodes > 0, "No bytecodes to publish"); + + uint256 currentBatchSize = 0; + uint256 batchStartIndex = 0; + + for (uint256 i = 0; i < totalBytecodes; i++) { + bytes32 hash = L2ContractHelper.hashL2Bytecode(bytecodes[i]); + if (bytecodesSupplier.publishingBlock(hash) != 0) { + console.log("The following bytecode has already been published:"); + console.logBytes32(hash); + continue; + } else { + console.log("Publishing the following bytecode:"); + console.logBytes32(hash); + } + + uint256 bytecodeSize = bytecodes[i].length; + + if (bytecodeSize > MAX_BATCH_SIZE) { + console.log("The following bytecode is too large ", i); + console.log("Its size ", bytecodeSize); + + revert("Bytecode is not publishable"); + } + + // Check if adding this bytecode exceeds the MAX_BATCH_SIZE + if (currentBatchSize + bytecodeSize > MAX_BATCH_SIZE) { + // Publish the current batch + bytes[] memory currentBatch = slice(bytecodes, batchStartIndex, i); + _publishBatch(bytecodesSupplier, currentBatch); + + // Reset for the next batch + batchStartIndex = i; + currentBatchSize = bytecodeSize; + } else { + currentBatchSize += bytecodeSize; + } + } + + // Publish the last batch if any + if (batchStartIndex < totalBytecodes) { + bytes[] memory lastBatch = slice(bytecodes, batchStartIndex, totalBytecodes); + _publishBatch(bytecodesSupplier, lastBatch); + } + } + + /// @notice Internal function to publish a single batch and emit an event + /// @param batch The batch of bytecodes to publish + function _publishBatch(BytecodesSupplier bytecodesSupplier, bytes[] memory batch) internal { + vm.broadcast(); + bytecodesSupplier.publishBytecodes(batch); + } + + /// @notice Slices a bytes[][] array from start index to end index (exclusive) + /// @param array The original bytes[][] array + /// @param start The starting index (inclusive) + /// @param end The ending index (exclusive) + /// @return sliced The sliced bytes[][] array + function slice(bytes[] memory array, uint256 start, uint256 end) internal pure returns (bytes[] memory sliced) { + require(start <= end && end <= array.length, "Invalid slice indices"); + sliced = new bytes[](end - start); + for (uint256 i = start; i < end; i++) { + sliced[i - start] = array[i]; + } + } +} diff --git a/l1-contracts/deploy-scripts/upgrade/ChainUpgrade.s.sol b/l1-contracts/deploy-scripts/upgrade/ChainUpgrade.s.sol new file mode 100644 index 000000000..103258d88 --- /dev/null +++ b/l1-contracts/deploy-scripts/upgrade/ChainUpgrade.s.sol @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +// solhint-disable no-console, gas-custom-errors + +import {Script, console2 as console} from "forge-std/Script.sol"; +import {stdToml} from "forge-std/StdToml.sol"; +import {Utils, L2_BRIDGEHUB_ADDRESS, L2_ASSET_ROUTER_ADDRESS, L2_NATIVE_TOKEN_VAULT_ADDRESS, L2_MESSAGE_ROOT_ADDRESS} from "../Utils.sol"; +import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; +import {L2ContractsBytecodesLib} from "../L2ContractsBytecodesLib.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {AccessControlRestriction} from "contracts/governance/AccessControlRestriction.sol"; +import {ChainAdmin} from "contracts/governance/ChainAdmin.sol"; +import {Call} from "contracts/governance/Common.sol"; +import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; + +interface LegacyChainAdmin { + function owner() external view returns (address); +} + +contract ChainUpgrade is Script { + using stdToml for string; + + struct ChainConfig { + address deployerAddress; + address ownerAddress; + uint256 chainChainId; + address chainDiamondProxyAddress; + bool permanentRollup; + address bridgehubProxyAddress; + address oldSharedBridgeProxyAddress; + } + + struct Output { + address accessControlRestriction; + address chainAdmin; + } + + address currentChainAdmin; + ChainConfig config; + Output output; + + function prepareChain( + string memory ecosystemInputPath, + string memory ecosystemOutputPath, + string memory configPath, + string memory outputPath + ) public { + string memory root = vm.projectRoot(); + ecosystemInputPath = string.concat(root, ecosystemInputPath); + ecosystemOutputPath = string.concat(root, ecosystemOutputPath); + configPath = string.concat(root, configPath); + outputPath = string.concat(root, outputPath); + + initializeConfig(configPath, ecosystemInputPath, ecosystemOutputPath); + + checkCorrectOwnerAddress(); + + governanceMoveToNewChainAdmin(); + + // This script does nothing, it only checks that the provided inputs are correct. + // It is just a wrapper to easily call `upgradeChain` + + saveOutput(outputPath); + } + + function run() public { + // TODO: maybe make it read from 1 exact input file, + // for now doing it this way is just faster + + prepareChain( + "/script-config/gateway-upgrade-ecosystem.toml", + "/script-out/gateway-upgrade-ecosystem.toml", + "/script-config/gateway-upgrade-chain.toml", + "/script-out/gateway-upgrade-chain.toml" + ); + } + + function upgradeChain(uint256 oldProtocolVersion, Diamond.DiamondCutData memory upgradeCutData) public { + Utils.adminExecute( + output.chainAdmin, + output.accessControlRestriction, + config.chainDiamondProxyAddress, + abi.encodeCall(IAdmin.upgradeChainFromVersion, (oldProtocolVersion, upgradeCutData)), + 0 + ); + } + + function initializeConfig( + string memory configPath, + string memory ecosystemInputPath, + string memory ecosystemOutputPath + ) internal { + config.deployerAddress = msg.sender; + + // Grab config from output of l1 deployment + string memory toml = vm.readFile(configPath); + + // Config file must be parsed key by key, otherwise values returned + // are parsed alfabetically and not by key. + // https://book.getfoundry.sh/cheatcodes/parse-toml + + config.ownerAddress = toml.readAddress("$.owner_address"); + config.chainChainId = toml.readUint("$.chain.chain_id"); + config.chainDiamondProxyAddress = toml.readAddress("$.chain.diamond_proxy_address"); + config.permanentRollup = toml.readBool("$.chain.permanent_rollup"); + + toml = vm.readFile(ecosystemInputPath); + + config.bridgehubProxyAddress = toml.readAddress("$.contracts.bridgehub_proxy_address"); + config.oldSharedBridgeProxyAddress = toml.readAddress("$.contracts.old_shared_bridge_proxy_address"); + } + + function checkCorrectOwnerAddress() internal { + currentChainAdmin = address(IZKChain(config.chainDiamondProxyAddress).getAdmin()); + address currentAdminOwner = LegacyChainAdmin(currentChainAdmin).owner(); + + require(currentAdminOwner == config.ownerAddress, "Only the owner of the chain admin can call this function"); + } + + // TODO(EVM-924): this function is not used. + function deployNewChainAdmin() internal { + vm.broadcast(config.ownerAddress); + AccessControlRestriction accessControlRestriction = new AccessControlRestriction(0, config.ownerAddress); + + address[] memory restrictions; + restrictions = new address[](1); + restrictions[0] = address(accessControlRestriction); + + vm.broadcast(config.ownerAddress); + ChainAdmin newChainAdmin = new ChainAdmin(restrictions); + output.chainAdmin = address(newChainAdmin); + output.accessControlRestriction = address(accessControlRestriction); + } + + /// @dev The caller of this function needs to be the owner of the chain admin + /// of the + function governanceMoveToNewChainAdmin() internal { + // Firstly, we need to call the legacy chain admin to transfer the ownership to the new chain admin + Call[] memory calls = new Call[](1); + calls[0] = Call({ + target: config.chainDiamondProxyAddress, + value: 0, + data: abi.encodeCall(IAdmin.setPendingAdmin, (output.chainAdmin)) + }); + + vm.startBroadcast(config.ownerAddress); + ChainAdmin(payable(currentChainAdmin)).multicall(calls, true); + vm.stopBroadcast(); + + // Now we need to accept the adminship + Utils.adminExecute({ + _admin: output.chainAdmin, + _accessControlRestriction: output.accessControlRestriction, + _target: config.chainDiamondProxyAddress, + _data: abi.encodeCall(IAdmin.acceptAdmin, ()), + _value: 0 + }); + } + + function saveOutput(string memory outputPath) internal { + vm.serializeAddress("root", "chain_admin_addr", output.chainAdmin); + + string memory toml = vm.serializeAddress("root", "access_control_restriction", output.accessControlRestriction); + string memory root = vm.projectRoot(); + vm.writeToml(toml, outputPath); + console.log("Output saved at:", outputPath); + } +} diff --git a/l1-contracts/deploy-scripts/upgrade/EcosystemUpgrade.s.sol b/l1-contracts/deploy-scripts/upgrade/EcosystemUpgrade.s.sol new file mode 100644 index 000000000..211a17212 --- /dev/null +++ b/l1-contracts/deploy-scripts/upgrade/EcosystemUpgrade.s.sol @@ -0,0 +1,1532 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +// solhint-disable no-console, gas-custom-errors + +import {Script, console2 as console} from "forge-std/Script.sol"; +import {stdToml} from "forge-std/StdToml.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts-v4/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy, ITransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; +import {Utils, L2_BRIDGEHUB_ADDRESS, L2_ASSET_ROUTER_ADDRESS, L2_NATIVE_TOKEN_VAULT_ADDRESS, L2_MESSAGE_ROOT_ADDRESS} from "../Utils.sol"; +import {Multicall3} from "contracts/dev-contracts/Multicall3.sol"; +import {Verifier} from "contracts/state-transition/Verifier.sol"; +import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; +import {VerifierParams, IVerifier} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; +import {DefaultUpgrade} from "contracts/upgrades/DefaultUpgrade.sol"; +import {Governance} from "contracts/governance/Governance.sol"; +import {L1GenesisUpgrade} from "contracts/upgrades/L1GenesisUpgrade.sol"; +import {GatewayUpgrade} from "contracts/upgrades/GatewayUpgrade.sol"; +import {ChainAdmin} from "contracts/governance/ChainAdmin.sol"; +import {ValidatorTimelock} from "contracts/state-transition/ValidatorTimelock.sol"; +import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; +import {MessageRoot} from "contracts/bridgehub/MessageRoot.sol"; +import {CTMDeploymentTracker} from "contracts/bridgehub/CTMDeploymentTracker.sol"; +import {L1NativeTokenVault} from "contracts/bridge/ntv/L1NativeTokenVault.sol"; +import {ExecutorFacet} from "contracts/state-transition/chain-deps/facets/Executor.sol"; +import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol"; +import {MailboxFacet} from "contracts/state-transition/chain-deps/facets/Mailbox.sol"; +import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; +import {DiamondInit} from "contracts/state-transition/chain-deps/DiamondInit.sol"; +import {ChainTypeManager} from "contracts/state-transition/ChainTypeManager.sol"; +import {ChainTypeManagerInitializeData, ChainCreationParams} from "contracts/state-transition/IChainTypeManager.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; +import {InitializeDataNewChain as DiamondInitializeDataNewChain} from "contracts/state-transition/chain-interfaces/IDiamondInit.sol"; +import {FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZKChainStorage.sol"; +import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol"; +import {L1ERC20Bridge} from "contracts/bridge/L1ERC20Bridge.sol"; +import {L1Nullifier} from "contracts/bridge/L1Nullifier.sol"; +import {DiamondProxy} from "contracts/state-transition/chain-deps/DiamondProxy.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {INativeTokenVault} from "contracts/bridge/ntv/INativeTokenVault.sol"; +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; +import {AddressHasNoCode} from "../ZkSyncScriptErrors.sol"; +import {ICTMDeploymentTracker} from "contracts/bridgehub/ICTMDeploymentTracker.sol"; +import {IMessageRoot} from "contracts/bridgehub/IMessageRoot.sol"; +import {IL2ContractDeployer} from "contracts/common/interfaces/IL2ContractDeployer.sol"; +import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {IL1Nullifier} from "contracts/bridge/L1Nullifier.sol"; +import {IL1NativeTokenVault} from "contracts/bridge/ntv/IL1NativeTokenVault.sol"; +import {L1NullifierDev} from "contracts/dev-contracts/L1NullifierDev.sol"; +import {AccessControlRestriction} from "contracts/governance/AccessControlRestriction.sol"; +import {PermanentRestriction} from "contracts/governance/PermanentRestriction.sol"; +import {ICTMDeploymentTracker} from "contracts/bridgehub/ICTMDeploymentTracker.sol"; +import {IMessageRoot} from "contracts/bridgehub/IMessageRoot.sol"; +import {IAssetRouterBase} from "contracts/bridge/asset-router/IAssetRouterBase.sol"; +import {L2ContractsBytecodesLib} from "../L2ContractsBytecodesLib.sol"; +import {ValidiumL1DAValidator} from "contracts/state-transition/data-availability/ValidiumL1DAValidator.sol"; +import {Call} from "contracts/governance/Common.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {ProposedUpgrade} from "contracts/upgrades/BaseZkSyncUpgrade.sol"; + +import {L2CanonicalTransaction} from "contracts/common/Messaging.sol"; +import {L2_FORCE_DEPLOYER_ADDR, L2_COMPLEX_UPGRADER_ADDR, L2_DEPLOYER_SYSTEM_CONTRACT_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {IComplexUpgrader} from "contracts/state-transition/l2-deps/IComplexUpgrader.sol"; +import {GatewayUpgradeEncodedInput} from "contracts/upgrades/GatewayUpgrade.sol"; +import {TransitionaryOwner} from "contracts/governance/TransitionaryOwner.sol"; +import {SystemContractsProcessing} from "./SystemContractsProcessing.s.sol"; +import {BytecodePublisher} from "./BytecodePublisher.s.sol"; +import {BytecodesSupplier} from "contracts/upgrades/BytecodesSupplier.sol"; +import {GovernanceUpgradeTimer} from "contracts/upgrades/GovernanceUpgradeTimer.sol"; +import {L2WrappedBaseTokenStore} from "contracts/bridge/L2WrappedBaseTokenStore.sol"; +import {RollupDAManager} from "contracts/state-transition/data-availability/RollupDAManager.sol"; + +struct FixedForceDeploymentsData { + uint256 l1ChainId; + uint256 eraChainId; + address l1AssetRouter; + bytes32 l2TokenProxyBytecodeHash; + address aliasedL1Governance; + uint256 maxNumberOfZKChains; + bytes32 bridgehubBytecodeHash; + bytes32 l2AssetRouterBytecodeHash; + bytes32 l2NtvBytecodeHash; + bytes32 messageRootBytecodeHash; + address l2SharedBridgeLegacyImpl; + address l2BridgedStandardERC20Impl; + // The forced beacon address. It is needed only for internal testing. + // MUST be equal to 0 in production. + // It will be the job of the governance to ensure that this value is set correctly. + address dangerousTestOnlyForcedBeacon; +} + +// A subset of the ones used for tests +struct StateTransitionDeployedAddresses { + address chainTypeManagerImplementation; + address verifier; + address adminFacet; + address mailboxFacet; + address executorFacet; + address gettersFacet; + address diamondInit; + address genesisUpgrade; + address defaultUpgrade; + address validatorTimelock; +} + +contract EcosystemUpgrade is Script { + using stdToml for string; + + address internal constant ADDRESS_ONE = 0x0000000000000000000000000000000000000001; + address internal constant DETERMINISTIC_CREATE2_ADDRESS = 0x4e59b44847b379578588920cA78FbF26c0B4956C; + + // solhint-disable-next-line gas-struct-packing + struct DeployedAddresses { + BridgehubDeployedAddresses bridgehub; + StateTransitionDeployedAddresses stateTransition; + BridgesDeployedAddresses bridges; + L1NativeTokenVaultAddresses vaults; + DataAvailabilityDeployedAddresses daAddresses; + ExpectedL2Addresses expectedL2Addresses; + address chainAdmin; + address accessControlRestrictionAddress; + address validatorTimelock; + address gatewayUpgrade; + address create2Factory; + address transitionaryOwner; + address upgradeTimer; + address bytecodesSupplier; + address l2WrappedBaseTokenStore; + address blobVersionedHashRetriever; + } + + struct ExpectedL2Addresses { + address expectedRollupL2DAValidator; + address expectedValidiumL2DAValidator; + address l2SharedBridgeLegacyImpl; + address l2BridgedStandardERC20Impl; + } + + // solhint-disable-next-line gas-struct-packing + struct L1NativeTokenVaultAddresses { + address l1NativeTokenVaultImplementation; + address l1NativeTokenVaultProxy; + } + + struct DataAvailabilityDeployedAddresses { + address rollupDAManager; + address l1RollupDAValidator; + address l1ValidiumDAValidator; + } + + // solhint-disable-next-line gas-struct-packing + struct BridgehubDeployedAddresses { + address bridgehubImplementation; + address ctmDeploymentTrackerImplementation; + address ctmDeploymentTrackerProxy; + address messageRootImplementation; + address messageRootProxy; + } + + // solhint-disable-next-line gas-struct-packing + struct BridgesDeployedAddresses { + address erc20BridgeImplementation; + address sharedBridgeProxy; + address sharedBridgeImplementation; + address l1NullifierImplementation; + address bridgedStandardERC20Implementation; + address bridgedTokenBeacon; + } + + // solhint-disable-next-line gas-struct-packing + struct Config { + uint256 l1ChainId; + address deployerAddress; + uint256 eraChainId; + address ownerAddress; + // This is the address of the ecosystem admin. + // Note, that it is not the owner, but rather the address that is responsible + // for facilitating partially trusted, but not critical tasks. + address ecosystemAdminAddress; + bool testnetVerifier; + uint256 governanceUpgradeTimerInitialDelay; + ContractsConfig contracts; + TokensConfig tokens; + } + + // solhint-disable-next-line gas-struct-packing + struct GeneratedData { + bytes forceDeploymentsData; + bytes diamondCutData; + } + + // solhint-disable-next-line gas-struct-packing + struct ContractsConfig { + bytes32 create2FactorySalt; + address create2FactoryAddr; + uint256 validatorTimelockExecutionDelay; + bytes32 genesisRoot; + uint256 genesisRollupLeafIndex; + bytes32 genesisBatchCommitment; + bytes32 recursionNodeLevelVkHash; + bytes32 recursionLeafLevelVkHash; + bytes32 recursionCircuitsSetVksHash; + uint256 priorityTxMaxGasLimit; + PubdataPricingMode diamondInitPubdataPricingMode; + uint256 diamondInitBatchOverheadL1Gas; + uint256 diamondInitMaxPubdataPerBatch; + uint256 diamondInitMaxL2GasPerBatch; + uint256 diamondInitPriorityTxMaxPubdata; + uint256 diamondInitMinimalL2GasPrice; + uint256 maxNumberOfChains; + bytes32 bootloaderHash; + bytes32 defaultAAHash; + address oldValidatorTimelock; + address legacyErc20BridgeAddress; + address bridgehubProxyAddress; + address oldSharedBridgeProxyAddress; + address stateTransitionManagerAddress; + address transparentProxyAdmin; + address eraDiamondProxy; + } + + struct TokensConfig { + address tokenWethAddress; + } + + Config internal config; + GeneratedData internal generatedData; + DeployedAddresses internal addresses; + + uint256[] factoryDepsHashes; + + struct CachedBytecodeHashes { + bytes32 sharedL2LegacyBridgeBytecodeHash; + bytes32 erc20StandardImplBytecodeHash; + bytes32 rollupL2DAValidatorBytecodeHash; + bytes32 validiumL2DAValidatorBytecodeHash; + bytes32 transparentUpgradableProxyBytecodeHash; + } + + CachedBytecodeHashes internal cachedBytecodeHashes; + + function prepareEcosystemContracts(string memory configPath, string memory outputPath) public { + string memory root = vm.projectRoot(); + configPath = string.concat(root, configPath); + outputPath = string.concat(root, outputPath); + + initializeConfig(configPath); + + instantiateCreate2Factory(); + + deployBytecodesSupplier(); + publishBytecodes(); + initializeExpectedL2Addresses(); + + deployBlobVersionedHashRetriever(); + deployVerifier(); + deployDefaultUpgrade(); + deployGenesisUpgrade(); + deployGatewayUpgrade(); + + deployDAValidators(); + deployValidatorTimelock(); + + deployBridgehubImplementation(); + deployMessageRootContract(); + + deployL1NullifierContracts(); + deploySharedBridgeContracts(); + deployBridgedStandardERC20Implementation(); + deployBridgedTokenBeacon(); + deployL1NativeTokenVaultImplementation(); + deployL1NativeTokenVaultProxy(); + deployErc20BridgeImplementation(); + + deployCTMDeploymentTracker(); + + // Important, this must come after the initializeExpectedL2Addresses + initializeGeneratedData(); + + deployChainTypeManagerContract(); + setChainTypeManagerInValidatorTimelock(); + + deployTransitionaryOwner(); + deployL2WrappedBaseTokenStore(); + deployGovernanceUpgradeTimer(); + + updateOwners(); + + saveOutput(outputPath); + } + + function run() public { + prepareEcosystemContracts( + "/script-config/gateway-upgrade-ecosystem.toml", + "/script-out/gateway-upgrade-ecosystem.toml" + ); + } + + function provideAcceptOwnershipCalls() public returns (Call[] memory calls) { + console.log("Providing accept ownership calls"); + + calls = new Call[](4); + calls[0] = Call({ + target: addresses.validatorTimelock, + data: abi.encodeCall(Ownable2StepUpgradeable.acceptOwnership, ()), + value: 0 + }); + calls[1] = Call({ + target: addresses.bridges.sharedBridgeProxy, + data: abi.encodeCall(Ownable2StepUpgradeable.acceptOwnership, ()), + value: 0 + }); + calls[2] = Call({ + target: addresses.bridgehub.ctmDeploymentTrackerProxy, + data: abi.encodeCall(Ownable2StepUpgradeable.acceptOwnership, ()), + value: 0 + }); + calls[3] = Call({ + target: addresses.daAddresses.rollupDAManager, + data: abi.encodeCall(Ownable2StepUpgradeable.acceptOwnership, ()), + value: 0 + }); + } + + function getOwnerAddress() public returns (address) { + return config.ownerAddress; + } + + function _getFacetCutsForDeletion() internal returns (Diamond.FacetCut[] memory facetCuts) { + IZKChain.Facet[] memory facets = IZKChain(config.contracts.eraDiamondProxy).facets(); + + // Freezability does not matter when deleting, so we just put false everywhere + facetCuts = new Diamond.FacetCut[](facets.length); + for (uint i = 0; i < facets.length; i++) { + facetCuts[i] = Diamond.FacetCut({ + facet: address(0), + action: Diamond.Action.Remove, + isFreezable: false, + selectors: facets[i].selectors + }); + } + } + + function _composeUpgradeTx() internal returns (L2CanonicalTransaction memory transaction) { + transaction = L2CanonicalTransaction({ + // TODO: dont use hardcoded values + txType: 254, + from: uint256(uint160(L2_FORCE_DEPLOYER_ADDR)), + // Note, that we actually do force deployments to the ContractDeployer and not complex upgrader. + // The implementation of the ComplexUpgrader will be deployed during one of the force deployments. + to: uint256(uint160(address(L2_DEPLOYER_SYSTEM_CONTRACT_ADDR))), + gasLimit: 72_000_000, + gasPerPubdataByteLimit: 800, + maxFeePerGas: 0, + maxPriorityFeePerGas: 0, + paymaster: uint256(uint160(address(0))), + nonce: getProtocolUpgradeNonce(), + value: 0, + reserved: [uint256(0), uint256(0), uint256(0), uint256(0)], + // Note, that the data is empty, it will be fully composed inside the `GatewayUpgrade` contract + data: new bytes(0), + signature: new bytes(0), + // All factory deps should've been published before + factoryDeps: factoryDepsHashes, + paymasterInput: new bytes(0), + // 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: new bytes(0) + }); + } + + function getNewProtocolVersion() public returns (uint256) { + return 0x1a00000000; + } + + function getProtocolUpgradeNonce() public returns (uint256) { + return (getNewProtocolVersion() >> 32); + } + + function getOldProtocolDeadline() public returns (uint256) { + // Note, that it is this way by design, on stage2 it + // will be set to 0 + return type(uint256).max; + } + + function getOldProtocolVersion() public returns (uint256) { + // Mainnet is the only network that has not been upgraded. + if (block.chainid == 1) { + return 0x1800000002; + } else { + return 0x1900000000; + } + } + + function provideSetNewVersionUpgradeCall() public returns (Call[] memory calls) { + // Just retrieved it from the contract + uint256 PREVIOUS_PROTOCOL_VERSION = getOldProtocolVersion(); + uint256 DEADLINE = getOldProtocolDeadline(); + uint256 NEW_PROTOCOL_VERSION = getNewProtocolVersion(); + Call memory ctmCall = Call({ + target: config.contracts.stateTransitionManagerAddress, + data: abi.encodeCall( + ChainTypeManager.setNewVersionUpgrade, + (getChainUpgradeInfo(), PREVIOUS_PROTOCOL_VERSION, DEADLINE, NEW_PROTOCOL_VERSION) + ), + value: 0 + }); + + // The call that will start the timer till the end of the upgrade. + Call memory timerCall = Call({ + target: addresses.upgradeTimer, + data: abi.encodeCall(GovernanceUpgradeTimer.startTimer, ()), + value: 0 + }); + + calls = new Call[](2); + calls[0] = ctmCall; + calls[1] = timerCall; + } + + function getChainUpgradeInfo() public returns (Diamond.DiamondCutData memory upgradeCutData) { + Diamond.FacetCut[] memory deletedFacets = _getFacetCutsForDeletion(); + + Diamond.FacetCut[] memory facetCuts = new Diamond.FacetCut[](deletedFacets.length + 4); + for (uint i = 0; i < deletedFacets.length; i++) { + facetCuts[i] = deletedFacets[i]; + } + facetCuts[deletedFacets.length] = Diamond.FacetCut({ + facet: addresses.stateTransition.adminFacet, + action: Diamond.Action.Add, + isFreezable: false, + selectors: Utils.getAllSelectors(addresses.stateTransition.adminFacet.code) + }); + facetCuts[deletedFacets.length + 1] = Diamond.FacetCut({ + facet: addresses.stateTransition.gettersFacet, + action: Diamond.Action.Add, + isFreezable: false, + selectors: Utils.getAllSelectors(addresses.stateTransition.gettersFacet.code) + }); + facetCuts[deletedFacets.length + 2] = Diamond.FacetCut({ + facet: addresses.stateTransition.mailboxFacet, + action: Diamond.Action.Add, + isFreezable: true, + selectors: Utils.getAllSelectors(addresses.stateTransition.mailboxFacet.code) + }); + facetCuts[deletedFacets.length + 3] = Diamond.FacetCut({ + facet: addresses.stateTransition.executorFacet, + action: Diamond.Action.Add, + isFreezable: true, + selectors: Utils.getAllSelectors(addresses.stateTransition.executorFacet.code) + }); + + VerifierParams memory verifierParams = VerifierParams({ + recursionNodeLevelVkHash: config.contracts.recursionNodeLevelVkHash, + recursionLeafLevelVkHash: config.contracts.recursionLeafLevelVkHash, + recursionCircuitsSetVksHash: config.contracts.recursionCircuitsSetVksHash + }); + + IL2ContractDeployer.ForceDeployment[] memory baseForceDeployments = SystemContractsProcessing + .getBaseForceDeployments(); + + // This upgrade has complex upgrade. We do not know whether its implementation has been deployed. + // We will do the following trick: + // - Deploy the upgrade implementation into the address of the complex upgrader. And execute the upgrade inside the constructor. + // - Deploy back the original bytecode. + // + // Also, we need to predeploy the bridges implementation + IL2ContractDeployer.ForceDeployment[] + memory additionalForceDeployments = new IL2ContractDeployer.ForceDeployment[](6); + additionalForceDeployments[0] = IL2ContractDeployer.ForceDeployment({ + bytecodeHash: cachedBytecodeHashes.sharedL2LegacyBridgeBytecodeHash, + newAddress: addresses.expectedL2Addresses.l2SharedBridgeLegacyImpl, + callConstructor: true, + value: 0, + input: "" + }); + additionalForceDeployments[1] = IL2ContractDeployer.ForceDeployment({ + bytecodeHash: cachedBytecodeHashes.erc20StandardImplBytecodeHash, + newAddress: addresses.expectedL2Addresses.l2BridgedStandardERC20Impl, + callConstructor: true, + value: 0, + input: "" + }); + additionalForceDeployments[2] = IL2ContractDeployer.ForceDeployment({ + bytecodeHash: cachedBytecodeHashes.rollupL2DAValidatorBytecodeHash, + newAddress: addresses.expectedL2Addresses.expectedRollupL2DAValidator, + callConstructor: true, + value: 0, + input: "" + }); + additionalForceDeployments[3] = IL2ContractDeployer.ForceDeployment({ + bytecodeHash: cachedBytecodeHashes.validiumL2DAValidatorBytecodeHash, + newAddress: addresses.expectedL2Addresses.expectedValidiumL2DAValidator, + callConstructor: true, + value: 0, + input: "" + }); + additionalForceDeployments[4] = IL2ContractDeployer.ForceDeployment({ + bytecodeHash: L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readGatewayUpgradeBytecode()), + newAddress: L2_COMPLEX_UPGRADER_ADDR, + callConstructor: true, + value: 0, + input: "" + }); + // Getting the contract back to normal + additionalForceDeployments[5] = IL2ContractDeployer.ForceDeployment({ + bytecodeHash: L2ContractHelper.hashL2Bytecode(Utils.readSystemContractsBytecode("ComplexUpgrader")), + newAddress: L2_COMPLEX_UPGRADER_ADDR, + callConstructor: false, + value: 0, + input: "" + }); + + IL2ContractDeployer.ForceDeployment[] memory forceDeployments = SystemContractsProcessing.mergeForceDeployments( + baseForceDeployments, + additionalForceDeployments + ); + + address ctmDeployer = addresses.bridgehub.ctmDeploymentTrackerProxy; + + GatewayUpgradeEncodedInput memory gateUpgradeInput = GatewayUpgradeEncodedInput({ + forceDeployments: forceDeployments, + l2GatewayUpgradePosition: forceDeployments.length - 2, + ctmDeployer: ctmDeployer, + fixedForceDeploymentsData: generatedData.forceDeploymentsData, + oldValidatorTimelock: config.contracts.oldValidatorTimelock, + newValidatorTimelock: addresses.validatorTimelock, + wrappedBaseTokenStore: addresses.l2WrappedBaseTokenStore + }); + + bytes memory postUpgradeCalldata = abi.encode(gateUpgradeInput); + + ProposedUpgrade memory proposedUpgrade = ProposedUpgrade({ + l2ProtocolUpgradeTx: _composeUpgradeTx(), + bootloaderHash: config.contracts.bootloaderHash, + defaultAccountHash: config.contracts.defaultAAHash, + verifier: addresses.stateTransition.verifier, + verifierParams: verifierParams, + l1ContractsUpgradeCalldata: new bytes(0), + postUpgradeCalldata: postUpgradeCalldata, + upgradeTimestamp: 0, + newProtocolVersion: getNewProtocolVersion() + }); + + upgradeCutData = Diamond.DiamondCutData({ + facetCuts: facetCuts, + initAddress: addresses.gatewayUpgrade, + initCalldata: abi.encodeCall(GatewayUpgrade.upgrade, (proposedUpgrade)) + }); + } + + function getEcosystemAdmin() external returns (address) { + return config.ecosystemAdminAddress; + } + + function getStage1UpgradeCalls() public returns (Call[] memory calls) { + // Stage 1 of the upgrade: + // - accept all the ownerships of the contracts + // - set the new upgrade data for chains + update validator timelock. + calls = mergeCalls(provideAcceptOwnershipCalls(), provideSetNewVersionUpgradeCall()); + } + + function getStage2UpgradeCalls() public returns (Call[] memory calls) { + calls = new Call[](11); + + // We need to firstly update all the contracts + calls[0] = Call({ + target: config.contracts.transparentProxyAdmin, + data: abi.encodeCall( + ProxyAdmin.upgrade, + ( + ITransparentUpgradeableProxy(payable(config.contracts.stateTransitionManagerAddress)), + addresses.stateTransition.chainTypeManagerImplementation + ) + ), + value: 0 + }); + calls[1] = Call({ + target: config.contracts.transparentProxyAdmin, + data: abi.encodeCall( + ProxyAdmin.upgradeAndCall, + ( + ITransparentUpgradeableProxy(payable(config.contracts.bridgehubProxyAddress)), + addresses.bridgehub.bridgehubImplementation, + abi.encodeCall(Bridgehub.initializeV2, ()) + ) + ), + value: 0 + }); + calls[2] = Call({ + target: config.contracts.transparentProxyAdmin, + data: abi.encodeCall( + ProxyAdmin.upgrade, + ( + // Note, that we do not need to run the initializer + ITransparentUpgradeableProxy(payable(config.contracts.oldSharedBridgeProxyAddress)), + addresses.bridges.l1NullifierImplementation + ) + ), + value: 0 + }); + calls[3] = Call({ + target: config.contracts.transparentProxyAdmin, + data: abi.encodeCall( + ProxyAdmin.upgrade, + ( + ITransparentUpgradeableProxy(payable(config.contracts.legacyErc20BridgeAddress)), + addresses.bridges.erc20BridgeImplementation + ) + ), + value: 0 + }); + + // Now, updating chain creation params + calls[4] = Call({ + target: config.contracts.stateTransitionManagerAddress, + data: abi.encodeCall(ChainTypeManager.setChainCreationParams, (prepareNewChainCreationParams())), + value: 0 + }); + calls[5] = Call({ + target: config.contracts.stateTransitionManagerAddress, + data: abi.encodeCall(ChainTypeManager.setValidatorTimelock, (addresses.validatorTimelock)), + value: 0 + }); + + // Now, we need to update the bridgehub + calls[6] = Call({ + target: config.contracts.bridgehubProxyAddress, + data: abi.encodeCall( + Bridgehub.setAddresses, + ( + addresses.bridges.sharedBridgeProxy, + CTMDeploymentTracker(addresses.bridgehub.ctmDeploymentTrackerProxy), + MessageRoot(addresses.bridgehub.messageRootProxy) + ) + ), + value: 0 + }); + + // Setting the necessary params for the L1Nullifier contract + calls[7] = Call({ + target: config.contracts.oldSharedBridgeProxyAddress, + data: abi.encodeCall( + L1Nullifier.setL1NativeTokenVault, + (L1NativeTokenVault(payable(addresses.vaults.l1NativeTokenVaultProxy))) + ), + value: 0 + }); + calls[8] = Call({ + target: config.contracts.oldSharedBridgeProxyAddress, + data: abi.encodeCall(L1Nullifier.setL1AssetRouter, (addresses.bridges.sharedBridgeProxy)), + value: 0 + }); + calls[9] = Call({ + target: config.contracts.stateTransitionManagerAddress, + // Making the old protocol version no longer invalid + data: abi.encodeCall(ChainTypeManager.setProtocolVersionDeadline, (getOldProtocolVersion(), 0)), + value: 0 + }); + calls[10] = Call({ + target: addresses.upgradeTimer, + // Double checking that the deadline has passed. + data: abi.encodeCall(GovernanceUpgradeTimer.checkDeadline, ()), + value: 0 + }); + } + + function initializeConfig(string memory configPath) internal { + string memory toml = vm.readFile(configPath); + + config.l1ChainId = block.chainid; + config.deployerAddress = msg.sender; + + // Config file must be parsed key by key, otherwise values returned + // are parsed alfabetically and not by key. + // https://book.getfoundry.sh/cheatcodes/parse-toml + config.eraChainId = toml.readUint("$.era_chain_id"); + config.ownerAddress = toml.readAddress("$.owner_address"); + config.testnetVerifier = toml.readBool("$.testnet_verifier"); + + config.contracts.maxNumberOfChains = toml.readUint("$.contracts.max_number_of_chains"); + config.contracts.create2FactorySalt = toml.readBytes32("$.contracts.create2_factory_salt"); + if (vm.keyExistsToml(toml, "$.contracts.create2_factory_addr")) { + config.contracts.create2FactoryAddr = toml.readAddress("$.contracts.create2_factory_addr"); + } + config.contracts.validatorTimelockExecutionDelay = toml.readUint( + "$.contracts.validator_timelock_execution_delay" + ); + config.contracts.genesisRoot = toml.readBytes32("$.contracts.genesis_root"); + config.contracts.genesisRollupLeafIndex = toml.readUint("$.contracts.genesis_rollup_leaf_index"); + config.contracts.genesisBatchCommitment = toml.readBytes32("$.contracts.genesis_batch_commitment"); + config.contracts.recursionNodeLevelVkHash = toml.readBytes32("$.contracts.recursion_node_level_vk_hash"); + config.contracts.recursionLeafLevelVkHash = toml.readBytes32("$.contracts.recursion_leaf_level_vk_hash"); + config.contracts.recursionCircuitsSetVksHash = toml.readBytes32("$.contracts.recursion_circuits_set_vks_hash"); + config.contracts.priorityTxMaxGasLimit = toml.readUint("$.contracts.priority_tx_max_gas_limit"); + config.contracts.diamondInitPubdataPricingMode = PubdataPricingMode( + toml.readUint("$.contracts.diamond_init_pubdata_pricing_mode") + ); + config.contracts.diamondInitBatchOverheadL1Gas = toml.readUint( + "$.contracts.diamond_init_batch_overhead_l1_gas" + ); + config.contracts.diamondInitMaxPubdataPerBatch = toml.readUint( + "$.contracts.diamond_init_max_pubdata_per_batch" + ); + config.contracts.diamondInitMaxL2GasPerBatch = toml.readUint("$.contracts.diamond_init_max_l2_gas_per_batch"); + config.contracts.diamondInitPriorityTxMaxPubdata = toml.readUint( + "$.contracts.diamond_init_priority_tx_max_pubdata" + ); + config.contracts.diamondInitMinimalL2GasPrice = toml.readUint("$.contracts.diamond_init_minimal_l2_gas_price"); + config.contracts.defaultAAHash = toml.readBytes32("$.contracts.default_aa_hash"); + config.contracts.bootloaderHash = toml.readBytes32("$.contracts.bootloader_hash"); + + config.contracts.stateTransitionManagerAddress = toml.readAddress( + "$.contracts.state_transition_manager_address" + ); + config.contracts.bridgehubProxyAddress = toml.readAddress("$.contracts.bridgehub_proxy_address"); + config.contracts.oldSharedBridgeProxyAddress = toml.readAddress("$.contracts.old_shared_bridge_proxy_address"); + config.contracts.transparentProxyAdmin = toml.readAddress("$.contracts.transparent_proxy_admin"); + config.contracts.eraDiamondProxy = toml.readAddress("$.contracts.era_diamond_proxy"); + config.contracts.legacyErc20BridgeAddress = toml.readAddress("$.contracts.legacy_erc20_bridge_address"); + config.contracts.oldValidatorTimelock = toml.readAddress("$.contracts.old_validator_timelock"); + + config.tokens.tokenWethAddress = toml.readAddress("$.tokens.token_weth_address"); + config.governanceUpgradeTimerInitialDelay = toml.readUint("$.governance_upgrade_timer_initial_delay"); + + config.ecosystemAdminAddress = Bridgehub(config.contracts.bridgehubProxyAddress).admin(); + } + + function deployBlobVersionedHashRetriever() internal { + bytes memory bytecode = hex"600b600b5f39600b5ff3fe5f358049805f5260205ff3"; + address contractAddress = deployViaCreate2(bytecode); + console.log("BlobVersionedHashRetriever deployed at:", contractAddress); + addresses.blobVersionedHashRetriever = contractAddress; + } + + function initializeGeneratedData() internal { + generatedData.forceDeploymentsData = prepareForceDeploymentsData(); + } + + function initializeExpectedL2Addresses() internal { + address aliasedGovernance = AddressAliasHelper.applyL1ToL2Alias(config.ownerAddress); + + addresses.expectedL2Addresses = ExpectedL2Addresses({ + expectedRollupL2DAValidator: Utils.getL2AddressViaCreate2Factory( + bytes32(0), + cachedBytecodeHashes.rollupL2DAValidatorBytecodeHash, + hex"" + ), + expectedValidiumL2DAValidator: Utils.getL2AddressViaCreate2Factory( + bytes32(0), + cachedBytecodeHashes.validiumL2DAValidatorBytecodeHash, + hex"" + ), + l2SharedBridgeLegacyImpl: Utils.getL2AddressViaCreate2Factory( + bytes32(0), + cachedBytecodeHashes.sharedL2LegacyBridgeBytecodeHash, + hex"" + ), + l2BridgedStandardERC20Impl: Utils.getL2AddressViaCreate2Factory( + bytes32(0), + cachedBytecodeHashes.erc20StandardImplBytecodeHash, + hex"" + ) + }); + } + + function instantiateCreate2Factory() internal { + address contractAddress; + + bool isDeterministicDeployed = DETERMINISTIC_CREATE2_ADDRESS.code.length > 0; + bool isConfigured = config.contracts.create2FactoryAddr != address(0); + + if (isConfigured) { + if (config.contracts.create2FactoryAddr.code.length == 0) { + revert AddressHasNoCode(config.contracts.create2FactoryAddr); + } + contractAddress = config.contracts.create2FactoryAddr; + console.log("Using configured Create2Factory address:", contractAddress); + } else if (isDeterministicDeployed) { + contractAddress = DETERMINISTIC_CREATE2_ADDRESS; + console.log("Using deterministic Create2Factory address:", contractAddress); + } else { + contractAddress = Utils.deployCreate2Factory(); + console.log("Create2Factory deployed at:", contractAddress); + } + + addresses.create2Factory = contractAddress; + } + + function deployBytecodesSupplier() internal { + address contractAddress = deployViaCreate2(type(BytecodesSupplier).creationCode); + console.log("BytecodesSupplier deployed at:", contractAddress); + addresses.bytecodesSupplier = contractAddress; + } + + function getFullListOfFactoryDependencies() internal returns (bytes[] memory factoryDeps) { + bytes[] memory basicDependencies = SystemContractsProcessing.getBaseListOfDependencies(); + + // This upgrade will also require to publish: + // - L2GatewayUpgrade + // - new L2 shared bridge legacy implementation + // - new bridged erc20 token implementation + // + // Also, not strictly necessary, but better for consistency with the new chains: + // - UpgradeableBeacon + // - BeaconProxy + + bytes[] memory upgradeSpecificDependencies = new bytes[](8); + upgradeSpecificDependencies[0] = L2ContractsBytecodesLib.readGatewayUpgradeBytecode(); + upgradeSpecificDependencies[1] = L2ContractsBytecodesLib.readL2LegacySharedBridgeBytecode(); + upgradeSpecificDependencies[2] = L2ContractsBytecodesLib.readStandardERC20Bytecode(); + + upgradeSpecificDependencies[3] = L2ContractsBytecodesLib.readUpgradeableBeaconBytecode(); + upgradeSpecificDependencies[4] = L2ContractsBytecodesLib.readBeaconProxyBytecode(); + + // We do not know whether the chain will be a rollup or a validium, just in case, we'll deploy + // both of the validators. + upgradeSpecificDependencies[5] = L2ContractsBytecodesLib.readRollupL2DAValidatorBytecode(); + upgradeSpecificDependencies[6] = L2ContractsBytecodesLib.readNoDAL2DAValidatorBytecode(); + + upgradeSpecificDependencies[7] = L2ContractsBytecodesLib + .readTransparentUpgradeableProxyBytecodeFromSystemContracts(); + + cachedBytecodeHashes = CachedBytecodeHashes({ + sharedL2LegacyBridgeBytecodeHash: L2ContractHelper.hashL2Bytecode(upgradeSpecificDependencies[1]), + erc20StandardImplBytecodeHash: L2ContractHelper.hashL2Bytecode(upgradeSpecificDependencies[2]), + rollupL2DAValidatorBytecodeHash: L2ContractHelper.hashL2Bytecode(upgradeSpecificDependencies[5]), + validiumL2DAValidatorBytecodeHash: L2ContractHelper.hashL2Bytecode(upgradeSpecificDependencies[6]), + transparentUpgradableProxyBytecodeHash: L2ContractHelper.hashL2Bytecode(upgradeSpecificDependencies[7]) + }); + + factoryDeps = SystemContractsProcessing.mergeBytesArrays(basicDependencies, upgradeSpecificDependencies); + factoryDeps = SystemContractsProcessing.deduplicateBytecodes(factoryDeps); + } + + function publishBytecodes() internal { + bytes[] memory allDeps = getFullListOfFactoryDependencies(); + uint256[] memory factoryDeps = new uint256[](allDeps.length); + require(factoryDeps.length <= 64, "Too many deps"); + + BytecodePublisher.publishBytecodesInBatches(BytecodesSupplier(addresses.bytecodesSupplier), allDeps); + + for (uint256 i = 0; i < allDeps.length; i++) { + factoryDeps[i] = uint256(L2ContractHelper.hashL2Bytecode(allDeps[i])); + } + + // Double check for consistency: + require(bytes32(factoryDeps[0]) == config.contracts.bootloaderHash, "bootloader hash factory dep mismatch"); + require(bytes32(factoryDeps[1]) == config.contracts.defaultAAHash, "default aa hash factory dep mismatch"); + + factoryDepsHashes = factoryDeps; + } + + function deployVerifier() internal { + bytes memory code; + if (config.testnetVerifier) { + code = type(TestnetVerifier).creationCode; + } else { + code = type(Verifier).creationCode; + } + address contractAddress = deployViaCreate2(code); + console.log("Verifier deployed at:", contractAddress); + addresses.stateTransition.verifier = contractAddress; + } + + function deployDefaultUpgrade() internal { + address contractAddress = deployViaCreate2(type(DefaultUpgrade).creationCode); + console.log("DefaultUpgrade deployed at:", contractAddress); + addresses.stateTransition.defaultUpgrade = contractAddress; + } + + function deployGenesisUpgrade() internal { + bytes memory bytecode = abi.encodePacked(type(L1GenesisUpgrade).creationCode); + address contractAddress = deployViaCreate2(bytecode); + console.log("GenesisUpgrade deployed at:", contractAddress); + addresses.stateTransition.genesisUpgrade = contractAddress; + } + + function deployGatewayUpgrade() internal { + bytes memory bytecode = abi.encodePacked(type(GatewayUpgrade).creationCode); + address contractAddress = deployViaCreate2(bytecode); + console.log("GatewayUpgrade deployed at:", contractAddress); + addresses.gatewayUpgrade = contractAddress; + } + + function deployDAValidators() internal { + vm.broadcast(msg.sender); + address rollupDAManager = address(new RollupDAManager()); + addresses.daAddresses.rollupDAManager = rollupDAManager; + + address rollupDAValidator = deployViaCreate2(Utils.readRollupDAValidatorBytecode()); + console.log("L1RollupDAValidator deployed at:", rollupDAValidator); + addresses.daAddresses.l1RollupDAValidator = rollupDAValidator; + + address validiumDAValidator = deployViaCreate2(type(ValidiumL1DAValidator).creationCode); + console.log("L1ValidiumDAValidator deployed at:", validiumDAValidator); + addresses.daAddresses.l1ValidiumDAValidator = validiumDAValidator; + + vm.broadcast(msg.sender); + RollupDAManager(rollupDAManager).updateDAPair( + address(rollupDAValidator), + addresses.expectedL2Addresses.expectedRollupL2DAValidator, + true + ); + } + + function deployValidatorTimelock() internal { + uint32 executionDelay = uint32(config.contracts.validatorTimelockExecutionDelay); + bytes memory bytecode = abi.encodePacked( + type(ValidatorTimelock).creationCode, + abi.encode(config.deployerAddress, executionDelay, config.eraChainId) + ); + address contractAddress = deployViaCreate2(bytecode); + console.log("ValidatorTimelock deployed at:", contractAddress); + addresses.validatorTimelock = contractAddress; + } + + function deployBridgehubImplementation() internal { + bytes memory bridgeHubBytecode = abi.encodePacked( + type(Bridgehub).creationCode, + abi.encode(config.l1ChainId, config.ownerAddress, (config.contracts.maxNumberOfChains)) + ); + address bridgehubImplementation = deployViaCreate2(bridgeHubBytecode); + console.log("Bridgehub Implementation deployed at:", bridgehubImplementation); + addresses.bridgehub.bridgehubImplementation = bridgehubImplementation; + } + + function deployMessageRootContract() internal { + bytes memory messageRootBytecode = abi.encodePacked( + type(MessageRoot).creationCode, + abi.encode(config.contracts.bridgehubProxyAddress) + ); + address messageRootImplementation = deployViaCreate2(messageRootBytecode); + console.log("MessageRoot Implementation deployed at:", messageRootImplementation); + addresses.bridgehub.messageRootImplementation = messageRootImplementation; + + bytes memory bytecode = abi.encodePacked( + type(TransparentUpgradeableProxy).creationCode, + abi.encode( + messageRootImplementation, + config.contracts.transparentProxyAdmin, + abi.encodeCall(MessageRoot.initialize, ()) + ) + ); + address messageRootProxy = deployViaCreate2(bytecode); + console.log("Message Root Proxy deployed at:", messageRootProxy); + addresses.bridgehub.messageRootProxy = messageRootProxy; + } + + function deployCTMDeploymentTracker() internal { + bytes memory ctmDTBytecode = abi.encodePacked( + type(CTMDeploymentTracker).creationCode, + abi.encode(config.contracts.bridgehubProxyAddress, addresses.bridges.sharedBridgeProxy) + ); + address ctmDTImplementation = deployViaCreate2(ctmDTBytecode); + console.log("CTM Deployment Tracker Implementation deployed at:", ctmDTImplementation); + addresses.bridgehub.ctmDeploymentTrackerImplementation = ctmDTImplementation; + + bytes memory bytecode = abi.encodePacked( + type(TransparentUpgradeableProxy).creationCode, + abi.encode( + ctmDTImplementation, + config.contracts.transparentProxyAdmin, + abi.encodeCall(CTMDeploymentTracker.initialize, (config.deployerAddress)) + ) + ); + address ctmDTProxy = deployViaCreate2(bytecode); + console.log("CTM Deployment Tracker Proxy deployed at:", ctmDTProxy); + addresses.bridgehub.ctmDeploymentTrackerProxy = ctmDTProxy; + } + + function deployChainTypeManagerContract() internal { + deployStateTransitionDiamondFacets(); + deployChainTypeManagerImplementation(); + } + + function deployStateTransitionDiamondFacets() internal { + address executorFacet = deployViaCreate2( + abi.encodePacked(type(ExecutorFacet).creationCode, abi.encode(config.l1ChainId)) + ); + console.log("ExecutorFacet deployed at:", executorFacet); + addresses.stateTransition.executorFacet = executorFacet; + + address adminFacet = deployViaCreate2( + abi.encodePacked( + type(AdminFacet).creationCode, + abi.encode(config.l1ChainId, addresses.daAddresses.rollupDAManager) + ) + ); + console.log("AdminFacet deployed at:", adminFacet); + addresses.stateTransition.adminFacet = adminFacet; + + address mailboxFacet = deployViaCreate2( + abi.encodePacked(type(MailboxFacet).creationCode, abi.encode(config.eraChainId, config.l1ChainId)) + ); + console.log("MailboxFacet deployed at:", mailboxFacet); + addresses.stateTransition.mailboxFacet = mailboxFacet; + + address gettersFacet = deployViaCreate2(type(GettersFacet).creationCode); + console.log("GettersFacet deployed at:", gettersFacet); + addresses.stateTransition.gettersFacet = gettersFacet; + + address diamondInit = deployViaCreate2(type(DiamondInit).creationCode); + console.log("DiamondInit deployed at:", diamondInit); + addresses.stateTransition.diamondInit = diamondInit; + } + + function deployChainTypeManagerImplementation() internal { + bytes memory bytecode = abi.encodePacked( + type(ChainTypeManager).creationCode, + abi.encode(config.contracts.bridgehubProxyAddress) + ); + address contractAddress = deployViaCreate2(bytecode); + console.log("ChainTypeManagerImplementation deployed at:", contractAddress); + addresses.stateTransition.chainTypeManagerImplementation = contractAddress; + } + + function setChainTypeManagerInValidatorTimelock() internal { + ValidatorTimelock validatorTimelock = ValidatorTimelock(addresses.validatorTimelock); + vm.broadcast(msg.sender); + validatorTimelock.setChainTypeManager(IChainTypeManager(config.contracts.stateTransitionManagerAddress)); + console.log("ChainTypeManager set in ValidatorTimelock"); + } + + function deploySharedBridgeContracts() internal { + deploySharedBridgeImplementation(); + deploySharedBridgeProxy(); + setL1LegacyBridge(); + } + + function deployL1NullifierContracts() internal { + deployL1NullifierImplementation(); + } + + function deployL1NullifierImplementation() internal { + bytes memory bytecode = abi.encodePacked( + type(L1Nullifier).creationCode, + // solhint-disable-next-line func-named-parameters + abi.encode(config.contracts.bridgehubProxyAddress, config.eraChainId, config.contracts.eraDiamondProxy) + ); + address contractAddress = deployViaCreate2(bytecode); + console.log("L1NullifierImplementation deployed at:", contractAddress); + addresses.bridges.l1NullifierImplementation = contractAddress; + } + + function deploySharedBridgeImplementation() internal { + bytes memory bytecode = abi.encodePacked( + type(L1AssetRouter).creationCode, + // solhint-disable-next-line func-named-parameters + abi.encode( + config.tokens.tokenWethAddress, + config.contracts.bridgehubProxyAddress, + config.contracts.oldSharedBridgeProxyAddress, + config.eraChainId, + config.contracts.eraDiamondProxy + ) + ); + address contractAddress = deployViaCreate2(bytecode); + console.log("SharedBridgeImplementation deployed at:", contractAddress); + addresses.bridges.sharedBridgeImplementation = contractAddress; + } + + function deploySharedBridgeProxy() internal { + bytes memory initCalldata = abi.encodeCall(L1AssetRouter.initialize, (config.deployerAddress)); + bytes memory bytecode = abi.encodePacked( + type(TransparentUpgradeableProxy).creationCode, + abi.encode( + addresses.bridges.sharedBridgeImplementation, + config.contracts.transparentProxyAdmin, + initCalldata + ) + ); + address contractAddress = deployViaCreate2(bytecode); + console.log("SharedBridgeProxy deployed at:", contractAddress); + addresses.bridges.sharedBridgeProxy = contractAddress; + } + + function setL1LegacyBridge() internal { + vm.startBroadcast(config.deployerAddress); + L1AssetRouter(addresses.bridges.sharedBridgeProxy).setL1Erc20Bridge( + L1ERC20Bridge(config.contracts.legacyErc20BridgeAddress) + ); + vm.stopBroadcast(); + } + + function deployErc20BridgeImplementation() internal { + bytes memory bytecode = abi.encodePacked( + type(L1ERC20Bridge).creationCode, + abi.encode( + config.contracts.oldSharedBridgeProxyAddress, + addresses.bridges.sharedBridgeProxy, + addresses.vaults.l1NativeTokenVaultProxy, + config.eraChainId + ) + ); + address contractAddress = deployViaCreate2(bytecode); + console.log("Erc20BridgeImplementation deployed at:", contractAddress); + addresses.bridges.erc20BridgeImplementation = contractAddress; + } + + function deployBridgedStandardERC20Implementation() internal { + bytes memory bytecode = abi.encodePacked( + type(BridgedStandardERC20).creationCode, + // solhint-disable-next-line func-named-parameters + abi.encode() + ); + address contractAddress = deployViaCreate2(bytecode); + console.log("BridgedStandardERC20Implementation deployed at:", contractAddress); + addresses.bridges.bridgedStandardERC20Implementation = contractAddress; + } + + function deployBridgedTokenBeacon() internal { + // Note, that the `msg.sender` will be set as the owner. + // This means that we can not use a naive create2factory. It may be replaced + // with a more advanced one, but CREATE from a hot wallet is fine too. + vm.startBroadcast(msg.sender); + UpgradeableBeacon beacon = new UpgradeableBeacon(addresses.bridges.bridgedStandardERC20Implementation); + beacon.transferOwnership(config.ownerAddress); + vm.stopBroadcast(); + address contractAddress = address(beacon); + console.log("BridgedTokenBeacon deployed at:", contractAddress); + addresses.bridges.bridgedTokenBeacon = contractAddress; + } + + function deployL1NativeTokenVaultImplementation() internal { + bytes memory bytecode = abi.encodePacked( + type(L1NativeTokenVault).creationCode, + // solhint-disable-next-line func-named-parameters + abi.encode( + config.tokens.tokenWethAddress, + addresses.bridges.sharedBridgeProxy, + config.contracts.oldSharedBridgeProxyAddress + ) + ); + address contractAddress = deployViaCreate2(bytecode); + console.log("L1NativeTokenVaultImplementation deployed at:", contractAddress); + addresses.vaults.l1NativeTokenVaultImplementation = contractAddress; + } + + function deployL1NativeTokenVaultProxy() internal { + bytes memory initCalldata = abi.encodeCall( + L1NativeTokenVault.initialize, + (config.ownerAddress, addresses.bridges.bridgedTokenBeacon) + ); + bytes memory bytecode = abi.encodePacked( + type(TransparentUpgradeableProxy).creationCode, + abi.encode( + addresses.vaults.l1NativeTokenVaultImplementation, + config.contracts.transparentProxyAdmin, + initCalldata + ) + ); + address contractAddress = deployViaCreate2(bytecode); + console.log("L1NativeTokenVaultProxy deployed at:", contractAddress); + addresses.vaults.l1NativeTokenVaultProxy = contractAddress; + + IL1AssetRouter sharedBridge = IL1AssetRouter(addresses.bridges.sharedBridgeProxy); + IL1Nullifier l1Nullifier = IL1Nullifier(config.contracts.oldSharedBridgeProxyAddress); + + vm.broadcast(msg.sender); + sharedBridge.setNativeTokenVault(INativeTokenVault(addresses.vaults.l1NativeTokenVaultProxy)); + vm.broadcast(msg.sender); + IL1NativeTokenVault(addresses.vaults.l1NativeTokenVaultProxy).registerEthToken(); + } + + function deployTransitionaryOwner() internal { + bytes memory bytecode = abi.encodePacked( + type(TransitionaryOwner).creationCode, + abi.encode(config.ownerAddress) + ); + + addresses.transitionaryOwner = deployViaCreate2(bytecode); + } + + function deployGovernanceUpgradeTimer() internal { + uint256 INITIAL_DELAY = config.governanceUpgradeTimerInitialDelay; + + uint256 MAX_ADDITIONAL_DELAY = 2 weeks; + + // It may make sense to have a separate admin there, but + // using the same as bridgehub is just as fine. + address bridgehubAdmin = Bridgehub(config.contracts.bridgehubProxyAddress).admin(); + + bytes memory bytecode = abi.encodePacked( + type(GovernanceUpgradeTimer).creationCode, + abi.encode(INITIAL_DELAY, MAX_ADDITIONAL_DELAY, config.ownerAddress, config.ecosystemAdminAddress) + ); + + addresses.upgradeTimer = deployViaCreate2(bytecode); + } + + function deployL2WrappedBaseTokenStore() internal { + bytes memory bytecode = abi.encodePacked( + type(L2WrappedBaseTokenStore).creationCode, + abi.encode(config.ownerAddress, config.ecosystemAdminAddress) + ); + + addresses.l2WrappedBaseTokenStore = deployViaCreate2(bytecode); + } + + function _moveGovernanceToOwner(address target) internal { + Ownable2StepUpgradeable(target).transferOwnership(addresses.transitionaryOwner); + TransitionaryOwner(addresses.transitionaryOwner).claimOwnershipAndGiveToGovernance(target); + } + + function _moveGovernanceToEcosystemAdmin(address target) internal { + // Is agile enough to accept ownership quickly `config.ecosystemAdminAddress` + Ownable2StepUpgradeable(target).transferOwnership(config.ecosystemAdminAddress); + } + + function updateOwners() internal { + vm.startBroadcast(msg.sender); + + // Note, that it will take some time for the governance to sign the "acceptOwnership" transaction, + // in order to avoid any possibility of the front-run, we will temporarily give the ownership to the + // contract that can only transfer ownership to the governance. + _moveGovernanceToOwner(addresses.validatorTimelock); + _moveGovernanceToOwner(addresses.bridges.sharedBridgeProxy); + _moveGovernanceToOwner(addresses.bridgehub.ctmDeploymentTrackerProxy); + _moveGovernanceToOwner(addresses.daAddresses.rollupDAManager); + + vm.stopBroadcast(); + console.log("Owners updated"); + } + + function prepareNewChainCreationParams() internal returns (ChainCreationParams memory chainCreationParams) { + Diamond.FacetCut[] memory facetCuts = new Diamond.FacetCut[](4); + facetCuts[0] = Diamond.FacetCut({ + facet: addresses.stateTransition.adminFacet, + action: Diamond.Action.Add, + isFreezable: false, + selectors: Utils.getAllSelectors(addresses.stateTransition.adminFacet.code) + }); + facetCuts[1] = Diamond.FacetCut({ + facet: addresses.stateTransition.gettersFacet, + action: Diamond.Action.Add, + isFreezable: false, + selectors: Utils.getAllSelectors(addresses.stateTransition.gettersFacet.code) + }); + facetCuts[2] = Diamond.FacetCut({ + facet: addresses.stateTransition.mailboxFacet, + action: Diamond.Action.Add, + isFreezable: true, + selectors: Utils.getAllSelectors(addresses.stateTransition.mailboxFacet.code) + }); + facetCuts[3] = Diamond.FacetCut({ + facet: addresses.stateTransition.executorFacet, + action: Diamond.Action.Add, + isFreezable: true, + selectors: Utils.getAllSelectors(addresses.stateTransition.executorFacet.code) + }); + + VerifierParams memory verifierParams = VerifierParams({ + recursionNodeLevelVkHash: config.contracts.recursionNodeLevelVkHash, + recursionLeafLevelVkHash: config.contracts.recursionLeafLevelVkHash, + recursionCircuitsSetVksHash: config.contracts.recursionCircuitsSetVksHash + }); + + FeeParams memory feeParams = FeeParams({ + pubdataPricingMode: config.contracts.diamondInitPubdataPricingMode, + batchOverheadL1Gas: uint32(config.contracts.diamondInitBatchOverheadL1Gas), + maxPubdataPerBatch: uint32(config.contracts.diamondInitMaxPubdataPerBatch), + maxL2GasPerBatch: uint32(config.contracts.diamondInitMaxL2GasPerBatch), + priorityTxMaxPubdata: uint32(config.contracts.diamondInitPriorityTxMaxPubdata), + minimalL2GasPrice: uint64(config.contracts.diamondInitMinimalL2GasPrice) + }); + + DiamondInitializeDataNewChain memory initializeData = DiamondInitializeDataNewChain({ + verifier: IVerifier(addresses.stateTransition.verifier), + verifierParams: verifierParams, + l2BootloaderBytecodeHash: config.contracts.bootloaderHash, + l2DefaultAccountBytecodeHash: config.contracts.defaultAAHash, + priorityTxMaxGasLimit: config.contracts.priorityTxMaxGasLimit, + feeParams: feeParams, + blobVersionedHashRetriever: addresses.blobVersionedHashRetriever + }); + + Diamond.DiamondCutData memory diamondCut = Diamond.DiamondCutData({ + facetCuts: facetCuts, + initAddress: addresses.stateTransition.diamondInit, + initCalldata: abi.encode(initializeData) + }); + generatedData.diamondCutData = abi.encode(diamondCut); + + chainCreationParams = ChainCreationParams({ + genesisUpgrade: addresses.stateTransition.genesisUpgrade, + genesisBatchHash: config.contracts.genesisRoot, + genesisIndexRepeatedStorageChanges: uint64(config.contracts.genesisRollupLeafIndex), + genesisBatchCommitment: config.contracts.genesisBatchCommitment, + diamondCut: diamondCut, + forceDeploymentsData: generatedData.forceDeploymentsData + }); + } + + function saveOutput(string memory outputPath) internal { + prepareNewChainCreationParams(); + + vm.serializeAddress("bridgehub", "bridgehub_implementation_addr", addresses.bridgehub.bridgehubImplementation); + vm.serializeAddress( + "bridgehub", + "ctm_deployment_tracker_proxy_addr", + addresses.bridgehub.ctmDeploymentTrackerProxy + ); + vm.serializeAddress( + "bridgehub", + "ctm_deployment_tracker_implementation_addr", + addresses.bridgehub.ctmDeploymentTrackerImplementation + ); + vm.serializeAddress("bridgehub", "message_root_proxy_addr", addresses.bridgehub.messageRootProxy); + string memory bridgehub = vm.serializeAddress( + "bridgehub", + "message_root_implementation_addr", + addresses.bridgehub.messageRootImplementation + ); + + // TODO(EVM-744): this has to be renamed to chain type manager + vm.serializeAddress( + "state_transition", + "state_transition_implementation_addr", + addresses.stateTransition.chainTypeManagerImplementation + ); + vm.serializeAddress("state_transition", "verifier_addr", addresses.stateTransition.verifier); + vm.serializeAddress("state_transition", "admin_facet_addr", addresses.stateTransition.adminFacet); + vm.serializeAddress("state_transition", "mailbox_facet_addr", addresses.stateTransition.mailboxFacet); + vm.serializeAddress("state_transition", "executor_facet_addr", addresses.stateTransition.executorFacet); + vm.serializeAddress("state_transition", "getters_facet_addr", addresses.stateTransition.gettersFacet); + vm.serializeAddress("state_transition", "diamond_init_addr", addresses.stateTransition.diamondInit); + vm.serializeAddress("state_transition", "genesis_upgrade_addr", addresses.stateTransition.genesisUpgrade); + string memory stateTransition = vm.serializeAddress( + "state_transition", + "default_upgrade_addr", + addresses.stateTransition.defaultUpgrade + ); + + vm.serializeAddress("bridges", "erc20_bridge_implementation_addr", addresses.bridges.erc20BridgeImplementation); + vm.serializeAddress("bridges", "l1_nullifier_implementation_addr", addresses.bridges.l1NullifierImplementation); + vm.serializeAddress( + "bridges", + "shared_bridge_implementation_addr", + addresses.bridges.sharedBridgeImplementation + ); + string memory bridges = vm.serializeAddress( + "bridges", + "shared_bridge_proxy_addr", + addresses.bridges.sharedBridgeProxy + ); + + vm.serializeUint( + "contracts_config", + "diamond_init_max_l2_gas_per_batch", + config.contracts.diamondInitMaxL2GasPerBatch + ); + vm.serializeUint( + "contracts_config", + "diamond_init_batch_overhead_l1_gas", + config.contracts.diamondInitBatchOverheadL1Gas + ); + vm.serializeUint( + "contracts_config", + "diamond_init_max_pubdata_per_batch", + config.contracts.diamondInitMaxPubdataPerBatch + ); + vm.serializeUint( + "contracts_config", + "diamond_init_minimal_l2_gas_price", + config.contracts.diamondInitMinimalL2GasPrice + ); + vm.serializeUint( + "contracts_config", + "diamond_init_priority_tx_max_pubdata", + config.contracts.diamondInitPriorityTxMaxPubdata + ); + vm.serializeUint( + "contracts_config", + "diamond_init_pubdata_pricing_mode", + uint256(config.contracts.diamondInitPubdataPricingMode) + ); + vm.serializeUint("contracts_config", "priority_tx_max_gas_limit", config.contracts.priorityTxMaxGasLimit); + vm.serializeBytes32( + "contracts_config", + "recursion_circuits_set_vks_hash", + config.contracts.recursionCircuitsSetVksHash + ); + vm.serializeBytes32( + "contracts_config", + "recursion_leaf_level_vk_hash", + config.contracts.recursionLeafLevelVkHash + ); + vm.serializeBytes32( + "contracts_config", + "recursion_node_level_vk_hash", + config.contracts.recursionNodeLevelVkHash + ); + + vm.serializeAddress( + "contracts_config", + "expected_rollup_l2_da_validator", + addresses.expectedL2Addresses.expectedRollupL2DAValidator + ); + vm.serializeAddress( + "contracts_config", + "expected_validium_l2_da_validator", + addresses.expectedL2Addresses.expectedValidiumL2DAValidator + ); + vm.serializeBytes("contracts_config", "diamond_cut_data", generatedData.diamondCutData); + + string memory contractsConfig = vm.serializeBytes( + "contracts_config", + "force_deployments_data", + generatedData.forceDeploymentsData + ); + + vm.serializeAddress("deployed_addresses", "validator_timelock_addr", addresses.validatorTimelock); + vm.serializeAddress("deployed_addresses", "chain_admin", addresses.chainAdmin); + vm.serializeAddress( + "deployed_addresses", + "access_control_restriction_addr", + addresses.accessControlRestrictionAddress + ); + vm.serializeString("deployed_addresses", "bridgehub", bridgehub); + vm.serializeString("deployed_addresses", "bridges", bridges); + vm.serializeString("deployed_addresses", "state_transition", stateTransition); + + vm.serializeAddress( + "deployed_addresses", + "rollup_l1_da_validator_addr", + addresses.daAddresses.l1RollupDAValidator + ); + vm.serializeAddress( + "deployed_addresses", + "validium_l1_da_validator_addr", + addresses.daAddresses.l1ValidiumDAValidator + ); + vm.serializeAddress("deployed_addresses", "l1_bytecodes_supplier_addr", addresses.bytecodesSupplier); + vm.serializeAddress( + "deployed_addresses", + "l2_wrapped_base_token_store_addr", + addresses.l2WrappedBaseTokenStore + ); + + string memory deployedAddresses = vm.serializeAddress( + "deployed_addresses", + "native_token_vault_addr", + addresses.vaults.l1NativeTokenVaultProxy + ); + + vm.serializeAddress("root", "create2_factory_addr", addresses.create2Factory); + vm.serializeBytes32("root", "create2_factory_salt", config.contracts.create2FactorySalt); + vm.serializeUint("root", "l1_chain_id", config.l1ChainId); + vm.serializeUint("root", "era_chain_id", config.eraChainId); + vm.serializeAddress("root", "deployer_addr", config.deployerAddress); + vm.serializeString("root", "deployed_addresses", deployedAddresses); + vm.serializeString("root", "contracts_config", contractsConfig); + + vm.serializeBytes("root", "governance_stage1_calls", abi.encode(getStage1UpgradeCalls())); + vm.serializeBytes("root", "governance_stage2_calls", abi.encode(getStage2UpgradeCalls())); + vm.serializeBytes("root", "chain_upgrade_diamond_cut", abi.encode(getChainUpgradeInfo())); + + string memory toml = vm.serializeAddress("root", "owner_address", config.ownerAddress); + + vm.writeToml(toml, outputPath); + } + + function deployViaCreate2(bytes memory _bytecode) internal returns (address) { + return Utils.deployViaCreate2(_bytecode, config.contracts.create2FactorySalt, addresses.create2Factory); + } + + function prepareForceDeploymentsData() internal view returns (bytes memory) { + require(config.ownerAddress != address(0), "owner not set"); + + FixedForceDeploymentsData memory data = FixedForceDeploymentsData({ + l1ChainId: config.l1ChainId, + eraChainId: config.eraChainId, + l1AssetRouter: addresses.bridges.sharedBridgeProxy, + l2TokenProxyBytecodeHash: L2ContractHelper.hashL2Bytecode( + L2ContractsBytecodesLib.readBeaconProxyBytecode() + ), + aliasedL1Governance: AddressAliasHelper.applyL1ToL2Alias(config.ownerAddress), + maxNumberOfZKChains: config.contracts.maxNumberOfChains, + bridgehubBytecodeHash: L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readBridgehubBytecode()), + l2AssetRouterBytecodeHash: L2ContractHelper.hashL2Bytecode( + L2ContractsBytecodesLib.readL2AssetRouterBytecode() + ), + l2NtvBytecodeHash: L2ContractHelper.hashL2Bytecode( + L2ContractsBytecodesLib.readL2NativeTokenVaultBytecode() + ), + messageRootBytecodeHash: L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readMessageRootBytecode()), + l2SharedBridgeLegacyImpl: addresses.expectedL2Addresses.l2SharedBridgeLegacyImpl, + l2BridgedStandardERC20Impl: addresses.expectedL2Addresses.l2BridgedStandardERC20Impl, + dangerousTestOnlyForcedBeacon: address(0) + }); + + return abi.encode(data); + } + + function mergeCalls(Call[] memory a, Call[] memory b) internal pure returns (Call[] memory result) { + result = new Call[](a.length + b.length); + for (uint256 i = 0; i < a.length; i++) { + result[i] = a[i]; + } + for (uint256 i = 0; i < b.length; i++) { + result[a.length + i] = b[i]; + } + } + + // add this to be excluded from coverage report + function test() internal {} + + function addL2WethToStore( + address storeAddress, + ChainAdmin chainAdmin, + uint256 chainId, + address l2WBaseToken + ) public { + L2WrappedBaseTokenStore l2WrappedBaseTokenStore = L2WrappedBaseTokenStore(storeAddress); + + Call[] memory calls = new Call[](1); + calls[0] = Call({ + target: storeAddress, + value: 0, + data: abi.encodeCall(l2WrappedBaseTokenStore.initializeChain, (chainId, l2WBaseToken)) + }); + + vm.startBroadcast(); + chainAdmin.multicall(calls, true); + vm.stopBroadcast(); + } +} diff --git a/l1-contracts/deploy-scripts/upgrade/FinalizeUpgrade.s.sol b/l1-contracts/deploy-scripts/upgrade/FinalizeUpgrade.s.sol new file mode 100644 index 000000000..6d2eb7c49 --- /dev/null +++ b/l1-contracts/deploy-scripts/upgrade/FinalizeUpgrade.s.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +// solhint-disable no-console, gas-custom-errors + +import {Script, console2 as console} from "forge-std/Script.sol"; +import {stdToml} from "forge-std/StdToml.sol"; +import {Utils, L2_BRIDGEHUB_ADDRESS, L2_ASSET_ROUTER_ADDRESS, L2_NATIVE_TOKEN_VAULT_ADDRESS, L2_MESSAGE_ROOT_ADDRESS} from "../Utils.sol"; +import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; +import {L2ContractsBytecodesLib} from "../L2ContractsBytecodesLib.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {AccessControlRestriction} from "contracts/governance/AccessControlRestriction.sol"; +import {ChainAdmin} from "contracts/governance/ChainAdmin.sol"; +import {Call} from "contracts/governance/Common.sol"; +import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; + +import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; +import {L1NativeTokenVault} from "contracts/bridge/ntv/L1NativeTokenVault.sol"; +import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; +import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; + +/// @notice Script intended to help us finalize the governance upgrade +contract FinalizeUpgrade is Script { + using stdToml for string; + + string constant FINALIZE_UPGRADE_CONFIG_PATH = "/script-config/gateway-finalize-upgrade.toml"; + + struct Config { + address bridgehub; + address l1NativeTokenVault; + } + + Config config; + + function initChains(address bridgehub, uint256[] calldata chains) external { + // TODO: we can optimize it to be done in mutlicall, does not matter + + for (uint256 i = 0; i < chains.length; ++i) { + Bridgehub bh = Bridgehub(bridgehub); + + if (bh.baseTokenAssetId(chains[i]) == bytes32(0)) { + vm.broadcast(); + Bridgehub(bridgehub).registerLegacyChain(chains[i]); + } + } + } + + function initTokens( + address payable l1NativeTokenVault, + address[] calldata tokens, + uint256[] calldata chains + ) external { + // TODO: we can optimize it to be done in mutlicall, does not matter + + L1NativeTokenVault vault = L1NativeTokenVault(l1NativeTokenVault); + address nullifier = address(vault.L1_NULLIFIER()); + + for (uint256 i = 0; i < tokens.length; i++) { + uint256 tokenBalance; + if (vault.assetId(tokens[i]) == bytes32(0)) { + if (tokens[i] != ETH_TOKEN_ADDRESS) { + uint256 balance = IERC20(tokens[i]).balanceOf(nullifier); + if (balance != 0) { + vm.broadcast(); + vault.transferFundsFromSharedBridge(tokens[i]); + } else { + vm.broadcast(); + vault.registerToken(tokens[i]); + } + } else { + vm.broadcast(); + vault.registerEthToken(); + + uint256 balance = address(nullifier).balance; + if (balance != 0) { + vm.broadcast(); + vault.transferFundsFromSharedBridge(tokens[i]); + } + } + } + + // TODO: we need to reduce complexity of this one + for (uint256 j = 0; j < chains.length; j++) { + vm.broadcast(); + vault.updateChainBalancesFromSharedBridge(tokens[i], chains[j]); + } + } + } +} diff --git a/l1-contracts/deploy-scripts/upgrade/SystemContractsProcessing.s.sol b/l1-contracts/deploy-scripts/upgrade/SystemContractsProcessing.s.sol new file mode 100644 index 000000000..38475ad02 --- /dev/null +++ b/l1-contracts/deploy-scripts/upgrade/SystemContractsProcessing.s.sol @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Script, console2 as console} from "forge-std/Script.sol"; +import {Utils, L2_WETH_IMPL_ADDRESS, L2_BRIDGEHUB_ADDRESS, L2_ASSET_ROUTER_ADDRESS, L2_NATIVE_TOKEN_VAULT_ADDRESS, L2_MESSAGE_ROOT_ADDRESS} from "../Utils.sol"; +import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; +import {L2ContractsBytecodesLib} from "../L2ContractsBytecodesLib.sol"; +import {IL2ContractDeployer} from "contracts/common/interfaces/IL2ContractDeployer.sol"; + +// solhint-disable no-console, gas-custom-errors + +/// @notice Enum representing the programming language of the contract +enum Language { + Solidity, + Yul +} + +/// @notice Struct representing a system contract's details +struct SystemContract { + address addr; // Contract address + string codeName; // Name of the contract code + Language lang; // Programming language used +} + +/// @dev The number of built-in contracts that reside within the "system-contracts" folder +uint256 constant SYSTEM_CONTRACTS_COUNT = 27; +/// @dev The number of built-in contracts that reside within the `l1-contracts` folder +uint256 constant OTHER_BUILT_IN_CONTRACTS_COUNT = 5; + +library SystemContractsProcessing { + /// @notice Retrieves the entire list of system contracts as a memory array + /// @dev Note that it does not include all built-in contracts. Rather all those + /// that are based in the `system-contracts` folder. + /// @return An array of SystemContract structs containing all system contracts + function getSystemContracts() public pure returns (SystemContract[] memory) { + // Initialize the in-memory array + SystemContract[] memory systemContracts = new SystemContract[](SYSTEM_CONTRACTS_COUNT); + + // Populate the array with system contract details + // Populate the array with system contract details using named parameters + systemContracts[0] = SystemContract({ + addr: 0x0000000000000000000000000000000000000000, + codeName: "EmptyContract", + lang: Language.Solidity + }); + + systemContracts[1] = SystemContract({ + addr: 0x0000000000000000000000000000000000000001, + codeName: "Ecrecover", + lang: Language.Yul + }); + + systemContracts[2] = SystemContract({ + addr: 0x0000000000000000000000000000000000000002, + codeName: "SHA256", + lang: Language.Yul + }); + + systemContracts[3] = SystemContract({ + addr: 0x0000000000000000000000000000000000000006, + codeName: "EcAdd", + lang: Language.Yul + }); + + systemContracts[4] = SystemContract({ + addr: 0x0000000000000000000000000000000000000007, + codeName: "EcMul", + lang: Language.Yul + }); + + systemContracts[5] = SystemContract({ + addr: 0x0000000000000000000000000000000000000008, + codeName: "EcPairing", + lang: Language.Yul + }); + + systemContracts[6] = SystemContract({ + addr: 0x0000000000000000000000000000000000008001, + codeName: "EmptyContract", + lang: Language.Solidity + }); + + systemContracts[7] = SystemContract({ + addr: 0x0000000000000000000000000000000000008002, + codeName: "AccountCodeStorage", + lang: Language.Solidity + }); + + systemContracts[8] = SystemContract({ + addr: 0x0000000000000000000000000000000000008003, + codeName: "NonceHolder", + lang: Language.Solidity + }); + + systemContracts[9] = SystemContract({ + addr: 0x0000000000000000000000000000000000008004, + codeName: "KnownCodesStorage", + lang: Language.Solidity + }); + + systemContracts[10] = SystemContract({ + addr: 0x0000000000000000000000000000000000008005, + codeName: "ImmutableSimulator", + lang: Language.Solidity + }); + + systemContracts[11] = SystemContract({ + addr: 0x0000000000000000000000000000000000008006, + codeName: "ContractDeployer", + lang: Language.Solidity + }); + + systemContracts[12] = SystemContract({ + addr: 0x0000000000000000000000000000000000008008, + codeName: "L1Messenger", + lang: Language.Solidity + }); + + systemContracts[13] = SystemContract({ + addr: 0x0000000000000000000000000000000000008009, + codeName: "MsgValueSimulator", + lang: Language.Solidity + }); + + systemContracts[14] = SystemContract({ + addr: 0x000000000000000000000000000000000000800A, + codeName: "L2BaseToken", + lang: Language.Solidity + }); + + systemContracts[15] = SystemContract({ + addr: 0x000000000000000000000000000000000000800B, + codeName: "SystemContext", + lang: Language.Solidity + }); + + systemContracts[16] = SystemContract({ + addr: 0x000000000000000000000000000000000000800c, + codeName: "BootloaderUtilities", + lang: Language.Solidity + }); + + systemContracts[17] = SystemContract({ + addr: 0x000000000000000000000000000000000000800d, + codeName: "EventWriter", + lang: Language.Yul + }); + + systemContracts[18] = SystemContract({ + addr: 0x000000000000000000000000000000000000800E, + codeName: "Compressor", + lang: Language.Solidity + }); + + systemContracts[19] = SystemContract({ + addr: 0x000000000000000000000000000000000000800f, + codeName: "ComplexUpgrader", + lang: Language.Solidity + }); + + systemContracts[20] = SystemContract({ + addr: 0x0000000000000000000000000000000000008010, + codeName: "Keccak256", + lang: Language.Yul + }); + + systemContracts[21] = SystemContract({ + addr: 0x0000000000000000000000000000000000008012, + codeName: "CodeOracle", + lang: Language.Yul + }); + + systemContracts[22] = SystemContract({ + addr: 0x0000000000000000000000000000000000000100, + codeName: "P256Verify", + lang: Language.Yul + }); + + systemContracts[23] = SystemContract({ + addr: 0x0000000000000000000000000000000000008011, + codeName: "PubdataChunkPublisher", + lang: Language.Solidity + }); + + systemContracts[24] = SystemContract({ + addr: 0x0000000000000000000000000000000000010000, + codeName: "Create2Factory", + lang: Language.Solidity + }); + systemContracts[25] = SystemContract({ + addr: 0x0000000000000000000000000000000000010001, + codeName: "L2GenesisUpgrade", + lang: Language.Solidity + }); + systemContracts[26] = SystemContract({ + addr: 0x0000000000000000000000000000000000010006, + codeName: "SloadContract", + lang: Language.Solidity + }); + return systemContracts; + } + + /// @notice Deduplicates the array of bytecodes. + function deduplicateBytecodes(bytes[] memory input) internal returns (bytes[] memory output) { + // A more efficient way would be to sort + deduplicate, but + // there is no built-in sorting in Solidity + this function should be only + // used in scripts, so ineffiency is fine. + // We'll do it on O(N^2) + + // In O(N^2) we'll mark duplicated hashes as zeroes. + bytes32[] memory hashes = new bytes32[](input.length); + for (uint256 i = 0; i < input.length; i++) { + hashes[i] = keccak256(input[i]); + } + + uint256 toInclude = 0; + + for (uint256 i = 0; i < hashes.length; i++) { + if (hashes[i] != bytes32(0)) { + toInclude += 1; + } + + for (uint j = i + 1; j < hashes.length; j++) { + if (hashes[i] == hashes[j]) { + hashes[j] = bytes32(0); + } + } + } + + output = new bytes[](toInclude); + uint256 included = 0; + for (uint256 i = 0; i < input.length; i++) { + if (hashes[i] != bytes32(0)) { + output[included] = input[i]; + ++included; + } + } + + // Sanity check + require(included == toInclude, "Internal error: included != toInclude"); + } + + function getSystemContractsBytecodes() internal returns (bytes[] memory result) { + result = new bytes[](SYSTEM_CONTRACTS_COUNT); + + SystemContract[] memory systemContracts = getSystemContracts(); + for (uint256 i = 0; i < SYSTEM_CONTRACTS_COUNT; i++) { + if (systemContracts[i].lang == Language.Solidity) { + result[i] = Utils.readSystemContractsBytecode(systemContracts[i].codeName); + } else { + result[i] = Utils.readPrecompileBytecode(systemContracts[i].codeName); + } + } + } + + function getSystemContractsForceDeployments() + internal + returns (IL2ContractDeployer.ForceDeployment[] memory forceDeployments) + { + forceDeployments = new IL2ContractDeployer.ForceDeployment[](SYSTEM_CONTRACTS_COUNT); + + SystemContract[] memory systemContracts = getSystemContracts(); + bytes[] memory bytecodes = getSystemContractsBytecodes(); + for (uint256 i = 0; i < SYSTEM_CONTRACTS_COUNT; i++) { + forceDeployments[i] = IL2ContractDeployer.ForceDeployment({ + bytecodeHash: L2ContractHelper.hashL2Bytecode(bytecodes[i]), + newAddress: systemContracts[i].addr, + callConstructor: false, + value: 0, + input: "" + }); + } + } + + function getOtherContractsBytecodes() internal view returns (bytes[] memory result) { + result = new bytes[](OTHER_BUILT_IN_CONTRACTS_COUNT); + + result[0] = L2ContractsBytecodesLib.readBridgehubBytecode(); + result[1] = L2ContractsBytecodesLib.readL2AssetRouterBytecode(); + result[2] = L2ContractsBytecodesLib.readL2NativeTokenVaultBytecode(); + result[3] = L2ContractsBytecodesLib.readMessageRootBytecode(); + result[4] = L2ContractsBytecodesLib.readL2WrappedBaseToken(); + } + + /// Note, that while proper initialization may require multiple steps, + /// those will be conducted inside a specialized upgrade. We still provide + /// these force deployments here for the sake of consistency + function getOtherBuiltinForceDeployments() + internal + returns (IL2ContractDeployer.ForceDeployment[] memory forceDeployments) + { + forceDeployments = new IL2ContractDeployer.ForceDeployment[](OTHER_BUILT_IN_CONTRACTS_COUNT); + bytes[] memory bytecodes = getOtherContractsBytecodes(); + + forceDeployments[0] = IL2ContractDeployer.ForceDeployment({ + bytecodeHash: L2ContractHelper.hashL2Bytecode(bytecodes[0]), + newAddress: L2_BRIDGEHUB_ADDRESS, + callConstructor: false, + value: 0, + input: "" + }); + forceDeployments[1] = IL2ContractDeployer.ForceDeployment({ + bytecodeHash: L2ContractHelper.hashL2Bytecode(bytecodes[1]), + newAddress: L2_ASSET_ROUTER_ADDRESS, + callConstructor: false, + value: 0, + input: "" + }); + forceDeployments[2] = IL2ContractDeployer.ForceDeployment({ + bytecodeHash: L2ContractHelper.hashL2Bytecode(bytecodes[2]), + newAddress: L2_NATIVE_TOKEN_VAULT_ADDRESS, + callConstructor: false, + value: 0, + input: "" + }); + forceDeployments[3] = IL2ContractDeployer.ForceDeployment({ + bytecodeHash: L2ContractHelper.hashL2Bytecode(bytecodes[3]), + newAddress: L2_MESSAGE_ROOT_ADDRESS, + callConstructor: false, + value: 0, + input: "" + }); + forceDeployments[4] = IL2ContractDeployer.ForceDeployment({ + bytecodeHash: L2ContractHelper.hashL2Bytecode(bytecodes[4]), + newAddress: L2_WETH_IMPL_ADDRESS, + callConstructor: false, + value: 0, + input: "" + }); + } + + function forceDeploymentsToHashes( + IL2ContractDeployer.ForceDeployment[] memory baseForceDeployments + ) internal returns (bytes32[] memory hashes) { + hashes = new bytes32[](baseForceDeployments.length); + for (uint256 i = 0; i < baseForceDeployments.length; i++) { + hashes[i] = baseForceDeployments[i].bytecodeHash; + } + } + + function mergeForceDeployments( + IL2ContractDeployer.ForceDeployment[] memory left, + IL2ContractDeployer.ForceDeployment[] memory right + ) internal returns (IL2ContractDeployer.ForceDeployment[] memory forceDeployments) { + forceDeployments = new IL2ContractDeployer.ForceDeployment[](left.length + right.length); + for (uint256 i = 0; i < left.length; i++) { + forceDeployments[i] = left[i]; + } + for (uint256 i = 0; i < right.length; i++) { + forceDeployments[left.length + i] = right[i]; + } + } + + function mergeBytesArrays(bytes[] memory left, bytes[] memory right) internal returns (bytes[] memory result) { + result = new bytes[](left.length + right.length); + for (uint256 i = 0; i < left.length; i++) { + result[i] = left[i]; + } + for (uint256 i = 0; i < right.length; i++) { + result[left.length + i] = right[i]; + } + } + + function getBaseForceDeployments() + internal + returns (IL2ContractDeployer.ForceDeployment[] memory forceDeployments) + { + IL2ContractDeployer.ForceDeployment[] memory otherForceDeployments = getOtherBuiltinForceDeployments(); + IL2ContractDeployer.ForceDeployment[] memory systemForceDeployments = getSystemContractsForceDeployments(); + + forceDeployments = mergeForceDeployments(systemForceDeployments, otherForceDeployments); + } + + function getBaseListOfDependencies() internal returns (bytes[] memory factoryDeps) { + // Note that it is *important* that these go first in this exact order, + // since the server will rely on it. + bytes[] memory bootloaderAndDefaultAABytecodes = new bytes[](2); + bootloaderAndDefaultAABytecodes[0] = Utils.getBatchBootloaderBytecodeHash(); + bootloaderAndDefaultAABytecodes[1] = Utils.readSystemContractsBytecode("DefaultAccount"); + + bytes[] memory systemBytecodes = getSystemContractsBytecodes(); + bytes[] memory otherBytecodes = getOtherContractsBytecodes(); + + factoryDeps = mergeBytesArrays( + mergeBytesArrays(bootloaderAndDefaultAABytecodes, systemBytecodes), + otherBytecodes + ); + } +} diff --git a/l1-contracts/foundry.toml b/l1-contracts/foundry.toml index 57dd0744a..84614c12c 100644 --- a/l1-contracts/foundry.toml +++ b/l1-contracts/foundry.toml @@ -1,30 +1,45 @@ [profile.default] +allow_paths = ["../l2-contracts/contracts"] src = "contracts" out = "out" -libs = ["lib"] +libs = ["node_modules", "./lib"] cache_path = "cache-forge" test = "test/foundry" solc_version = "0.8.24" evm_version = "cancun" -allow_paths = ["../l2-contracts/contracts"] fs_permissions = [ { access = "read", path = "../system-contracts/bootloader/build/artifacts" }, - { access = "read", path = "../system-contracts/artifacts-zk/contracts-preprocessed" }, + { access = "read", path = "../system-contracts/artifacts-zk/" }, + { access = "read", path = "../system-contracts/contracts-preprocessed/precompiles/artifacts" }, + { access = "read", path = "../system-contracts/contracts-preprocessed/artifacts" }, + { access = "read", path = "../system-contracts/zkout" }, { access = "read", path = "../l2-contracts/artifacts-zk/" }, { access = "read", path = "../l2-contracts/zkout/" }, + { access = "read", path = "../l1-contracts/artifacts-zk/" }, + { access = "read", path = "../da-contracts/" }, { access = "read", path = "../system-contracts/zkout/" }, { access = "read", path = "./script-config" }, { access = "read-write", path = "./script-out" }, - { access = "read", path = "./out" } + { access = "read", path = "./out" }, + { access = "read-write", path = "./test/foundry/l1/integration/deploy-scripts/script-config/" }, + { access = "read-write", path = "./test/foundry/l1/integration/deploy-scripts/script-out/" }, + { access = "read-write", path = "./test/foundry/l1/integration/upgrade-envs/script-config/" }, + { access = "read-write", path = "./test/foundry/l1/integration/upgrade-envs/script-out/" }, + { access = "read", path = "zkout" }, ] ignored_error_codes = ["missing-receive-ether", "code-size"] ignored_warnings_from = ["test", "contracts/dev-contracts"] +suppressed_warnings = ["txorigin"] remappings = [ "forge-std/=lib/forge-std/src/", "murky/=lib/murky/src/", "foundry-test/=test/foundry/", + "l2-contracts/=../l2-contracts/contracts/", "@openzeppelin/contracts-v4/=lib/openzeppelin-contracts-v4/contracts/", "@openzeppelin/contracts-upgradeable-v4/=lib/openzeppelin-contracts-upgradeable-v4/contracts/", ] optimizer = true -optimizer_runs = 9999999 +optimizer_runs = 200 +[profile.default.zksync] +enable_eravm_extensions = true +zksolc = "1.5.7" diff --git a/l1-contracts/hardhat.config.ts b/l1-contracts/hardhat.config.ts index 884dc43d3..931008a31 100644 --- a/l1-contracts/hardhat.config.ts +++ b/l1-contracts/hardhat.config.ts @@ -1,3 +1,4 @@ +import "@matterlabs/hardhat-zksync-solc"; import "@nomiclabs/hardhat-ethers"; import "@nomiclabs/hardhat-etherscan"; import "@nomiclabs/hardhat-waffle"; @@ -12,6 +13,18 @@ if (!process.env.CHAIN_ETH_NETWORK) { require("dotenv").config(); } +// These are L2/ETH networks defined by environment in `dev.env` of zksync-era default development environment +// const DEFAULT_L2_NETWORK = "http://127.0.0.1:3050"; +const DEFAULT_ETH_NETWORK = "http://127.0.0.1:8545"; + +const zkSyncBaseNetworkEnv = + process.env.CONTRACTS_BASE_NETWORK_ZKSYNC === "true" + ? { + ethNetwork: "localL1", + zksync: true, + } + : {}; + export default { defaultNetwork: "env", solidity: { @@ -19,7 +32,7 @@ export default { settings: { optimizer: { enabled: true, - runs: 9999999, + runs: 200, }, outputSelection: { "*": { @@ -28,6 +41,14 @@ export default { }, evmVersion: "cancun", }, + eraVersion: "1.0.1", + }, + zksolc: { + compilerSource: "binary", + version: "1.5.7", + settings: { + isSystem: true, + }, }, contractSizer: { runOnCompile: false, @@ -39,6 +60,7 @@ export default { networks: { env: { url: process.env.ETH_CLIENT_WEB3_URL?.split(",")[0], + ...zkSyncBaseNetworkEnv, }, hardhat: { allowUnlimitedContractSize: false, @@ -47,6 +69,9 @@ export default { enabled: process.env.TEST_CONTRACTS_FORK === "1", }, }, + localL1: { + url: DEFAULT_ETH_NETWORK, + }, }, etherscan: { apiKey: process.env.MISC_ETHERSCAN_API_KEY, diff --git a/l1-contracts/package.json b/l1-contracts/package.json index aaceec20c..813790bdc 100644 --- a/l1-contracts/package.json +++ b/l1-contracts/package.json @@ -53,15 +53,19 @@ "zksync-ethers": "^5.9.0" }, "scripts": { - "build": "hardhat compile", - "clean": "hardhat clean", + "build": "hardhat compile && CONTRACTS_BASE_NETWORK_ZKSYNC=true hardhat compile ", + "build-l1": "hardhat compile", + "build:foundry": "forge build && forge build --zksync --skip '*/l1-contracts/test/*'", + "clean": "hardhat clean && CONTRACTS_BASE_NETWORK_ZKSYNC=true hardhat clean", "clean:foundry": "forge clean", - "test": "hardhat test test/unit_tests/*.spec.ts --network hardhat", - "test:foundry": "forge test", + "test": "yarn workspace da-contracts build && hardhat test test/unit_tests/*.spec.ts --network hardhat", + "test:foundry": "forge test --ffi --match-path 'test/foundry/l1/*' --no-match-test 'test_MainnetFork'", + "test:zkfoundry": "forge script --sig 0x2dd0ebe3 DeployL1Script --ffi && forge test --zksync --match-path 'test/foundry/l2/*'", + "test:mainnet-upgrade-fork": "forge test --match-test test_MainnetFork --ffi --rpc-url $INFURA_MAINNET --gas-limit 2000000000", "test:fork": "TEST_CONTRACTS_FORK=1 yarn run hardhat test test/unit_tests/*.fork.ts --network hardhat", - "coverage:foundry": "forge coverage", + "coverage:foundry": "forge coverage --ffi --match-path 'test/foundry/l1/*' --no-match-coverage 'contracts/(bridge/.*L2.*\\.sol|governance/L2AdminFactory\\.sol)' --no-match-test test_MainnetFork", "deploy-no-build": "ts-node scripts/deploy.ts", - "register-hyperchain": "ts-node scripts/register-hyperchain.ts", + "register-zk-chain": "ts-node scripts/register-zk-chain.ts", "deploy-weth-bridges": "ts-node scripts/deploy-weth-bridges.ts", "initialize-l2-weth-token": "ts-node scripts/initialize-l2-weth-token.ts", "deploy-erc20": "ts-node scripts/deploy-erc20.ts", @@ -77,7 +81,11 @@ "upgrade-system": "ts-node upgrade-system/index.ts", "token-migration": "ts-node scripts/token-migration.ts", "setup-legacy-bridge-era": "ts-node scripts/setup-legacy-bridge-era.ts", - "upgrade-consistency-checker": "ts-node scripts/upgrade-consistency-checker.ts" + "upgrade-consistency-checker": "ts-node scripts/upgrade-consistency-checker.ts", + "governance-accept-ownership": "ts-node scripts/governance-accept-ownership.ts", + "sync-layer": "ts-node scripts/sync-layer.ts", + "size": "hardhat size-contracts", + "errors-lint": "ts-node scripts/errors-lint.ts" }, "dependencies": { "dotenv": "^16.0.3", diff --git a/l1-contracts/remappings.txt b/l1-contracts/remappings.txt new file mode 100644 index 000000000..f869351d3 --- /dev/null +++ b/l1-contracts/remappings.txt @@ -0,0 +1,7 @@ +@ensdomains/=node_modules/@ensdomains/ +ds-test/=lib/forge-std/lib/ds-test/src/ +eth-gas-reporter/=node_modules/eth-gas-reporter/ +forge-std/=lib/forge-std/src/ +hardhat/=node_modules/hardhat/ +murky/=lib/murky/src/ +foundry-test/=test/foundry/ \ No newline at end of file diff --git a/l1-contracts/scripts/deploy-erc20.ts b/l1-contracts/scripts/deploy-erc20.ts index 3dd7be14e..67ddf3a04 100644 --- a/l1-contracts/scripts/deploy-erc20.ts +++ b/l1-contracts/scripts/deploy-erc20.ts @@ -55,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, ethTestConfig.mnemonic); + const result = await mintTokens(tokens, wallet, nonce, [ethTestConfig.test_mnemonic, ethTestConfig.mnemonic]); console.log(JSON.stringify(result, null, 2)); }); diff --git a/l1-contracts/scripts/deploy-withdrawal-helpers.ts b/l1-contracts/scripts/deploy-withdrawal-helpers.ts index 15b9734da..bdbf8ddba 100644 --- a/l1-contracts/scripts/deploy-withdrawal-helpers.ts +++ b/l1-contracts/scripts/deploy-withdrawal-helpers.ts @@ -9,13 +9,14 @@ import { ethers } from "ethers"; import * as fs from "fs"; import * as path from "path"; import { web3Provider } from "./utils"; +import { isCurrentNetworkLocal } from "../src.ts/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" })); async function main() { try { - if (!["test", "localhost"].includes(process.env.CHAIN_ETH_NETWORK)) { + if (!isCurrentNetworkLocal()) { console.error("This deploy script is only for localhost-test network"); process.exit(1); } diff --git a/l1-contracts/scripts/deploy.ts b/l1-contracts/scripts/deploy.ts index b085e6a9e..6f487df52 100644 --- a/l1-contracts/scripts/deploy.ts +++ b/l1-contracts/scripts/deploy.ts @@ -5,10 +5,11 @@ import { Command } from "commander"; import { Wallet, ethers } from "ethers"; import { Deployer } from "../src.ts/deploy"; import { formatUnits, parseUnits } from "ethers/lib/utils"; -import { web3Provider, GAS_MULTIPLIER } from "./utils"; +import { web3Provider, GAS_MULTIPLIER, web3Url } from "./utils"; import { deployedAddressesFromEnv } from "../src.ts/deploy-utils"; import { initialBridgehubDeployment } from "../src.ts/deploy-process"; import { ethTestConfig } from "../src.ts/utils"; +import { Wallet as ZkWallet, Provider as ZkProvider } from "zksync-ethers"; const provider = web3Provider(); @@ -27,12 +28,25 @@ async function main() { .option("--diamond-upgrade-init ") .option("--only-verifier") .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); + let deployWallet: ethers.Wallet | ZkWallet; + + if (process.env.CONTRACTS_BASE_NETWORK_ZKSYNC === "true") { + const provider = new ZkProvider(web3Url()); + deployWallet = cmd.privateKey + ? new ZkWallet(cmd.privateKey, provider) + : ZkWallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + } else { + 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.ownerAddress ? cmd.ownerAddress : deployWallet.address; @@ -55,6 +69,10 @@ async function main() { verbose: true, }); + if (deployer.isZkMode()) { + console.log("Deploying on a zkSync network!"); + } + await initialBridgehubDeployment(deployer, [], gasPrice, cmd.onlyVerifier, create2Salt, nonce); }); diff --git a/l1-contracts/scripts/display-governance.ts b/l1-contracts/scripts/display-governance.ts index 0593d580e..4b6741386 100644 --- a/l1-contracts/scripts/display-governance.ts +++ b/l1-contracts/scripts/display-governance.ts @@ -13,7 +13,7 @@ import { UpgradeableBeaconFactory } from "../../l2-contracts/typechain/Upgradeab import { Provider } from "zksync-ethers"; const l2SharedBridgeABI = JSON.parse( - fs.readFileSync("../zksync/artifacts-zk/contracts/bridge/L2SharedBridge.sol/L2SharedBridge.json").toString() + fs.readFileSync("../zksync/artifacts-zk/contracts/bridge/L2AssetRouter.sol/L2SharedBridge.json").toString() ).abi; async function getERC20BeaconAddress(l2SharedBridgeAddress: string) { diff --git a/l1-contracts/scripts/errors-lint.ts b/l1-contracts/scripts/errors-lint.ts new file mode 100644 index 000000000..9c34444b7 --- /dev/null +++ b/l1-contracts/scripts/errors-lint.ts @@ -0,0 +1,220 @@ +import { Command } from "commander"; +import * as fs from "fs"; +import * as path from "path"; +import { ethers } from "ethers"; + +// Constant arrays +const FILES_WITH_CUSTOM_ERRORS = [ + "contracts/common/L1ContractErrors.sol", + "contracts/bridge/L1BridgeContractErrors.sol", + "contracts/bridgehub/L1BridgehubErrors.sol", + "contracts/state-transition/L1StateTransitionErrors.sol", + "contracts/upgrades/ZkSyncUpgradeErrors.sol", + "deploy-scripts/ZkSyncScriptErrors.sol", + "../l2-contracts/contracts/errors/L2ContractErrors.sol", + "../system-contracts/contracts/SystemContractErrors.sol", + "../da-contracts/contracts/DAContractsErrors.sol", +]; // Replace with your file paths +const CONTRACTS_DIRECTORIES = [ + "contracts", + "deploy-scripts", + "test/foundry", + "../l2-contracts/contracts", + "../system-contracts/contracts", + "../da-contracts/contracts", +]; // Replace with your directories + +// Function to extract the error signature +function getErrorSignature(errorString: string): string { + errorString = errorString.trim(); + const parenIndex = errorString.indexOf("("); + + if (parenIndex === -1) { + throw new Error("No '(' in error"); + } else { + const errorName = errorString.substring(0, parenIndex).trim(); + const paramsString = errorString.substring(parenIndex + 1, errorString.lastIndexOf(")")).trim(); + + const params = paramsString + .split(",") + .map((param) => { + const typeMatch = param.trim().match(/^(\w+(\[\])*)\s*(\w*)$/); + if (typeMatch) { + return typeMatch[1]; + } else { + return ""; + } + }) + .filter((paramType) => paramType !== ""); + + return `${errorName}(${params.join(",")})`; + } +} + +// Function to process each file +function processFile(filePath: string, fix: boolean, collectedErrors: Set): boolean { + const fileContent = fs.readFileSync(filePath, "utf8"); + const lines = fileContent.split(/\r?\n/); + let modified = false; + const newLines = []; + let lineNumber = 0; + + while (lineNumber < lines.length) { + const line = lines[lineNumber]; + const errorMatch = line.match(/^\s*error\s+(.+);\s*$/); + + if (errorMatch) { + const errorString = errorMatch[1].trim(); + + // Get the error signature + const signature = getErrorSignature(errorString); + + const errorName = signature.substring(0, signature.indexOf("(")); + collectedErrors.add(errorName); + + // Calculate the selector + const hash = ethers.utils.id(signature); + const selector = hash.substring(0, 10); + + // Check the line above + const previousLine = newLines[newLines.length - 1]; + const selectorComment = `// ${selector}`; + + if (!previousLine || previousLine.trim() !== selectorComment) { + if (fix) { + // We allow fixing incorrect signature + if (previousLine.startsWith("//")) { + newLines[newLines.length - 1] = selectorComment; + } else { + // Insert the selector line + newLines.push(selectorComment); + } + modified = true; + } else { + throw new Error(`Missing selector comment above error at ${filePath}:${lineNumber + 1}`); + } + } + // Push the current line + newLines.push(line); + } else { + // Not an error line, just copy + newLines.push(line); + } + lineNumber++; + } + + if (fix && modified) { + // Write back to file + const newContent = newLines.join("\n"); + fs.writeFileSync(filePath, newContent, "utf8"); + } + + return modified; +} + +// Recursively collects all custom error usages from the given contract directories.s +function collectErrorUsages(directories: string[], usedErrors: Set) { + // Iterate over each directory provided in the directories array + for (const dir of directories) { + // Resolve the directory path to an absolute path + const absoluteDir = path.resolve(dir); + + // Check if the directory exists and is indeed a directory + if (fs.existsSync(absoluteDir) && fs.lstatSync(absoluteDir).isDirectory()) { + // Read all entries (files and subdirectories) within the directory + const files = fs.readdirSync(absoluteDir); + + // Iterate over each entry in the directory + for (const file of files) { + // Construct the full path of the current entry + const fullPath = path.join(absoluteDir, file); + + // Check if the current entry is a directory + if (fs.lstatSync(fullPath).isDirectory()) { + // If it is a directory, recursively call collectErrorUsages on this subdirectory + collectErrorUsages([fullPath], usedErrors); + } + // Check if the current entry is a Solidity file (ends with .sol) + else if (file.endsWith(".sol")) { + // Read the content of the Solidity file as a string + const fileContent = fs.readFileSync(fullPath, "utf8"); + + // Regular expression to match 'revert ' patterns in the file + const revertRegex = /revert\s+([A-Za-z0-9_]+)/g; + + let match; + // Use a loop to find all matches of the pattern in the file content + while ((match = revertRegex.exec(fileContent)) !== null) { + // match[1] contains the captured error name after 'revert' + const errorName = match[1]; + // Add the error name to the usedErrors set + usedErrors.add(errorName); + } + } + // If the entry is neither a directory nor a Solidity file, it is ignored + } + } + // If the path does not exist or is not a directory, it is ignored + } +} + +async function main() { + // Initialize the command parser + const program = new Command(); + + program + .option("--fix", "Fix the errors by inserting missing selectors") + .option("--check", "Check if the selectors are present without modifying files") + .parse(process.argv); + + const options = program.opts(); + + // Validate arguments + if ((!options.fix && !options.check) || (options.fix && options.check)) { + console.error("Error: You must provide either --fix or --check, but not both."); + process.exit(1); + } + + // Main execution + let hasErrors = false; + const declaredErrors = new Set(); + const usedErrors = new Set(); + + for (const filePath of FILES_WITH_CUSTOM_ERRORS) { + const absolutePath = path.resolve(filePath); + const result = processFile(absolutePath, options.fix, declaredErrors); + + if (result && options.check) { + hasErrors = true; + } + } + + if (options.check && hasErrors) { + console.error("Some errors were found."); + process.exit(1); + } + + if (options.check) { + collectErrorUsages(CONTRACTS_DIRECTORIES, usedErrors); + + // Find declared errors that are never used + const unusedErrors = [...declaredErrors].filter((error) => !usedErrors.has(error)); + + if (unusedErrors.length > 0) { + for (const error of unusedErrors) { + console.error(`Error "${error}" is declared but never used.`); + } + process.exit(1); + } + } + + if (options.check && !hasErrors) { + console.log("All files are correct."); + } + + if (options.fix) { + console.log("All files have been processed and fixed."); + } +} + +main(); diff --git a/l1-contracts/scripts/initialize-l2-weth-token.ts b/l1-contracts/scripts/initialize-l2-weth-token.ts index 4bf9dd933..7cb09f075 100644 --- a/l1-contracts/scripts/initialize-l2-weth-token.ts +++ b/l1-contracts/scripts/initialize-l2-weth-token.ts @@ -16,7 +16,7 @@ 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 contractArtifactsPath = path.join(process.env.ZKSYNC_HOME as string, "contracts/l1-contracts/artifacts-zk/"); const l2BridgeArtifactsPath = path.join(contractArtifactsPath, "contracts/bridge/"); const openzeppelinTransparentProxyArtifactsPath = path.join( contractArtifactsPath, diff --git a/l1-contracts/scripts/migrate-governance.ts b/l1-contracts/scripts/migrate-governance.ts index f8f44a8b6..0c04a79c5 100644 --- a/l1-contracts/scripts/migrate-governance.ts +++ b/l1-contracts/scripts/migrate-governance.ts @@ -23,7 +23,7 @@ const priorityTxMaxGasLimit = BigNumber.from(getNumberFromEnv("CONTRACTS_PRIORIT const l2SharedBridgeABI = JSON.parse( fs - .readFileSync("../l2-contracts/artifacts-zk/contracts-preprocessed/bridge/L2SharedBridge.sol/L2SharedBridge.json") + .readFileSync("../l2-contracts/artifacts-zk/contracts-preprocessed/bridge/L2AssetRouter.sol/L2SharedBridge.json") .toString() ).abi; diff --git a/l1-contracts/scripts/register-hyperchain.ts b/l1-contracts/scripts/register-zk-chain.ts similarity index 80% rename from l1-contracts/scripts/register-hyperchain.ts rename to l1-contracts/scripts/register-zk-chain.ts index 1c60ad58d..b16f81fa8 100644 --- a/l1-contracts/scripts/register-hyperchain.ts +++ b/l1-contracts/scripts/register-zk-chain.ts @@ -8,7 +8,7 @@ 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 { ADDRESS_ONE, encodeNTVAssetId, isCurrentNetworkLocal } from "../src.ts/utils"; import { getTokens } from "../src.ts/deploy-token"; const ETH_TOKEN_ADDRESS = ADDRESS_ONE; @@ -55,7 +55,7 @@ const chooseBaseTokenAddress = async (name?: string, address?: string) => { async function main() { const program = new Command(); - program.version("0.1.0").name("register-hyperchain").description("register hyperchains"); + program.version("0.1.0").name("register-zk-chain").description("register zk-chains"); program .option("--private-key ") @@ -66,7 +66,7 @@ async function main() { .option("--validium-mode") .option("--base-token-name ") .option("--base-token-address ") - .option("--use-governance ") + .option("--use-governance") .option("--token-multiplier-setter-address ") .action(async (cmd) => { const deployWallet = cmd.privateKey @@ -92,21 +92,33 @@ async function main() { deployWallet, ownerAddress, verbose: true, + l1ChainId: process.env.CONTRACTS_L1_CHAIN_ID || "31337", }); const baseTokenAddress = await chooseBaseTokenAddress(cmd.baseTokenName, cmd.baseTokenAddress); await checkTokenAddress(baseTokenAddress); console.log(`Using base token address: ${baseTokenAddress}`); - - const useGovernance = !!cmd.useGovernance && cmd.useGovernance === "true"; - - if (!(await deployer.bridgehubContract(deployWallet).tokenIsRegistered(baseTokenAddress))) { - await deployer.registerToken(baseTokenAddress, useGovernance); + console.log(deployer.addresses.Bridgehub.BridgehubProxy); + const baseTokenAssetId = encodeNTVAssetId(deployer.l1ChainId, baseTokenAddress); + if (!(await deployer.bridgehubContract(deployWallet).assetIdIsRegistered(baseTokenAssetId))) { + await deployer.registerTokenBridgehub(baseTokenAddress, cmd.useGovernance); } + if (baseTokenAddress != ETH_TOKEN_ADDRESS) { + await deployer.registerTokenInNativeTokenVault(baseTokenAddress); + } + await deployer.registerZKChain( + baseTokenAssetId, + cmd.validiumMode, + null, + gasPrice, + true, + null, + null, + cmd.useGovernance, + isCurrentNetworkLocal() || cmd.localLegacyBridgeTesting + ); const tokenMultiplierSetterAddress = cmd.tokenMultiplierSetterAddress || ""; - - await deployer.registerHyperchain(baseTokenAddress, cmd.validiumMode, null, gasPrice, useGovernance); if (tokenMultiplierSetterAddress != "") { console.log(`Using token multiplier setter address: ${tokenMultiplierSetterAddress}`); await deployer.setTokenMultiplierSetterAddress(tokenMultiplierSetterAddress); diff --git a/l1-contracts/scripts/revert-reason.ts b/l1-contracts/scripts/revert-reason.ts index 2816f282b..2cd8eae83 100644 --- a/l1-contracts/scripts/revert-reason.ts +++ b/l1-contracts/scripts/revert-reason.ts @@ -7,10 +7,18 @@ import { Interface } from "ethers/lib/utils"; import { web3Url } from "./utils"; const erc20BridgeInterface = new Interface(hardhat.artifacts.readArtifactSync("L1ERC20Bridge").abi); -const zkSyncInterface = new Interface(hardhat.artifacts.readArtifactSync("IZkSync").abi); +const zkSyncInterface = new Interface(hardhat.artifacts.readArtifactSync("IZKChain").abi); const verifierInterface = new Interface(hardhat.artifacts.readArtifactSync("Verifier").abi); - -const interfaces = [erc20BridgeInterface, zkSyncInterface, verifierInterface]; +const bridgehubInterface = new Interface(hardhat.artifacts.readArtifactSync("Bridgehub").abi); +const sharedBridgeInterface = new Interface(hardhat.artifacts.readArtifactSync("L1SharedBridge").abi); + +const interfaces = [ + erc20BridgeInterface, + zkSyncInterface, + verifierInterface, + bridgehubInterface, + sharedBridgeInterface, +]; function decodeTransaction(contractInterface, tx) { try { diff --git a/l1-contracts/scripts/setup-legacy-bridge-era.ts b/l1-contracts/scripts/setup-legacy-bridge-era.ts index deaf060fa..37eeef9cc 100644 --- a/l1-contracts/scripts/setup-legacy-bridge-era.ts +++ b/l1-contracts/scripts/setup-legacy-bridge-era.ts @@ -94,6 +94,7 @@ async function main() { ); const l2SharedBridgeAddress = getAddressFromEnv("CONTRACTS_L2_SHARED_BRIDGE_ADDR"); + const L2NativeTokenVaultAddress = getAddressFromEnv("CONTRACTS_L2_NATIVE_TOKEN_VAULT_PROXY_ADDR"); const l2TokenBytecodeHash = hashL2Bytecode(beaconProxy.bytecode); const l2Provider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); // For the server to start up. @@ -104,12 +105,12 @@ async function main() { // Wait a bit more after the server is ready to ensure that all of its components are ready. await sleep(2); - const l2SharedBridge = new ethers.Contract( - l2SharedBridgeAddress, + const L2NativeTokenVault = new ethers.Contract( + L2NativeTokenVaultAddress, ["function l2TokenBeacon() view returns (address)"], l2Provider ); - const l2TokenBeacon = await l2SharedBridge.l2TokenBeacon(); + const l2TokenBeacon = await L2NativeTokenVault.l2TokenBeacon(); console.log("Retrieved storage values for TestERC20Bridge:"); console.log("l2SharedBridgeAddress:", l2SharedBridgeAddress); @@ -117,7 +118,8 @@ async function main() { console.log("l2TokenBytecodeHash:", ethers.utils.hexlify(l2TokenBytecodeHash)); // set storage values - const tx = await dummyBridge.setValues(l2SharedBridgeAddress, l2TokenBeacon, l2TokenBytecodeHash); + // FIXME(EVM-716): we provide the `L2NativeTokenVaultAddress` as the "shared bridge value" as it is only used for calculating of L2 token addresses. + const tx = await dummyBridge.setValues(L2NativeTokenVaultAddress, l2TokenBeacon, l2TokenBytecodeHash); await tx.wait(); console.log("Set storage values for TestERC20Bridge"); diff --git a/l1-contracts/scripts/sync-layer.ts b/l1-contracts/scripts/sync-layer.ts new file mode 100644 index 000000000..b4e20f873 --- /dev/null +++ b/l1-contracts/scripts/sync-layer.ts @@ -0,0 +1,513 @@ +// 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 { web3Provider, GAS_MULTIPLIER, SYSTEM_CONFIG } from "./utils"; +import { deployedAddressesFromEnv } from "../src.ts/deploy-utils"; +import { initialBridgehubDeployment } from "../src.ts/deploy-process"; +import { + ethTestConfig, + getAddressFromEnv, + getNumberFromEnv, + ADDRESS_ONE, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + priorityTxMaxGasLimit, + L2_BRIDGEHUB_ADDRESS, + computeL2Create2Address, + DIAMOND_CUT_DATA_ABI_STRING, +} from "../src.ts/utils"; + +import { Wallet as ZkWallet, Provider as ZkProvider, utils as zkUtils } from "zksync-ethers"; +import { IChainTypeManagerFactory } from "../typechain/IChainTypeManagerFactory"; +import { IDiamondInitFactory } from "../typechain/IDiamondInitFactory"; +import { TestnetERC20TokenFactory } from "../typechain/TestnetERC20TokenFactory"; +import { BOOTLOADER_FORMAL_ADDRESS } from "zksync-ethers/build/utils"; + +const provider = web3Provider(); + +async function main() { + const program = new Command(); + + program.version("0.1.0").name("deploy").description("deploy L1 contracts"); + + program + .command("compute-migrated-chain-address") + .requiredOption("--chain-id ") + .option("--private-key ") + .action(async (cmd) => { + if (process.env.CONTRACTS_BASE_NETWORK_ZKSYNC !== "true") { + throw new Error("This script is only for zkSync network"); + } + + const provider = new ZkProvider(process.env.API_WEB3_JSON_RPC_HTTP_URL); + const ethProvider = new ethers.providers.JsonRpcProvider(process.env.ETH_CLIENT_WEB3_URL); + const deployWallet = cmd.privateKey + ? new ZkWallet(cmd.privateKey, provider) + : (ZkWallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider) as ethers.Wallet | ZkWallet); + + const deployer = new Deployer({ + deployWallet, + addresses: deployedAddressesFromEnv(), + verbose: true, + }); + + deployer.addresses.StateTransition.AdminFacet = getAddressFromEnv("GATEWAY_ADMIN_FACET_ADDR"); + deployer.addresses.StateTransition.MailboxFacet = getAddressFromEnv("GATEWAY_MAILBOX_FACET_ADDR"); + deployer.addresses.StateTransition.ExecutorFacet = getAddressFromEnv("GATEWAY_EXECUTOR_FACET_ADDR"); + deployer.addresses.StateTransition.GettersFacet = getAddressFromEnv("GATEWAY_GETTERS_FACET_ADDR"); + deployer.addresses.StateTransition.DiamondInit = getAddressFromEnv("GATEWAY_DIAMOND_INIT_ADDR"); + deployer.addresses.StateTransition.Verifier = getAddressFromEnv("GATEWAY_VERIFIER_ADDR"); + deployer.addresses.BlobVersionedHashRetriever = getAddressFromEnv("GATEWAY_BLOB_VERSIONED_HASH_RETRIEVER_ADDR"); + deployer.addresses.ValidatorTimeLock = getAddressFromEnv("GATEWAY_VALIDATOR_TIMELOCK_ADDR"); + deployer.addresses.Bridges.SharedBridgeProxy = getAddressFromEnv("CONTRACTS_L2_SHARED_BRIDGE_ADDR"); + deployer.addresses.StateTransition.StateTransitionProxy = getAddressFromEnv( + "GATEWAY_STATE_TRANSITION_PROXY_ADDR" + ); + + const stm = deployer.chainTypeManagerContract(provider); + const bridgehub = deployer.bridgehubContract(ethProvider); + const diamondInit = IDiamondInitFactory.connect(deployer.addresses.StateTransition.DiamondInit, provider); + const bytes32 = (x: ethers.BigNumberish) => ethers.utils.hexZeroPad(ethers.utils.hexlify(x), 32); + + const diamondCut = await deployer.initialZkSyncZKChainDiamondCut([], true); + const mandatoryInitData = [ + diamondInit.interface.getSighash("initialize"), + bytes32(parseInt(cmd.chainId)), + bytes32(getAddressFromEnv("GATEWAY_BRIDGEHUB_PROXY_ADDR")), + bytes32(deployer.addresses.StateTransition.StateTransitionProxy), + bytes32(await stm.protocolVersion()), + bytes32(deployer.deployWallet.address), + bytes32(deployer.addresses.ValidatorTimeLock), + await bridgehub.baseTokenAssetId(cmd.chainId), + bytes32(deployer.addresses.Bridges.SharedBridgeProxy), + await stm.storedBatchZero(), + ]; + + diamondCut.initCalldata = ethers.utils.hexConcat([...mandatoryInitData, diamondCut.initCalldata]); + const bytecode = hardhat.artifacts.readArtifactSync("DiamondProxy").bytecode; + const gatewayChainId = (await provider.getNetwork()).chainId; + const constructorData = new ethers.utils.AbiCoder().encode( + ["uint256", DIAMOND_CUT_DATA_ABI_STRING], + [gatewayChainId, diamondCut] + ); + + const address = computeL2Create2Address( + deployer.addresses.StateTransition.StateTransitionProxy, + bytecode, + constructorData, + ethers.constants.HashZero + ); + + console.log(address); + }); + + program + .command("deploy-sync-layer-contracts") + .option("--private-key ") + .option("--chain-id ") + .option("--gas-price ") + .option("--owner-address ") + .option("--create2-salt ") + .option("--diamond-upgrade-init ") + .option("--only-verifier") + .action(async (cmd) => { + if (process.env.CONTRACTS_BASE_NETWORK_ZKSYNC !== "true") { + throw new Error("This script is only for zkSync network"); + } + + const provider = new ZkProvider(process.env.API_WEB3_JSON_RPC_HTTP_URL); + const deployWallet = cmd.privateKey + ? new ZkWallet(cmd.privateKey, provider) + : (ZkWallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider) as ethers.Wallet | ZkWallet); + + console.log(`Using deployer wallet: ${deployWallet.address}`); + + 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()).mul(GAS_MULTIPLIER); + console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); + + const 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, + }); + + if (deployer.isZkMode()) { + console.log("Deploying on a zkSync network!"); + } + deployer.addresses.Bridges.SharedBridgeProxy = getAddressFromEnv("CONTRACTS_L2_SHARED_BRIDGE_ADDR"); + + await initialBridgehubDeployment(deployer, [], gasPrice, true, create2Salt); + await initialBridgehubDeployment(deployer, [], gasPrice, false, create2Salt); + }); + + program + .command("register-sync-layer") + .option("--private-key ") + .option("--chain-id ") + .option("--gas-price ") + .option("--owner-address ") + .option("--create2-salt ") + .option("--diamond-upgrade-init ") + .option("--only-verifier") + .action(async (cmd) => { + // Now, all the operations are done on L1 + 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); + + const ownerAddress = cmd.ownerAddress ? cmd.ownerAddress : deployWallet.address; + console.log(`Using owner address: ${ownerAddress}`); + const deployer = new Deployer({ + deployWallet, + addresses: deployedAddressesFromEnv(), + ownerAddress, + verbose: true, + }); + await registerSLContractsOnL1(deployer); + }); + + program + .command("migrate-to-sync-layer") + .option("--private-key ") + .option("--chain-id ") + .option("--gas-price ") + .option("--owner-address ") + .option("--create2-salt ") + .option("--diamond-upgrade-init ") + .option("--only-verifier") + .action(async (cmd) => { + console.log("Starting migration of the current chain to sync layer"); + + 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); + const ownerAddress = cmd.ownerAddress ? cmd.ownerAddress : deployWallet.address; + + const deployer = new Deployer({ + deployWallet, + addresses: deployedAddressesFromEnv(), + ownerAddress, + verbose: true, + }); + + const gatewayChainId = getNumberFromEnv("GATEWAY_CHAIN_ID"); + const gasPrice = cmd.gasPrice + ? parseUnits(cmd.gasPrice, "gwei") + : (await provider.getGasPrice()).mul(GAS_MULTIPLIER); + + const currentChainId = getNumberFromEnv("CHAIN_ETH_ZKSYNC_NETWORK_ID"); + + const ctm = deployer.chainTypeManagerContract(deployer.deployWallet); + + const counterPart = getAddressFromEnv("GATEWAY_STATE_TRANSITION_PROXY_ADDR"); + + // FIXME: do it more gracefully + deployer.addresses.StateTransition.AdminFacet = getAddressFromEnv("GATEWAY_ADMIN_FACET_ADDR"); + deployer.addresses.StateTransition.MailboxFacet = getAddressFromEnv("GATEWAY_MAILBOX_FACET_ADDR"); + deployer.addresses.StateTransition.ExecutorFacet = getAddressFromEnv("GATEWAY_EXECUTOR_FACET_ADDR"); + deployer.addresses.StateTransition.GettersFacet = getAddressFromEnv("GATEWAY_GETTERS_FACET_ADDR"); + deployer.addresses.StateTransition.Verifier = getAddressFromEnv("GATEWAY_VERIFIER_ADDR"); + deployer.addresses.BlobVersionedHashRetriever = getAddressFromEnv("GATEWAY_BLOB_VERSIONED_HASH_RETRIEVER_ADDR"); + deployer.addresses.StateTransition.DiamondInit = getAddressFromEnv("GATEWAY_DIAMOND_INIT_ADDR"); + + const receipt = await deployer.moveChainToGateway(gatewayChainId, gasPrice); + + const gatewayAddress = await ctm.getZKChain(gatewayChainId); + + const l2TxHash = zkUtils.getL2HashFromPriorityOp(receipt, gatewayAddress); + + console.log("Hash of the transaction on SL chain: ", l2TxHash); + + const gatewayProvider = new ZkProvider(process.env.GATEWAY_API_WEB3_JSON_RPC_HTTP_URL); + + const txL2Handle = gatewayProvider.getL2TransactionFromPriorityOp( + await deployWallet.provider.getTransaction(receipt.transactionHash) + ); + + const receiptOnSL = await (await txL2Handle).wait(); + console.log("Finalized on SL with hash:", receiptOnSL.transactionHash); + + const ctmOnSL = IChainTypeManagerFactory.connect(counterPart, gatewayProvider); + const zkChainAddress = await ctmOnSL.getZKChain(currentChainId); + console.log(`CONTRACTS_DIAMOND_PROXY_ADDR=${zkChainAddress}`); + + console.log("Success!"); + }); + + program + .command("recover-from-failed-migration") + .option("--private-key ") + .option("--failed-tx-l2-hash ") + .option("--chain-id ") + .option("--gas-price ") + .option("--owner-address ") + .option("--create2-salt ") + .option("--diamond-upgrade-init ") + .option("--only-verifier") + .action(async (cmd) => { + const gatewayChainId = getNumberFromEnv("GATEWAY_CHAIN_ID"); + const gatewayProvider = new ZkProvider(process.env.GATEWAY_API_WEB3_JSON_RPC_HTTP_URL); + console.log("Obtaining proof..."); + const proof = await getTxFailureProof(gatewayProvider, cmd.failedTxL2Hash); + + 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(deployWallet.address); + const ownerAddress = cmd.ownerAddress ? cmd.ownerAddress : deployWallet.address; + const deployer = new Deployer({ + deployWallet, + addresses: deployedAddressesFromEnv(), + ownerAddress, + verbose: true, + }); + + const zkChain = deployer.stateTransitionContract(deployer.deployWallet); + + console.log(await zkChain.getAdmin()); + + console.log("Executing recovery..."); + + await ( + await zkChain.recoverFromFailedMigrationToGateway( + gatewayChainId, + proof.l2BatchNumber, + proof.l2MessageIndex, + proof.l2TxNumberInBatch, + proof.merkleProof + ) + ).wait(); + + console.log("Success!"); + }); + + program + .command("prepare-validators") + .option("--private-key ") + .option("--chain-id ") + .option("--gas-price ") + .option("--owner-address ") + .option("--create2-salt ") + .option("--diamond-upgrade-init ") + .option("--only-verifier") + .action(async (cmd) => { + const gatewayProvider = new ZkProvider(process.env.GATEWAY_API_WEB3_JSON_RPC_HTTP_URL); + const currentChainId = getNumberFromEnv("CHAIN_ETH_ZKSYNC_NETWORK_ID"); + + // Right now the new admin is the wallet itself. + const adminWallet = cmd.privateKey + ? new ZkWallet(cmd.privateKey, gatewayProvider) + : ZkWallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(gatewayProvider); + + const operators = [ + process.env.ETH_SENDER_SENDER_OPERATOR_COMMIT_ETH_ADDR, + process.env.ETH_SENDER_SENDER_OPERATOR_BLOBS_ETH_ADDR, + ]; + + const deployer = new Deployer({ + deployWallet: adminWallet, + addresses: deployedAddressesFromEnv(), + ownerAddress: adminWallet.address, + verbose: true, + }); + + console.log("Enabling validators"); + + // FIXME: do it in cleaner way + deployer.addresses.ValidatorTimeLock = getAddressFromEnv("GATEWAY_VALIDATOR_TIMELOCK_ADDR"); + const timelock = deployer.validatorTimelock(deployer.deployWallet); + + for (const operator of operators) { + if (await timelock.validators(currentChainId, operator)) { + continue; + } + + await deployer.deployWallet.sendTransaction({ + to: operator, + value: ethers.utils.parseEther("5"), + }); + + await (await timelock.addValidator(currentChainId, operator)).wait(); + } + + // FIXME: this method includes bridgehub manipulation, but in the future it won't. + deployer.addresses.StateTransition.StateTransitionProxy = getAddressFromEnv( + "GATEWAY_STATE_TRANSITION_PROXY_ADDR" + ); + deployer.addresses.Bridgehub.BridgehubProxy = getAddressFromEnv("GATEWAY_BRIDGEHUB_PROXY_ADDR"); + + const zkChain = deployer.stateTransitionContract(deployer.deployWallet); + + console.log("Setting SL DA validators"); + // This logic should be distinctive between Validium and Rollup + const l1DaValidator = getAddressFromEnv("GATEWAY_L1_RELAYED_SL_DA_VALIDATOR"); + const l2DaValidator = getAddressFromEnv("CONTRACTS_L2_DA_VALIDATOR_ADDR"); + await (await zkChain.setDAValidatorPair(l1DaValidator, l2DaValidator)).wait(); + + console.log("Success!"); + }); + + await program.parseAsync(process.argv); +} + +async function registerSLContractsOnL1(deployer: Deployer) { + /// CTM asset info + /// l2Bridgehub in L1Bridghub + + const chainId = getNumberFromEnv("CHAIN_ETH_ZKSYNC_NETWORK_ID"); + + console.log(`Gateway chain Id: ${chainId}`); + + const l1Bridgehub = deployer.bridgehubContract(deployer.deployWallet); + const l1CTM = deployer.chainTypeManagerContract(deployer.deployWallet); + console.log(deployer.addresses.StateTransition.StateTransitionProxy); + const gatewayAddress = await l1Bridgehub.getZKChain(chainId); + // this script only works when owner is the deployer + console.log("Registering Gateway chain id on the CTM"); + const receipt1 = await deployer.executeUpgrade( + l1Bridgehub.address, + 0, + l1Bridgehub.interface.encodeFunctionData("registerSettlementLayer", [chainId, true]) + ); + + console.log("Registering Gateway as settlement layer on the L1", receipt1.transactionHash); + + const gasPrice = (await deployer.deployWallet.provider.getGasPrice()).mul(GAS_MULTIPLIER); + const value = ( + await l1Bridgehub.l2TransactionBaseCost(chainId, gasPrice, priorityTxMaxGasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA) + ).mul(10); + const baseTokenAddress = await l1Bridgehub.baseToken(chainId); + const ethIsBaseToken = baseTokenAddress == ADDRESS_ONE; + if (!ethIsBaseToken) { + const baseToken = TestnetERC20TokenFactory.connect(baseTokenAddress, this.deployWallet); + await (await baseToken.transfer(this.addresses.Governance, value)).wait(); + await this.executeUpgrade( + baseTokenAddress, + 0, + baseToken.interface.encodeFunctionData("approve", [this.addresses.Bridges.SharedBridgeProxy, value.mul(2)]) + ); + } + const ctmDeploymentTracker = deployer.ctmDeploymentTracker(deployer.deployWallet); + const assetRouter = deployer.defaultSharedBridge(deployer.deployWallet); + const assetId = await l1Bridgehub.ctmAssetIdFromChainId(chainId); + + // Setting the L2 bridgehub as the counterpart for the CTM asset + const receipt2 = await deployer.executeUpgrade( + l1Bridgehub.address, + ethIsBaseToken ? value : 0, + l1Bridgehub.interface.encodeFunctionData("requestL2TransactionTwoBridges", [ + { + chainId, + mintValue: value, + l2Value: 0, + l2GasLimit: priorityTxMaxGasLimit, + l2GasPerPubdataByteLimit: SYSTEM_CONFIG.requiredL2GasPricePerPubdata, + refundRecipient: deployer.deployWallet.address, + secondBridgeAddress: assetRouter.address, + secondBridgeValue: 0, + secondBridgeCalldata: + "0x02" + + ethers.utils.defaultAbiCoder.encode(["bytes32", "address"], [assetId, L2_BRIDGEHUB_ADDRESS]).slice(2), + }, + ]) + ); + const l2TxHash = zkUtils.getL2HashFromPriorityOp(receipt2, gatewayAddress); + console.log("CTM asset registered in L2SharedBridge on SL tx hash: ", receipt2.transactionHash); + console.log("CTM asset registered in L2SharedBridge on SL l2 tx hash: ", l2TxHash); + + const l2CTMAddress = getAddressFromEnv("GATEWAY_STATE_TRANSITION_PROXY_ADDR"); + + // Whitelisting the CTM address on L2 + const receipt3 = await deployer.executeUpgradeOnL2( + chainId, + L2_BRIDGEHUB_ADDRESS, + gasPrice, + l1Bridgehub.interface.encodeFunctionData("addChainTypeManager", [l2CTMAddress]), + priorityTxMaxGasLimit + ); + const l2TxHash2dot5 = zkUtils.getL2HashFromPriorityOp(receipt3, gatewayAddress); + console.log(`L2 CTM ,l2 txHash: ${l2TxHash2dot5}`); + console.log(`L2 CTM address ${l2CTMAddress} registered on gateway, txHash: ${receipt3.transactionHash}`); + + // Setting the corresponding CTM address on L2. + const receipt4 = await deployer.executeUpgrade( + l1Bridgehub.address, + value, + l1Bridgehub.interface.encodeFunctionData("requestL2TransactionTwoBridges", [ + { + chainId, + mintValue: value, + l2Value: 0, + l2GasLimit: priorityTxMaxGasLimit, + l2GasPerPubdataByteLimit: SYSTEM_CONFIG.requiredL2GasPricePerPubdata, + refundRecipient: deployer.deployWallet.address, + secondBridgeAddress: ctmDeploymentTracker.address, + secondBridgeValue: 0, + secondBridgeCalldata: + "0x01" + ethers.utils.defaultAbiCoder.encode(["address", "address"], [l1CTM.address, l2CTMAddress]).slice(2), + }, + ]) + ); + const l2TxHash3 = zkUtils.getL2HashFromPriorityOp(receipt4, gatewayAddress); + console.log("CTM asset registered in L2 Bridgehub on SL", receipt4.transactionHash); + console.log("CTM asset registered in L2 Bridgehub on SL l2TxHash", l2TxHash3); +} + +// TODO: maybe move it to SDK +async function getTxFailureProof(provider: ZkProvider, l2TxHash: string) { + const receipt = await provider.getTransactionReceipt(ethers.utils.hexlify(l2TxHash)); + const successL2ToL1LogIndex = receipt.l2ToL1Logs.findIndex( + (l2ToL1log) => l2ToL1log.sender == BOOTLOADER_FORMAL_ADDRESS && l2ToL1log.key == l2TxHash + ); + const successL2ToL1Log = receipt.l2ToL1Logs[successL2ToL1LogIndex]; + if (successL2ToL1Log.value != ethers.constants.HashZero) { + throw new Error("The tx was successful"); + } + + const proof = await provider.getLogProof(l2TxHash, successL2ToL1LogIndex); + return { + l2BatchNumber: receipt.l1BatchNumber, + l2MessageIndex: proof.id, + l2TxNumberInBatch: receipt.l1BatchTxIndex, + merkleProof: proof.proof, + }; +} + +main() + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/token-migration.ts b/l1-contracts/scripts/token-migration.ts index b18260ca3..a3b4ef67d 100644 --- a/l1-contracts/scripts/token-migration.ts +++ b/l1-contracts/scripts/token-migration.ts @@ -197,7 +197,7 @@ async function prepareGovernanceTokenMigrationCall( delay: number ) { const governanceAbi = new ethers.utils.Interface((await hardhat.artifacts.readArtifact("IGovernance")).abi); - const sharedBridgeAbi = new ethers.utils.Interface((await hardhat.artifacts.readArtifact("L1SharedBridge")).abi); + const sharedBridgeAbi = new ethers.utils.Interface((await hardhat.artifacts.readArtifact("L1AssetRouter")).abi); const calls = tokens.map((token) => { const target = token == utils.ETH_ADDRESS_IN_CONTRACTS ? eraChainAddress : l1LegacyBridgeAddr; diff --git a/l1-contracts/scripts/upgrade-consistency-checker.ts b/l1-contracts/scripts/upgrade-consistency-checker.ts index 530d47dc3..798f6f36a 100644 --- a/l1-contracts/scripts/upgrade-consistency-checker.ts +++ b/l1-contracts/scripts/upgrade-consistency-checker.ts @@ -10,16 +10,17 @@ import { BigNumber, ethers } from "ethers"; import { utils } from "zksync-ethers"; import type { FacetCut } from "../src.ts/diamondCut"; import { getCurrentFacetCutsForAdd } from "../src.ts/diamondCut"; +import { encodeNTVAssetId } from "../src.ts/utils"; // Things that still have to be manually double checked: // 1. Contracts must be verified. -// 2. Getter methods in STM. +// 2. Getter methods in CTM. // List the contracts that should become the upgrade targets const genesisUpgrade = process.env.CONTRACTS_GENESIS_UPGRADE_ADDR!; const validatorTimelockDeployTx = "0xde4ef2b77241b605acaa1658ff8815df0911bf81555a80c9cbdde42fbcaaea30"; const validatorTimelock = process.env.CONTRACTS_VALIDATOR_TIMELOCK_ADDR!; -const upgradeHyperchains = process.env.CONTRACTS_HYPERCHAIN_UPGRADE_ADDR!; +const upgradeZKChains = process.env.CONTRACTS_ZK_CHAIN_UPGRADE_ADDR!; const verifier = process.env.CONTRACTS_VERIFIER_ADDR!; const proxyAdmin = process.env.CONTRACTS_TRANSPARENT_PROXY_ADMIN_ADDR!; @@ -35,10 +36,10 @@ const gettersFacet = process.env.CONTRACTS_GETTERS_FACET_ADDR!; const diamondInit = process.env.CONTRACTS_DIAMOND_INIT_ADDR!; -const stmImplDeployTx = "0xe01c0bb497017a25c92bfc712e370e8f900554b107fe0b6022976d05c349f2b6"; -const stmImpl = process.env.CONTRACTS_STATE_TRANSITION_IMPL_ADDR!; -const stmDeployTx = "0x514bbf46d227eee8567825bf5c8ee1855aa8a1916f7fee7b191e2e3d5ecba849"; -const stm = process.env.CONTRACTS_STATE_TRANSITION_PROXY_ADDR!; +const ctmImplDeployTx = "0xe01c0bb497017a25c92bfc712e370e8f900554b107fe0b6022976d05c349f2b6"; +const ctmImpl = process.env.CONTRACTS_STATE_TRANSITION_IMPL_ADDR!; +const ctmDeployTx = "0x514bbf46d227eee8567825bf5c8ee1855aa8a1916f7fee7b191e2e3d5ecba849"; +const ctm = process.env.CONTRACTS_STATE_TRANSITION_PROXY_ADDR!; const sharedBridgeImplDeployTx = "0x074204db79298c2f6beccae881c2ad7321c331e97fb4bd93adce2eb23bf17a17"; const sharedBridgeImpl = process.env.CONTRACTS_L1_SHARED_BRIDGE_IMPL_ADDR!; @@ -52,9 +53,10 @@ const initialOwner = "0x71d84c3404a6ae258E6471d4934B96a2033F9438"; const expectedOwner = "0x71d84c3404a6ae258E6471d4934B96a2033F9438"; //process.env.CONTRACTS_GOVERNANCE_ADDR!; const expectedDelay = "75600"; const eraChainId = process.env.CONTRACTS_ERA_CHAIN_ID!; +const l1ChainId = process.env.CONTRACTS_L1_CHAIN_ID!; const expectedSalt = "0x0000000000000000000000000000000000000000000000000000000000000001"; -const expectedHyperchainAddr = "0x32400084c286cf3e17e7b677ea9583e60a000324"; -const maxNumberOfHyperchains = 100; +const expectedZKChainAddr = "0x32400084c286cf3e17e7b677ea9583e60a000324"; +const maxNumberOfZKChains = 100; const expectedStoredBatchHashZero = "0x1574fa776dec8da2071e5f20d71840bfcbd82c2bca9ad68680edfedde1710bc4"; const expectedL2BridgeAddress = "0x11f943b2c77b743AB90f4A0Ae7d5A4e7FCA3E102"; const expectedL1LegacyBridge = "0x57891966931Eb4Bb6FB81430E6cE0A03AAbDe063"; @@ -276,7 +278,7 @@ async function extractProxyInitializationData(contract: ethers.Contract, data: s throw new Error("L2 default account bytecode hash is not correct"); } - console.log("STM init data correct!"); + console.log("CTM init data correct!"); } async function checkValidatorTimelock() { @@ -288,9 +290,9 @@ async function checkValidatorTimelock() { throw new Error("ValidatorTimelock owner is not correct"); } - const usedStm = await contract.stateTransitionManager(); - if (usedStm.toLowerCase() != stm.toLowerCase()) { - throw new Error("ValidatorTimelock stateTransitionManager is not correct"); + const usedCtm = await contract.chainTypeManager(); + if (usedCtm.toLowerCase() != ctm.toLowerCase()) { + throw new Error("ValidatorTimelock chainTypeManager is not correct"); } const validatorOneIsSet = await contract.validators(eraChainId, validatorOne); @@ -326,9 +328,9 @@ async function checkBridgehub() { throw new Error("Bridgehub baseToken is not correct"); } - const hyperchain = await contract.getHyperchain(eraChainId); - if (hyperchain.toLowerCase() != expectedHyperchainAddr.toLowerCase()) { - throw new Error("Bridgehub hyperchain is not correct"); + const zkChain = await contract.getZKChain(eraChainId); + if (zkChain.toLowerCase() != expectedZKChainAddr.toLowerCase()) { + throw new Error("Bridgehub zkChain is not correct"); } const sharedBridge = await contract.sharedBridge(); @@ -336,17 +338,21 @@ async function checkBridgehub() { throw new Error("Bridgehub sharedBridge is not correct"); } - const usedSTM = await contract.stateTransitionManager(eraChainId); - if (usedSTM.toLowerCase() != stm.toLowerCase()) { - throw new Error("Bridgehub stateTransitionManager is not correct"); + const usedCTM = await contract.chainTypeManager(eraChainId); + if (usedCTM.toLowerCase() != ctm.toLowerCase()) { + throw new Error("Bridgehub chainTypeManager is not correct"); } - const isRegistered = await contract.stateTransitionManagerIsRegistered(usedSTM); + const isRegistered = await contract.chainTypeManagerIsRegistered(usedCTM); if (!isRegistered) { - throw new Error("Bridgehub stateTransitionManager is not registered"); + throw new Error("Bridgehub chainTypeManager is not registered"); } - const tokenIsRegistered = await contract.tokenIsRegistered(utils.ETH_ADDRESS_IN_CONTRACTS); + const baseTokenAssetId = encodeNTVAssetId( + parseInt(l1ChainId), + ethers.utils.hexZeroPad(utils.ETH_ADDRESS_IN_CONTRACTS, 32) + ); + const tokenIsRegistered = contract.assetIdIsRegistered(baseTokenAssetId); if (!tokenIsRegistered) { throw new Error("Bridgehub token is not registered"); } @@ -362,65 +368,65 @@ async function checkMailbox() { console.log("Mailbox is correct!"); } -async function checkSTMImpl() { - const artifact = await hardhat.artifacts.readArtifact("StateTransitionManager"); - const contract = new ethers.Contract(stmImpl, artifact.abi, l1Provider); +async function checkCTMImpl() { + const artifact = await hardhat.artifacts.readArtifact("ChainTypeManager"); + const contract = new ethers.Contract(ctmImpl, artifact.abi, l1Provider); - await checkCorrectInitCode(stmImplDeployTx, contract, artifact.bytecode, [bridgeHub, maxNumberOfHyperchains]); + await checkCorrectInitCode(ctmImplDeployTx, contract, artifact.bytecode, [bridgeHub, maxNumberOfZKChains]); - console.log("STM impl correct!"); + console.log("CTM impl correct!"); } -async function checkSTM() { - const artifact = await hardhat.artifacts.readArtifact("StateTransitionManager"); +async function checkCTM() { + const artifact = await hardhat.artifacts.readArtifact("ChainTypeManager"); - const contract = new ethers.Contract(stm, artifact.abi, l1Provider); + const contract = new ethers.Contract(ctm, artifact.abi, l1Provider); const usedBH = await contract.BRIDGE_HUB(); if (usedBH.toLowerCase() != bridgeHub.toLowerCase()) { - throw new Error("STM bridgeHub is not correct"); + throw new Error("CTM bridgeHub is not correct"); } - const usedMaxNumberOfHyperchains = (await contract.MAX_NUMBER_OF_HYPERCHAINS()).toNumber(); - if (usedMaxNumberOfHyperchains != maxNumberOfHyperchains) { - throw new Error("STM maxNumberOfHyperchains is not correct"); + const usedMaxNumberOfZKChains = (await contract.MAX_NUMBER_OF_ZK_CHAINS()).toNumber(); + if (usedMaxNumberOfZKChains != maxNumberOfZKChains) { + throw new Error("CTM maxNumberOfZKChains is not correct"); } const genUpgrade = await contract.genesisUpgrade(); if (genUpgrade.toLowerCase() != genesisUpgrade.toLowerCase()) { - throw new Error("STM genesisUpgrade is not correct"); + throw new Error("CTM genesisUpgrade is not correct"); } const storedBatchHashZero = await contract.storedBatchZero(); if (storedBatchHashZero.toLowerCase() != expectedStoredBatchHashZero.toLowerCase()) { - throw new Error("STM storedBatchHashZero is not correct"); + throw new Error("CTM storedBatchHashZero is not correct"); } const currentOwner = await contract.owner(); if (currentOwner.toLowerCase() != expectedOwner.toLowerCase()) { - throw new Error("STM owner is not correct"); + throw new Error("CTM owner is not correct"); } - console.log("STM is correct!"); + console.log("CTM is correct!"); - await extractProxyInitializationData(contract, (await l1Provider.getTransaction(stmDeployTx)).data); + await extractProxyInitializationData(contract, (await l1Provider.getTransaction(ctmDeployTx)).data); } -async function checkL1SharedBridgeImpl() { - const artifact = await hardhat.artifacts.readArtifact("L1SharedBridge"); +async function checkL1AssetRouterImpl() { + const artifact = await hardhat.artifacts.readArtifact("L1AssetRouter"); const contract = new ethers.Contract(sharedBridgeImpl, artifact.abi, l1Provider); await checkCorrectInitCode(sharedBridgeImplDeployTx, contract, artifact.bytecode, [ expectedL1WethAddress, bridgeHub, eraChainId, - expectedHyperchainAddr, + expectedZKChainAddr, ]); console.log("L1 shared bridge impl correct!"); } async function checkSharedBridge() { - const artifact = await hardhat.artifacts.readArtifact("L1SharedBridge"); + const artifact = await hardhat.artifacts.readArtifact("L1AssetRouter"); const contract = new ethers.Contract(sharedBridgeProxy, artifact.abi, l1Provider); const l2BridgeAddr = await contract.l2BridgeAddress(eraChainId); @@ -476,7 +482,7 @@ async function main() { program.action(async () => { await checkIdenticalBytecode(genesisUpgrade, "GenesisUpgrade"); - await checkIdenticalBytecode(upgradeHyperchains, "UpgradeHyperchains"); + await checkIdenticalBytecode(upgradeZKChains, "UpgradeZKChains"); await checkIdenticalBytecode(executorFacet, "ExecutorFacet"); await checkIdenticalBytecode(gettersFacet, "GettersFacet"); await checkIdenticalBytecode(adminFacet, "AdminFacet"); @@ -491,13 +497,13 @@ async function main() { await checkValidatorTimelock(); await checkBridgehub(); - await checkL1SharedBridgeImpl(); + await checkL1AssetRouterImpl(); await checkSharedBridge(); await checkLegacyBridge(); - await checkSTMImpl(); - await checkSTM(); + await checkCTMImpl(); + await checkCTM(); }); await program.parseAsync(process.argv); diff --git a/l1-contracts/scripts/utils.ts b/l1-contracts/scripts/utils.ts index 5ae1bceac..b1573f39f 100644 --- a/l1-contracts/scripts/utils.ts +++ b/l1-contracts/scripts/utils.ts @@ -5,6 +5,7 @@ import * as chalk from "chalk"; import { ethers } from "ethers"; import * as fs from "fs"; import * as path from "path"; +import { isCurrentNetworkLocal } from "../src.ts/utils"; const warning = chalk.bold.yellow; export const L1_TO_L2_ALIAS_OFFSET = "0x1111000000000000000000000000000000001111"; @@ -50,7 +51,7 @@ export function web3Provider() { } // Short polling interval for local network - if (network === "localhost" || network === "hardhat") { + if (isCurrentNetworkLocal()) { provider.pollingInterval = 100; } diff --git a/l1-contracts/scripts/verify.ts b/l1-contracts/scripts/verify.ts index e1726a5d6..25255bad7 100644 --- a/l1-contracts/scripts/verify.ts +++ b/l1-contracts/scripts/verify.ts @@ -1,7 +1,13 @@ // hardhat import should be the first import in the file import * as hardhat from "hardhat"; import { deployedAddressesFromEnv } from "../src.ts/deploy-utils"; -import { ethTestConfig, getNumberFromEnv, getHashFromEnv, getAddressFromEnv } from "../src.ts/utils"; +import { + getNumberFromEnv, + getHashFromEnv, + getAddressFromEnv, + isCurrentNetworkLocal, + ethTestConfig, +} from "../src.ts/utils"; import { Interface } from "ethers/lib/utils"; import { Deployer } from "../src.ts/deploy"; @@ -34,7 +40,7 @@ function verifyPromise( // Note: running all verifications in parallel might be too much for etherscan, comment out some of them if needed async function main() { - if (process.env.CHAIN_ETH_NETWORK == "localhost") { + if (isCurrentNetworkLocal()) { console.log("Skip contract verification on localhost"); return; } @@ -82,7 +88,7 @@ async function main() { const promise3 = verifyPromise(process.env.CONTRACTS_DEFAULT_UPGRADE_ADDR); promises.push(promise3); - const promise4 = verifyPromise(process.env.CONTRACTS_HYPERCHAIN_UPGRADE_ADDR); + const promise4 = verifyPromise(process.env.CONTRACTS_ZK_CHAIN_UPGRADE_ADDR); promises.push(promise4); const promise5 = verifyPromise(addresses.TransparentProxyAdmin); @@ -102,7 +108,7 @@ async function main() { ]); promises.push(promise7); - // stm + // ctm // Contracts without constructor parameters for (const address of [ @@ -121,18 +127,18 @@ async function main() { const promise8 = verifyPromise(addresses.StateTransition.StateTransitionImplementation, [ addresses.Bridgehub.BridgehubProxy, - getNumberFromEnv("CONTRACTS_MAX_NUMBER_OF_HYPERCHAINS"), + getNumberFromEnv("CONTRACTS_MAX_NUMBER_OF_ZK_CHAINS"), ]); promises.push(promise8); - const stateTransitionManager = new Interface(hardhat.artifacts.readArtifactSync("StateTransitionManager").abi); + const chainTypeManager = new Interface(hardhat.artifacts.readArtifactSync("ChainTypeManager").abi); 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 deployer.initialZkSyncHyperchainDiamondCut([]); + const diamondCut = await deployer.initialZkSyncZKChainDiamondCut([]); const protocolVersion = packSemver(...unpackStringSemVer(process.env.CONTRACTS_GENESIS_PROTOCOL_SEMANTIC_VERSION)); - const initCalldata2 = stateTransitionManager.encodeFunctionData("initialize", [ + const initCalldata2 = chainTypeManager.encodeFunctionData("initialize", [ { owner: addresses.Governance, validatorTimelock: addresses.ValidatorTimeLock, @@ -174,7 +180,7 @@ async function main() { eraDiamondProxy, ]); promises.push(promise12); - const initCalldata4 = new Interface(hardhat.artifacts.readArtifactSync("L1SharedBridge").abi).encodeFunctionData( + const initCalldata4 = new Interface(hardhat.artifacts.readArtifactSync("L1AssetRouter").abi).encodeFunctionData( "initialize", [deployWalletAddress] ); diff --git a/l1-contracts/selectors b/l1-contracts/selectors new file mode 100644 index 000000000..ba2b254d7 --- /dev/null +++ b/l1-contracts/selectors @@ -0,0 +1,6096 @@ +Listing selectors for contracts in the project... +IAssetRouterBase ++----------+---------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=====================================================================================================================================================+ +| Function | BRIDGE_HUB() | 0x5d4edca7 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | assetHandlerAddress(bytes32) | 0x53b9e632 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeDeposit(uint256,bytes32,bytes) | 0x9c884fd1 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setAssetHandlerAddressThisChain(bytes32,address) | 0x548a5a33 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | AssetHandlerRegistered(bytes32,address) | 0x2632cc0d58b0cb1017b99cc0b6cc66ad86440cc0dd923bfdaa294f95ba1b0201 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | AssetHandlerRegisteredInitial(bytes32,address,bytes32,address) | 0xb1e82bee3e85b2755fbceb4b7e051f5c66a7f35f0476657504e77e18ebd3a17d | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubDepositBaseTokenInitiated(uint256,address,bytes32,uint256) | 0x0f87e1ea5eb1f034a6071ef630c174063e3d48756f853efaaf4292b929298240 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubDepositInitiated(uint256,bytes32,address,bytes32,bytes) | 0xe21913bc89c1320d9709a5d236ffe06b54cf88aecfc9509ebd68f1adba45781e | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubWithdrawalInitiated(uint256,address,bytes32,bytes32) | 0x9a3d4025b7294a1754ea5b56309c1e72328d97b73718183db595c850d14a3ae0 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | DepositFinalizedAssetRouter(uint256,bytes32,bytes) | 0x44eb9a840094a49b3cd0a5205042598a1c08c4e87bafb5760bc2d8efa170c541 | ++----------+---------------------------------------------------------------------+--------------------------------------------------------------------+ + +IL1AssetRouter ++----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=====================================================================================================================================================================================+ +| Function | BRIDGE_HUB() | 0x5d4edca7 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_NULLIFIER() | 0xe60ccaba | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_WETH_TOKEN() | 0x41c841c3 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | assetHandlerAddress(bytes32) | 0x53b9e632 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeRecoverFailedTransfer(uint256,address,bytes32,bytes) | 0x1346ca3b | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeRecoverFailedTransfer(uint256,address,bytes32,bytes,bytes32,uint256,uint256,uint16,bytes32[]) | 0x3601e63e | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgehubConfirmL2Transaction(uint256,bytes32,bytes32) | 0x8eb7db57 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgehubDeposit(uint256,address,uint256,bytes) | 0xca408c23 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgehubDepositBaseToken(uint256,bytes32,address,uint256) | 0xc4879440 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | depositLegacyErc20Bridge(address,address,address,uint256,uint256,uint256,address) | 0x9e6ea417 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeDeposit(uint256,bytes32,bytes) | 0x9c884fd1 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeWithdrawal(uint256,uint256,uint256,uint16,bytes,bytes32[]) | 0xc87325f1 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getDepositCalldata(address,bytes32,bytes) | 0x2ff0b2ea | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isWithdrawalFinalized(uint256,uint256,uint256) | 0x8f31f052 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2BridgeAddress(uint256) | 0x07ee9355 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | nativeTokenVault() | 0x64e130cf | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setAssetDeploymentTracker(bytes32,address) | 0xc0a16dda | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setAssetHandlerAddressThisChain(bytes32,address) | 0x548a5a33 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setNativeTokenVault(address) | 0x0f3fa211 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferFundsToNTV(bytes32,uint256,address) | 0x57d4ca5c | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | AssetDeploymentTrackerSet(bytes32,address,bytes32) | 0x14c1bae9bcc3777747463b66a36584aa75e4ded1aa38089f447beecb125a2175 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | AssetHandlerRegistered(bytes32,address) | 0x2632cc0d58b0cb1017b99cc0b6cc66ad86440cc0dd923bfdaa294f95ba1b0201 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | AssetHandlerRegisteredInitial(bytes32,address,bytes32,address) | 0xb1e82bee3e85b2755fbceb4b7e051f5c66a7f35f0476657504e77e18ebd3a17d | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubDepositBaseTokenInitiated(uint256,address,bytes32,uint256) | 0x0f87e1ea5eb1f034a6071ef630c174063e3d48756f853efaaf4292b929298240 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubDepositFinalized(uint256,bytes32,bytes32) | 0xe4def01b981193a97a9e81230d7b9f31812ceaf23f864a828a82c687911cb2df | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubDepositInitiated(uint256,bytes32,address,bytes32,bytes) | 0xe21913bc89c1320d9709a5d236ffe06b54cf88aecfc9509ebd68f1adba45781e | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubMintData(bytes) | 0x31a15cb4f69820f57afabeaff74feae31dc25875c07c952ba742a3acf8690f91 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubWithdrawalInitiated(uint256,address,bytes32,bytes32) | 0x9a3d4025b7294a1754ea5b56309c1e72328d97b73718183db595c850d14a3ae0 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ClaimedFailedDepositAssetRouter(uint256,bytes32,bytes) | 0x4250817d22c13fba8067153d85ccd9706326ac2bd14d5c3898c8b1bccc440658 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | DepositFinalizedAssetRouter(uint256,bytes32,bytes) | 0x44eb9a840094a49b3cd0a5205042598a1c08c4e87bafb5760bc2d8efa170c541 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | LegacyDepositInitiated(uint256,bytes32,address,address,address,uint256) | 0xa1846a4248529db592da99da276f761d9f37a84d0f3d4e83819b869759000700 | ++----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +IL2AssetRouter ++----------+---------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=====================================================================================================================================================+ +| Function | BRIDGE_HUB() | 0x5d4edca7 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | assetHandlerAddress(bytes32) | 0x53b9e632 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeDeposit(uint256,bytes32,bytes) | 0x9c884fd1 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeDepositLegacyBridge(address,address,address,uint256,bytes) | 0x54b2e69c | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l1AssetRouter() | 0x6d9860e1 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setAssetHandlerAddress(uint256,bytes32,address) | 0xda556bdc | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setAssetHandlerAddressThisChain(bytes32,address) | 0x548a5a33 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | withdraw(bytes32,bytes) | 0x4a2e35ba | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | withdrawLegacyBridge(address,address,uint256,address) | 0x7ac3a553 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | AssetHandlerRegistered(bytes32,address) | 0x2632cc0d58b0cb1017b99cc0b6cc66ad86440cc0dd923bfdaa294f95ba1b0201 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | AssetHandlerRegisteredInitial(bytes32,address,bytes32,address) | 0xb1e82bee3e85b2755fbceb4b7e051f5c66a7f35f0476657504e77e18ebd3a17d | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubDepositBaseTokenInitiated(uint256,address,bytes32,uint256) | 0x0f87e1ea5eb1f034a6071ef630c174063e3d48756f853efaaf4292b929298240 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubDepositInitiated(uint256,bytes32,address,bytes32,bytes) | 0xe21913bc89c1320d9709a5d236ffe06b54cf88aecfc9509ebd68f1adba45781e | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubWithdrawalInitiated(uint256,address,bytes32,bytes32) | 0x9a3d4025b7294a1754ea5b56309c1e72328d97b73718183db595c850d14a3ae0 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | DepositFinalizedAssetRouter(uint256,bytes32,bytes) | 0x44eb9a840094a49b3cd0a5205042598a1c08c4e87bafb5760bc2d8efa170c541 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | WithdrawalInitiatedAssetRouter(uint256,address,bytes32,bytes) | 0x55362fc62473cb1255e770af5d5e02ba6ee5bc7ed6969c30eb11ca31b92384dc | ++----------+---------------------------------------------------------------------+--------------------------------------------------------------------+ + +IAssetHandler ++----------+-----------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=====================================================================================================================================+ +| Function | bridgeBurn(uint256,uint256,bytes32,address,bytes) | 0x699b0fb9 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeMint(uint256,bytes32,bytes) | 0x36ba0355 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeBurn(uint256,bytes32,address,address,uint256) | 0x1cd02155ad1064c60598a8bd0e4e795d7e7d0a0f3c38aad04d261f1297fb2545 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeInitialize(address,string,string,uint8) | 0x81e8e92e5873539605a102eddae7ed06d19bea042099a437cbc3644415eb7404 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeMint(uint256,bytes32,address,uint256) | 0xbc0f4055a7869d8ecad34b33382a0bc181c5811565fec42f335505be5fd661d2 | ++----------+-----------------------------------------------------+--------------------------------------------------------------------+ + +IBridgedStandardToken ++----------+-----------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++===============================================================================================================================+ +| Function | assetId() | 0x44de240a | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeBurn(address,uint256) | 0x74f4f547 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeMint(address,uint256) | 0x8c2a993e | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | l1Address() | 0xc2eeeebd | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | l2Bridge() | 0xae1f6aaf | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | nativeTokenVault() | 0x64e130cf | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | originToken() | 0x13096a41 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeBurn(address,uint256) | 0x9b5b9a05e4726d8bb959f1440e05c6b8109443f2083bc4e386237d7654526553 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeInitialize(address,string,string,uint8) | 0x81e8e92e5873539605a102eddae7ed06d19bea042099a437cbc3644415eb7404 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeMint(address,uint256) | 0x397b33b307fc137878ebfc75b295289ec0ee25a31bb5bf034f33256fe8ea2aa6 | ++----------+-----------------------------------------------+--------------------------------------------------------------------+ + +IL1AssetDeploymentTracker ++----------+----------------------------------------------------------------+------------+ +| Type | Signature | Selector | ++========================================================================================+ +| Function | bridgeCheckCounterpartAddress(uint256,bytes32,address,address) | 0x9cc395d0 | ++----------+----------------------------------------------------------------+------------+ + +IL1AssetHandler ++----------+------------------------------------------------------------+------------+ +| Type | Signature | Selector | ++====================================================================================+ +| Function | bridgeRecoverFailedTransfer(uint256,bytes32,address,bytes) | 0xc2e90293 | ++----------+------------------------------------------------------------+------------+ + +IL1BaseTokenAssetHandler ++----------+-----------------------+------------+ +| Type | Signature | Selector | ++===============================================+ +| Function | tokenAddress(bytes32) | 0x97bb3ce9 | ++----------+-----------------------+------------+ + +IL1ERC20Bridge ++----------+------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++==============================================================================================================================================================+ +| Function | L1_ASSET_ROUTER() | 0xcdf25430 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_NATIVE_TOKEN_VAULT() | 0x293e8520 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_NULLIFIER() | 0xe60ccaba | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | claimFailedDeposit(address,address,bytes32,uint256,uint256,uint16,bytes32[]) | 0x19fa7f62 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | deposit(address,address,uint256,uint256,uint256) | 0x933999fb | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | deposit(address,address,uint256,uint256,uint256,address) | 0xe8b99b1b | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | depositAmount(address,address,bytes32) | 0x01eae183 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeWithdrawal(uint256,uint256,uint16,bytes,bytes32[]) | 0x11a2ccc1 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isWithdrawalFinalized(uint256,uint256) | 0x4bed8212 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2Bridge() | 0xae1f6aaf | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TokenAddress(address) | 0xf5f15168 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TokenBeacon() | 0x6dde7209 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ClaimedFailedDeposit(address,address,uint256) | 0xbe066dc591f4a444f75176d387c3e6c775e5706d9ea9a91d11eb49030c66cf60 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | DepositInitiated(bytes32,address,address,address,uint256) | 0xdd341179f4edc78148d894d0213a96d212af2cbaf223d19ef6d483bdd47ab81d | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | WithdrawalFinalized(address,address,uint256) | 0xac1b18083978656d557d6e91c88203585cfda1031bdb14538327121ef140d383 | ++----------+------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +IL1Nullifier ++----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=======================================================================================================================================================================================+ +| Function | BRIDGE_HUB() | 0x5d4edca7 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeRecoverFailedTransfer(uint256,address,bytes32,bytes,bytes32,uint256,uint256,uint16,bytes32[]) | 0x3601e63e | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgehubConfirmL2TransactionForwarded(uint256,bytes32,bytes32) | 0x4bc2c8c0 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | chainBalance(uint256,address) | 0x9cd45184 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | claimFailedDeposit(uint256,address,address,uint256,bytes32,uint256,uint256,uint16,bytes32[]) | 0xc0991525 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | claimFailedDepositLegacyErc20Bridge(address,address,uint256,bytes32,uint256,uint256,uint16,bytes32[]) | 0x8fbb3711 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | depositHappened(uint256,bytes32) | 0x9fa8826b | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeDeposit((uint256,uint256,uint256,address,uint16,bytes,bytes32[])) | 0x74beea82 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isWithdrawalFinalized(uint256,uint256,uint256) | 0x8f31f052 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l1NativeTokenVault() | 0x6f513211 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2BridgeAddress(uint256) | 0x07ee9355 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | legacyBridge() | 0x6e9d7899 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | nullifyChainBalanceByNTV(uint256,address) | 0x5de097b1 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setL1AssetRouter(address) | 0x780ce114 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setL1NativeTokenVault(address) | 0xb7cc6f46 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferTokenToNTV(address) | 0x40a434d5 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubDepositFinalized(uint256,bytes32,bytes32) | 0xe4def01b981193a97a9e81230d7b9f31812ceaf23f864a828a82c687911cb2df | ++----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +IL1SharedBridgeLegacy ++----------+--------------------------+------------+ +| Type | Signature | Selector | ++==================================================+ +| Function | l2BridgeAddress(uint256) | 0x07ee9355 | ++----------+--------------------------+------------+ + +IL2Bridge ++----------+-----------------------------------------+------------+ +| Type | Signature | Selector | ++=================================================================+ +| Function | finalizeDeposit(bytes32,bytes) | 0xca65fe79 | +|----------+-----------------------------------------+------------| +| Function | l1Bridge() | 0x969b53da | +|----------+-----------------------------------------+------------| +| Function | setAssetHandlerAddress(bytes32,address) | 0x3f704d2a | +|----------+-----------------------------------------+------------| +| Function | withdraw(bytes32,bytes) | 0x4a2e35ba | ++----------+-----------------------------------------+------------+ + +IL2SharedBridgeLegacy ++----------+--------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++==================================================================================================================================+ +| Function | deployBeaconProxy(bytes32) | 0x07168226 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Function | l1Bridge() | 0x969b53da | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Function | l1SharedBridge() | 0xb852ad36 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Function | l1TokenAddress(address) | 0xf54266a2 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TokenAddress(address) | 0xf5f15168 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TokenBeacon() | 0x6dde7209 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Function | sendMessageToL1(bytes) | 0xff21c125 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Function | withdraw(address,address,uint256) | 0xd9caed12 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Event | FinalizeDeposit(address,address,address,uint256) | 0xb84fba9af218da60d299dc177abd5805e7ac541d2673cbee7808c10017874f63 | ++----------+--------------------------------------------------+--------------------------------------------------------------------+ + +IL2SharedBridgeLegacyFunctions ++----------+--------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++========================================================================================================================================+ +| Function | finalizeDeposit(address,address,address,uint256,bytes) | 0xcfe7af7c | +|----------+--------------------------------------------------------+--------------------------------------------------------------------| +| Event | FinalizeDeposit(address,address,address,uint256) | 0xb84fba9af218da60d299dc177abd5805e7ac541d2673cbee7808c10017874f63 | +|----------+--------------------------------------------------------+--------------------------------------------------------------------| +| Event | WithdrawalInitiated(address,address,address,uint256) | 0x2fc3848834aac8e883a2d2a17a7514dc4f2d3dd268089df9b9f5d918259ef3b0 | ++----------+--------------------------------------------------------+--------------------------------------------------------------------+ + +IL2WrappedBaseToken ++----------+---------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=================================================================================================================+ +| Function | deposit() | 0xd0e30db0 | +|----------+---------------------------------+--------------------------------------------------------------------| +| Function | depositTo(address) | 0xb760faf9 | +|----------+---------------------------------+--------------------------------------------------------------------| +| Function | withdraw(uint256) | 0x2e1a7d4d | +|----------+---------------------------------+--------------------------------------------------------------------| +| Function | withdrawTo(address,uint256) | 0x205c2878 | +|----------+---------------------------------+--------------------------------------------------------------------| +| Event | Initialize(string,string,uint8) | 0xc21caeb4e8f73861400d4c0870ad3e468ddb4e45225da3832ce1da5561f1f61e | ++----------+---------------------------------+--------------------------------------------------------------------+ + +IWETH9 ++----------+-------------------+------------+ +| Type | Signature | Selector | ++===========================================+ +| Function | deposit() | 0xd0e30db0 | +|----------+-------------------+------------| +| Function | withdraw(uint256) | 0x2e1a7d4d | ++----------+-------------------+------------+ + +IL1NativeTokenVault ++----------+----------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++================================================================================================================================================+ +| Function | ASSET_ROUTER() | 0xc6a70bbb | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_CHAIN_ID() | 0x2f90b184 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_NULLIFIER() | 0xe60ccaba | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | WETH_TOKEN() | 0x37d277d4 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | assetId(address) | 0xfd3f60df | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeCheckCounterpartAddress(uint256,bytes32,address,address) | 0x9cc395d0 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | calculateCreate2TokenAddress(uint256,address) | 0xc487412c | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | chainBalance(uint256,bytes32) | 0x3345359b | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | ensureTokenIsRegistered(address) | 0x19a2a285 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getERC20Getters(address,uint256) | 0xa7236d16 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | originChainId(bytes32) | 0x5f3455b5 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | registerEthToken() | 0xcb6da609 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | registerToken(address) | 0x09824a80 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | tokenAddress(bytes32) | 0x97bb3ce9 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgedTokenBeaconUpdated(address,bytes32) | 0xc3f14dba68f86c42f518e5c0e8a5cbc9514da6f388e2f52c5b1a6263d8588bfb | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Event | TokenBeaconUpdated(address) | 0x5ed5e4f58bf9a324a38beaa1177fb96fcb7bf3a5f4c4585ebb78c4a8c0249d0f | ++----------+----------------------------------------------------------------+--------------------------------------------------------------------+ + +IL2NativeTokenVault ++----------+------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++======================================================================================================================================+ +| Function | ASSET_ROUTER() | 0xc6a70bbb | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_CHAIN_ID() | 0x2f90b184 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | WETH_TOKEN() | 0x37d277d4 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | assetId(address) | 0xfd3f60df | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | calculateCreate2TokenAddress(uint256,address) | 0xc487412c | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | ensureTokenIsRegistered(address) | 0x19a2a285 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | getERC20Getters(address,uint256) | 0xa7236d16 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TokenAddress(address) | 0xf5f15168 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | originChainId(bytes32) | 0x5f3455b5 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | registerToken(address) | 0x09824a80 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | tokenAddress(bytes32) | 0x97bb3ce9 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgedTokenBeaconUpdated(address,bytes32) | 0xc3f14dba68f86c42f518e5c0e8a5cbc9514da6f388e2f52c5b1a6263d8588bfb | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | FinalizeDeposit(address,address,address,uint256) | 0xb84fba9af218da60d299dc177abd5805e7ac541d2673cbee7808c10017874f63 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | L2TokenBeaconUpdated(address,bytes32) | 0x01fd5911e6d04aec6b21f19752502ad7f3e9876279643c8fa7a4d30c88a29fb2 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | WithdrawalInitiated(address,address,address,uint256) | 0x2fc3848834aac8e883a2d2a17a7514dc4f2d3dd268089df9b9f5d918259ef3b0 | ++----------+------------------------------------------------------+--------------------------------------------------------------------+ + +INativeTokenVault ++----------+-----------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++===============================================================================================================================+ +| Function | ASSET_ROUTER() | 0xc6a70bbb | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | L1_CHAIN_ID() | 0x2f90b184 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | WETH_TOKEN() | 0x37d277d4 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | assetId(address) | 0xfd3f60df | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | calculateCreate2TokenAddress(uint256,address) | 0xc487412c | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | ensureTokenIsRegistered(address) | 0x19a2a285 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | getERC20Getters(address,uint256) | 0xa7236d16 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | originChainId(bytes32) | 0x5f3455b5 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | registerToken(address) | 0x09824a80 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | tokenAddress(bytes32) | 0x97bb3ce9 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Event | BridgedTokenBeaconUpdated(address,bytes32) | 0xc3f14dba68f86c42f518e5c0e8a5cbc9514da6f388e2f52c5b1a6263d8588bfb | ++----------+-----------------------------------------------+--------------------------------------------------------------------+ + +IBridgehub ++----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=========================================================================================================================================================================================+ +| Function | L1_CHAIN_ID() | 0x2f90b184 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptAdmin() | 0x0e18b681 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | addChainTypeManager(address) | 0xff5a62a1 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | addTokenAssetId(bytes32) | 0x1c50cfea | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | admin() | 0xf851a440 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | assetIdIsRegistered(bytes32) | 0xe0ab6368 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | baseToken(uint256) | 0x59ec65a2 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | baseTokenAssetId(uint256) | 0xe52db4ca | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeBurn(uint256,uint256,bytes32,address,bytes) | 0x699b0fb9 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeMint(uint256,bytes32,bytes) | 0x36ba0355 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeRecoverFailedTransfer(uint256,bytes32,address,bytes) | 0xc2e90293 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | chainTypeManager(uint256) | 0x9d5bd3da | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | chainTypeManagerIsRegistered(address) | 0xb93c9366 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | createNewChain(uint256,address,bytes32,uint256,address,bytes,bytes[]) | 0xf113c88b | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | ctmAssetIdFromAddress(address) | 0x70fccb52 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | ctmAssetIdFromChainId(uint256) | 0x24358c61 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | ctmAssetIdToAddress(bytes32) | 0x07621f84 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardTransactionOnGateway(uint256,bytes32,uint64) | 0x524c0cfa | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getAllZKChainChainIDs() | 0x68b8d331 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getAllZKChains() | 0x49707f31 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getHyperchain(uint256) | 0xdead6f7f | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getZKChain(uint256) | 0xe680c4c1 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l1CtmDeployer() | 0xcbe83612 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TransactionBaseCost(uint256,uint256,uint256,uint256) | 0x71623274 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | messageRoot() | 0xd4b9f4fa | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | migrationPaused() | 0x2a641114 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL1ToL2TransactionStatus(uint256,bytes32,uint256,uint256,uint16,bytes32[],uint8) | 0xb292f5f1 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL2LogInclusion(uint256,uint256,uint256,(uint8,bool,uint16,address,bytes32,bytes32),bytes32[]) | 0xe6d9923b | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL2MessageInclusion(uint256,uint256,uint256,(uint16,address,bytes),bytes32[]) | 0x99c16d1a | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | registerAlreadyDeployedZKChain(uint256,address) | 0xb5662c5d | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | registerSettlementLayer(uint256,bool) | 0xdc8e4b26 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | removeChainTypeManager(address) | 0x332b96dc | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | requestL2TransactionDirect((uint256,uint256,address,uint256,bytes,uint256,uint256,bytes[],address)) | 0xd52471c1 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | requestL2TransactionTwoBridges((uint256,uint256,uint256,uint256,uint256,address,address,uint256,bytes)) | 0x24fd57fb | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setAddresses(address,address,address) | 0x363bf964 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setAssetHandlerAddress(bytes32,address) | 0x3f704d2a | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setLegacyBaseTokenAssetId(uint256) | 0xca8f93f1 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setLegacyChainAddress(uint256) | 0xd92f86a2 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPendingAdmin(address) | 0x4dd18bf5 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | settlementLayer(uint256) | 0x671a7131 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | sharedBridge() | 0x38720778 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | whitelistedSettlementLayers(uint256) | 0xe9420f8c | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | AssetRegistered(bytes32,address,bytes32,address) | 0x8f09d7694a9ae17acec5cf132d594d7eee23572f7fe132396ce72b1afbf7ef20 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BaseTokenAssetIdRegistered(bytes32) | 0x3df150949161462acf3be30521d7da9e533b247327a254e55dd01875897a6df3 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeBurn(uint256,bytes32,address,address,uint256) | 0x1cd02155ad1064c60598a8bd0e4e795d7e7d0a0f3c38aad04d261f1297fb2545 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeInitialize(address,string,string,uint8) | 0x81e8e92e5873539605a102eddae7ed06d19bea042099a437cbc3644415eb7404 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeMint(uint256,bytes32,address,uint256) | 0xbc0f4055a7869d8ecad34b33382a0bc181c5811565fec42f335505be5fd661d2 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ChainTypeManagerAdded(address) | 0x2eae91be1021e05cc8076387b0182458ae474ae44ee44cc59aefda6ca53c1f42 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ChainTypeManagerRemoved(address) | 0x4e04a497739580efe78a7ee09cdabe6f6fe90965c683292a519102ce5193b68a | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | MigrationFinalized(uint256,bytes32,address) | 0xb0cc16029b506b2a262b52711e71db4fcd1cb078bd4bb86c7ba82cd3be2eadd3 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | MigrationStarted(uint256,bytes32,uint256) | 0xc60eb6d595da5361c68f60aa7c8286b8f73c3a99e9db1818e146c522f512496f | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewAdmin(address,address) | 0xf9ffabca9c8276e99321725bcb43fb076a6c66a54b7f21c4e8146d8519b417dc | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewChain(uint256,address,address) | 0x1e9125bc72db22c58abff6821d7333551967e26454b419ffa958e4cb8ef47600 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewPendingAdmin(address,address) | 0xca4f2f25d0898edd99413412fb94012f9e54ec8142f9b093e7720646a95b16a9 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | SettlementLayerRegistered(uint256,bool) | 0x02629feb109d94b16a367231d248ba81c462f51ce5b984835f150f1c9f49ed25 | ++----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +ICTMDeploymentTracker ++----------+----------------------------------------------------------------+------------+ +| Type | Signature | Selector | ++========================================================================================+ +| Function | BRIDGE_HUB() | 0x5d4edca7 | +|----------+----------------------------------------------------------------+------------| +| Function | L1_ASSET_ROUTER() | 0xcdf25430 | +|----------+----------------------------------------------------------------+------------| +| Function | bridgeCheckCounterpartAddress(uint256,bytes32,address,address) | 0x9cc395d0 | +|----------+----------------------------------------------------------------+------------| +| Function | bridgehubDeposit(uint256,address,uint256,bytes) | 0xca408c23 | +|----------+----------------------------------------------------------------+------------| +| Function | calculateAssetId(address) | 0xb68c104a | +|----------+----------------------------------------------------------------+------------| +| Function | registerCTMAssetOnL1(address) | 0x2f9db630 | ++----------+----------------------------------------------------------------+------------+ + +IMessageRoot ++----------+--------------------------------------------+------------+ +| Type | Signature | Selector | ++====================================================================+ +| Function | BRIDGE_HUB() | 0x5d4edca7 | +|----------+--------------------------------------------+------------| +| Function | addChainBatchRoot(uint256,uint256,bytes32) | 0xfb644fc5 | +|----------+--------------------------------------------+------------| +| Function | addNewChain(uint256) | 0xd4ce08c2 | ++----------+--------------------------------------------+------------+ + +IL2Messenger ++----------+-----------------+------------+ +| Type | Signature | Selector | ++=========================================+ +| Function | sendToL1(bytes) | 0x62f84b24 | ++----------+-----------------+------------+ + +IL1Messenger ++----------+-----------------+------------+ +| Type | Signature | Selector | ++=========================================+ +| Function | sendToL1(bytes) | 0x62f84b24 | ++----------+-----------------+------------+ + +IL2ContractDeployer ++----------+----------------------------------------------------------------+------------+ +| Type | Signature | Selector | ++========================================================================================+ +| Function | create2(bytes32,bytes32,bytes) | 0x3cda3351 | +|----------+----------------------------------------------------------------+------------| +| Function | forceDeployOnAddresses((bytes32,address,bool,uint256,bytes)[]) | 0xe9f18c17 | ++----------+----------------------------------------------------------------+------------+ + +DummyRestriction ++----------+-----------------------------------------------+------------+ +| Type | Signature | Selector | ++=======================================================================+ +| Function | getSupportsRestrictionMagic() | 0x83e866f5 | +|----------+-----------------------------------------------+------------| +| Function | validateCall((address,uint256,bytes),address) | 0x9a9debe9 | ++----------+-----------------------------------------------+------------+ + +EventOnFallback ++-------+-------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++============================================================================================================+ +| Event | Called(address,uint256,bytes) | 0x998729e624ef59107d5781f8c021b04a5d7b5153dd0ef79e7a08a9f8ab40a5a3 | ++-------+-------------------------------+--------------------------------------------------------------------+ + +FeeOnTransferToken ++----------+---------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=======================================================================================================================+ +| Function | allowance(address,address) | 0xdd62ed3e | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | approve(address,uint256) | 0x095ea7b3 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | balanceOf(address) | 0x70a08231 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | decimals() | 0x313ce567 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | decreaseAllowance(address,uint256) | 0xa457c2d7 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | increaseAllowance(address,uint256) | 0x39509351 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | mint(address,uint256) | 0x40c10f19 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | name() | 0x06fdde03 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | symbol() | 0x95d89b41 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | totalSupply() | 0x18160ddd | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | transfer(address,uint256) | 0xa9059cbb | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | transferFrom(address,address,uint256) | 0x23b872dd | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Event | Approval(address,address,uint256) | 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Event | Transfer(address,address,uint256) | 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef | ++----------+---------------------------------------+--------------------------------------------------------------------+ + +Forwarder ++----------+------------------------+------------+ +| Type | Signature | Selector | ++================================================+ +| Function | forward(address,bytes) | 0x6fadcf72 | ++----------+------------------------+------------+ + +Multicall ++----------+------------------------------+------------+ +| Type | Signature | Selector | ++======================================================+ +| Function | aggregate((address,bytes)[]) | 0x252dba42 | +|----------+------------------------------+------------| +| Function | getBlockHash(uint256) | 0xee82ac5e | +|----------+------------------------------+------------| +| Function | getCurrentBlockCoinbase() | 0xa8b0574e | +|----------+------------------------------+------------| +| Function | getCurrentBlockDifficulty() | 0x72425d9d | +|----------+------------------------------+------------| +| Function | getCurrentBlockGasLimit() | 0x86d516e8 | +|----------+------------------------------+------------| +| Function | getCurrentBlockTimestamp() | 0x0f28c97d | +|----------+------------------------------+------------| +| Function | getEthBalance(address) | 0x4d2301cc | +|----------+------------------------------+------------| +| Function | getLastBlockHash() | 0x27e86d6e | ++----------+------------------------------+------------+ + +Multicall3 ++----------+-------------------------------------------------+------------+ +| Type | Signature | Selector | ++=========================================================================+ +| Function | aggregate((address,bytes)[]) | 0x252dba42 | +|----------+-------------------------------------------------+------------| +| Function | aggregate3((address,bool,bytes)[]) | 0x82ad56cb | +|----------+-------------------------------------------------+------------| +| Function | aggregate3Value((address,bool,uint256,bytes)[]) | 0x174dea71 | +|----------+-------------------------------------------------+------------| +| Function | blockAndAggregate((address,bytes)[]) | 0xc3077fa9 | +|----------+-------------------------------------------------+------------| +| Function | getBasefee() | 0x3e64a696 | +|----------+-------------------------------------------------+------------| +| Function | getBlockHash(uint256) | 0xee82ac5e | +|----------+-------------------------------------------------+------------| +| Function | getBlockNumber() | 0x42cbb15c | +|----------+-------------------------------------------------+------------| +| Function | getChainId() | 0x3408e470 | +|----------+-------------------------------------------------+------------| +| Function | getCurrentBlockCoinbase() | 0xa8b0574e | +|----------+-------------------------------------------------+------------| +| Function | getCurrentBlockDifficulty() | 0x72425d9d | +|----------+-------------------------------------------------+------------| +| Function | getCurrentBlockGasLimit() | 0x86d516e8 | +|----------+-------------------------------------------------+------------| +| Function | getCurrentBlockTimestamp() | 0x0f28c97d | +|----------+-------------------------------------------------+------------| +| Function | getEthBalance(address) | 0x4d2301cc | +|----------+-------------------------------------------------+------------| +| Function | getLastBlockHash() | 0x27e86d6e | +|----------+-------------------------------------------------+------------| +| Function | tryAggregate(bool,(address,bytes)[]) | 0xbce38bd7 | +|----------+-------------------------------------------------+------------| +| Function | tryBlockAndAggregate(bool,(address,bytes)[]) | 0x399542e9 | ++----------+-------------------------------------------------+------------+ + +RevertReceiveAccount ++----------+------------------------+------------+ +| Type | Signature | Selector | ++================================================+ +| Function | revertReceive() | 0xaa4593dc | +|----------+------------------------+------------| +| Function | setRevertReceive(bool) | 0x607e2cb2 | ++----------+------------------------+------------+ + +RevertTransferERC20 ++----------+---------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=======================================================================================================================+ +| Function | allowance(address,address) | 0xdd62ed3e | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | approve(address,uint256) | 0x095ea7b3 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | balanceOf(address) | 0x70a08231 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | decimals() | 0x313ce567 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | decreaseAllowance(address,uint256) | 0xa457c2d7 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | increaseAllowance(address,uint256) | 0x39509351 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | mint(address,uint256) | 0x40c10f19 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | name() | 0x06fdde03 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | revertTransfer() | 0x1f067457 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | setRevertTransfer(bool) | 0xf20265d2 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | symbol() | 0x95d89b41 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | totalSupply() | 0x18160ddd | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | transfer(address,uint256) | 0xa9059cbb | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | transferFrom(address,address,uint256) | 0x23b872dd | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Event | Approval(address,address,uint256) | 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Event | Transfer(address,address,uint256) | 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef | ++----------+---------------------------------------+--------------------------------------------------------------------+ + +SingletonFactory ++----------+-----------------------+------------+ +| Type | Signature | Selector | ++===============================================+ +| Function | deploy(bytes,bytes32) | 0x4af63f02 | ++----------+-----------------------+------------+ + +TestnetERC20Token ++----------+---------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=======================================================================================================================+ +| Function | allowance(address,address) | 0xdd62ed3e | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | approve(address,uint256) | 0x095ea7b3 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | balanceOf(address) | 0x70a08231 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | decimals() | 0x313ce567 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | decreaseAllowance(address,uint256) | 0xa457c2d7 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | increaseAllowance(address,uint256) | 0x39509351 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | mint(address,uint256) | 0x40c10f19 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | name() | 0x06fdde03 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | symbol() | 0x95d89b41 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | totalSupply() | 0x18160ddd | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | transfer(address,uint256) | 0xa9059cbb | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | transferFrom(address,address,uint256) | 0x23b872dd | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Event | Approval(address,address,uint256) | 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Event | Transfer(address,address,uint256) | 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef | ++----------+---------------------------------------+--------------------------------------------------------------------+ + +WETH9 ++----------+---------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=======================================================================================================================+ +| Function | allowance(address,address) | 0xdd62ed3e | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | approve(address,uint256) | 0x095ea7b3 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | balanceOf(address) | 0x70a08231 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | decimals() | 0x313ce567 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | deposit() | 0xd0e30db0 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | name() | 0x06fdde03 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | symbol() | 0x95d89b41 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | totalSupply() | 0x18160ddd | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | transfer(address,uint256) | 0xa9059cbb | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | transferFrom(address,address,uint256) | 0x23b872dd | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Function | withdraw(uint256) | 0x2e1a7d4d | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Event | Approval(address,address,uint256) | 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925 | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Event | Deposit(address,uint256) | 0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Event | Transfer(address,address,uint256) | 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef | +|----------+---------------------------------------+--------------------------------------------------------------------| +| Event | Withdrawal(address,uint256) | 0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65 | ++----------+---------------------------------------+--------------------------------------------------------------------+ + +ITestnetERC20Token ++----------+-----------------------+------------+ +| Type | Signature | Selector | ++===============================================+ +| Function | decimals() | 0x313ce567 | +|----------+-----------------------+------------| +| Function | mint(address,uint256) | 0x40c10f19 | ++----------+-----------------------+------------+ + +AddressAliasHelperTest ++----------+---------------------------+------------+ +| Type | Signature | Selector | ++===================================================+ +| Function | applyL1ToL2Alias(address) | 0x7528c2c6 | +|----------+---------------------------+------------| +| Function | undoL1ToL2Alias(address) | 0x689992b3 | ++----------+---------------------------+------------+ + +DummyChainTypeManagerForValidatorTimelock ++----------+-----------------------------+------------+ +| Type | Signature | Selector | ++=====================================================+ +| Function | chainAdmin() | 0x6478d8ed | +|----------+-----------------------------+------------| +| Function | getChainAdmin(uint256) | 0x301e7765 | +|----------+-----------------------------+------------| +| Function | getZKChain(uint256) | 0xe680c4c1 | +|----------+-----------------------------+------------| +| Function | setZKChain(uint256,address) | 0xce214549 | +|----------+-----------------------------+------------| +| Function | zkChainAddress() | 0xbfcdc5bd | ++----------+-----------------------------+------------+ + +DummyERC20BytesTransferReturnValue ++----------+---------------------------+------------+ +| Type | Signature | Selector | ++===================================================+ +| Function | transfer(address,uint256) | 0xa9059cbb | ++----------+---------------------------+------------+ + +DummyERC20NoTransferReturnValue ++----------+---------------------------+------------+ +| Type | Signature | Selector | ++===================================================+ +| Function | transfer(address,uint256) | 0xa9059cbb | ++----------+---------------------------+------------+ + +DummyEraBaseTokenBridge ++----------+------------------------------------------------------------+------------+ +| Type | Signature | Selector | ++====================================================================================+ +| Function | bridgehubDepositBaseToken(uint256,address,address,uint256) | 0x2c4f2a58 | ++----------+------------------------------------------------------------+------------+ + +TransactionFiltererFalse ++----------+---------------------------------------------------------------------+------------+ +| Type | Signature | Selector | ++=============================================================================================+ +| Function | isTransactionAllowed(address,address,uint256,uint256,bytes,address) | 0xcbcf2e3c | ++----------+---------------------------------------------------------------------+------------+ + +TransactionFiltererTrue ++----------+---------------------------------------------------------------------+------------+ +| Type | Signature | Selector | ++=============================================================================================+ +| Function | isTransactionAllowed(address,address,uint256,uint256,bytes,address) | 0xcbcf2e3c | ++----------+---------------------------------------------------------------------+------------+ + +ReenterGovernance ++----------+-----------------------------------------------------------------------+------------+ +| Type | Signature | Selector | ++===============================================================================================+ +| Function | initialize(address,((address,uint256,bytes)[],bytes32,bytes32),uint8) | 0xb2ded522 | ++----------+-----------------------------------------------------------------------+------------+ + +ReenterL1ERC20Bridge ++----------+--------------------------+------------+ +| Type | Signature | Selector | ++==================================================+ +| Function | setBridge(address) | 0x8dd14802 | +|----------+--------------------------+------------| +| Function | setFunctionToCall(uint8) | 0x77421056 | ++----------+--------------------------+------------+ + +TestCalldataDA ++----------+-------------------------------------------------------------+------------+ +| Type | Signature | Selector | ++=====================================================================================+ +| Function | processCalldataDA(uint256,bytes32,uint256,bytes) | 0xfd3c6b55 | +|----------+-------------------------------------------------------------+------------| +| Function | processL2RollupDAValidatorOutputHash(bytes32,uint256,bytes) | 0x53e61bdc | ++----------+-------------------------------------------------------------+------------+ + +UncheckedMathTest ++----------+-------------------------------+------------+ +| Type | Signature | Selector | ++=======================================================+ +| Function | uncheckedAdd(uint256,uint256) | 0xb277f199 | +|----------+-------------------------------+------------| +| Function | uncheckedInc(uint256) | 0x8cf2b2f0 | ++----------+-------------------------------+------------+ + +UnsafeBytesTest ++----------+----------------------------+------------+ +| Type | Signature | Selector | ++====================================================+ +| Function | readAddress(bytes,uint256) | 0xf0e9da23 | +|----------+----------------------------+------------| +| Function | readBytes32(bytes,uint256) | 0xaf500fb7 | +|----------+----------------------------+------------| +| Function | readUint256(bytes,uint256) | 0xe02da327 | +|----------+----------------------------+------------| +| Function | readUint32(bytes,uint256) | 0x69c76df2 | ++----------+----------------------------+------------+ + +VerifierRecursiveTest ++----------+-----------------------------+------------+ +| Type | Signature | Selector | ++=====================================================+ +| Function | verificationKeyHash() | 0x9e8945d2 | +|----------+-----------------------------+------------| +| Function | verify(uint256[],uint256[]) | 0xb864f5a9 | ++----------+-----------------------------+------------+ + +VerifierTest ++----------+-----------------------------+------------+ +| Type | Signature | Selector | ++=====================================================+ +| Function | verificationKeyHash() | 0x9e8945d2 | +|----------+-----------------------------+------------| +| Function | verify(uint256[],uint256[]) | 0xb864f5a9 | ++----------+-----------------------------+------------+ + +IAccessControlRestriction ++-------+----------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++===============================================================================================================+ +| Event | FallbackRoleSet(address,bytes32) | 0xacb22a6b6e4593545bea6f5cdd10238b8a592aba079d9cfc76adb71edf02d40b | +|-------+----------------------------------+--------------------------------------------------------------------| +| Event | RoleSet(address,bytes4,bytes32) | 0x25c1ec7629ef447de39798ef5e6e0da89139b2257681441b45f1779c3efda036 | ++-------+----------------------------------+--------------------------------------------------------------------+ + +IChainAdmin ++----------+--------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++==================================================================================================================================+ +| Function | addRestriction(address) | 0x33163987 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Function | getRestrictions() | 0xc4195cb8 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Function | isRestrictionActive(address) | 0x02f0277d | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Function | multicall((address,uint256,bytes)[],bool) | 0x69340beb | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Function | removeRestriction(address) | 0x69a5b950 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Event | CallExecuted((address,uint256,bytes),bool,bytes) | 0x157c677a40c50f832f816d7b59c8c3e94774acae328c8ccb145b73aea7566d75 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Event | RestrictionAdded(address) | 0x83cfa0dec28fa91596ce8081b6279e7d1c402d3d4bc40934fc51f8830e7d82c6 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Event | RestrictionRemoved(address) | 0xdf4f1ec932dcab3c66fb7845ba6fc669816121e5d4a81d6955d3c6d3bff7b7e9 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Event | UpdateUpgradeTimestamp(uint256,uint256) | 0xd50ef21701c8ef211433b140724b8d6de471e7d822c8a616c3d424fe2d0e98a9 | ++----------+--------------------------------------------------+--------------------------------------------------------------------+ + +IGovernance ++----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++============================================================================================================================================================================+ +| Function | cancel(bytes32) | 0xc4d252f5 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | execute(((address,uint256,bytes)[],bytes32,bytes32)) | 0x74da756b | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | executeInstant(((address,uint256,bytes)[],bytes32,bytes32)) | 0x95218ecd | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getOperationState(bytes32) | 0x7958004c | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | hashOperation(((address,uint256,bytes)[],bytes32,bytes32)) | 0xc126e860 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isOperation(bytes32) | 0x31d50750 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isOperationDone(bytes32) | 0x2ab0f529 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isOperationPending(bytes32) | 0x584b153e | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isOperationReady(bytes32) | 0x13bc9f20 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | scheduleShadow(bytes32,uint256) | 0x6d1d8363 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | scheduleTransparent(((address,uint256,bytes)[],bytes32,bytes32),uint256) | 0x2c431917 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | updateDelay(uint256) | 0x64d62353 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | updateSecurityCouncil(address) | 0xdbfe3e96 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ChangeMinDelay(uint256,uint256) | 0x0c5ff76c31d24175d9e84ef46e328eafbcaeb2aa67a2333035eb082dd34324f1 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ChangeSecurityCouncil(address,address) | 0xe55ac8ae7914efca1278b8ed4ae21728d06ad9f6e7637bb2c905aacf2a6f5951 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OperationCancelled(bytes32) | 0xcf0f63b97f3387253cbc0bde884f975df77e39184dc3280c2c81be495f58eef4 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OperationExecuted(bytes32) | 0x1277662f4b42b8a4069e99fb5e41ce8919d3c621156090ac08fb11adbcec66f9 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ShadowOperationScheduled(bytes32,uint256) | 0xbcb40fd953364ec8aed99fa0bd6dcc03103f979efde4744ad7452e556ff20ba6 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | TransparentOperationScheduled(bytes32,uint256,((address,uint256,bytes)[],bytes32,bytes32)) | 0x23bc9f5dc037eb49c162fd08c2a4d43dfe70063149e140d502273168da0a0625 | ++----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +IPermanentRestriction ++-------+------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=======================================================================================================================+ +| Event | AdminImplementationAllowed(bytes32,bool) | 0xb9b1bc97b8d56d15ac2abdd71d788b0d6e14dc92fa9b8df811d0a2f41c14354a | +|-------+------------------------------------------+--------------------------------------------------------------------| +| Event | AllowL2Admin(address) | 0x43472806f9101fbc243f5a64948e56332b1d18bb082e7d4772a29368eb80991e | +|-------+------------------------------------------+--------------------------------------------------------------------| +| Event | AllowedDataChanged(bytes,bool) | 0xbb577d7ca5995b00ceaae0c75f77ab5df42a1e24673e2e36858ee718f3f1eb8c | +|-------+------------------------------------------+--------------------------------------------------------------------| +| Event | SelectorValidationChanged(bytes4,bool) | 0x85198d5146ae402b94f01f31a2049f75a6ecadc1d321090cd97467979b8d09d7 | ++-------+------------------------------------------+--------------------------------------------------------------------+ + +L2ProxyAdminDeployer ++----------+-----------------------+------------+ +| Type | Signature | Selector | ++===============================================+ +| Function | PROXY_ADMIN_ADDRESS() | 0x7e1dfea3 | ++----------+-----------------------+------------+ + +TransitionaryOwner ++----------+--------------------------------------------+------------+ +| Type | Signature | Selector | ++====================================================================+ +| Function | GOVERNANCE_ADDRESS() | 0x0143dd86 | +|----------+--------------------------------------------+------------| +| Function | claimOwnershipAndGiveToGovernance(address) | 0xfbdb0a0b | ++----------+--------------------------------------------+------------+ + +IRestriction ++----------+-----------------------------------------------+------------+ +| Type | Signature | Selector | ++=======================================================================+ +| Function | getSupportsRestrictionMagic() | 0x83e866f5 | +|----------+-----------------------------------------------+------------| +| Function | validateCall((address,uint256,bytes),address) | 0x9a9debe9 | ++----------+-----------------------------------------------+------------+ + +Restriction ++----------+-----------------------------------------------+------------+ +| Type | Signature | Selector | ++=======================================================================+ +| Function | getSupportsRestrictionMagic() | 0x83e866f5 | +|----------+-----------------------------------------------+------------| +| Function | validateCall((address,uint256,bytes),address) | 0x9a9debe9 | ++----------+-----------------------------------------------+------------+ + +TestnetVerifier ++----------+-----------------------------+------------+ +| Type | Signature | Selector | ++=====================================================+ +| Function | verificationKeyHash() | 0x9e8945d2 | +|----------+-----------------------------+------------| +| Function | verify(uint256[],uint256[]) | 0xb864f5a9 | ++----------+-----------------------------+------------+ + +Verifier ++----------+-----------------------------+------------+ +| Type | Signature | Selector | ++=====================================================+ +| Function | verificationKeyHash() | 0x9e8945d2 | +|----------+-----------------------------+------------| +| Function | verify(uint256[],uint256[]) | 0xb864f5a9 | ++----------+-----------------------------+------------+ + +IExecutor ++----------+-----------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++===========================================================================================================================================+ +| Function | commitBatchesSharedBridge(uint256,uint256,uint256,bytes) | 0x98f81962 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | executeBatchesSharedBridge(uint256,uint256,uint256,bytes) | 0xcf02827d | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | getName() | 0x17d7de7c | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveBatchesSharedBridge(uint256,uint256,uint256,bytes) | 0xe12a6137 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | revertBatchesSharedBridge(uint256,uint256) | 0x0f23da43 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlockCommit(uint256,bytes32,bytes32) | 0x8f2916b2f2d78cc5890ead36c06c0f6d5d112c7e103589947e8e2f0d6eddb763 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlockExecution(uint256,bytes32,bytes32) | 0x2402307311a4d6604e4e7b4c8a15a7e1213edb39c16a31efa70afb06030d3165 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlocksRevert(uint256,uint256,uint256) | 0x8bd4b15ea7d1bc41ea9abc3fc487ccb89cd678a00786584714faa9d751c84ee5 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlocksVerification(uint256,uint256) | 0x22c9005dd88c18b552a1cd7e8b3b937fcde9ca69213c1f658f54d572e4877a81 | ++----------+-----------------------------------------------------------+--------------------------------------------------------------------+ + +IL1DAValidator ++----------+------------------------------------------------+------------+ +| Type | Signature | Selector | ++========================================================================+ +| Function | checkDA(uint256,uint256,bytes32,bytes,uint256) | 0x381c3f13 | ++----------+------------------------------------------------+------------+ + +ILegacyGetters ++----------+------------------------------------------+------------+ +| Type | Signature | Selector | ++==================================================================+ +| Function | getL2SystemContractsUpgradeBlockNumber() | 0x9d1b5a81 | +|----------+------------------------------------------+------------| +| Function | getName() | 0x17d7de7c | +|----------+------------------------------------------+------------| +| Function | getTotalBlocksCommitted() | 0xfe26699e | +|----------+------------------------------------------+------------| +| Function | getTotalBlocksExecuted() | 0x39607382 | +|----------+------------------------------------------+------------| +| Function | getTotalBlocksVerified() | 0xaf6a2dcd | +|----------+------------------------------------------+------------| +| Function | storedBlockHash(uint256) | 0x74f4d30d | ++----------+------------------------------------------+------------+ + +IMailbox ++----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++===================================================================================================================================================================================================================================================================+ +| Function | bridgehubRequestL2Transaction((address,address,uint256,uint256,bytes,uint256,uint256,bytes[],address)) | 0x12f43dab | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgehubRequestL2TransactionOnGateway(bytes32,uint64) | 0xddcc9eec | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeEthWithdrawal(uint256,uint256,uint16,bytes,bytes32[]) | 0x6c0960f9 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getName() | 0x17d7de7c | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TransactionBaseCost(uint256,uint256,uint256) | 0xb473318e | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL1ToL2TransactionStatus(bytes32,uint256,uint256,uint16,bytes32[],uint8) | 0x042901c7 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL2LeafInclusion(uint256,uint256,bytes32,bytes32[]) | 0x7efda2ae | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL2LogInclusion(uint256,uint256,(uint8,bool,uint16,address,bytes32,bytes32),bytes32[]) | 0x263b7f8e | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL2MessageInclusion(uint256,uint256,(uint16,address,bytes),bytes32[]) | 0xe4948f43 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | requestL2Transaction(address,uint256,bytes,uint256,uint256,bytes[],address) | 0xeb672419 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | requestL2TransactionToGatewayMailbox(uint256,bytes32,uint64) | 0xd0772551 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewPriorityRequest(uint256,bytes32,uint64,(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[]) | 0x4531cd5795773d7101c17bdeb9f5ab7f47d7056017506f937083be5d6e77a382 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewRelayedPriorityTransaction(uint256,bytes32,uint64) | 0x0137d2eaa6ec5b7e4f233f6d6f441410014535d0f3985367994c94bf15a2a564 | ++----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +ITransactionFilterer ++----------+---------------------------------------------------------------------+------------+ +| Type | Signature | Selector | ++=============================================================================================+ +| Function | isTransactionAllowed(address,address,uint256,uint256,bytes,address) | 0xcbcf2e3c | ++----------+---------------------------------------------------------------------+------------+ + +IVerifier ++----------+-----------------------------+------------+ +| Type | Signature | Selector | ++=====================================================+ +| Function | verificationKeyHash() | 0x9e8945d2 | +|----------+-----------------------------+------------| +| Function | verify(uint256[],uint256[]) | 0xb864f5a9 | ++----------+-----------------------------+------------+ + +IZKChainBase ++----------+-----------+------------+ +| Type | Signature | Selector | ++===================================+ +| Function | getName() | 0x17d7de7c | ++----------+-----------+------------+ + +RelayedSLDAValidator ++----------+------------------------------------------------+------------+ +| Type | Signature | Selector | ++========================================================================+ +| Function | checkDA(uint256,uint256,bytes32,bytes,uint256) | 0x381c3f13 | ++----------+------------------------------------------------+------------+ + +ValidiumL1DAValidator ++----------+------------------------------------------------+------------+ +| Type | Signature | Selector | ++========================================================================+ +| Function | checkDA(uint256,uint256,bytes32,bytes,uint256) | 0x381c3f13 | ++----------+------------------------------------------------+------------+ + +IComplexUpgrader ++----------+------------------------+------------+ +| Type | Signature | Selector | ++================================================+ +| Function | upgrade(address,bytes) | 0xc987336c | ++----------+------------------------+------------+ + +IL2GatewayUpgrade ++----------+---------------------------------------------------------------------+------------+ +| Type | Signature | Selector | ++=============================================================================================+ +| Function | upgrade((bytes32,address,bool,uint256,bytes)[],address,bytes,bytes) | 0xff87ffa5 | ++----------+---------------------------------------------------------------------+------------+ + +IL2GenesisUpgrade ++----------+---------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=============================================================================================================================+ +| Function | genesisUpgrade(uint256,address,bytes,bytes) | 0x012aa9a4 | +|----------+---------------------------------------------+--------------------------------------------------------------------| +| Event | UpgradeComplete(uint256) | 0xd01e21be66147e834d803d6e8b8656aabf0c7c4cb36947b8c3cac47be96afd84 | ++----------+---------------------------------------------+--------------------------------------------------------------------+ + +ISystemContext ++----------+---------------------+------------+ +| Type | Signature | Selector | ++=============================================+ +| Function | setChainId(uint256) | 0xef0e2ff4 | ++----------+---------------------+------------+ + +IL1GenesisUpgrade ++----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++========================================================================================================================================================================================================================================================+ +| Function | genesisUpgrade(address,uint256,uint256,address,bytes,bytes[]) | 0xd83e4e03 | +|----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | GenesisUpgrade(address,(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),uint256,bytes[]) | 0xc5902263211386c797097c5eef7ce20567f0e13359623233314cfa01a55341da | ++----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +BridgedStandardERC20 ++----------+---------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++===============================================================================================================================================+ +| Function | DOMAIN_SEPARATOR() | 0x3644e515 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | allowance(address,address) | 0xdd62ed3e | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | approve(address,uint256) | 0x095ea7b3 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | assetId() | 0x44de240a | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | balanceOf(address) | 0x70a08231 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeBurn(address,uint256) | 0x74f4f547 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeInitialize(bytes32,address,bytes) | 0x9a6ab870 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeMint(address,uint256) | 0x8c2a993e | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | decimals() | 0x313ce567 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | decodeString(bytes) | 0x95ce3e93 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | decodeUint8(bytes) | 0x7ba8be34 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | decreaseAllowance(address,uint256) | 0xa457c2d7 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | eip712Domain() | 0x84b0196e | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | increaseAllowance(address,uint256) | 0x39509351 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l1Address() | 0xc2eeeebd | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2Bridge() | 0xae1f6aaf | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | name() | 0x06fdde03 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | nativeTokenVault() | 0x64e130cf | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | nonces(address) | 0x7ecebe00 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | originToken() | 0x13096a41 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | permit(address,address,uint256,uint256,uint8,bytes32,bytes32) | 0xd505accf | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | reinitializeToken((bool,bool,bool),string,string,uint8) | 0xb71bcf90 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | symbol() | 0x95d89b41 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | totalSupply() | 0x18160ddd | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transfer(address,uint256) | 0xa9059cbb | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferFrom(address,address,uint256) | 0x23b872dd | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Event | AdminChanged(address,address) | 0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Approval(address,address,uint256) | 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BeaconUpgraded(address) | 0x1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeBurn(address,uint256) | 0x9b5b9a05e4726d8bb959f1440e05c6b8109443f2083bc4e386237d7654526553 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeInitialize(address,string,string,uint8) | 0x81e8e92e5873539605a102eddae7ed06d19bea042099a437cbc3644415eb7404 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeMint(address,uint256) | 0x397b33b307fc137878ebfc75b295289ec0ee25a31bb5bf034f33256fe8ea2aa6 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Event | EIP712DomainChanged() | 0x0a6387c9ea3628b88a633bb4f3b151770f70085117a15f9bf3787cda53f13d31 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Initialized(uint8) | 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Transfer(address,address,uint256) | 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Upgraded(address) | 0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NonSequentialVersion() | 0x0ac76f01 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnsupportedEncodingVersion() | 0x084a1449 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ZeroAddress() | 0xd92e233d | ++----------+---------------------------------------------------------------+--------------------------------------------------------------------+ + +L1ERC20Bridge ++----------+------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++==============================================================================================================================================================+ +| Function | ERA_CHAIN_ID() | 0xef011dff | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_ASSET_ROUTER() | 0xcdf25430 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_NATIVE_TOKEN_VAULT() | 0x293e8520 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_NULLIFIER() | 0xe60ccaba | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | claimFailedDeposit(address,address,bytes32,uint256,uint256,uint16,bytes32[]) | 0x19fa7f62 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | deposit(address,address,uint256,uint256,uint256) | 0x933999fb | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | deposit(address,address,uint256,uint256,uint256,address) | 0xe8b99b1b | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | depositAmount(address,address,bytes32) | 0x01eae183 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeWithdrawal(uint256,uint256,uint16,bytes,bytes32[]) | 0x11a2ccc1 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialize() | 0x8129fc1c | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isWithdrawalFinalized(uint256,uint256) | 0x4bed8212 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2Bridge() | 0xae1f6aaf | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TokenAddress(address) | 0xf5f15168 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TokenBeacon() | 0x6dde7209 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TokenProxyBytecodeHash() | 0x823f1d96 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ClaimedFailedDeposit(address,address,uint256) | 0xbe066dc591f4a444f75176d387c3e6c775e5706d9ea9a91d11eb49030c66cf60 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | DepositInitiated(bytes32,address,address,address,uint256) | 0xdd341179f4edc78148d894d0213a96d212af2cbaf223d19ef6d483bdd47ab81d | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | WithdrawalFinalized(address,address,uint256) | 0xac1b18083978656d557d6e91c88203585cfda1031bdb14538327121ef140d383 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ApprovalFailed() | 0x8164f842 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ETHDepositNotSupported() | 0x627e0872 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | EmptyDeposit() | 0x95b66fe9 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NotInitializedReentrancyGuard() | 0xdd7e3621 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Reentrancy() | 0xab143c06 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SlotOccupied() | 0xdf3a8fdd | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TokensWithFeesNotSupported() | 0x23830e28 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | WithdrawalAlreadyFinalized() | 0xae899454 | ++----------+------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +L1Nullifier ++----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=======================================================================================================================================================================================+ +| Function | BRIDGE_HUB() | 0x5d4edca7 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | __DEPRECATED_admin() | 0xf7a5cec0 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | __DEPRECATED_chainBalance(uint256,address) | 0x6182877b | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | __DEPRECATED_l2BridgeAddress(uint256) | 0xfdbb0301 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | __DEPRECATED_pendingAdmin() | 0x6cdecb2b | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptOwnership() | 0x79ba5097 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeRecoverFailedTransfer(uint256,address,bytes32,bytes,bytes32,uint256,uint256,uint16,bytes32[]) | 0x3601e63e | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgehubConfirmL2TransactionForwarded(uint256,bytes32,bytes32) | 0x4bc2c8c0 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | chainBalance(uint256,address) | 0x9cd45184 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | claimFailedDeposit(uint256,address,address,uint256,bytes32,uint256,uint256,uint16,bytes32[]) | 0xc0991525 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | claimFailedDepositLegacyErc20Bridge(address,address,uint256,bytes32,uint256,uint256,uint16,bytes32[]) | 0x8fbb3711 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | depositHappened(uint256,bytes32) | 0x9fa8826b | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | encodeTxDataHash(bytes1,address,bytes32,bytes) | 0xf120e6c4 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeDeposit((uint256,uint256,uint256,address,uint16,bytes,bytes32[])) | 0x74beea82 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialize(address,uint256,uint256,uint256,uint256) | 0xf92ad219 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isWithdrawalFinalized(uint256,uint256,uint256) | 0x8f31f052 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l1AssetRouter() | 0x6d9860e1 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l1NativeTokenVault() | 0x6f513211 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2BridgeAddress(uint256) | 0x07ee9355 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | legacyBridge() | 0x6e9d7899 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | nullifyChainBalanceByNTV(uint256,address) | 0x5de097b1 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | owner() | 0x8da5cb5b | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pause() | 0x8456cb59 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | paused() | 0x5c975abb | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pendingOwner() | 0xe30c3978 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | renounceOwnership() | 0x715018a6 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setL1AssetRouter(address) | 0x780ce114 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setL1Erc20Bridge(address) | 0x30bda03e | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setL1NativeTokenVault(address) | 0xb7cc6f46 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferOwnership(address) | 0xf2fde38b | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferTokenToNTV(address) | 0x40a434d5 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | unpause() | 0x3f4ba83a | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubDepositFinalized(uint256,bytes32,bytes32) | 0xe4def01b981193a97a9e81230d7b9f31812ceaf23f864a828a82c687911cb2df | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Initialized(uint8) | 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Paused(address) | 0x62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Unpaused(address) | 0x5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AddressAlreadySet(address) | 0x0dfb42bf | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | DepositDoesNotExist() | 0xc7c9660f | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | DepositExists() | 0xad2fa98e | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidProof() | 0x09bde339 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidSelector(bytes4) | 0x12ba286f | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | L2WithdrawalMessageWrongLength(uint256) | 0x97e1359e | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NotInitializedReentrancyGuard() | 0xdd7e3621 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Reentrancy() | 0xab143c06 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SharedBridgeValueNotSet(uint8) | 0x7774d2f9 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SlotOccupied() | 0xdf3a8fdd | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnsupportedEncodingVersion() | 0x084a1449 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | WithdrawalAlreadyFinalized() | 0xae899454 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ZeroAddress() | 0xd92e233d | ++----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +L2SharedBridgeLegacy ++----------+--------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++========================================================================================================================================+ +| Function | deployBeaconProxy(bytes32) | 0x07168226 | +|----------+--------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeDeposit(address,address,address,uint256,bytes) | 0xcfe7af7c | +|----------+--------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialize(address,bytes32,address) | 0xd26b3e6e | +|----------+--------------------------------------------------------+--------------------------------------------------------------------| +| Function | l1Bridge() | 0x969b53da | +|----------+--------------------------------------------------------+--------------------------------------------------------------------| +| Function | l1SharedBridge() | 0xb852ad36 | +|----------+--------------------------------------------------------+--------------------------------------------------------------------| +| Function | l1TokenAddress(address) | 0xf54266a2 | +|----------+--------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TokenAddress(address) | 0xf5f15168 | +|----------+--------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TokenBeacon() | 0x6dde7209 | +|----------+--------------------------------------------------------+--------------------------------------------------------------------| +| Function | sendMessageToL1(bytes) | 0xff21c125 | +|----------+--------------------------------------------------------+--------------------------------------------------------------------| +| Function | withdraw(address,address,uint256) | 0xd9caed12 | +|----------+--------------------------------------------------------+--------------------------------------------------------------------| +| Event | FinalizeDeposit(address,address,address,uint256) | 0xb84fba9af218da60d299dc177abd5805e7ac541d2673cbee7808c10017874f63 | +|----------+--------------------------------------------------------+--------------------------------------------------------------------| +| Event | Initialized(uint8) | 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498 | +|----------+--------------------------------------------------------+--------------------------------------------------------------------| +| Error | AmountMustBeGreaterThanZero() | 0x5e85ae73 | +|----------+--------------------------------------------------------+--------------------------------------------------------------------| +| Error | DeployFailed() | 0xb4f54111 | +|----------+--------------------------------------------------------+--------------------------------------------------------------------| +| Error | EmptyBytes32() | 0x1c25715b | +|----------+--------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidCaller(address) | 0xcbd9d2e0 | +|----------+--------------------------------------------------------+--------------------------------------------------------------------| +| Error | U32CastOverflow() | 0xfe3c3c45 | +|----------+--------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+--------------------------------------------------------+--------------------------------------------------------------------| +| Error | ZeroAddress() | 0xd92e233d | ++----------+--------------------------------------------------------+--------------------------------------------------------------------+ + +L2WrappedBaseToken ++----------+---------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++===============================================================================================================================================+ +| Function | DOMAIN_SEPARATOR() | 0x3644e515 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | allowance(address,address) | 0xdd62ed3e | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | approve(address,uint256) | 0x095ea7b3 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | assetId() | 0x44de240a | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | balanceOf(address) | 0x70a08231 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | baseTokenAssetId() | 0x22ce9d15 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeBurn(address,uint256) | 0x74f4f547 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeMint(address,uint256) | 0x8c2a993e | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | decimals() | 0x313ce567 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | decreaseAllowance(address,uint256) | 0xa457c2d7 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | deposit() | 0xd0e30db0 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | depositTo(address) | 0xb760faf9 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | eip712Domain() | 0x84b0196e | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | increaseAllowance(address,uint256) | 0x39509351 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initializeV2(string,string,address,address,bytes32) | 0x92f9f0a0 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l1Address() | 0xc2eeeebd | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2Bridge() | 0xae1f6aaf | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | name() | 0x06fdde03 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | nativeTokenVault() | 0x64e130cf | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | nonces(address) | 0x7ecebe00 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | originToken() | 0x13096a41 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | permit(address,address,uint256,uint256,uint8,bytes32,bytes32) | 0xd505accf | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | symbol() | 0x95d89b41 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | totalSupply() | 0x18160ddd | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transfer(address,uint256) | 0xa9059cbb | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferFrom(address,address,uint256) | 0x23b872dd | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | withdraw(uint256) | 0x2e1a7d4d | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Function | withdrawTo(address,uint256) | 0x205c2878 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Approval(address,address,uint256) | 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeBurn(address,uint256) | 0x9b5b9a05e4726d8bb959f1440e05c6b8109443f2083bc4e386237d7654526553 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeInitialize(address,string,string,uint8) | 0x81e8e92e5873539605a102eddae7ed06d19bea042099a437cbc3644415eb7404 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeMint(address,uint256) | 0x397b33b307fc137878ebfc75b295289ec0ee25a31bb5bf034f33256fe8ea2aa6 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Event | EIP712DomainChanged() | 0x0a6387c9ea3628b88a633bb4f3b151770f70085117a15f9bf3787cda53f13d31 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Initialize(string,string,uint8) | 0xc21caeb4e8f73861400d4c0870ad3e468ddb4e45225da3832ce1da5561f1f61e | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Initialized(uint8) | 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Transfer(address,address,uint256) | 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Error | BridgeMintNotImplemented() | 0xdb538614 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Error | WithdrawFailed() | 0x750b219c | +|----------+---------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ZeroAddress() | 0xd92e233d | ++----------+---------------------------------------------------------------+--------------------------------------------------------------------+ + +AssetRouterBase ++----------+---------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=====================================================================================================================================================+ +| Function | BRIDGE_HUB() | 0x5d4edca7 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | ERA_CHAIN_ID() | 0xef011dff | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_CHAIN_ID() | 0x2f90b184 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptOwnership() | 0x79ba5097 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | assetDeploymentTracker(bytes32) | 0x85e4e16a | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | assetHandlerAddress(bytes32) | 0x53b9e632 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeDeposit(uint256,bytes32,bytes) | 0x9c884fd1 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | owner() | 0x8da5cb5b | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pause() | 0x8456cb59 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | paused() | 0x5c975abb | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pendingOwner() | 0xe30c3978 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | renounceOwnership() | 0x715018a6 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setAssetHandlerAddressThisChain(bytes32,address) | 0x548a5a33 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferOwnership(address) | 0xf2fde38b | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | unpause() | 0x3f4ba83a | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | AssetHandlerRegistered(bytes32,address) | 0x2632cc0d58b0cb1017b99cc0b6cc66ad86440cc0dd923bfdaa294f95ba1b0201 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | AssetHandlerRegisteredInitial(bytes32,address,bytes32,address) | 0xb1e82bee3e85b2755fbceb4b7e051f5c66a7f35f0476657504e77e18ebd3a17d | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubDepositBaseTokenInitiated(uint256,address,bytes32,uint256) | 0x0f87e1ea5eb1f034a6071ef630c174063e3d48756f853efaaf4292b929298240 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubDepositInitiated(uint256,bytes32,address,bytes32,bytes) | 0xe21913bc89c1320d9709a5d236ffe06b54cf88aecfc9509ebd68f1adba45781e | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubWithdrawalInitiated(uint256,address,bytes32,bytes32) | 0x9a3d4025b7294a1754ea5b56309c1e72328d97b73718183db595c850d14a3ae0 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | DepositFinalizedAssetRouter(uint256,bytes32,bytes) | 0x44eb9a840094a49b3cd0a5205042598a1c08c4e87bafb5760bc2d8efa170c541 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Initialized(uint8) | 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Paused(address) | 0x62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Unpaused(address) | 0x5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa | ++----------+---------------------------------------------------------------------+--------------------------------------------------------------------+ + +L1AssetRouter ++----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=====================================================================================================================================================================================+ +| Function | BRIDGE_HUB() | 0x5d4edca7 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | ERA_CHAIN_ID() | 0xef011dff | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_CHAIN_ID() | 0x2f90b184 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_NULLIFIER() | 0xe60ccaba | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_WETH_TOKEN() | 0x41c841c3 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptOwnership() | 0x79ba5097 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | assetDeploymentTracker(bytes32) | 0x85e4e16a | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | assetHandlerAddress(bytes32) | 0x53b9e632 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeRecoverFailedTransfer(uint256,address,bytes32,bytes) | 0x1346ca3b | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeRecoverFailedTransfer(uint256,address,bytes32,bytes,bytes32,uint256,uint256,uint16,bytes32[]) | 0x3601e63e | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgehubConfirmL2Transaction(uint256,bytes32,bytes32) | 0x8eb7db57 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgehubDeposit(uint256,address,uint256,bytes) | 0xca408c23 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgehubDepositBaseToken(uint256,bytes32,address,uint256) | 0xc4879440 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | claimFailedDeposit(uint256,address,address,uint256,bytes32,uint256,uint256,uint16,bytes32[]) | 0xc0991525 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | depositLegacyErc20Bridge(address,address,address,uint256,uint256,uint256,address) | 0x9e6ea417 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeDeposit(uint256,bytes32,bytes) | 0x9c884fd1 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeWithdrawal(uint256,uint256,uint256,uint16,bytes,bytes32[]) | 0xc87325f1 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getDepositCalldata(address,bytes32,bytes) | 0x2ff0b2ea | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialize(address) | 0xc4d66de8 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isWithdrawalFinalized(uint256,uint256,uint256) | 0x8f31f052 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2BridgeAddress(uint256) | 0x07ee9355 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | legacyBridge() | 0x6e9d7899 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | nativeTokenVault() | 0x64e130cf | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | owner() | 0x8da5cb5b | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pause() | 0x8456cb59 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | paused() | 0x5c975abb | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pendingOwner() | 0xe30c3978 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | renounceOwnership() | 0x715018a6 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setAssetDeploymentTracker(bytes32,address) | 0xc0a16dda | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setAssetHandlerAddressThisChain(bytes32,address) | 0x548a5a33 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setL1Erc20Bridge(address) | 0x30bda03e | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setNativeTokenVault(address) | 0x0f3fa211 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferFundsToNTV(bytes32,uint256,address) | 0x57d4ca5c | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferOwnership(address) | 0xf2fde38b | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | unpause() | 0x3f4ba83a | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | AssetDeploymentTrackerSet(bytes32,address,bytes32) | 0x14c1bae9bcc3777747463b66a36584aa75e4ded1aa38089f447beecb125a2175 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | AssetHandlerRegistered(bytes32,address) | 0x2632cc0d58b0cb1017b99cc0b6cc66ad86440cc0dd923bfdaa294f95ba1b0201 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | AssetHandlerRegisteredInitial(bytes32,address,bytes32,address) | 0xb1e82bee3e85b2755fbceb4b7e051f5c66a7f35f0476657504e77e18ebd3a17d | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubDepositBaseTokenInitiated(uint256,address,bytes32,uint256) | 0x0f87e1ea5eb1f034a6071ef630c174063e3d48756f853efaaf4292b929298240 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubDepositFinalized(uint256,bytes32,bytes32) | 0xe4def01b981193a97a9e81230d7b9f31812ceaf23f864a828a82c687911cb2df | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubDepositInitiated(uint256,bytes32,address,bytes32,bytes) | 0xe21913bc89c1320d9709a5d236ffe06b54cf88aecfc9509ebd68f1adba45781e | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubMintData(bytes) | 0x31a15cb4f69820f57afabeaff74feae31dc25875c07c952ba742a3acf8690f91 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubWithdrawalInitiated(uint256,address,bytes32,bytes32) | 0x9a3d4025b7294a1754ea5b56309c1e72328d97b73718183db595c850d14a3ae0 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ClaimedFailedDepositAssetRouter(uint256,bytes32,bytes) | 0x4250817d22c13fba8067153d85ccd9706326ac2bd14d5c3898c8b1bccc440658 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | DepositFinalizedAssetRouter(uint256,bytes32,bytes) | 0x44eb9a840094a49b3cd0a5205042598a1c08c4e87bafb5760bc2d8efa170c541 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Initialized(uint8) | 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | LegacyDepositInitiated(uint256,bytes32,address,address,address,uint256) | 0xa1846a4248529db592da99da276f761d9f37a84d0f3d4e83819b869759000700 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Paused(address) | 0x62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Unpaused(address) | 0x5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AddressAlreadyUsed(address) | 0x1ff9d522 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AssetHandlerDoesNotExist(bytes32) | 0xfde974f4 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AssetIdNotSupported(bytes32) | 0x04a0b7e9 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NotInitializedReentrancyGuard() | 0xdd7e3621 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Reentrancy() | 0xab143c06 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SlotOccupied() | 0xdf3a8fdd | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TokenNotSupported(address) | 0x06439c6b | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnsupportedEncodingVersion() | 0x084a1449 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ZeroAddress() | 0xd92e233d | ++----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +L2AssetRouter ++----------+---------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=====================================================================================================================================================+ +| Function | BASE_TOKEN_ASSET_ID() | 0xcb944dec | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | BRIDGE_HUB() | 0x5d4edca7 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | ERA_CHAIN_ID() | 0xef011dff | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_CHAIN_ID() | 0x2f90b184 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L2_LEGACY_SHARED_BRIDGE() | 0xc438a9f2 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptOwnership() | 0x79ba5097 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | assetDeploymentTracker(bytes32) | 0x85e4e16a | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | assetHandlerAddress(bytes32) | 0x53b9e632 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeDeposit(uint256,bytes32,bytes) | 0x9c884fd1 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeDeposit(address,address,address,uint256,bytes) | 0xcfe7af7c | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeDepositLegacyBridge(address,address,address,uint256,bytes) | 0x54b2e69c | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l1AssetRouter() | 0x6d9860e1 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l1Bridge() | 0x969b53da | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l1TokenAddress(address) | 0xf54266a2 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TokenAddress(address) | 0xf5f15168 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | owner() | 0x8da5cb5b | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pause() | 0x8456cb59 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | paused() | 0x5c975abb | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pendingOwner() | 0xe30c3978 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | renounceOwnership() | 0x715018a6 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setAssetHandlerAddress(uint256,bytes32,address) | 0xda556bdc | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setAssetHandlerAddressThisChain(bytes32,address) | 0x548a5a33 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferOwnership(address) | 0xf2fde38b | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | unpause() | 0x3f4ba83a | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | withdraw(bytes32,bytes) | 0x4a2e35ba | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | withdraw(address,address,uint256) | 0xd9caed12 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | withdrawLegacyBridge(address,address,uint256,address) | 0x7ac3a553 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | withdrawToken(address,bytes) | 0x958a9f51 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | AssetHandlerRegistered(bytes32,address) | 0x2632cc0d58b0cb1017b99cc0b6cc66ad86440cc0dd923bfdaa294f95ba1b0201 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | AssetHandlerRegisteredInitial(bytes32,address,bytes32,address) | 0xb1e82bee3e85b2755fbceb4b7e051f5c66a7f35f0476657504e77e18ebd3a17d | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubDepositBaseTokenInitiated(uint256,address,bytes32,uint256) | 0x0f87e1ea5eb1f034a6071ef630c174063e3d48756f853efaaf4292b929298240 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubDepositInitiated(uint256,bytes32,address,bytes32,bytes) | 0xe21913bc89c1320d9709a5d236ffe06b54cf88aecfc9509ebd68f1adba45781e | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubWithdrawalInitiated(uint256,address,bytes32,bytes32) | 0x9a3d4025b7294a1754ea5b56309c1e72328d97b73718183db595c850d14a3ae0 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | DepositFinalizedAssetRouter(uint256,bytes32,bytes) | 0x44eb9a840094a49b3cd0a5205042598a1c08c4e87bafb5760bc2d8efa170c541 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Initialized(uint8) | 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Paused(address) | 0x62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Unpaused(address) | 0x5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | WithdrawalInitiatedAssetRouter(uint256,address,bytes32,bytes) | 0x55362fc62473cb1255e770af5d5e02ba6ee5bc7ed6969c30eb11ca31b92384dc | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AmountMustBeGreaterThanZero() | 0x5e85ae73 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AssetIdNotSupported(bytes32) | 0x04a0b7e9 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | EmptyAddress() | 0x7138356f | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidCaller(address) | 0xcbd9d2e0 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | ++----------+---------------------------------------------------------------------+--------------------------------------------------------------------+ + +L1NativeTokenVault ++----------+----------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++================================================================================================================================================+ +| Function | ASSET_ROUTER() | 0xc6a70bbb | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | BASE_TOKEN_ASSET_ID() | 0xcb944dec | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | ERA_CHAIN_ID() | 0xef011dff | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_CHAIN_ID() | 0x2f90b184 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_NULLIFIER() | 0xe60ccaba | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | WETH_TOKEN() | 0x37d277d4 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptOwnership() | 0x79ba5097 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | assetId(address) | 0xfd3f60df | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeBurn(uint256,uint256,bytes32,address,bytes) | 0x699b0fb9 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeCheckCounterpartAddress(uint256,bytes32,address,address) | 0x9cc395d0 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeMint(uint256,bytes32,bytes) | 0x36ba0355 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeRecoverFailedTransfer(uint256,bytes32,address,bytes) | 0xc2e90293 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgedTokenBeacon() | 0xf2d44246 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | calculateCreate2TokenAddress(uint256,address) | 0xc487412c | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | chainBalance(uint256,bytes32) | 0x3345359b | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | ensureTokenIsRegistered(address) | 0x19a2a285 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getERC20Getters(address,uint256) | 0xa7236d16 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialize(address,address) | 0x485cc955 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | originChainId(bytes32) | 0x5f3455b5 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | owner() | 0x8da5cb5b | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pause() | 0x8456cb59 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | paused() | 0x5c975abb | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pendingOwner() | 0xe30c3978 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | registerEthToken() | 0xcb6da609 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | registerToken(address) | 0x09824a80 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | renounceOwnership() | 0x715018a6 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | tokenAddress(bytes32) | 0x97bb3ce9 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | tokenDataOriginChainId(bytes) | 0x07a6d4bc | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferFundsFromSharedBridge(address) | 0x8310f2c6 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferOwnership(address) | 0xf2fde38b | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | unpause() | 0x3f4ba83a | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | updateChainBalancesFromSharedBridge(address,uint256) | 0x1c9f0149 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeBurn(uint256,bytes32,address,address,uint256) | 0x1cd02155ad1064c60598a8bd0e4e795d7e7d0a0f3c38aad04d261f1297fb2545 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeInitialize(address,string,string,uint8) | 0x81e8e92e5873539605a102eddae7ed06d19bea042099a437cbc3644415eb7404 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeMint(uint256,bytes32,address,uint256) | 0xbc0f4055a7869d8ecad34b33382a0bc181c5811565fec42f335505be5fd661d2 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgedTokenBeaconUpdated(address,bytes32) | 0xc3f14dba68f86c42f518e5c0e8a5cbc9514da6f388e2f52c5b1a6263d8588bfb | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Initialized(uint8) | 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Paused(address) | 0x62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Event | TokenBeaconUpdated(address) | 0x5ed5e4f58bf9a324a38beaa1177fb96fcb7bf3a5f4c4585ebb78c4a8c0249d0f | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Unpaused(address) | 0x5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AddressMismatch(address,address) | 0x1f73225f | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AmountMustBeGreaterThanZero() | 0x5e85ae73 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AssetIdMismatch(bytes32,bytes32) | 0x1294e9e1 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Error | DeployingBridgedTokenForNativeToken() | 0x138ee1a3 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Error | EmptyDeposit() | 0x95b66fe9 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InsufficientChainBalance() | 0x826fb11e | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NoFundsTransferred() | 0xcab098d8 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NonEmptyMsgValue() | 0x536ec84b | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Error | OriginChainIdNotFound() | 0xb926450e | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TokenNotSupported(address) | 0x06439c6b | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TokensWithFeesNotSupported() | 0x23830e28 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnsupportedEncodingVersion() | 0x084a1449 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ValueMismatch(uint256,uint256) | 0x626ade30 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Error | WithdrawFailed() | 0x750b219c | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ZeroAddress() | 0xd92e233d | ++----------+----------------------------------------------------------------+--------------------------------------------------------------------+ + +L2NativeTokenVault ++----------+------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++======================================================================================================================================+ +| Function | ASSET_ROUTER() | 0xc6a70bbb | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | BASE_TOKEN_ASSET_ID() | 0xcb944dec | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_CHAIN_ID() | 0x2f90b184 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | L2_LEGACY_SHARED_BRIDGE() | 0xc438a9f2 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | WETH_TOKEN() | 0x37d277d4 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptOwnership() | 0x79ba5097 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | assetId(address) | 0xfd3f60df | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeBurn(uint256,uint256,bytes32,address,bytes) | 0x699b0fb9 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeMint(uint256,bytes32,bytes) | 0x36ba0355 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgedTokenBeacon() | 0xf2d44246 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | calculateCreate2TokenAddress(uint256,address) | 0xc487412c | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | ensureTokenIsRegistered(address) | 0x19a2a285 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | getERC20Getters(address,uint256) | 0xa7236d16 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TokenAddress(address) | 0xf5f15168 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | originChainId(bytes32) | 0x5f3455b5 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | owner() | 0x8da5cb5b | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | pause() | 0x8456cb59 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | paused() | 0x5c975abb | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | pendingOwner() | 0xe30c3978 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | registerToken(address) | 0x09824a80 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | renounceOwnership() | 0x715018a6 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | setLegacyTokenAssetId(address) | 0x4cd40a02 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | tokenAddress(bytes32) | 0x97bb3ce9 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | tokenDataOriginChainId(bytes) | 0x07a6d4bc | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferOwnership(address) | 0xf2fde38b | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | unpause() | 0x3f4ba83a | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeBurn(uint256,bytes32,address,address,uint256) | 0x1cd02155ad1064c60598a8bd0e4e795d7e7d0a0f3c38aad04d261f1297fb2545 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeInitialize(address,string,string,uint8) | 0x81e8e92e5873539605a102eddae7ed06d19bea042099a437cbc3644415eb7404 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeMint(uint256,bytes32,address,uint256) | 0xbc0f4055a7869d8ecad34b33382a0bc181c5811565fec42f335505be5fd661d2 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgedTokenBeaconUpdated(address,bytes32) | 0xc3f14dba68f86c42f518e5c0e8a5cbc9514da6f388e2f52c5b1a6263d8588bfb | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | FinalizeDeposit(address,address,address,uint256) | 0xb84fba9af218da60d299dc177abd5805e7ac541d2673cbee7808c10017874f63 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | Initialized(uint8) | 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | L2TokenBeaconUpdated(address,bytes32) | 0x01fd5911e6d04aec6b21f19752502ad7f3e9876279643c8fa7a4d30c88a29fb2 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | Paused(address) | 0x62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | Unpaused(address) | 0x5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | WithdrawalInitiated(address,address,address,uint256) | 0x2fc3848834aac8e883a2d2a17a7514dc4f2d3dd268089df9b9f5d918259ef3b0 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | AddressMismatch(address,address) | 0x1f73225f | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | AmountMustBeGreaterThanZero() | 0x5e85ae73 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | AssetIdMismatch(bytes32,bytes32) | 0x1294e9e1 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | AssetIdNotSupported(bytes32) | 0x04a0b7e9 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | DeployFailed() | 0xb4f54111 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | DeployingBridgedTokenForNativeToken() | 0x138ee1a3 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | EmptyAddress() | 0x7138356f | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | EmptyBytes32() | 0x1c25715b | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | EmptyDeposit() | 0x95b66fe9 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | NonEmptyMsgValue() | 0x536ec84b | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | TokenNotSupported(address) | 0x06439c6b | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | TokensWithFeesNotSupported() | 0x23830e28 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | U32CastOverflow() | 0xfe3c3c45 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnsupportedEncodingVersion() | 0x084a1449 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | ValueMismatch(uint256,uint256) | 0x626ade30 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | ZeroAddress() | 0xd92e233d | ++----------+------------------------------------------------------+--------------------------------------------------------------------+ + +NativeTokenVault ++----------+-----------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=====================================================================================================================================+ +| Function | ASSET_ROUTER() | 0xc6a70bbb | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | BASE_TOKEN_ASSET_ID() | 0xcb944dec | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_CHAIN_ID() | 0x2f90b184 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | WETH_TOKEN() | 0x37d277d4 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptOwnership() | 0x79ba5097 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | assetId(address) | 0xfd3f60df | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeBurn(uint256,uint256,bytes32,address,bytes) | 0x699b0fb9 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeMint(uint256,bytes32,bytes) | 0x36ba0355 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgedTokenBeacon() | 0xf2d44246 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | calculateCreate2TokenAddress(uint256,address) | 0xc487412c | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | ensureTokenIsRegistered(address) | 0x19a2a285 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | getERC20Getters(address,uint256) | 0xa7236d16 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | originChainId(bytes32) | 0x5f3455b5 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | owner() | 0x8da5cb5b | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | pause() | 0x8456cb59 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | paused() | 0x5c975abb | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | pendingOwner() | 0xe30c3978 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | registerToken(address) | 0x09824a80 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | renounceOwnership() | 0x715018a6 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | tokenAddress(bytes32) | 0x97bb3ce9 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | tokenDataOriginChainId(bytes) | 0x07a6d4bc | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | transferOwnership(address) | 0xf2fde38b | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Function | unpause() | 0x3f4ba83a | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeBurn(uint256,bytes32,address,address,uint256) | 0x1cd02155ad1064c60598a8bd0e4e795d7e7d0a0f3c38aad04d261f1297fb2545 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeInitialize(address,string,string,uint8) | 0x81e8e92e5873539605a102eddae7ed06d19bea042099a437cbc3644415eb7404 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeMint(uint256,bytes32,address,uint256) | 0xbc0f4055a7869d8ecad34b33382a0bc181c5811565fec42f335505be5fd661d2 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgedTokenBeaconUpdated(address,bytes32) | 0xc3f14dba68f86c42f518e5c0e8a5cbc9514da6f388e2f52c5b1a6263d8588bfb | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Event | Initialized(uint8) | 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Event | Paused(address) | 0x62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Event | Unpaused(address) | 0x5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Error | AddressMismatch(address,address) | 0x1f73225f | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Error | AmountMustBeGreaterThanZero() | 0x5e85ae73 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Error | AssetIdMismatch(bytes32,bytes32) | 0x1294e9e1 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Error | DeployingBridgedTokenForNativeToken() | 0x138ee1a3 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Error | EmptyDeposit() | 0x95b66fe9 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Error | NonEmptyMsgValue() | 0x536ec84b | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Error | TokenNotSupported(address) | 0x06439c6b | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Error | TokensWithFeesNotSupported() | 0x23830e28 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Error | UnsupportedEncodingVersion() | 0x084a1449 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Error | ValueMismatch(uint256,uint256) | 0x626ade30 | +|----------+-----------------------------------------------------+--------------------------------------------------------------------| +| Error | ZeroAddress() | 0xd92e233d | ++----------+-----------------------------------------------------+--------------------------------------------------------------------+ + +Bridgehub ++----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=========================================================================================================================================================================================+ +| Function | L1_CHAIN_ID() | 0x2f90b184 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | MAX_NUMBER_OF_ZK_CHAINS() | 0x8f8d37a8 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | __DEPRECATED_baseToken(uint256) | 0x5d83b6da | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | __DEPRECATED_tokenIsRegistered(address) | 0xeced0bf0 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptAdmin() | 0x0e18b681 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptOwnership() | 0x79ba5097 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | addChainTypeManager(address) | 0xff5a62a1 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | addTokenAssetId(bytes32) | 0x1c50cfea | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | admin() | 0xf851a440 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | assetIdIsRegistered(bytes32) | 0xe0ab6368 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | assetRouter() | 0xbc0aac10 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | baseToken(uint256) | 0x59ec65a2 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | baseTokenAssetId(uint256) | 0xe52db4ca | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeBurn(uint256,uint256,bytes32,address,bytes) | 0x699b0fb9 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeMint(uint256,bytes32,bytes) | 0x36ba0355 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeRecoverFailedTransfer(uint256,bytes32,address,bytes) | 0xc2e90293 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | chainTypeManager(uint256) | 0x9d5bd3da | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | chainTypeManagerIsRegistered(address) | 0xb93c9366 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | createNewChain(uint256,address,bytes32,uint256,address,bytes,bytes[]) | 0xf113c88b | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | ctmAssetIdFromAddress(address) | 0x70fccb52 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | ctmAssetIdFromChainId(uint256) | 0x24358c61 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | ctmAssetIdToAddress(bytes32) | 0x07621f84 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardTransactionOnGateway(uint256,bytes32,uint64) | 0x524c0cfa | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getAllZKChainChainIDs() | 0x68b8d331 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getAllZKChains() | 0x49707f31 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getHyperchain(uint256) | 0xdead6f7f | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getZKChain(uint256) | 0xe680c4c1 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialize(address) | 0xc4d66de8 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initializeV2() | 0x5cd8a76b | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l1CtmDeployer() | 0xcbe83612 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TransactionBaseCost(uint256,uint256,uint256,uint256) | 0x71623274 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | messageRoot() | 0xd4b9f4fa | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | migrationPaused() | 0x2a641114 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | owner() | 0x8da5cb5b | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pause() | 0x8456cb59 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pauseMigration() | 0xac700e63 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | paused() | 0x5c975abb | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pendingOwner() | 0xe30c3978 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL1ToL2TransactionStatus(uint256,bytes32,uint256,uint256,uint16,bytes32[],uint8) | 0xb292f5f1 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL2LogInclusion(uint256,uint256,uint256,(uint8,bool,uint16,address,bytes32,bytes32),bytes32[]) | 0xe6d9923b | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL2MessageInclusion(uint256,uint256,uint256,(uint16,address,bytes),bytes32[]) | 0x99c16d1a | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | registerAlreadyDeployedZKChain(uint256,address) | 0xb5662c5d | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | registerSettlementLayer(uint256,bool) | 0xdc8e4b26 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | removeChainTypeManager(address) | 0x332b96dc | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | renounceOwnership() | 0x715018a6 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | requestL2TransactionDirect((uint256,uint256,address,uint256,bytes,uint256,uint256,bytes[],address)) | 0xd52471c1 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | requestL2TransactionTwoBridges((uint256,uint256,uint256,uint256,uint256,address,address,uint256,bytes)) | 0x24fd57fb | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setAddresses(address,address,address) | 0x363bf964 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setAssetHandlerAddress(bytes32,address) | 0x3f704d2a | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setLegacyBaseTokenAssetId(uint256) | 0xca8f93f1 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setLegacyChainAddress(uint256) | 0xd92f86a2 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPendingAdmin(address) | 0x4dd18bf5 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | settlementLayer(uint256) | 0x671a7131 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | sharedBridge() | 0x38720778 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferOwnership(address) | 0xf2fde38b | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | unpause() | 0x3f4ba83a | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | unpauseMigration() | 0xf7c7eb92 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | whitelistedSettlementLayers(uint256) | 0xe9420f8c | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | AssetRegistered(bytes32,address,bytes32,address) | 0x8f09d7694a9ae17acec5cf132d594d7eee23572f7fe132396ce72b1afbf7ef20 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BaseTokenAssetIdRegistered(bytes32) | 0x3df150949161462acf3be30521d7da9e533b247327a254e55dd01875897a6df3 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeBurn(uint256,bytes32,address,address,uint256) | 0x1cd02155ad1064c60598a8bd0e4e795d7e7d0a0f3c38aad04d261f1297fb2545 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeInitialize(address,string,string,uint8) | 0x81e8e92e5873539605a102eddae7ed06d19bea042099a437cbc3644415eb7404 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeMint(uint256,bytes32,address,uint256) | 0xbc0f4055a7869d8ecad34b33382a0bc181c5811565fec42f335505be5fd661d2 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ChainTypeManagerAdded(address) | 0x2eae91be1021e05cc8076387b0182458ae474ae44ee44cc59aefda6ca53c1f42 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ChainTypeManagerRemoved(address) | 0x4e04a497739580efe78a7ee09cdabe6f6fe90965c683292a519102ce5193b68a | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Initialized(uint8) | 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | MigrationFinalized(uint256,bytes32,address) | 0xb0cc16029b506b2a262b52711e71db4fcd1cb078bd4bb86c7ba82cd3be2eadd3 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | MigrationStarted(uint256,bytes32,uint256) | 0xc60eb6d595da5361c68f60aa7c8286b8f73c3a99e9db1818e146c522f512496f | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewAdmin(address,address) | 0xf9ffabca9c8276e99321725bcb43fb076a6c66a54b7f21c4e8146d8519b417dc | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewChain(uint256,address,address) | 0x1e9125bc72db22c58abff6821d7333551967e26454b419ffa958e4cb8ef47600 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewPendingAdmin(address,address) | 0xca4f2f25d0898edd99413412fb94012f9e54ec8142f9b093e7720646a95b16a9 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Paused(address) | 0x62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | SettlementLayerRegistered(uint256,bool) | 0x02629feb109d94b16a367231d248ba81c462f51ce5b984835f150f1c9f49ed25 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Unpaused(address) | 0x5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AddressTooLow(address) | 0x1eee5481 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AssetHandlerNotRegistered(bytes32) | 0x64107968 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AssetIdAlreadyRegistered() | 0xfe919e28 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AssetIdNotSupported(bytes32) | 0x04a0b7e9 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | BridgeHubAlreadyRegistered() | 0x6cf12312 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | CTMAlreadyRegistered() | 0xec273439 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | CTMNotRegistered() | 0xc630ef3c | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ChainAlreadyLive() | 0x78d2ed02 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ChainIdAlreadyExists() | 0x24591d89 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ChainIdCantBeCurrentChain() | 0x717a1656 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ChainIdMismatch() | 0xa179f8c9 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ChainIdNotRegistered(uint256) | 0x23f3c357 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ChainIdTooBig() | 0x8f620a06 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ChainNotLegacy() | 0x5de72107 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | EmptyAssetId() | 0x2d4d012f | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | IncorrectBridgeHubAddress(address) | 0xdd381a4c | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MigrationPaused() | 0x3312a450 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MsgValueMismatch(uint256,uint256) | 0x4a094431 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NotInitializedReentrancyGuard() | 0xdd7e3621 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Reentrancy() | 0xab143c06 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SharedBridgeNotSet() | 0x856d5b77 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SlotOccupied() | 0xdf3a8fdd | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | WrongMagicValue(uint256,uint256) | 0x15e8e429 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ZKChainLimitReached() | 0x601b6882 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ZeroAddress() | 0xd92e233d | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ZeroChainId() | 0xc84885d4 | ++----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +CTMDeploymentTracker ++----------+----------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++================================================================================================================================================+ +| Function | BRIDGE_HUB() | 0x5d4edca7 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_ASSET_ROUTER() | 0xcdf25430 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptOwnership() | 0x79ba5097 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeCheckCounterpartAddress(uint256,bytes32,address,address) | 0x9cc395d0 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgehubConfirmL2Transaction(uint256,bytes32,bytes32) | 0x8eb7db57 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgehubDeposit(uint256,address,uint256,bytes) | 0xca408c23 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | calculateAssetId(address) | 0xb68c104a | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialize(address) | 0xc4d66de8 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | owner() | 0x8da5cb5b | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | paused() | 0x5c975abb | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pendingOwner() | 0xe30c3978 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | registerCTMAssetOnL1(address) | 0x2f9db630 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | renounceOwnership() | 0x715018a6 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferOwnership(address) | 0xf2fde38b | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Initialized(uint8) | 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Paused(address) | 0x62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258 | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Unpaused(address) | 0x5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa | +|----------+----------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SlotOccupied() | 0xdf3a8fdd | ++----------+----------------------------------------------------------------+--------------------------------------------------------------------+ + +MessageRoot ++----------+-------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=================================================================================================================================+ +| Function | BRIDGE_HUB() | 0x5d4edca7 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | addChainBatchRoot(uint256,uint256,bytes32) | 0xfb644fc5 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | addNewChain(uint256) | 0xd4ce08c2 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | chainCount() | 0xe02e1bfd | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | chainIndex(uint256) | 0x48ceb85e | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | chainIndexToId(uint256) | 0xed1d7d97 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | chainRegistered(uint256) | 0xb8776d4d | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | getAggregatedRoot() | 0x3977d71c | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | getChainRoot(uint256) | 0x1e4fba05 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | initialize() | 0x8129fc1c | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | sharedTree() | 0xb1fde1a8 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | updateFullTree() | 0xbcd1b23d | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Event | AddedChain(uint256,uint256) | 0x5d96eda109bfd71cf9f4f70c83de31c4150760e8828979a95d9e5f9f15455af7 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Event | AppendedChainBatchRoot(uint256,uint256,bytes32) | 0x4f7fd9ed016150a623d5a2cf43053fe313a56293a77e060a05db49ed22579520 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Event | Preimage(bytes32,bytes32) | 0x79a6ee6d75f19524cbfb09fe5df3f30024a7a557e62bea09bfdd3843b7fe5bda | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Error | SlotOccupied() | 0xdf3a8fdd | ++----------+-------------------------------------------------+--------------------------------------------------------------------+ + +IContractDeployer ++----------+----------------------------------------------------------------+------------+ +| Type | Signature | Selector | ++========================================================================================+ +| Function | create2(bytes32,bytes32,bytes) | 0x3cda3351 | +|----------+----------------------------------------------------------------+------------| +| Function | forceDeployOnAddresses((bytes32,address,bool,uint256,bytes)[]) | 0xe9f18c17 | ++----------+----------------------------------------------------------------+------------+ + +DummyL1ERC20Bridge ++----------+------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++==============================================================================================================================================================+ +| Function | ERA_CHAIN_ID() | 0xef011dff | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_ASSET_ROUTER() | 0xcdf25430 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_NATIVE_TOKEN_VAULT() | 0x293e8520 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_NULLIFIER() | 0xe60ccaba | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | claimFailedDeposit(address,address,bytes32,uint256,uint256,uint16,bytes32[]) | 0x19fa7f62 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | deposit(address,address,uint256,uint256,uint256) | 0x933999fb | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | deposit(address,address,uint256,uint256,uint256,address) | 0xe8b99b1b | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | depositAmount(address,address,bytes32) | 0x01eae183 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeWithdrawal(uint256,uint256,uint16,bytes,bytes32[]) | 0x11a2ccc1 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialize() | 0x8129fc1c | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isWithdrawalFinalized(uint256,uint256) | 0x4bed8212 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2Bridge() | 0xae1f6aaf | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TokenAddress(address) | 0xf5f15168 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TokenBeacon() | 0x6dde7209 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TokenProxyBytecodeHash() | 0x823f1d96 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setValues(address,address,bytes32) | 0xf5407abe | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ClaimedFailedDeposit(address,address,uint256) | 0xbe066dc591f4a444f75176d387c3e6c775e5706d9ea9a91d11eb49030c66cf60 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | DepositInitiated(bytes32,address,address,address,uint256) | 0xdd341179f4edc78148d894d0213a96d212af2cbaf223d19ef6d483bdd47ab81d | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | WithdrawalFinalized(address,address,uint256) | 0xac1b18083978656d557d6e91c88203585cfda1031bdb14538327121ef140d383 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ApprovalFailed() | 0x8164f842 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ETHDepositNotSupported() | 0x627e0872 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | EmptyDeposit() | 0x95b66fe9 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NotInitializedReentrancyGuard() | 0xdd7e3621 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Reentrancy() | 0xab143c06 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SlotOccupied() | 0xdf3a8fdd | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TokensWithFeesNotSupported() | 0x23830e28 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | WithdrawalAlreadyFinalized() | 0xae899454 | ++----------+------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +L1NullifierDev ++----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=======================================================================================================================================================================================+ +| Function | BRIDGE_HUB() | 0x5d4edca7 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | __DEPRECATED_admin() | 0xf7a5cec0 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | __DEPRECATED_chainBalance(uint256,address) | 0x6182877b | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | __DEPRECATED_l2BridgeAddress(uint256) | 0xfdbb0301 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | __DEPRECATED_pendingAdmin() | 0x6cdecb2b | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptOwnership() | 0x79ba5097 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeRecoverFailedTransfer(uint256,address,bytes32,bytes,bytes32,uint256,uint256,uint16,bytes32[]) | 0x3601e63e | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgehubConfirmL2TransactionForwarded(uint256,bytes32,bytes32) | 0x4bc2c8c0 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | chainBalance(uint256,address) | 0x9cd45184 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | claimFailedDeposit(uint256,address,address,uint256,bytes32,uint256,uint256,uint16,bytes32[]) | 0xc0991525 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | claimFailedDepositLegacyErc20Bridge(address,address,uint256,bytes32,uint256,uint256,uint16,bytes32[]) | 0x8fbb3711 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | depositHappened(uint256,bytes32) | 0x9fa8826b | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | encodeTxDataHash(bytes1,address,bytes32,bytes) | 0xf120e6c4 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeDeposit((uint256,uint256,uint256,address,uint16,bytes,bytes32[])) | 0x74beea82 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialize(address,uint256,uint256,uint256,uint256) | 0xf92ad219 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isWithdrawalFinalized(uint256,uint256,uint256) | 0x8f31f052 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l1AssetRouter() | 0x6d9860e1 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l1NativeTokenVault() | 0x6f513211 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2BridgeAddress(uint256) | 0x07ee9355 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | legacyBridge() | 0x6e9d7899 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | nullifyChainBalanceByNTV(uint256,address) | 0x5de097b1 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | owner() | 0x8da5cb5b | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pause() | 0x8456cb59 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | paused() | 0x5c975abb | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pendingOwner() | 0xe30c3978 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | renounceOwnership() | 0x715018a6 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setL1AssetRouter(address) | 0x780ce114 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setL1Erc20Bridge(address) | 0x30bda03e | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setL1NativeTokenVault(address) | 0xb7cc6f46 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setL2LegacySharedBridge(uint256,address) | 0x4771ebad | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferOwnership(address) | 0xf2fde38b | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferTokenToNTV(address) | 0x40a434d5 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | unpause() | 0x3f4ba83a | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubDepositFinalized(uint256,bytes32,bytes32) | 0xe4def01b981193a97a9e81230d7b9f31812ceaf23f864a828a82c687911cb2df | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Initialized(uint8) | 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Paused(address) | 0x62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Unpaused(address) | 0x5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AddressAlreadySet(address) | 0x0dfb42bf | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | DepositDoesNotExist() | 0xc7c9660f | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | DepositExists() | 0xad2fa98e | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidProof() | 0x09bde339 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidSelector(bytes4) | 0x12ba286f | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | L2WithdrawalMessageWrongLength(uint256) | 0x97e1359e | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NotInitializedReentrancyGuard() | 0xdd7e3621 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Reentrancy() | 0xab143c06 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SharedBridgeValueNotSet(uint8) | 0x7774d2f9 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SlotOccupied() | 0xdf3a8fdd | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnsupportedEncodingVersion() | 0x084a1449 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | WithdrawalAlreadyFinalized() | 0xae899454 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ZeroAddress() | 0xd92e233d | ++----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +AdminFacetTest ++----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=====================================================================================================================================================================================+ +| Function | acceptAdmin() | 0x0e18b681 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | changeFeeParams((uint8,uint32,uint32,uint32,uint32,uint64)) | 0x64bf8d66 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | executeUpgrade(((address,uint8,bool,bytes4[])[],address,bytes)) | 0xa9f6d941 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeBurn(address,address,bytes) | 0x64b554ad | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeMint(bytes,bool) | 0x3f42d5dd | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeRecoverFailedTransfer(uint256,bytes32,address,bytes) | 0xb7846107 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | freezeDiamond() | 0x27ae4c16 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | genesisUpgrade(address,address,bytes,bytes[]) | 0x2878fe74 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getAdmin() | 0x6e9960c3 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getName() | 0x17d7de7c | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getPendingAdmin() | 0xd0468156 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getPorterAvailability() | 0xf90eb963 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getPriorityTxMaxGasLimit() | 0x0ec6b0b7 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isValidator(address) | 0xfacd743b | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | prepareChainCommitment() | 0x41cf49bb | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setDAValidatorPair(address,address) | 0x6223258e | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPendingAdmin(address) | 0x4dd18bf5 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPorterAvailability(bool) | 0x1cc5d103 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPriorityTxMaxGasLimit(uint256) | 0xbe6f11cf | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPubdataPricingMode(uint8) | 0xe76db865 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setTokenMultiplier(uint128,uint128) | 0x235d9eb5 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setTransactionFilterer(address) | 0x21f603d7 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setValidator(address,bool) | 0x4623c91d | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | unfreezeDiamond() | 0x17338945 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | upgradeChainFromVersion(uint256,((address,uint8,bool,bytes4[])[],address,bytes)) | 0xfc57565f | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeInitialize(address,string,string,uint8) | 0x81e8e92e5873539605a102eddae7ed06d19bea042099a437cbc3644415eb7404 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeMint(address,uint256) | 0x397b33b307fc137878ebfc75b295289ec0ee25a31bb5bf034f33256fe8ea2aa6 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | DiamondCut((address,uint8,bool,bytes4[])[],address,bytes) | 0x87b829356b3403d36217eff1f66ee48eacd0a69015153aba4f0de29fe5340c30 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ExecuteUpgrade(((address,uint8,bool,bytes4[])[],address,bytes)) | 0x1dabfc3f4f6a4e74e19be10cc9d1d8e23db03e415d5d3547a1a7d5eb766513ba | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Freeze() | 0x615acbaede366d76a8b8cb2a9ada6a71495f0786513d71aa97aaf0c3910b78de | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | IsPorterAvailableStatusUpdate(bool) | 0x036b81a8a07344698cb5aa4142c5669a9317c9ce905264a08f0b9f9331883936 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | MigrationComplete() | 0x96e718f44bd77cb63370212c5aa24a0396d8f43e88e7ce175d160e371c8e2a6a | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewAdmin(address,address) | 0xf9ffabca9c8276e99321725bcb43fb076a6c66a54b7f21c4e8146d8519b417dc | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewBaseTokenMultiplier(uint128,uint128,uint128,uint128) | 0xc9cbdb110bd58a133e82c62b1488e57677be1f326df10a4d8096b5f170c370c8 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewFeeParams((uint8,uint32,uint32,uint32,uint32,uint64),(uint8,uint32,uint32,uint32,uint32,uint64)) | 0xc8b245ac8b138b17b6b1dbbbb8860adc66b373afa000d99f3cdc775d8ae0bbed | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewL1DAValidator(address,address) | 0x08b0b676d456a0431162145d2821f30c665b69ca626521c937ba7a77a29ed67c | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewL2DAValidator(address,address) | 0x866a71b85fb8625f74adc67901361962f0aa3730a1615400911ae610d65dc130 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewPendingAdmin(address,address) | 0xca4f2f25d0898edd99413412fb94012f9e54ec8142f9b093e7720646a95b16a9 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewPriorityTxMaxGasLimit(uint256,uint256) | 0x83dd728f7e76a849126c55ffabdc6e299eb8c85dccf12498701376d9f5c954d1 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewTransactionFilterer(address,address) | 0xa9b43a66c5d1c607986f49e932a0c08058d843210db03dd6e8a621b96b4770f4 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Unfreeze() | 0x2f05ba71d0df11bf5fa562a6569d70c4f80da84284badbe015ce1456063d0ded | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ValidatorStatusUpdate(address,bool) | 0x065b77b53864e46fda3d8986acb51696223d6dde7ced42441eb150bae6d48136 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | PubdataPricingModeUpdate(uint8) | 0xaa01146df0a628c6b214e79a281f7524b792de4a52ad6f07c78e6e035d58c896 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AddressHasNoCode(address) | 0x86bb51b8 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ChainAlreadyLive() | 0x78d2ed02 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | DelegateCallFailed(bytes) | 0xf7a01e4d | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | DenominatorIsZero() | 0x0a8ed92c | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | DiamondAlreadyFrozen() | 0x0e7ee319 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | DiamondNotFrozen() | 0xa7151b9a | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | FacetExists(bytes4,address) | 0xac4a3f98 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | HashMismatch(bytes32,bytes32) | 0x0b08d5be | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidPubdataPricingMode() | 0x6f1cf752 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NoFunctionsForDiamondCut() | 0xa6fef710 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NonEmptyCalldata() | 0xc21b1ab7 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PriorityTxPubdataExceedsMaxPubDataPerBatch() | 0x1a4d284a | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ProtocolIdMismatch(uint256,uint256) | 0xa461f651 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ProtocolIdNotGreater() | 0x64f94ec2 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | RemoveFunctionFacetAddressNotZero(address) | 0x667d17de | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | RemoveFunctionFacetAddressZero() | 0xa2d4b16c | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ReplaceFunctionFacetAddressZero() | 0x3580370c | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SelectorsMustAllHaveSameFreezability() | 0xd3b6535b | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TooMuchGas() | 0xf0b4e88f | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | UndefinedDiamondCutAction() | 0xe52478c7 | ++----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +CustomUpgradeTest ++----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=========================================================================================================================================================================================================================================================================================================================================+ +| Function | upgrade(((uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[],bytes32,bytes32,address,(bytes32,bytes32,bytes32),bytes,bytes,uint256,uint256)) | 0x08284e57 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewL2BootloaderBytecodeHash(bytes32,bytes32) | 0x271b33af94e3f065ecd8659833e6b1daf851f063700c36ddefefab35d4ce4746 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewL2DefaultAccountBytecodeHash(bytes32,bytes32) | 0x36df93a47cc02081d9d8208022ab736fdf98fac566e5fc6f5762bf7666e521f3 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewProtocolVersion(uint256,uint256) | 0x4235104f56661fe2e9d2f2a460b42766581bc45ce366c6a30a9f86c8a2b371a7 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewVerifier(address,address) | 0x2ff4895c300d6993c27f2bb507b4b59d29464dc640af727383451365631ba8b2 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewVerifierParams((bytes32,bytes32,bytes32),(bytes32,bytes32,bytes32)) | 0x4c055dbc5f14dcb6e081c9421d9657d950dcd6372f6db0a714b9135171cbc15d | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Test() | 0xa163a6249e860c278ef4049759a7f7c7e8c141d30fd634fda9b5a6a95d111a30 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | UpgradeComplete(uint256,bytes32,((uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[],bytes32,bytes32,address,(bytes32,bytes32,bytes32),bytes,bytes,uint256,uint256)) | 0x56405fee20a4cf3c21d1b23cbbedc0f54921b0347dc19a7641c80645f6916798 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidTxType(uint256) | 0x5cb29523 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidUpgradeTxn(uint8) | 0x5f1aa154 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | L2BytecodeHashMismatch(bytes32,bytes32) | 0xcb5e4247 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | L2UpgradeNonceNotEqualToNewProtocolVersion(uint256,uint256) | 0xd2c011d6 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | LengthIsNotDivisibleBy32(uint256) | 0xe37d2c02 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MalformedBytecode(uint8) | 0x43e266b0 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NewProtocolMajorVersionNotZero() | 0x72ea85ad | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PatchCantSetUpgradeTxn() | 0xd7f50a9d | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PatchUpgradeCantSetBootloader() | 0x962fd7d0 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PatchUpgradeCantSetDefaultAccount() | 0x559cc34e | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PreviousProtocolMajorVersionNotZero() | 0x5c598b60 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PreviousUpgradeNotCleaned() | 0xa0f47245 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PreviousUpgradeNotFinalized(bytes32) | 0x101ba748 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ProtocolVersionMinorDeltaTooBig(uint256,uint256) | 0xd328c12a | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ProtocolVersionTooSmall() | 0x88d7b498 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PubdataGreaterThanLimit(uint256,uint256) | 0x959f26fb | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TooManyFactoryDeps() | 0x76da24b9 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TooMuchGas() | 0xf0b4e88f | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TxnBodyGasLimitNotEnoughGas() | 0x2e311df8 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnexpectedNumberOfFactoryDeps() | 0x07218375 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ValidateTxnNotEnoughGas() | 0x47b3b145 | ++----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +DiamondCutTestContract ++----------+-------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=============================================================================================================================================+ +| Function | baseTokenGasPriceMultiplierDenominator() | 0x1de72e34 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | baseTokenGasPriceMultiplierNominator() | 0xea6c029c | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | diamondCut(((address,uint8,bool,bytes4[])[],address,bytes)) | 0x4cc5b15e | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | facetAddress(bytes4) | 0xcdffacc6 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | facetAddresses() | 0x52ef6b2c | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | facetFunctionSelectors(address) | 0xadfca15e | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | facets() | 0x7a0ed627 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getAdmin() | 0x6e9960c3 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getBaseToken() | 0x98acd7a6 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getBaseTokenAssetId() | 0x960dcf24 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getBridgehub() | 0x3591c1a0 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getChainId() | 0x3408e470 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getChainTypeManager() | 0x946ebad1 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getDAValidatorPair() | 0x5a590335 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getFirstUnprocessedPriorityTx() | 0x79823c9a | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getL2BootloaderBytecodeHash() | 0xd86970d8 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getL2DefaultAccountBytecodeHash() | 0xfd791f3c | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getL2SystemContractsUpgradeBatchNumber() | 0xe5355c75 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getL2SystemContractsUpgradeBlockNumber() | 0x9d1b5a81 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getL2SystemContractsUpgradeTxHash() | 0x7b30c8da | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getName() | 0x17d7de7c | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getPendingAdmin() | 0xd0468156 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getPriorityQueueSize() | 0x631f4bac | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getPriorityTreeRoot() | 0x39d7d4aa | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getPriorityTxMaxGasLimit() | 0x0ec6b0b7 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getProtocolVersion() | 0x33ce93fe | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getPubdataPricingMode() | 0x06d49e5b | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getSemverProtocolVersion() | 0xf5c1182c | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getSettlementLayer() | 0x6a27e8b5 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getTotalBatchesCommitted() | 0xdb1f0bf9 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getTotalBatchesExecuted() | 0xb8c2f66f | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getTotalBatchesVerified() | 0xef3f0bae | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getTotalBlocksCommitted() | 0xfe26699e | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getTotalBlocksExecuted() | 0x39607382 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getTotalBlocksVerified() | 0xaf6a2dcd | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getTotalPriorityTxs() | 0xa1954fc5 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getTransactionFilterer() | 0x22c5cf23 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getVerifier() | 0x46657fe9 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getVerifierParams() | 0x18e3a941 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isDiamondStorageFrozen() | 0x29b98c67 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isEthWithdrawalFinalized(uint256,uint256) | 0xbd7c5412 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isFacetFreezable(address) | 0xc3bbd2d7 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isFunctionFreezable(bytes4) | 0xe81e0ba1 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isValidator(address) | 0xfacd743b | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2LogsRootHash(uint256) | 0x9cd939e4 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | storedBatchHash(uint256) | 0xb22dd78e | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Function | storedBlockHash(uint256) | 0x74f4d30d | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Event | DiamondCut((address,uint8,bool,bytes4[])[],address,bytes) | 0x87b829356b3403d36217eff1f66ee48eacd0a69015153aba4f0de29fe5340c30 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AddressHasNoCode(address) | 0x86bb51b8 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Error | DelegateCallFailed(bytes) | 0xf7a01e4d | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Error | FacetExists(bytes4,address) | 0xac4a3f98 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidSelector(bytes4) | 0x12ba286f | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NoFunctionsForDiamondCut() | 0xa6fef710 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NonEmptyCalldata() | 0xc21b1ab7 | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Error | RemoveFunctionFacetAddressNotZero(address) | 0x667d17de | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Error | RemoveFunctionFacetAddressZero() | 0xa2d4b16c | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ReplaceFunctionFacetAddressZero() | 0x3580370c | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SelectorsMustAllHaveSameFreezability() | 0xd3b6535b | +|----------+-------------------------------------------------------------+--------------------------------------------------------------------| +| Error | UndefinedDiamondCutAction() | 0xe52478c7 | ++----------+-------------------------------------------------------------+--------------------------------------------------------------------+ + +DiamondProxyTest ++----------+-----------------------+------------+ +| Type | Signature | Selector | ++===============================================+ +| Function | setFreezability(bool) | 0xbf529569 | ++----------+-----------------------+------------+ + +DummyAdminFacet ++----------+----------------------------+------------+ +| Type | Signature | Selector | ++====================================================+ +| Function | dummySetValidator(address) | 0x7321c485 | +|----------+----------------------------+------------| +| Function | getName() | 0x17d7de7c | ++----------+----------------------------+------------+ + +DummyAdminFacetNoOverlap ++----------+--------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++==========================================================================================================================================================+ +| Function | executeUpgradeNoOverlap(((address,uint8,bool,bytes4[])[],address,bytes)) | 0x68c09202 | +|----------+--------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getName() | 0x17d7de7c | +|----------+--------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | receiveEther() | 0xa3912ec8 | +|----------+--------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | DiamondCut((address,uint8,bool,bytes4[])[],address,bytes) | 0x87b829356b3403d36217eff1f66ee48eacd0a69015153aba4f0de29fe5340c30 | +|----------+--------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AddressHasNoCode(address) | 0x86bb51b8 | +|----------+--------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | DelegateCallFailed(bytes) | 0xf7a01e4d | +|----------+--------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | FacetExists(bytes4,address) | 0xac4a3f98 | +|----------+--------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NoFunctionsForDiamondCut() | 0xa6fef710 | +|----------+--------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NonEmptyCalldata() | 0xc21b1ab7 | +|----------+--------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | RemoveFunctionFacetAddressNotZero(address) | 0x667d17de | +|----------+--------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | RemoveFunctionFacetAddressZero() | 0xa2d4b16c | +|----------+--------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ReplaceFunctionFacetAddressZero() | 0x3580370c | +|----------+--------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SelectorsMustAllHaveSameFreezability() | 0xd3b6535b | +|----------+--------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | UndefinedDiamondCutAction() | 0xe52478c7 | ++----------+--------------------------------------------------------------------------+--------------------------------------------------------------------+ + +DummyBridgehub ++----------+-----------------------------+------------+ +| Type | Signature | Selector | ++=====================================================+ +| Function | baseTokenAssetId(uint256) | 0xe52db4ca | +|----------+-----------------------------+------------| +| Function | getZKChain(uint256) | 0xe680c4c1 | +|----------+-----------------------------+------------| +| Function | messageRoot() | 0xd4b9f4fa | +|----------+-----------------------------+------------| +| Function | setMessageRoot(address) | 0x70f5c679 | +|----------+-----------------------------+------------| +| Function | setSharedBridge(address) | 0xd0bf6fd4 | +|----------+-----------------------------+------------| +| Function | setZKChain(uint256,address) | 0xce214549 | +|----------+-----------------------------+------------| +| Function | sharedBridge() | 0x38720778 | +|----------+-----------------------------+------------| +| Function | zkChain() | 0x2b3558a6 | ++----------+-----------------------------+------------+ + +DummyBridgehubSetter ++----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=========================================================================================================================================================================================+ +| Function | L1_CHAIN_ID() | 0x2f90b184 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | MAX_NUMBER_OF_ZK_CHAINS() | 0x8f8d37a8 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | __DEPRECATED_baseToken(uint256) | 0x5d83b6da | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | __DEPRECATED_tokenIsRegistered(address) | 0xeced0bf0 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptAdmin() | 0x0e18b681 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptOwnership() | 0x79ba5097 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | addChainTypeManager(address) | 0xff5a62a1 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | addTokenAssetId(bytes32) | 0x1c50cfea | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | admin() | 0xf851a440 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | assetIdIsRegistered(bytes32) | 0xe0ab6368 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | assetRouter() | 0xbc0aac10 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | baseToken(uint256) | 0x59ec65a2 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | baseTokenAssetId(uint256) | 0xe52db4ca | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeBurn(uint256,uint256,bytes32,address,bytes) | 0x699b0fb9 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeMint(uint256,bytes32,bytes) | 0x36ba0355 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeRecoverFailedTransfer(uint256,bytes32,address,bytes) | 0xc2e90293 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | chainTypeManager(uint256) | 0x9d5bd3da | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | chainTypeManagerIsRegistered(address) | 0xb93c9366 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | createNewChain(uint256,address,bytes32,uint256,address,bytes,bytes[]) | 0xf113c88b | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | ctmAssetIdFromAddress(address) | 0x70fccb52 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | ctmAssetIdFromChainId(uint256) | 0x24358c61 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | ctmAssetIdToAddress(bytes32) | 0x07621f84 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardTransactionOnGateway(uint256,bytes32,uint64) | 0x524c0cfa | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getAllZKChainChainIDs() | 0x68b8d331 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getAllZKChains() | 0x49707f31 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getHyperchain(uint256) | 0xdead6f7f | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getZKChain(uint256) | 0xe680c4c1 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialize(address) | 0xc4d66de8 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initializeV2() | 0x5cd8a76b | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l1CtmDeployer() | 0xcbe83612 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TransactionBaseCost(uint256,uint256,uint256,uint256) | 0x71623274 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | messageRoot() | 0xd4b9f4fa | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | migrationPaused() | 0x2a641114 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | owner() | 0x8da5cb5b | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pause() | 0x8456cb59 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pauseMigration() | 0xac700e63 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | paused() | 0x5c975abb | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pendingOwner() | 0xe30c3978 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL1ToL2TransactionStatus(uint256,bytes32,uint256,uint256,uint16,bytes32[],uint8) | 0xb292f5f1 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL2LogInclusion(uint256,uint256,uint256,(uint8,bool,uint16,address,bytes32,bytes32),bytes32[]) | 0xe6d9923b | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL2MessageInclusion(uint256,uint256,uint256,(uint16,address,bytes),bytes32[]) | 0x99c16d1a | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | registerAlreadyDeployedZKChain(uint256,address) | 0xb5662c5d | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | registerSettlementLayer(uint256,bool) | 0xdc8e4b26 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | removeChainTypeManager(address) | 0x332b96dc | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | renounceOwnership() | 0x715018a6 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | requestL2TransactionDirect((uint256,uint256,address,uint256,bytes,uint256,uint256,bytes[],address)) | 0xd52471c1 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | requestL2TransactionTwoBridges((uint256,uint256,uint256,uint256,uint256,address,address,uint256,bytes)) | 0x24fd57fb | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setAddresses(address,address,address) | 0x363bf964 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setAssetHandlerAddress(bytes32,address) | 0x3f704d2a | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setCTM(uint256,address) | 0xa52c6753 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setLegacyBaseTokenAssetId(uint256) | 0xca8f93f1 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setLegacyChainAddress(uint256) | 0xd92f86a2 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPendingAdmin(address) | 0x4dd18bf5 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setZKChain(uint256,address) | 0xce214549 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | settlementLayer(uint256) | 0x671a7131 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | sharedBridge() | 0x38720778 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferOwnership(address) | 0xf2fde38b | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | unpause() | 0x3f4ba83a | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | unpauseMigration() | 0xf7c7eb92 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | whitelistedSettlementLayers(uint256) | 0xe9420f8c | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | AssetRegistered(bytes32,address,bytes32,address) | 0x8f09d7694a9ae17acec5cf132d594d7eee23572f7fe132396ce72b1afbf7ef20 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BaseTokenAssetIdRegistered(bytes32) | 0x3df150949161462acf3be30521d7da9e533b247327a254e55dd01875897a6df3 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeBurn(uint256,bytes32,address,address,uint256) | 0x1cd02155ad1064c60598a8bd0e4e795d7e7d0a0f3c38aad04d261f1297fb2545 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeInitialize(address,string,string,uint8) | 0x81e8e92e5873539605a102eddae7ed06d19bea042099a437cbc3644415eb7404 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeMint(uint256,bytes32,address,uint256) | 0xbc0f4055a7869d8ecad34b33382a0bc181c5811565fec42f335505be5fd661d2 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ChainTypeManagerAdded(address) | 0x2eae91be1021e05cc8076387b0182458ae474ae44ee44cc59aefda6ca53c1f42 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ChainTypeManagerRemoved(address) | 0x4e04a497739580efe78a7ee09cdabe6f6fe90965c683292a519102ce5193b68a | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Initialized(uint8) | 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | MigrationFinalized(uint256,bytes32,address) | 0xb0cc16029b506b2a262b52711e71db4fcd1cb078bd4bb86c7ba82cd3be2eadd3 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | MigrationStarted(uint256,bytes32,uint256) | 0xc60eb6d595da5361c68f60aa7c8286b8f73c3a99e9db1818e146c522f512496f | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewAdmin(address,address) | 0xf9ffabca9c8276e99321725bcb43fb076a6c66a54b7f21c4e8146d8519b417dc | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewChain(uint256,address,address) | 0x1e9125bc72db22c58abff6821d7333551967e26454b419ffa958e4cb8ef47600 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewPendingAdmin(address,address) | 0xca4f2f25d0898edd99413412fb94012f9e54ec8142f9b093e7720646a95b16a9 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Paused(address) | 0x62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | SettlementLayerRegistered(uint256,bool) | 0x02629feb109d94b16a367231d248ba81c462f51ce5b984835f150f1c9f49ed25 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Unpaused(address) | 0x5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AddressTooLow(address) | 0x1eee5481 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AssetHandlerNotRegistered(bytes32) | 0x64107968 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AssetIdAlreadyRegistered() | 0xfe919e28 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AssetIdNotSupported(bytes32) | 0x04a0b7e9 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | BridgeHubAlreadyRegistered() | 0x6cf12312 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | CTMAlreadyRegistered() | 0xec273439 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | CTMNotRegistered() | 0xc630ef3c | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ChainAlreadyLive() | 0x78d2ed02 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ChainIdAlreadyExists() | 0x24591d89 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ChainIdCantBeCurrentChain() | 0x717a1656 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ChainIdMismatch() | 0xa179f8c9 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ChainIdNotRegistered(uint256) | 0x23f3c357 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ChainIdTooBig() | 0x8f620a06 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ChainNotLegacy() | 0x5de72107 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | EmptyAssetId() | 0x2d4d012f | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | IncorrectBridgeHubAddress(address) | 0xdd381a4c | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MigrationPaused() | 0x3312a450 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MsgValueMismatch(uint256,uint256) | 0x4a094431 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NotInitializedReentrancyGuard() | 0xdd7e3621 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Reentrancy() | 0xab143c06 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SharedBridgeNotSet() | 0x856d5b77 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SlotOccupied() | 0xdf3a8fdd | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | WrongMagicValue(uint256,uint256) | 0x15e8e429 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ZKChainLimitReached() | 0x601b6882 | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ZeroAddress() | 0xd92e233d | +|----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ZeroChainId() | 0xc84885d4 | ++----------+---------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +DummyChainTypeManager ++----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++================================================================================================================================================================================================================================================+ +| Function | BRIDGE_HUB() | 0x5d4edca7 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptAdmin() | 0x0e18b681 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptOwnership() | 0x79ba5097 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | admin() | 0xf851a440 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | changeFeeParams(uint256,(uint8,uint32,uint32,uint32,uint32,uint64)) | 0x027f12e1 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | createNewChain(uint256,bytes32,address,bytes,bytes[]) | 0x88c7c5d2 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | executeUpgrade(uint256,((address,uint8,bool,bytes4[])[],address,bytes)) | 0xe34a329a | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeBurn(uint256,bytes) | 0xf85894c5 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeMint(uint256,bytes) | 0xe8a71ca9 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeRecoverFailedTransfer(uint256,bytes32,address,bytes) | 0xb7846107 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | freezeChain(uint256) | 0xaccdd16c | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getChainAdmin(uint256) | 0x301e7765 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getHyperchain(uint256) | 0xdead6f7f | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getProtocolVersion(uint256) | 0xba238947 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getSemverProtocolVersion() | 0xf5c1182c | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getZKChain(uint256) | 0xe680c4c1 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getZKChainLegacy(uint256) | 0x4caa740f | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialCutHash() | 0x57e6246b | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialForceDeploymentHash() | 0x61f91b2e | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialize((address,address,(address,bytes32,uint64,bytes32,((address,uint8,bool,bytes4[])[],address,bytes),bytes),uint256)) | 0xce7df3d6 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l1GenesisUpgrade() | 0x3437949a | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | owner() | 0x8da5cb5b | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pendingOwner() | 0xe30c3978 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | protocolVersion() | 0x2ae9c600 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | protocolVersionDeadline(uint256) | 0xf4943a20 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | protocolVersionIsActive(uint256) | 0xdef9d6af | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | registerSettlementLayer(uint256,bool) | 0xdc8e4b26 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | renounceOwnership() | 0x715018a6 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | revertBatches(uint256,uint256) | 0x53ce2061 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setChainCreationParams((address,bytes32,uint64,bytes32,((address,uint8,bool,bytes4[])[],address,bytes),bytes)) | 0x9b016b8b | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setNewVersionUpgrade(((address,uint8,bool,bytes4[])[],address,bytes),uint256,uint256,uint256) | 0x2e522851 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPendingAdmin(address) | 0x4dd18bf5 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPorterAvailability(uint256,bool) | 0x18717dc1 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPriorityTxMaxGasLimit(uint256,uint256) | 0xec3d5f88 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setProtocolVersionDeadline(uint256,uint256) | 0xaad74262 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setTokenMultiplier(uint256,uint128,uint128) | 0x7ebba672 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setUpgradeDiamondCut(((address,uint8,bool,bytes4[])[],address,bytes),uint256) | 0x98461504 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setValidator(uint256,address,bool) | 0xcf347e17 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setValidatorTimelock(address) | 0x7fb67816 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setZKChain(uint256,address) | 0xce214549 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | storedBatchZero() | 0xd2ef1b0e | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferOwnership(address) | 0xf2fde38b | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | unfreezeChain(uint256) | 0x51d218f7 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | upgradeChainFromVersion(uint256,uint256,((address,uint8,bool,bytes4[])[],address,bytes)) | 0x0dbad27e | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | upgradeCutHash(uint256) | 0x52c9eacb | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | validatorTimelock() | 0xe66c8c44 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeInitialize(address,string,string,uint8) | 0x81e8e92e5873539605a102eddae7ed06d19bea042099a437cbc3644415eb7404 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | GenesisUpgrade(address,(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),uint256) | 0x778b7f95aad9610955002f243aabbbf9546bb6e792f7b3b89dab86b4f210e30f | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Initialized(uint8) | 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewAdmin(address,address) | 0xf9ffabca9c8276e99321725bcb43fb076a6c66a54b7f21c4e8146d8519b417dc | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewChainCreationParams(address,bytes32,uint64,bytes32,bytes32,bytes32) | 0x04b363b4a0200ada216b1cb4aaf2736ff6f332d5f1d90f98e60b1159f3dac3aa | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewPendingAdmin(address,address) | 0xca4f2f25d0898edd99413412fb94012f9e54ec8142f9b093e7720646a95b16a9 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewProtocolVersion(uint256,uint256) | 0x4235104f56661fe2e9d2f2a460b42766581bc45ce366c6a30a9f86c8a2b371a7 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewUpgradeCutData(uint256,((address,uint8,bool,bytes4[])[],address,bytes)) | 0xf99295383247eabb6bee8798669fa768502f8843d3be0e82a0aa81d7b6c4f60c | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewUpgradeCutHash(uint256,bytes32) | 0x71b0aeaf8eaa06ed78ccb9a4981da026eea05ca1d818c22dd120446db4c936d4 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewValidatorTimelock(address,address) | 0x5a1b0d8808a8dca64c1f7c230dce7a09f7f9a1c26507e190e03dcd382e69018e | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewZKChain(uint256,address) | 0xf83c256407747903308213919067f883f683c5cde6a64ebbf25096b8bb555ddc | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | GenesisBatchCommitmentZero() | 0x6d4a7df8 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | GenesisBatchHashZero() | 0x7940c83f | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | GenesisIndexStorageZero() | 0xb4fc6835 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | GenesisUpgradeZero() | 0x3a1a8589 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | HashMismatch(bytes32,bytes32) | 0x0b08d5be | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SlotOccupied() | 0xdf3a8fdd | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ZeroAddress() | 0xd92e233d | ++----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +DummyChainTypeManagerWBH ++----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++================================================================================================================================================================================================================================================+ +| Function | BRIDGE_HUB() | 0x5d4edca7 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptAdmin() | 0x0e18b681 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptOwnership() | 0x79ba5097 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | admin() | 0xf851a440 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | changeFeeParams(uint256,(uint8,uint32,uint32,uint32,uint32,uint64)) | 0x027f12e1 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | createNewChain(uint256,bytes32,address,bytes,bytes[]) | 0x88c7c5d2 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | executeUpgrade(uint256,((address,uint8,bool,bytes4[])[],address,bytes)) | 0xe34a329a | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeBurn(uint256,bytes) | 0xf85894c5 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeMint(uint256,bytes) | 0xe8a71ca9 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeRecoverFailedTransfer(uint256,bytes32,address,bytes) | 0xb7846107 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | freezeChain(uint256) | 0xaccdd16c | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getChainAdmin(uint256) | 0x301e7765 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getHyperchain(uint256) | 0xdead6f7f | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getProtocolVersion(uint256) | 0xba238947 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getSemverProtocolVersion() | 0xf5c1182c | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getZKChain(uint256) | 0xe680c4c1 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getZKChainLegacy(uint256) | 0x4caa740f | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialCutHash() | 0x57e6246b | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialForceDeploymentHash() | 0x61f91b2e | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialize((address,address,(address,bytes32,uint64,bytes32,((address,uint8,bool,bytes4[])[],address,bytes),bytes),uint256)) | 0xce7df3d6 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l1GenesisUpgrade() | 0x3437949a | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | owner() | 0x8da5cb5b | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pendingOwner() | 0xe30c3978 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | protocolVersion() | 0x2ae9c600 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | protocolVersionDeadline(uint256) | 0xf4943a20 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | protocolVersionIsActive(uint256) | 0xdef9d6af | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | registerSettlementLayer(uint256,bool) | 0xdc8e4b26 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | renounceOwnership() | 0x715018a6 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | revertBatches(uint256,uint256) | 0x53ce2061 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setChainCreationParams((address,bytes32,uint64,bytes32,((address,uint8,bool,bytes4[])[],address,bytes),bytes)) | 0x9b016b8b | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setNewVersionUpgrade(((address,uint8,bool,bytes4[])[],address,bytes),uint256,uint256,uint256) | 0x2e522851 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPendingAdmin(address) | 0x4dd18bf5 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPorterAvailability(uint256,bool) | 0x18717dc1 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPriorityTxMaxGasLimit(uint256,uint256) | 0xec3d5f88 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setProtocolVersionDeadline(uint256,uint256) | 0xaad74262 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setTokenMultiplier(uint256,uint128,uint128) | 0x7ebba672 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setUpgradeDiamondCut(((address,uint8,bool,bytes4[])[],address,bytes),uint256) | 0x98461504 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setValidator(uint256,address,bool) | 0xcf347e17 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setValidatorTimelock(address) | 0x7fb67816 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setZKChain(uint256,address) | 0xce214549 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | storedBatchZero() | 0xd2ef1b0e | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferOwnership(address) | 0xf2fde38b | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | unfreezeChain(uint256) | 0x51d218f7 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | upgradeChainFromVersion(uint256,uint256,((address,uint8,bool,bytes4[])[],address,bytes)) | 0x0dbad27e | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | upgradeCutHash(uint256) | 0x52c9eacb | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | validatorTimelock() | 0xe66c8c44 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeInitialize(address,string,string,uint8) | 0x81e8e92e5873539605a102eddae7ed06d19bea042099a437cbc3644415eb7404 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | GenesisUpgrade(address,(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),uint256) | 0x778b7f95aad9610955002f243aabbbf9546bb6e792f7b3b89dab86b4f210e30f | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Initialized(uint8) | 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewAdmin(address,address) | 0xf9ffabca9c8276e99321725bcb43fb076a6c66a54b7f21c4e8146d8519b417dc | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewChainCreationParams(address,bytes32,uint64,bytes32,bytes32,bytes32) | 0x04b363b4a0200ada216b1cb4aaf2736ff6f332d5f1d90f98e60b1159f3dac3aa | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewPendingAdmin(address,address) | 0xca4f2f25d0898edd99413412fb94012f9e54ec8142f9b093e7720646a95b16a9 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewProtocolVersion(uint256,uint256) | 0x4235104f56661fe2e9d2f2a460b42766581bc45ce366c6a30a9f86c8a2b371a7 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewUpgradeCutData(uint256,((address,uint8,bool,bytes4[])[],address,bytes)) | 0xf99295383247eabb6bee8798669fa768502f8843d3be0e82a0aa81d7b6c4f60c | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewUpgradeCutHash(uint256,bytes32) | 0x71b0aeaf8eaa06ed78ccb9a4981da026eea05ca1d818c22dd120446db4c936d4 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewValidatorTimelock(address,address) | 0x5a1b0d8808a8dca64c1f7c230dce7a09f7f9a1c26507e190e03dcd382e69018e | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewZKChain(uint256,address) | 0xf83c256407747903308213919067f883f683c5cde6a64ebbf25096b8bb555ddc | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | GenesisBatchCommitmentZero() | 0x6d4a7df8 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | GenesisBatchHashZero() | 0x7940c83f | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | GenesisIndexStorageZero() | 0xb4fc6835 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | GenesisUpgradeZero() | 0x3a1a8589 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | HashMismatch(bytes32,bytes32) | 0x0b08d5be | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SlotOccupied() | 0xdf3a8fdd | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ZeroAddress() | 0xd92e233d | ++----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +DummySharedBridge ++----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=======================================================================================================================================================================================+ +| Function | assetHandlerAddress(bytes32) | 0x53b9e632 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgehubConfirmL2Transaction(uint256,bytes32,bytes32) | 0x8eb7db57 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgehubDeposit(uint256,address,uint256,bytes) | 0xca408c23 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgehubDepositBaseToken(uint256,bytes32,address,uint256) | 0xc4879440 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | chainBalance(uint256,address) | 0x9cd45184 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | claimFailedDeposit(uint256,address,address,uint256,bytes32,uint256,uint256,uint16,bytes32[]) | 0xc0991525 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | claimFailedDepositLegacyErc20Bridge(address,address,uint256,bytes32,uint256,uint256,uint16,bytes32[]) | 0x8fbb3711 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | depositLegacyErc20Bridge(address,address,address,uint256,uint256,uint256,address) | 0x9e6ea417 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeWithdrawal(uint256,uint256,uint256,uint16,bytes,bytes32[]) | 0xc87325f1 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeWithdrawalLegacyErc20Bridge(uint256,uint256,uint16,bytes,bytes32[]) | 0x7ab08472 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | nativeTokenVault() | 0x64e130cf | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pause() | 0x8456cb59 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | paused() | 0x5c975abb | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | receiveEth(uint256) | 0xc2aaf9c4 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setAssetHandlerAddressThisChain(bytes32,address) | 0x548a5a33 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setDataToBeReturnedInFinalizeWithdrawal(address,address,uint256) | 0xa6f2c076 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setNativeTokenVault(address) | 0x0f3fa211 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | unpause() | 0x3f4ba83a | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgehubDepositBaseTokenInitiated(uint256,address,bytes32,uint256) | 0x0f87e1ea5eb1f034a6071ef630c174063e3d48756f853efaaf4292b929298240 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Debugger(uint256) | 0x8d9c8ca1e649d98242c05e8424ce820b45ac79086a871220eec3a570ac0a9828 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Initialized(uint8) | 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Paused(address) | 0x62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258 | +|----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Unpaused(address) | 0x5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa | ++----------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +DummyZKChain ++----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++===================================================================================================================================================================================================================================================================+ +| Function | bridgehubRequestL2Transaction((address,address,uint256,uint256,bytes,uint256,uint256,bytes[],address)) | 0x12f43dab | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgehubRequestL2TransactionOnGateway(bytes32,uint64) | 0xddcc9eec | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeEthWithdrawal(uint256,uint256,uint16,bytes,bytes32[]) | 0x6c0960f9 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | genesisUpgrade(address,bytes,bytes[]) | 0xa3bd0112 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getBridgeHubAddress() | 0x8466d8d1 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getEraChainId() | 0xe23d2563 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getName() | 0x17d7de7c | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TransactionBaseCost(uint256,uint256,uint256) | 0xb473318e | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL1ToL2TransactionStatus(bytes32,uint256,uint256,uint16,bytes32[],uint8) | 0x042901c7 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL1ToL2TransactionStatusViaGateway(bytes32,uint256,uint256,uint16,bytes32[],uint8) | 0xe717bab7 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL2LeafInclusion(uint256,uint256,bytes32,bytes32[]) | 0x7efda2ae | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL2LogInclusion(uint256,uint256,(uint8,bool,uint16,address,bytes32,bytes32),bytes32[]) | 0x263b7f8e | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL2MessageInclusion(uint256,uint256,(uint16,address,bytes),bytes32[]) | 0xe4948f43 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | requestL2Transaction(address,uint256,bytes,uint256,uint256,bytes[],address) | 0xeb672419 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | requestL2TransactionToGatewayMailbox(uint256,bytes32,uint64) | 0xd0772551 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setBaseTokenGasMultiplierPrice(uint128,uint128) | 0x328ef4fe | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setBridgeHubAddress(address) | 0x8ffe1b81 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setFeeParams() | 0x47fcedb8 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewPriorityRequest(uint256,bytes32,uint64,(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[]) | 0x4531cd5795773d7101c17bdeb9f5ab7f47d7056017506f937083be5d6e77a382 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewRelayedPriorityTransaction(uint256,bytes32,uint64) | 0x0137d2eaa6ec5b7e4f233f6d6f441410014535d0f3985367994c94bf15a2a564 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | BaseTokenGasPriceDenominatorNotSet() | 0x6ef9a972 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | BatchNotExecuted(uint256) | 0x2078a6a0 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | GasPerPubdataMismatch() | 0xc91cf3b1 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | HashedLogIsDefault() | 0xd356e6ba | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | LengthIsNotDivisibleBy32(uint256) | 0xe37d2c02 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MalformedBytecode(uint8) | 0x43e266b0 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MerkleIndexOutOfBounds() | 0x9bb54c35 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MerklePathEmpty() | 0x8e23ac1a | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MerklePathOutOfBounds() | 0x1c500385 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MsgValueTooLow(uint256,uint256) | 0xb385a3da | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NotInitializedReentrancyGuard() | 0xdd7e3621 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | OnlyEraSupported() | 0xf3ed9dfa | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PubdataGreaterThanLimit(uint256,uint256) | 0x959f26fb | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Reentrancy() | 0xab143c06 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TooManyFactoryDeps() | 0x76da24b9 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TooMuchGas() | 0xf0b4e88f | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TransactionNotAllowed() | 0x00c5a6a9 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TxnBodyGasLimitNotEnoughGas() | 0x2e311df8 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ValidateTxnNotEnoughGas() | 0x47b3b145 | ++----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +ExecutorProvingTest ++----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++===============================================================================================================================================================================================================+ +| Function | commitBatchesSharedBridge(uint256,uint256,uint256,bytes) | 0x98f81962 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | createBatchCommitment((uint64,uint64,uint64,bytes32,uint256,bytes32,bytes32,bytes32,bytes,bytes),bytes32,bytes32[],bytes32[]) | 0xc1899c1d | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | executeBatchesSharedBridge(uint256,uint256,uint256,bytes) | 0xcf02827d | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getBatchProofPublicInput(bytes32,bytes32) | 0x868085b1 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getName() | 0x17d7de7c | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | processL2Logs((uint64,uint64,uint64,bytes32,uint256,bytes32,bytes32,bytes32,bytes,bytes),bytes32,uint8) | 0xc75ac8fa | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveBatchesSharedBridge(uint256,uint256,uint256,bytes) | 0xe12a6137 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | revertBatchesSharedBridge(uint256,uint256) | 0x0f23da43 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setHashes(bytes32,bytes32) | 0xced531eb | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlockCommit(uint256,bytes32,bytes32) | 0x8f2916b2f2d78cc5890ead36c06c0f6d5d112c7e103589947e8e2f0d6eddb763 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlockExecution(uint256,bytes32,bytes32) | 0x2402307311a4d6604e4e7b4c8a15a7e1213edb39c16a31efa70afb06030d3165 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlocksRevert(uint256,uint256,uint256) | 0x8bd4b15ea7d1bc41ea9abc3fc487ccb89cd678a00786584714faa9d751c84ee5 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlocksVerification(uint256,uint256) | 0x22c9005dd88c18b552a1cd7e8b3b937fcde9ca69213c1f658f54d572e4877a81 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | BatchHashMismatch(bytes32,bytes32) | 0x55ad3fd3 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | BatchNumberMismatch(uint256,uint256) | 0xbd4455ff | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | CanOnlyProcessOneBatch() | 0xe85392f9 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | CantExecuteUnprovenBatches() | 0x00c6ead2 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | CantRevertExecutedBatch() | 0xe18cb383 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | EmptyData() | 0x99d8fec9 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | HashMismatch(bytes32,bytes32) | 0x0b08d5be | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | IncorrectBatchBounds(uint256,uint256,uint256,uint256) | 0xd7d93e1f | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidLogSender(address,uint256) | 0xc1780bd6 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidNumberOfBlobs(uint256,uint256,uint256) | 0xd8e9405c | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidProof() | 0x09bde339 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidProtocolVersion() | 0x5428eae7 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | L2TimestampTooBig() | 0xfb5c22e6 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | LogAlreadyProcessed(uint8) | 0x1b6825bb | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MerklePathEmpty() | 0x8e23ac1a | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MerklePathOutOfBounds() | 0x1c500385 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MissingSystemLogs(uint256,uint256) | 0xfa44b527 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NonIncreasingTimestamp() | 0xd018e08e | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NonSequentialBatch() | 0x0105f9c0 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NotInitializedReentrancyGuard() | 0xdd7e3621 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PriorityOperationsRollingHashMismatch() | 0xd5a99014 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | QueueIsEmpty() | 0x63c36549 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Reentrancy() | 0xab143c06 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | RevertedBatchNotAfterNewLastBatch() | 0x9a67c1cb | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SystemLogsSizeTooBig() | 0xae43b424 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TimeNotReached(uint256,uint256) | 0x08753982 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TimestampError() | 0x2d50c33b | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TxHashMismatch() | 0x4c991078 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnexpectedSystemLog(uint256) | 0x6aa39880 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnsupportedCommitBatchEncoding(uint8) | 0xf3dd1b9c | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnsupportedExecuteBatchEncoding(uint8) | 0x14d2ed8a | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnsupportedProofBatchEncoding(uint8) | 0xf338f830 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | UpgradeBatchNumberIsNotZero() | 0xf093c2e5 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ValueMismatch(uint256,uint256) | 0x626ade30 | +|----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | VerifiedBatchesExceedsCommittedBatches() | 0xe1022469 | ++----------+-------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +FullMerkleTest ++----------+-------------------------------------------+------------+ +| Type | Signature | Selector | ++===================================================================+ +| Function | height() | 0x0ef26743 | +|----------+-------------------------------------------+------------| +| Function | index() | 0x2986c0e5 | +|----------+-------------------------------------------+------------| +| Function | node(uint256,uint256) | 0xae65def1 | +|----------+-------------------------------------------+------------| +| Function | nodeCount(uint256) | 0xcdc4878b | +|----------+-------------------------------------------+------------| +| Function | pushNewLeaf(bytes32) | 0xb6ea1757 | +|----------+-------------------------------------------+------------| +| Function | root() | 0xebf0c717 | +|----------+-------------------------------------------+------------| +| Function | updateAllLeaves(bytes32[]) | 0x505e6d47 | +|----------+-------------------------------------------+------------| +| Function | updateAllNodesAtHeight(uint256,bytes32[]) | 0x580d6bff | +|----------+-------------------------------------------+------------| +| Function | updateLeaf(uint256,bytes32) | 0xfcc73360 | +|----------+-------------------------------------------+------------| +| Function | zeros(uint256) | 0xe8295588 | ++----------+-------------------------------------------+------------+ + +IncrementalMerkleTest ++----------+----------------+------------+ +| Type | Signature | Selector | ++========================================+ +| Function | height() | 0x0ef26743 | +|----------+----------------+------------| +| Function | index() | 0x2986c0e5 | +|----------+----------------+------------| +| Function | push(bytes32) | 0xb298e36b | +|----------+----------------+------------| +| Function | root() | 0xebf0c717 | +|----------+----------------+------------| +| Function | side(uint256) | 0x7890e5da | +|----------+----------------+------------| +| Function | zeros(uint256) | 0xe8295588 | ++----------+----------------+------------+ + +L1ERC20BridgeTest ++----------+------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++==============================================================================================================================================================+ +| Function | ERA_CHAIN_ID() | 0xef011dff | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_ASSET_ROUTER() | 0xcdf25430 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_NATIVE_TOKEN_VAULT() | 0x293e8520 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_NULLIFIER() | 0xe60ccaba | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | claimFailedDeposit(address,address,bytes32,uint256,uint256,uint16,bytes32[]) | 0x19fa7f62 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | deposit(address,address,uint256,uint256,uint256) | 0x933999fb | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | deposit(address,address,uint256,uint256,uint256,address) | 0xe8b99b1b | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | depositAmount(address,address,bytes32) | 0x01eae183 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeWithdrawal(uint256,uint256,uint16,bytes,bytes32[]) | 0x11a2ccc1 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialize() | 0x8129fc1c | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isWithdrawalFinalized(uint256,uint256) | 0x4bed8212 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2Bridge() | 0xae1f6aaf | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TokenAddress(address) | 0xf5f15168 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TokenBeacon() | 0x6dde7209 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TokenProxyBytecodeHash() | 0x823f1d96 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ClaimedFailedDeposit(address,address,uint256) | 0xbe066dc591f4a444f75176d387c3e6c775e5706d9ea9a91d11eb49030c66cf60 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | DepositInitiated(bytes32,address,address,address,uint256) | 0xdd341179f4edc78148d894d0213a96d212af2cbaf223d19ef6d483bdd47ab81d | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | WithdrawalFinalized(address,address,uint256) | 0xac1b18083978656d557d6e91c88203585cfda1031bdb14538327121ef140d383 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ApprovalFailed() | 0x8164f842 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ETHDepositNotSupported() | 0x627e0872 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | EmptyDeposit() | 0x95b66fe9 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NotInitializedReentrancyGuard() | 0xdd7e3621 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Reentrancy() | 0xab143c06 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SlotOccupied() | 0xdf3a8fdd | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TokensWithFeesNotSupported() | 0x23830e28 | +|----------+------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | WithdrawalAlreadyFinalized() | 0xae899454 | ++----------+------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +L2NativeTokenVaultDev ++----------+------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++======================================================================================================================================+ +| Function | ASSET_ROUTER() | 0xc6a70bbb | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | BASE_TOKEN_ASSET_ID() | 0xcb944dec | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_CHAIN_ID() | 0x2f90b184 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | L2_LEGACY_SHARED_BRIDGE() | 0xc438a9f2 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | WETH_TOKEN() | 0x37d277d4 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptOwnership() | 0x79ba5097 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | assetId(address) | 0xfd3f60df | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeBurn(uint256,uint256,bytes32,address,bytes) | 0x699b0fb9 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgeMint(uint256,bytes32,bytes) | 0x36ba0355 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgedTokenBeacon() | 0xf2d44246 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | calculateCreate2TokenAddress(uint256,address) | 0xc487412c | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | deployBridgedStandardERC20(address) | 0xbfff27c2 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | ensureTokenIsRegistered(address) | 0x19a2a285 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | getERC20Getters(address,uint256) | 0xa7236d16 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TokenAddress(address) | 0xf5f15168 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | originChainId(bytes32) | 0x5f3455b5 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | owner() | 0x8da5cb5b | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | pause() | 0x8456cb59 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | paused() | 0x5c975abb | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | pendingOwner() | 0xe30c3978 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | registerToken(address) | 0x09824a80 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | renounceOwnership() | 0x715018a6 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | setLegacyTokenAssetId(address) | 0x4cd40a02 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | test() | 0xf8a8fd6d | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | tokenAddress(bytes32) | 0x97bb3ce9 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | tokenDataOriginChainId(bytes) | 0x07a6d4bc | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferOwnership(address) | 0xf2fde38b | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Function | unpause() | 0x3f4ba83a | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeBurn(uint256,bytes32,address,address,uint256) | 0x1cd02155ad1064c60598a8bd0e4e795d7e7d0a0f3c38aad04d261f1297fb2545 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeInitialize(address,string,string,uint8) | 0x81e8e92e5873539605a102eddae7ed06d19bea042099a437cbc3644415eb7404 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeMint(uint256,bytes32,address,uint256) | 0xbc0f4055a7869d8ecad34b33382a0bc181c5811565fec42f335505be5fd661d2 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgedTokenBeaconUpdated(address,bytes32) | 0xc3f14dba68f86c42f518e5c0e8a5cbc9514da6f388e2f52c5b1a6263d8588bfb | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | FinalizeDeposit(address,address,address,uint256) | 0xb84fba9af218da60d299dc177abd5805e7ac541d2673cbee7808c10017874f63 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | Initialized(uint8) | 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | L2TokenBeaconUpdated(address,bytes32) | 0x01fd5911e6d04aec6b21f19752502ad7f3e9876279643c8fa7a4d30c88a29fb2 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | Paused(address) | 0x62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | Unpaused(address) | 0x5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Event | WithdrawalInitiated(address,address,address,uint256) | 0x2fc3848834aac8e883a2d2a17a7514dc4f2d3dd268089df9b9f5d918259ef3b0 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | AddressMismatch(address,address) | 0x1f73225f | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | AmountMustBeGreaterThanZero() | 0x5e85ae73 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | AssetIdMismatch(bytes32,bytes32) | 0x1294e9e1 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | AssetIdNotSupported(bytes32) | 0x04a0b7e9 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | DeployingBridgedTokenForNativeToken() | 0x138ee1a3 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | EmptyAddress() | 0x7138356f | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | EmptyBytes32() | 0x1c25715b | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | EmptyDeposit() | 0x95b66fe9 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | NonEmptyMsgValue() | 0x536ec84b | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | TokenNotSupported(address) | 0x06439c6b | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | TokensWithFeesNotSupported() | 0x23830e28 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnsupportedEncodingVersion() | 0x084a1449 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | ValueMismatch(uint256,uint256) | 0x626ade30 | +|----------+------------------------------------------------------+--------------------------------------------------------------------| +| Error | ZeroAddress() | 0xd92e233d | ++----------+------------------------------------------------------+--------------------------------------------------------------------+ + +MailboxFacetTest ++----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++===================================================================================================================================================================================================================================================================+ +| Function | bridgehubRequestL2Transaction((address,address,uint256,uint256,bytes,uint256,uint256,bytes[],address)) | 0x12f43dab | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgehubRequestL2TransactionOnGateway(bytes32,uint64) | 0xddcc9eec | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeEthWithdrawal(uint256,uint256,uint16,bytes,bytes32[]) | 0x6c0960f9 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getL2GasPrice(uint256) | 0xab07b2e9 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getName() | 0x17d7de7c | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TransactionBaseCost(uint256,uint256,uint256) | 0xb473318e | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL1ToL2TransactionStatus(bytes32,uint256,uint256,uint16,bytes32[],uint8) | 0x042901c7 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL1ToL2TransactionStatusViaGateway(bytes32,uint256,uint256,uint16,bytes32[],uint8) | 0xe717bab7 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL2LeafInclusion(uint256,uint256,bytes32,bytes32[]) | 0x7efda2ae | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL2LogInclusion(uint256,uint256,(uint8,bool,uint16,address,bytes32,bytes32),bytes32[]) | 0x263b7f8e | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL2MessageInclusion(uint256,uint256,(uint16,address,bytes),bytes32[]) | 0xe4948f43 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | requestL2Transaction(address,uint256,bytes,uint256,uint256,bytes[],address) | 0xeb672419 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | requestL2TransactionToGatewayMailbox(uint256,bytes32,uint64) | 0xd0772551 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setFeeParams((uint8,uint32,uint32,uint32,uint32,uint64)) | 0xb4866c43 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewPriorityRequest(uint256,bytes32,uint64,(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[]) | 0x4531cd5795773d7101c17bdeb9f5ab7f47d7056017506f937083be5d6e77a382 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewRelayedPriorityTransaction(uint256,bytes32,uint64) | 0x0137d2eaa6ec5b7e4f233f6d6f441410014535d0f3985367994c94bf15a2a564 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | BaseTokenGasPriceDenominatorNotSet() | 0x6ef9a972 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | BatchNotExecuted(uint256) | 0x2078a6a0 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | GasPerPubdataMismatch() | 0xc91cf3b1 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | HashedLogIsDefault() | 0xd356e6ba | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | LengthIsNotDivisibleBy32(uint256) | 0xe37d2c02 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MalformedBytecode(uint8) | 0x43e266b0 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MerkleIndexOutOfBounds() | 0x9bb54c35 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MerklePathEmpty() | 0x8e23ac1a | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MerklePathOutOfBounds() | 0x1c500385 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MsgValueTooLow(uint256,uint256) | 0xb385a3da | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NotInitializedReentrancyGuard() | 0xdd7e3621 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | OnlyEraSupported() | 0xf3ed9dfa | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PubdataGreaterThanLimit(uint256,uint256) | 0x959f26fb | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Reentrancy() | 0xab143c06 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TooManyFactoryDeps() | 0x76da24b9 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TooMuchGas() | 0xf0b4e88f | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TransactionNotAllowed() | 0x00c5a6a9 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TxnBodyGasLimitNotEnoughGas() | 0x2e311df8 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ValidateTxnNotEnoughGas() | 0x47b3b145 | ++----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +MerkleTest ++----------+------------------------------------------------------+------------+ +| Type | Signature | Selector | ++==============================================================================+ +| Function | calculateRoot(bytes32[],uint256,bytes32) | 0x7a592065 | +|----------+------------------------------------------------------+------------| +| Function | calculateRoot(bytes32[],bytes32[],uint256,bytes32[]) | 0xee7fb38b | +|----------+------------------------------------------------------+------------| +| Error | MerkleIndexOutOfBounds() | 0x9bb54c35 | +|----------+------------------------------------------------------+------------| +| Error | MerklePathEmpty() | 0x8e23ac1a | +|----------+------------------------------------------------------+------------| +| Error | MerklePathOutOfBounds() | 0x1c500385 | ++----------+------------------------------------------------------+------------+ + +MockExecutorFacet ++----------+-------------------------------------+------------+ +| Type | Signature | Selector | ++=============================================================+ +| Function | saveL2LogsRootHash(uint256,bytes32) | 0x8a75bb09 | +|----------+-------------------------------------+------------| +| Function | setExecutedBatches(uint256) | 0x59890bcb | ++----------+-------------------------------------+------------+ + +PriorityQueueTest ++----------+------------------------------------+------------+ +| Type | Signature | Selector | ++============================================================+ +| Function | front() | 0xba75bbd8 | +|----------+------------------------------------+------------| +| Function | getFirstUnprocessedPriorityTx() | 0x79823c9a | +|----------+------------------------------------+------------| +| Function | getSize() | 0xde8fa431 | +|----------+------------------------------------+------------| +| Function | getTotalPriorityTxs() | 0xa1954fc5 | +|----------+------------------------------------+------------| +| Function | isEmpty() | 0x681fe70c | +|----------+------------------------------------+------------| +| Function | popFront() | 0x84d9fedd | +|----------+------------------------------------+------------| +| Function | pushBack((bytes32,uint64,uint192)) | 0x75fe6a99 | +|----------+------------------------------------+------------| +| Error | QueueIsEmpty() | 0x63c36549 | ++----------+------------------------------------+------------+ + +PriorityTreeTest ++----------+---------------------------------------------------------+------------+ +| Type | Signature | Selector | ++=================================================================================+ +| Function | getCommitment() | 0x2a79c611 | +|----------+---------------------------------------------------------+------------| +| Function | getFirstUnprocessedPriorityTx() | 0x79823c9a | +|----------+---------------------------------------------------------+------------| +| Function | getRoot() | 0x5ca1e165 | +|----------+---------------------------------------------------------+------------| +| Function | getSize() | 0xde8fa431 | +|----------+---------------------------------------------------------+------------| +| Function | getTotalPriorityTxs() | 0xa1954fc5 | +|----------+---------------------------------------------------------+------------| +| Function | getZero() | 0x9f3f89dc | +|----------+---------------------------------------------------------+------------| +| Function | initFromCommitment((uint256,uint256,uint256,bytes32[])) | 0x4636d6f9 | +|----------+---------------------------------------------------------+------------| +| Function | processBatch((bytes32[],bytes32[],bytes32[])) | 0xc9daf4b1 | +|----------+---------------------------------------------------------+------------| +| Function | push(bytes32) | 0xb298e36b | +|----------+---------------------------------------------------------+------------| +| Error | MerklePathEmpty() | 0x8e23ac1a | +|----------+---------------------------------------------------------+------------| +| Error | MerklePathOutOfBounds() | 0x1c500385 | ++----------+---------------------------------------------------------+------------+ + +TestExecutor ++----------+-----------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++===========================================================================================================================================+ +| Function | commitBatchesSharedBridge(uint256,uint256,uint256,bytes) | 0x98f81962 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | executeBatchesSharedBridge(uint256,uint256,uint256,bytes) | 0xcf02827d | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | getName() | 0x17d7de7c | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveBatchesSharedBridge(uint256,uint256,uint256,bytes) | 0xe12a6137 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | revertBatchesSharedBridge(uint256,uint256) | 0x0f23da43 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPriorityTreeStartIndex(uint256) | 0xa9b0d128 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlockCommit(uint256,bytes32,bytes32) | 0x8f2916b2f2d78cc5890ead36c06c0f6d5d112c7e103589947e8e2f0d6eddb763 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlockExecution(uint256,bytes32,bytes32) | 0x2402307311a4d6604e4e7b4c8a15a7e1213edb39c16a31efa70afb06030d3165 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlocksRevert(uint256,uint256,uint256) | 0x8bd4b15ea7d1bc41ea9abc3fc487ccb89cd678a00786584714faa9d751c84ee5 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlocksVerification(uint256,uint256) | 0x22c9005dd88c18b552a1cd7e8b3b937fcde9ca69213c1f658f54d572e4877a81 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | BatchHashMismatch(bytes32,bytes32) | 0x55ad3fd3 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | BatchNumberMismatch(uint256,uint256) | 0xbd4455ff | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | CanOnlyProcessOneBatch() | 0xe85392f9 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | CantExecuteUnprovenBatches() | 0x00c6ead2 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | CantRevertExecutedBatch() | 0xe18cb383 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | EmptyData() | 0x99d8fec9 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | HashMismatch(bytes32,bytes32) | 0x0b08d5be | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | IncorrectBatchBounds(uint256,uint256,uint256,uint256) | 0xd7d93e1f | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidLogSender(address,uint256) | 0xc1780bd6 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidNumberOfBlobs(uint256,uint256,uint256) | 0xd8e9405c | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidProof() | 0x09bde339 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidProtocolVersion() | 0x5428eae7 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | L2TimestampTooBig() | 0xfb5c22e6 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | LogAlreadyProcessed(uint8) | 0x1b6825bb | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | MerklePathEmpty() | 0x8e23ac1a | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | MerklePathOutOfBounds() | 0x1c500385 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | MissingSystemLogs(uint256,uint256) | 0xfa44b527 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | NonIncreasingTimestamp() | 0xd018e08e | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | NonSequentialBatch() | 0x0105f9c0 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | NotInitializedReentrancyGuard() | 0xdd7e3621 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | PriorityOperationsRollingHashMismatch() | 0xd5a99014 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | QueueIsEmpty() | 0x63c36549 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | Reentrancy() | 0xab143c06 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | RevertedBatchNotAfterNewLastBatch() | 0x9a67c1cb | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | SystemLogsSizeTooBig() | 0xae43b424 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | TimeNotReached(uint256,uint256) | 0x08753982 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | TimestampError() | 0x2d50c33b | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | TxHashMismatch() | 0x4c991078 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnexpectedSystemLog(uint256) | 0x6aa39880 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnsupportedCommitBatchEncoding(uint8) | 0xf3dd1b9c | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnsupportedExecuteBatchEncoding(uint8) | 0x14d2ed8a | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnsupportedProofBatchEncoding(uint8) | 0xf338f830 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | UpgradeBatchNumberIsNotZero() | 0xf093c2e5 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | ValueMismatch(uint256,uint256) | 0x626ade30 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | VerifiedBatchesExceedsCommittedBatches() | 0xe1022469 | ++----------+-----------------------------------------------------------+--------------------------------------------------------------------+ + +AccessControlRestriction ++----------+-------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=================================================================================================================================+ +| Function | DEFAULT_ADMIN_ROLE() | 0xa217fddf | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptDefaultAdminTransfer() | 0xcefc1429 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | beginDefaultAdminTransfer(address) | 0x634e93da | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | cancelDefaultAdminTransfer() | 0xd602b9fd | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | changeDefaultAdminDelay(uint48) | 0x649a5ec7 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | defaultAdmin() | 0x84ef8ffc | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | defaultAdminDelay() | 0xcc8463c8 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | defaultAdminDelayIncreaseWait() | 0x022d63fb | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | getRoleAdmin(bytes32) | 0x248a9ca3 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | getSupportsRestrictionMagic() | 0x83e866f5 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | grantRole(bytes32,address) | 0x2f2ff15d | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | hasRole(bytes32,address) | 0x91d14854 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | owner() | 0x8da5cb5b | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | pendingDefaultAdmin() | 0xcf6eefb7 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | pendingDefaultAdminDelay() | 0xa1eda53c | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | renounceRole(bytes32,address) | 0x36568abe | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | requiredRoles(address,bytes4) | 0x3f620077 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | requiredRolesForFallback(address) | 0x92c25350 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | revokeRole(bytes32,address) | 0xd547741f | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | rollbackDefaultAdminDelay() | 0x0aa6220b | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | setRequiredRoleForCall(address,bytes4,bytes32) | 0xb915e405 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | setRequiredRoleForFallback(address,bytes32) | 0x2de34186 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | supportsInterface(bytes4) | 0x01ffc9a7 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Function | validateCall((address,uint256,bytes),address) | 0x9a9debe9 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Event | DefaultAdminDelayChangeCanceled() | 0x2b1fa2edafe6f7b9e97c1a9e0c3660e645beb2dcaa2d45bdbf9beaf5472e1ec5 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Event | DefaultAdminDelayChangeScheduled(uint48,uint48) | 0xf1038c18cf84a56e432fdbfaf746924b7ea511dfe03a6506a0ceba4888788d9b | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Event | DefaultAdminTransferCanceled() | 0x8886ebfc4259abdbc16601dd8fb5678e54878f47b3c34836cfc51154a9605109 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Event | DefaultAdminTransferScheduled(address,uint48) | 0x3377dc44241e779dd06afab5b788a35ca5f3b778836e2990bdb26a2a4b2e5ed6 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Event | FallbackRoleSet(address,bytes32) | 0xacb22a6b6e4593545bea6f5cdd10238b8a592aba079d9cfc76adb71edf02d40b | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Event | RoleAdminChanged(bytes32,bytes32,bytes32) | 0xbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Event | RoleGranted(bytes32,address,address) | 0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Event | RoleRevoked(bytes32,address,address) | 0xf6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Event | RoleSet(address,bytes4,bytes32) | 0x25c1ec7629ef447de39798ef5e6e0da89139b2257681441b45f1779c3efda036 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Error | AccessToFallbackDenied(address,address) | 0x5ecf2d7a | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Error | AccessToFunctionDenied(address,bytes4,address) | 0x3995f750 | +|----------+-------------------------------------------------+--------------------------------------------------------------------| +| Error | ZeroAddress() | 0xd92e233d | ++----------+-------------------------------------------------+--------------------------------------------------------------------+ + +ChainAdmin ++----------+--------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++==================================================================================================================================+ +| Function | addRestriction(address) | 0x33163987 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Function | getRestrictions() | 0xc4195cb8 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Function | isRestrictionActive(address) | 0x02f0277d | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Function | multicall((address,uint256,bytes)[],bool) | 0x69340beb | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Function | protocolVersionToUpgradeTimestamp(uint256) | 0xede25608 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Function | removeRestriction(address) | 0x69a5b950 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Function | setUpgradeTimestamp(uint256,uint256) | 0xe2a9d554 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Event | CallExecuted((address,uint256,bytes),bool,bytes) | 0x157c677a40c50f832f816d7b59c8c3e94774acae328c8ccb145b73aea7566d75 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Event | RestrictionAdded(address) | 0x83cfa0dec28fa91596ce8081b6279e7d1c402d3d4bc40934fc51f8830e7d82c6 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Event | RestrictionRemoved(address) | 0xdf4f1ec932dcab3c66fb7845ba6fc669816121e5d4a81d6955d3c6d3bff7b7e9 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Event | UpdateUpgradeTimestamp(uint256,uint256) | 0xd50ef21701c8ef211433b140724b8d6de471e7d822c8a616c3d424fe2d0e98a9 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Error | NoCallsProvided() | 0x79cc2d22 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Error | NotARestriction(address) | 0x64846fe4 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Error | NotInitializedReentrancyGuard() | 0xdd7e3621 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Error | OnlySelfAllowed() | 0x6c167909 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Error | Reentrancy() | 0xab143c06 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Error | RestrictionWasAlreadyPresent(address) | 0xf126e113 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Error | RestrictionWasNotPresent(address) | 0x52e22c98 | +|----------+--------------------------------------------------+--------------------------------------------------------------------| +| Error | SlotOccupied() | 0xdf3a8fdd | ++----------+--------------------------------------------------+--------------------------------------------------------------------+ + +Governance ++----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++============================================================================================================================================================================+ +| Function | acceptOwnership() | 0x79ba5097 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | cancel(bytes32) | 0xc4d252f5 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | execute(((address,uint256,bytes)[],bytes32,bytes32)) | 0x74da756b | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | executeInstant(((address,uint256,bytes)[],bytes32,bytes32)) | 0x95218ecd | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getOperationState(bytes32) | 0x7958004c | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | hashOperation(((address,uint256,bytes)[],bytes32,bytes32)) | 0xc126e860 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isOperation(bytes32) | 0x31d50750 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isOperationDone(bytes32) | 0x2ab0f529 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isOperationPending(bytes32) | 0x584b153e | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isOperationReady(bytes32) | 0x13bc9f20 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | minDelay() | 0xc63c4e9b | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | owner() | 0x8da5cb5b | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pendingOwner() | 0xe30c3978 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | renounceOwnership() | 0x715018a6 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | scheduleShadow(bytes32,uint256) | 0x6d1d8363 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | scheduleTransparent(((address,uint256,bytes)[],bytes32,bytes32),uint256) | 0x2c431917 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | securityCouncil() | 0x27eb6c0f | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | timestamps(bytes32) | 0xb5872958 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferOwnership(address) | 0xf2fde38b | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | updateDelay(uint256) | 0x64d62353 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | updateSecurityCouncil(address) | 0xdbfe3e96 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ChangeMinDelay(uint256,uint256) | 0x0c5ff76c31d24175d9e84ef46e328eafbcaeb2aa67a2333035eb082dd34324f1 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ChangeSecurityCouncil(address,address) | 0xe55ac8ae7914efca1278b8ed4ae21728d06ad9f6e7637bb2c905aacf2a6f5951 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OperationCancelled(bytes32) | 0xcf0f63b97f3387253cbc0bde884f975df77e39184dc3280c2c81be495f58eef4 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OperationExecuted(bytes32) | 0x1277662f4b42b8a4069e99fb5e41ce8919d3c621156090ac08fb11adbcec66f9 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ShadowOperationScheduled(bytes32,uint256) | 0xbcb40fd953364ec8aed99fa0bd6dcc03103f979efde4744ad7452e556ff20ba6 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | TransparentOperationScheduled(bytes32,uint256,((address,uint256,bytes)[],bytes32,bytes32)) | 0x23bc9f5dc037eb49c162fd08c2a4d43dfe70063149e140d502273168da0a0625 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidDelay() | 0x4fbe5dba | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | OperationExists() | 0x1a21feed | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | OperationMustBePending() | 0xeda2fbb1 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | OperationMustBeReady() | 0xe1c1ff37 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PreviousOperationNotExecuted() | 0x9b48e060 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ZeroAddress() | 0xd92e233d | ++----------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +L2AdminFactory ++----------+--------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++================================================================================================================+ +| Function | deployAdmin(address[],bytes32) | 0x8f466729 | +|----------+--------------------------------+--------------------------------------------------------------------| +| Function | requiredRestrictions(uint256) | 0x63cf72b8 | +|----------+--------------------------------+--------------------------------------------------------------------| +| Event | AdminDeployed(address) | 0xc2be628060b55b6da5404f7ce7a00e0996aa76a0a6447b084fdc5af1b056cb40 | +|----------+--------------------------------+--------------------------------------------------------------------| +| Error | NotARestriction(address) | 0x64846fe4 | ++----------+--------------------------------+--------------------------------------------------------------------+ + +PermanentRestriction ++----------+-----------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++===============================================================================================================================+ +| Function | BRIDGE_HUB() | 0x5d4edca7 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | L2_ADMIN_FACTORY() | 0x8761cf4b | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | acceptOwnership() | 0x79ba5097 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | allowAdminImplementation(bytes32,bool) | 0xce21e9cd | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | allowL2Admin(bytes32,bytes32,bytes32) | 0xa1603458 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | allowedAdminImplementations(bytes32) | 0x1b808213 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | allowedCalls(bytes) | 0x5c2bdd21 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | allowedL2Admins(address) | 0xac1cb217 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | getSupportsRestrictionMagic() | 0x83e866f5 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | initialize(address) | 0xc4d66de8 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | owner() | 0x8da5cb5b | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | pendingOwner() | 0xe30c3978 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | renounceOwnership() | 0x715018a6 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | setAllowedData(bytes,bool) | 0xdbaabcfd | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | setSelectorIsValidated(bytes4,bool) | 0x636ccb60 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | transferOwnership(address) | 0xf2fde38b | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | validateCall((address,uint256,bytes),address) | 0x9a9debe9 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Function | validatedSelectors(bytes4) | 0x430d2d36 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Event | AdminImplementationAllowed(bytes32,bool) | 0xb9b1bc97b8d56d15ac2abdd71d788b0d6e14dc92fa9b8df811d0a2f41c14354a | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Event | AllowL2Admin(address) | 0x43472806f9101fbc243f5a64948e56332b1d18bb082e7d4772a29368eb80991e | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Event | AllowedDataChanged(bytes,bool) | 0xbb577d7ca5995b00ceaae0c75f77ab5df42a1e24673e2e36858ee718f3f1eb8c | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Event | Initialized(uint8) | 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Event | SelectorValidationChanged(bytes4,bool) | 0x85198d5146ae402b94f01f31a2049f75a6ecadc1d321090cd97467979b8d09d7 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Error | AlreadyWhitelisted(address) | 0x0bfcef28 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Error | CallNotAllowed(bytes) | 0x3331e9c0 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Error | NotAllowed(address) | 0xfa5cd00f | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Error | RemovingPermanentRestriction() | 0xf6fd7071 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Error | UnallowedImplementation(bytes32) | 0xfcb9b2e1 | +|----------+-----------------------------------------------+--------------------------------------------------------------------| +| Error | ZeroAddress() | 0xd92e233d | ++----------+-----------------------------------------------+--------------------------------------------------------------------+ + +ChainTypeManager ++----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++================================================================================================================================================================================================================================================+ +| Function | BRIDGE_HUB() | 0x5d4edca7 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptAdmin() | 0x0e18b681 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptOwnership() | 0x79ba5097 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | admin() | 0xf851a440 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | changeFeeParams(uint256,(uint8,uint32,uint32,uint32,uint32,uint64)) | 0x027f12e1 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | createNewChain(uint256,bytes32,address,bytes,bytes[]) | 0x88c7c5d2 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | executeUpgrade(uint256,((address,uint8,bool,bytes4[])[],address,bytes)) | 0xe34a329a | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeBurn(uint256,bytes) | 0xf85894c5 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeMint(uint256,bytes) | 0xe8a71ca9 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeRecoverFailedTransfer(uint256,bytes32,address,bytes) | 0xb7846107 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | freezeChain(uint256) | 0xaccdd16c | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getChainAdmin(uint256) | 0x301e7765 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getHyperchain(uint256) | 0xdead6f7f | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getProtocolVersion(uint256) | 0xba238947 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getSemverProtocolVersion() | 0xf5c1182c | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getZKChain(uint256) | 0xe680c4c1 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getZKChainLegacy(uint256) | 0x4caa740f | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialCutHash() | 0x57e6246b | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialForceDeploymentHash() | 0x61f91b2e | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialize((address,address,(address,bytes32,uint64,bytes32,((address,uint8,bool,bytes4[])[],address,bytes),bytes),uint256)) | 0xce7df3d6 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l1GenesisUpgrade() | 0x3437949a | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | owner() | 0x8da5cb5b | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pendingOwner() | 0xe30c3978 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | protocolVersion() | 0x2ae9c600 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | protocolVersionDeadline(uint256) | 0xf4943a20 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | protocolVersionIsActive(uint256) | 0xdef9d6af | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | registerSettlementLayer(uint256,bool) | 0xdc8e4b26 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | renounceOwnership() | 0x715018a6 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | revertBatches(uint256,uint256) | 0x53ce2061 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setChainCreationParams((address,bytes32,uint64,bytes32,((address,uint8,bool,bytes4[])[],address,bytes),bytes)) | 0x9b016b8b | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setNewVersionUpgrade(((address,uint8,bool,bytes4[])[],address,bytes),uint256,uint256,uint256) | 0x2e522851 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPendingAdmin(address) | 0x4dd18bf5 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPorterAvailability(uint256,bool) | 0x18717dc1 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPriorityTxMaxGasLimit(uint256,uint256) | 0xec3d5f88 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setProtocolVersionDeadline(uint256,uint256) | 0xaad74262 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setTokenMultiplier(uint256,uint128,uint128) | 0x7ebba672 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setUpgradeDiamondCut(((address,uint8,bool,bytes4[])[],address,bytes),uint256) | 0x98461504 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setValidator(uint256,address,bool) | 0xcf347e17 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setValidatorTimelock(address) | 0x7fb67816 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | storedBatchZero() | 0xd2ef1b0e | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferOwnership(address) | 0xf2fde38b | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | unfreezeChain(uint256) | 0x51d218f7 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | upgradeChainFromVersion(uint256,uint256,((address,uint8,bool,bytes4[])[],address,bytes)) | 0x0dbad27e | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | upgradeCutHash(uint256) | 0x52c9eacb | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | validatorTimelock() | 0xe66c8c44 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeInitialize(address,string,string,uint8) | 0x81e8e92e5873539605a102eddae7ed06d19bea042099a437cbc3644415eb7404 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | GenesisUpgrade(address,(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),uint256) | 0x778b7f95aad9610955002f243aabbbf9546bb6e792f7b3b89dab86b4f210e30f | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Initialized(uint8) | 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewAdmin(address,address) | 0xf9ffabca9c8276e99321725bcb43fb076a6c66a54b7f21c4e8146d8519b417dc | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewChainCreationParams(address,bytes32,uint64,bytes32,bytes32,bytes32) | 0x04b363b4a0200ada216b1cb4aaf2736ff6f332d5f1d90f98e60b1159f3dac3aa | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewPendingAdmin(address,address) | 0xca4f2f25d0898edd99413412fb94012f9e54ec8142f9b093e7720646a95b16a9 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewProtocolVersion(uint256,uint256) | 0x4235104f56661fe2e9d2f2a460b42766581bc45ce366c6a30a9f86c8a2b371a7 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewUpgradeCutData(uint256,((address,uint8,bool,bytes4[])[],address,bytes)) | 0xf99295383247eabb6bee8798669fa768502f8843d3be0e82a0aa81d7b6c4f60c | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewUpgradeCutHash(uint256,bytes32) | 0x71b0aeaf8eaa06ed78ccb9a4981da026eea05ca1d818c22dd120446db4c936d4 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewValidatorTimelock(address,address) | 0x5a1b0d8808a8dca64c1f7c230dce7a09f7f9a1c26507e190e03dcd382e69018e | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewZKChain(uint256,address) | 0xf83c256407747903308213919067f883f683c5cde6a64ebbf25096b8bb555ddc | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | GenesisBatchCommitmentZero() | 0x6d4a7df8 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | GenesisBatchHashZero() | 0x7940c83f | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | GenesisIndexStorageZero() | 0xb4fc6835 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | GenesisUpgradeZero() | 0x3a1a8589 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | HashMismatch(bytes32,bytes32) | 0x0b08d5be | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SlotOccupied() | 0xdf3a8fdd | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ZeroAddress() | 0xd92e233d | ++----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +IChainTypeManager ++----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++================================================================================================================================================================================================================================================+ +| Function | BRIDGE_HUB() | 0x5d4edca7 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptAdmin() | 0x0e18b681 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | changeFeeParams(uint256,(uint8,uint32,uint32,uint32,uint32,uint64)) | 0x027f12e1 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | createNewChain(uint256,bytes32,address,bytes,bytes[]) | 0x88c7c5d2 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | executeUpgrade(uint256,((address,uint8,bool,bytes4[])[],address,bytes)) | 0xe34a329a | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeBurn(uint256,bytes) | 0xf85894c5 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeMint(uint256,bytes) | 0xe8a71ca9 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeRecoverFailedTransfer(uint256,bytes32,address,bytes) | 0xb7846107 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | freezeChain(uint256) | 0xaccdd16c | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getChainAdmin(uint256) | 0x301e7765 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getProtocolVersion(uint256) | 0xba238947 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getSemverProtocolVersion() | 0xf5c1182c | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getZKChain(uint256) | 0xe680c4c1 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getZKChainLegacy(uint256) | 0x4caa740f | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialCutHash() | 0x57e6246b | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialize((address,address,(address,bytes32,uint64,bytes32,((address,uint8,bool,bytes4[])[],address,bytes),bytes),uint256)) | 0xce7df3d6 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l1GenesisUpgrade() | 0x3437949a | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | protocolVersion() | 0x2ae9c600 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | protocolVersionDeadline(uint256) | 0xf4943a20 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | protocolVersionIsActive(uint256) | 0xdef9d6af | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | registerSettlementLayer(uint256,bool) | 0xdc8e4b26 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setChainCreationParams((address,bytes32,uint64,bytes32,((address,uint8,bool,bytes4[])[],address,bytes),bytes)) | 0x9b016b8b | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setNewVersionUpgrade(((address,uint8,bool,bytes4[])[],address,bytes),uint256,uint256,uint256) | 0x2e522851 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPendingAdmin(address) | 0x4dd18bf5 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPorterAvailability(uint256,bool) | 0x18717dc1 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPriorityTxMaxGasLimit(uint256,uint256) | 0xec3d5f88 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setTokenMultiplier(uint256,uint128,uint128) | 0x7ebba672 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setUpgradeDiamondCut(((address,uint8,bool,bytes4[])[],address,bytes),uint256) | 0x98461504 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setValidator(uint256,address,bool) | 0xcf347e17 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setValidatorTimelock(address) | 0x7fb67816 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | storedBatchZero() | 0xd2ef1b0e | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | unfreezeChain(uint256) | 0x51d218f7 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | upgradeChainFromVersion(uint256,uint256,((address,uint8,bool,bytes4[])[],address,bytes)) | 0x0dbad27e | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | upgradeCutHash(uint256) | 0x52c9eacb | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeInitialize(address,string,string,uint8) | 0x81e8e92e5873539605a102eddae7ed06d19bea042099a437cbc3644415eb7404 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | GenesisUpgrade(address,(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),uint256) | 0x778b7f95aad9610955002f243aabbbf9546bb6e792f7b3b89dab86b4f210e30f | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewAdmin(address,address) | 0xf9ffabca9c8276e99321725bcb43fb076a6c66a54b7f21c4e8146d8519b417dc | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewChainCreationParams(address,bytes32,uint64,bytes32,bytes32,bytes32) | 0x04b363b4a0200ada216b1cb4aaf2736ff6f332d5f1d90f98e60b1159f3dac3aa | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewPendingAdmin(address,address) | 0xca4f2f25d0898edd99413412fb94012f9e54ec8142f9b093e7720646a95b16a9 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewProtocolVersion(uint256,uint256) | 0x4235104f56661fe2e9d2f2a460b42766581bc45ce366c6a30a9f86c8a2b371a7 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewUpgradeCutData(uint256,((address,uint8,bool,bytes4[])[],address,bytes)) | 0xf99295383247eabb6bee8798669fa768502f8843d3be0e82a0aa81d7b6c4f60c | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewUpgradeCutHash(uint256,bytes32) | 0x71b0aeaf8eaa06ed78ccb9a4981da026eea05ca1d818c22dd120446db4c936d4 | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewValidatorTimelock(address,address) | 0x5a1b0d8808a8dca64c1f7c230dce7a09f7f9a1c26507e190e03dcd382e69018e | +|----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewZKChain(uint256,address) | 0xf83c256407747903308213919067f883f683c5cde6a64ebbf25096b8bb555ddc | ++----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +ValidatorTimelock ++----------+-----------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++===========================================================================================================================================+ +| Function | acceptOwnership() | 0x79ba5097 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | addValidator(uint256,address) | 0x4b561753 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | chainTypeManager() | 0xc0e42a5c | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | commitBatchesSharedBridge(uint256,uint256,uint256,bytes) | 0x98f81962 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | executeBatchesSharedBridge(uint256,uint256,uint256,bytes) | 0xcf02827d | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | executionDelay() | 0x8b257989 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | getCommittedBatchTimestamp(uint256,uint256) | 0xb993549e | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | getName() | 0x17d7de7c | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | owner() | 0x8da5cb5b | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | pendingOwner() | 0xe30c3978 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveBatchesSharedBridge(uint256,uint256,uint256,bytes) | 0xe12a6137 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | removeValidator(uint256,address) | 0x6a0cd1f5 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | renounceOwnership() | 0x715018a6 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | revertBatchesSharedBridge(uint256,uint256) | 0x0f23da43 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | setChainTypeManager(address) | 0xbd5bcf93 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | setExecutionDelay(uint32) | 0xf34d1868 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferOwnership(address) | 0xf2fde38b | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | validators(uint256,address) | 0x91b19874 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlockCommit(uint256,bytes32,bytes32) | 0x8f2916b2f2d78cc5890ead36c06c0f6d5d112c7e103589947e8e2f0d6eddb763 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlockExecution(uint256,bytes32,bytes32) | 0x2402307311a4d6604e4e7b4c8a15a7e1213edb39c16a31efa70afb06030d3165 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlocksRevert(uint256,uint256,uint256) | 0x8bd4b15ea7d1bc41ea9abc3fc487ccb89cd678a00786584714faa9d751c84ee5 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlocksVerification(uint256,uint256) | 0x22c9005dd88c18b552a1cd7e8b3b937fcde9ca69213c1f658f54d572e4877a81 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewExecutionDelay(uint256) | 0xd32d6d626bb9c7077c559fc3b4e5ce71ef14609d7d216d030ee63dcf2422c2c4 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Event | ValidatorAdded(uint256,address) | 0x7429a06e9412e469f0d64f9d222640b0af359f556b709e2913588c227851b88d | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Event | ValidatorRemoved(uint256,address) | 0x7126bef88d1149ccdff9681ed5aecd3ba5ae70c96517551de250af09cebd1a0b | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | AddressAlreadyValidator(uint256) | 0x004aef8a | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | TimeNotReached(uint256,uint256) | 0x08753982 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | ValidatorDoesNotExist(uint256) | 0x5fd1e44b | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | ZeroAddress() | 0xd92e233d | ++----------+-----------------------------------------------------------+--------------------------------------------------------------------+ + +DiamondInit ++----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+ +| Type | Signature | Selector | ++====================================================================================================================================================================================================================+ +| Function | initialize((uint256,address,address,uint256,address,address,bytes32,bytes32,address,(bytes32,bytes32,bytes32),bytes32,bytes32,uint256,(uint8,uint32,uint32,uint32,uint32,uint64),address)) | 0xadad4b1b | +|----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| Error | SlotOccupied() | 0xdf3a8fdd | +|----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| Error | TooMuchGas() | 0xf0b4e88f | +|----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| Error | ZeroAddress() | 0xd92e233d | ++----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+ + +DiamondProxy ++-------+-----------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++========================================================================================================================================+ +| Event | DiamondCut((address,uint8,bool,bytes4[])[],address,bytes) | 0x87b829356b3403d36217eff1f66ee48eacd0a69015153aba4f0de29fe5340c30 | +|-------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | AddressHasNoCode(address) | 0x86bb51b8 | +|-------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | DelegateCallFailed(bytes) | 0xf7a01e4d | +|-------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | FacetExists(bytes4,address) | 0xac4a3f98 | +|-------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | NoFunctionsForDiamondCut() | 0xa6fef710 | +|-------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | NonEmptyCalldata() | 0xc21b1ab7 | +|-------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | RemoveFunctionFacetAddressNotZero(address) | 0x667d17de | +|-------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | RemoveFunctionFacetAddressZero() | 0xa2d4b16c | +|-------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | ReplaceFunctionFacetAddressZero() | 0x3580370c | +|-------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | SelectorsMustAllHaveSameFreezability() | 0xd3b6535b | +|-------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | UndefinedDiamondCutAction() | 0xe52478c7 | ++-------+-----------------------------------------------------------+--------------------------------------------------------------------+ + +AdminFacet ++----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=====================================================================================================================================================================================+ +| Function | acceptAdmin() | 0x0e18b681 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | changeFeeParams((uint8,uint32,uint32,uint32,uint32,uint64)) | 0x64bf8d66 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | executeUpgrade(((address,uint8,bool,bytes4[])[],address,bytes)) | 0xa9f6d941 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeBurn(address,address,bytes) | 0x64b554ad | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeMint(bytes,bool) | 0x3f42d5dd | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeRecoverFailedTransfer(uint256,bytes32,address,bytes) | 0xb7846107 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | freezeDiamond() | 0x27ae4c16 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | genesisUpgrade(address,address,bytes,bytes[]) | 0x2878fe74 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getName() | 0x17d7de7c | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | prepareChainCommitment() | 0x41cf49bb | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setDAValidatorPair(address,address) | 0x6223258e | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPendingAdmin(address) | 0x4dd18bf5 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPorterAvailability(bool) | 0x1cc5d103 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPriorityTxMaxGasLimit(uint256) | 0xbe6f11cf | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPubdataPricingMode(uint8) | 0xe76db865 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setTokenMultiplier(uint128,uint128) | 0x235d9eb5 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setTransactionFilterer(address) | 0x21f603d7 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setValidator(address,bool) | 0x4623c91d | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | unfreezeDiamond() | 0x17338945 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | upgradeChainFromVersion(uint256,((address,uint8,bool,bytes4[])[],address,bytes)) | 0xfc57565f | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeInitialize(address,string,string,uint8) | 0x81e8e92e5873539605a102eddae7ed06d19bea042099a437cbc3644415eb7404 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeMint(address,uint256) | 0x397b33b307fc137878ebfc75b295289ec0ee25a31bb5bf034f33256fe8ea2aa6 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | DiamondCut((address,uint8,bool,bytes4[])[],address,bytes) | 0x87b829356b3403d36217eff1f66ee48eacd0a69015153aba4f0de29fe5340c30 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ExecuteUpgrade(((address,uint8,bool,bytes4[])[],address,bytes)) | 0x1dabfc3f4f6a4e74e19be10cc9d1d8e23db03e415d5d3547a1a7d5eb766513ba | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Freeze() | 0x615acbaede366d76a8b8cb2a9ada6a71495f0786513d71aa97aaf0c3910b78de | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | IsPorterAvailableStatusUpdate(bool) | 0x036b81a8a07344698cb5aa4142c5669a9317c9ce905264a08f0b9f9331883936 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | MigrationComplete() | 0x96e718f44bd77cb63370212c5aa24a0396d8f43e88e7ce175d160e371c8e2a6a | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewAdmin(address,address) | 0xf9ffabca9c8276e99321725bcb43fb076a6c66a54b7f21c4e8146d8519b417dc | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewBaseTokenMultiplier(uint128,uint128,uint128,uint128) | 0xc9cbdb110bd58a133e82c62b1488e57677be1f326df10a4d8096b5f170c370c8 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewFeeParams((uint8,uint32,uint32,uint32,uint32,uint64),(uint8,uint32,uint32,uint32,uint32,uint64)) | 0xc8b245ac8b138b17b6b1dbbbb8860adc66b373afa000d99f3cdc775d8ae0bbed | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewL1DAValidator(address,address) | 0x08b0b676d456a0431162145d2821f30c665b69ca626521c937ba7a77a29ed67c | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewL2DAValidator(address,address) | 0x866a71b85fb8625f74adc67901361962f0aa3730a1615400911ae610d65dc130 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewPendingAdmin(address,address) | 0xca4f2f25d0898edd99413412fb94012f9e54ec8142f9b093e7720646a95b16a9 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewPriorityTxMaxGasLimit(uint256,uint256) | 0x83dd728f7e76a849126c55ffabdc6e299eb8c85dccf12498701376d9f5c954d1 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewTransactionFilterer(address,address) | 0xa9b43a66c5d1c607986f49e932a0c08058d843210db03dd6e8a621b96b4770f4 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Unfreeze() | 0x2f05ba71d0df11bf5fa562a6569d70c4f80da84284badbe015ce1456063d0ded | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ValidatorStatusUpdate(address,bool) | 0x065b77b53864e46fda3d8986acb51696223d6dde7ced42441eb150bae6d48136 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | PubdataPricingModeUpdate(uint8) | 0xaa01146df0a628c6b214e79a281f7524b792de4a52ad6f07c78e6e035d58c896 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AddressHasNoCode(address) | 0x86bb51b8 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ChainAlreadyLive() | 0x78d2ed02 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | DelegateCallFailed(bytes) | 0xf7a01e4d | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | DenominatorIsZero() | 0x0a8ed92c | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | DiamondAlreadyFrozen() | 0x0e7ee319 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | DiamondNotFrozen() | 0xa7151b9a | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | FacetExists(bytes4,address) | 0xac4a3f98 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | HashMismatch(bytes32,bytes32) | 0x0b08d5be | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidPubdataPricingMode() | 0x6f1cf752 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NoFunctionsForDiamondCut() | 0xa6fef710 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NonEmptyCalldata() | 0xc21b1ab7 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PriorityTxPubdataExceedsMaxPubDataPerBatch() | 0x1a4d284a | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ProtocolIdMismatch(uint256,uint256) | 0xa461f651 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ProtocolIdNotGreater() | 0x64f94ec2 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | RemoveFunctionFacetAddressNotZero(address) | 0x667d17de | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | RemoveFunctionFacetAddressZero() | 0xa2d4b16c | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ReplaceFunctionFacetAddressZero() | 0x3580370c | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SelectorsMustAllHaveSameFreezability() | 0xd3b6535b | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TooMuchGas() | 0xf0b4e88f | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | UndefinedDiamondCutAction() | 0xe52478c7 | ++----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +ExecutorFacet ++----------+-----------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++===========================================================================================================================================+ +| Function | commitBatchesSharedBridge(uint256,uint256,uint256,bytes) | 0x98f81962 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | executeBatchesSharedBridge(uint256,uint256,uint256,bytes) | 0xcf02827d | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | getName() | 0x17d7de7c | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveBatchesSharedBridge(uint256,uint256,uint256,bytes) | 0xe12a6137 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Function | revertBatchesSharedBridge(uint256,uint256) | 0x0f23da43 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlockCommit(uint256,bytes32,bytes32) | 0x8f2916b2f2d78cc5890ead36c06c0f6d5d112c7e103589947e8e2f0d6eddb763 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlockExecution(uint256,bytes32,bytes32) | 0x2402307311a4d6604e4e7b4c8a15a7e1213edb39c16a31efa70afb06030d3165 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlocksRevert(uint256,uint256,uint256) | 0x8bd4b15ea7d1bc41ea9abc3fc487ccb89cd678a00786584714faa9d751c84ee5 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlocksVerification(uint256,uint256) | 0x22c9005dd88c18b552a1cd7e8b3b937fcde9ca69213c1f658f54d572e4877a81 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | BatchHashMismatch(bytes32,bytes32) | 0x55ad3fd3 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | BatchNumberMismatch(uint256,uint256) | 0xbd4455ff | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | CanOnlyProcessOneBatch() | 0xe85392f9 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | CantExecuteUnprovenBatches() | 0x00c6ead2 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | CantRevertExecutedBatch() | 0xe18cb383 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | EmptyData() | 0x99d8fec9 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | HashMismatch(bytes32,bytes32) | 0x0b08d5be | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | IncorrectBatchBounds(uint256,uint256,uint256,uint256) | 0xd7d93e1f | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidLogSender(address,uint256) | 0xc1780bd6 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidNumberOfBlobs(uint256,uint256,uint256) | 0xd8e9405c | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidProof() | 0x09bde339 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidProtocolVersion() | 0x5428eae7 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | L2TimestampTooBig() | 0xfb5c22e6 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | LogAlreadyProcessed(uint8) | 0x1b6825bb | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | MerklePathEmpty() | 0x8e23ac1a | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | MerklePathOutOfBounds() | 0x1c500385 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | MissingSystemLogs(uint256,uint256) | 0xfa44b527 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | NonIncreasingTimestamp() | 0xd018e08e | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | NonSequentialBatch() | 0x0105f9c0 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | NotInitializedReentrancyGuard() | 0xdd7e3621 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | PriorityOperationsRollingHashMismatch() | 0xd5a99014 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | QueueIsEmpty() | 0x63c36549 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | Reentrancy() | 0xab143c06 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | RevertedBatchNotAfterNewLastBatch() | 0x9a67c1cb | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | SystemLogsSizeTooBig() | 0xae43b424 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | TimeNotReached(uint256,uint256) | 0x08753982 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | TimestampError() | 0x2d50c33b | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | TxHashMismatch() | 0x4c991078 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnexpectedSystemLog(uint256) | 0x6aa39880 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnsupportedCommitBatchEncoding(uint8) | 0xf3dd1b9c | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnsupportedExecuteBatchEncoding(uint8) | 0x14d2ed8a | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnsupportedProofBatchEncoding(uint8) | 0xf338f830 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | UpgradeBatchNumberIsNotZero() | 0xf093c2e5 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | ValueMismatch(uint256,uint256) | 0x626ade30 | +|----------+-----------------------------------------------------------+--------------------------------------------------------------------| +| Error | VerifiedBatchesExceedsCommittedBatches() | 0xe1022469 | ++----------+-----------------------------------------------------------+--------------------------------------------------------------------+ + +GettersFacet ++----------+-------------------------------------------+------------+ +| Type | Signature | Selector | ++===================================================================+ +| Function | baseTokenGasPriceMultiplierDenominator() | 0x1de72e34 | +|----------+-------------------------------------------+------------| +| Function | baseTokenGasPriceMultiplierNominator() | 0xea6c029c | +|----------+-------------------------------------------+------------| +| Function | facetAddress(bytes4) | 0xcdffacc6 | +|----------+-------------------------------------------+------------| +| Function | facetAddresses() | 0x52ef6b2c | +|----------+-------------------------------------------+------------| +| Function | facetFunctionSelectors(address) | 0xadfca15e | +|----------+-------------------------------------------+------------| +| Function | facets() | 0x7a0ed627 | +|----------+-------------------------------------------+------------| +| Function | getAdmin() | 0x6e9960c3 | +|----------+-------------------------------------------+------------| +| Function | getBaseToken() | 0x98acd7a6 | +|----------+-------------------------------------------+------------| +| Function | getBaseTokenAssetId() | 0x960dcf24 | +|----------+-------------------------------------------+------------| +| Function | getBridgehub() | 0x3591c1a0 | +|----------+-------------------------------------------+------------| +| Function | getChainId() | 0x3408e470 | +|----------+-------------------------------------------+------------| +| Function | getChainTypeManager() | 0x946ebad1 | +|----------+-------------------------------------------+------------| +| Function | getDAValidatorPair() | 0x5a590335 | +|----------+-------------------------------------------+------------| +| Function | getFirstUnprocessedPriorityTx() | 0x79823c9a | +|----------+-------------------------------------------+------------| +| Function | getL2BootloaderBytecodeHash() | 0xd86970d8 | +|----------+-------------------------------------------+------------| +| Function | getL2DefaultAccountBytecodeHash() | 0xfd791f3c | +|----------+-------------------------------------------+------------| +| Function | getL2SystemContractsUpgradeBatchNumber() | 0xe5355c75 | +|----------+-------------------------------------------+------------| +| Function | getL2SystemContractsUpgradeBlockNumber() | 0x9d1b5a81 | +|----------+-------------------------------------------+------------| +| Function | getL2SystemContractsUpgradeTxHash() | 0x7b30c8da | +|----------+-------------------------------------------+------------| +| Function | getName() | 0x17d7de7c | +|----------+-------------------------------------------+------------| +| Function | getPendingAdmin() | 0xd0468156 | +|----------+-------------------------------------------+------------| +| Function | getPriorityQueueSize() | 0x631f4bac | +|----------+-------------------------------------------+------------| +| Function | getPriorityTreeRoot() | 0x39d7d4aa | +|----------+-------------------------------------------+------------| +| Function | getPriorityTxMaxGasLimit() | 0x0ec6b0b7 | +|----------+-------------------------------------------+------------| +| Function | getProtocolVersion() | 0x33ce93fe | +|----------+-------------------------------------------+------------| +| Function | getPubdataPricingMode() | 0x06d49e5b | +|----------+-------------------------------------------+------------| +| Function | getSemverProtocolVersion() | 0xf5c1182c | +|----------+-------------------------------------------+------------| +| Function | getSettlementLayer() | 0x6a27e8b5 | +|----------+-------------------------------------------+------------| +| Function | getTotalBatchesCommitted() | 0xdb1f0bf9 | +|----------+-------------------------------------------+------------| +| Function | getTotalBatchesExecuted() | 0xb8c2f66f | +|----------+-------------------------------------------+------------| +| Function | getTotalBatchesVerified() | 0xef3f0bae | +|----------+-------------------------------------------+------------| +| Function | getTotalBlocksCommitted() | 0xfe26699e | +|----------+-------------------------------------------+------------| +| Function | getTotalBlocksExecuted() | 0x39607382 | +|----------+-------------------------------------------+------------| +| Function | getTotalBlocksVerified() | 0xaf6a2dcd | +|----------+-------------------------------------------+------------| +| Function | getTotalPriorityTxs() | 0xa1954fc5 | +|----------+-------------------------------------------+------------| +| Function | getTransactionFilterer() | 0x22c5cf23 | +|----------+-------------------------------------------+------------| +| Function | getVerifier() | 0x46657fe9 | +|----------+-------------------------------------------+------------| +| Function | getVerifierParams() | 0x18e3a941 | +|----------+-------------------------------------------+------------| +| Function | isDiamondStorageFrozen() | 0x29b98c67 | +|----------+-------------------------------------------+------------| +| Function | isEthWithdrawalFinalized(uint256,uint256) | 0xbd7c5412 | +|----------+-------------------------------------------+------------| +| Function | isFacetFreezable(address) | 0xc3bbd2d7 | +|----------+-------------------------------------------+------------| +| Function | isFunctionFreezable(bytes4) | 0xe81e0ba1 | +|----------+-------------------------------------------+------------| +| Function | isValidator(address) | 0xfacd743b | +|----------+-------------------------------------------+------------| +| Function | l2LogsRootHash(uint256) | 0x9cd939e4 | +|----------+-------------------------------------------+------------| +| Function | storedBatchHash(uint256) | 0xb22dd78e | +|----------+-------------------------------------------+------------| +| Function | storedBlockHash(uint256) | 0x74f4d30d | +|----------+-------------------------------------------+------------| +| Error | InvalidSelector(bytes4) | 0x12ba286f | ++----------+-------------------------------------------+------------+ + +MailboxFacet ++----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++===================================================================================================================================================================================================================================================================+ +| Function | bridgehubRequestL2Transaction((address,address,uint256,uint256,bytes,uint256,uint256,bytes[],address)) | 0x12f43dab | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgehubRequestL2TransactionOnGateway(bytes32,uint64) | 0xddcc9eec | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeEthWithdrawal(uint256,uint256,uint16,bytes,bytes32[]) | 0x6c0960f9 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getName() | 0x17d7de7c | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TransactionBaseCost(uint256,uint256,uint256) | 0xb473318e | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL1ToL2TransactionStatus(bytes32,uint256,uint256,uint16,bytes32[],uint8) | 0x042901c7 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL1ToL2TransactionStatusViaGateway(bytes32,uint256,uint256,uint16,bytes32[],uint8) | 0xe717bab7 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL2LeafInclusion(uint256,uint256,bytes32,bytes32[]) | 0x7efda2ae | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL2LogInclusion(uint256,uint256,(uint8,bool,uint16,address,bytes32,bytes32),bytes32[]) | 0x263b7f8e | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL2MessageInclusion(uint256,uint256,(uint16,address,bytes),bytes32[]) | 0xe4948f43 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | requestL2Transaction(address,uint256,bytes,uint256,uint256,bytes[],address) | 0xeb672419 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | requestL2TransactionToGatewayMailbox(uint256,bytes32,uint64) | 0xd0772551 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewPriorityRequest(uint256,bytes32,uint64,(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[]) | 0x4531cd5795773d7101c17bdeb9f5ab7f47d7056017506f937083be5d6e77a382 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewRelayedPriorityTransaction(uint256,bytes32,uint64) | 0x0137d2eaa6ec5b7e4f233f6d6f441410014535d0f3985367994c94bf15a2a564 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | BaseTokenGasPriceDenominatorNotSet() | 0x6ef9a972 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | BatchNotExecuted(uint256) | 0x2078a6a0 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | GasPerPubdataMismatch() | 0xc91cf3b1 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | HashedLogIsDefault() | 0xd356e6ba | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | LengthIsNotDivisibleBy32(uint256) | 0xe37d2c02 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MalformedBytecode(uint8) | 0x43e266b0 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MerkleIndexOutOfBounds() | 0x9bb54c35 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MerklePathEmpty() | 0x8e23ac1a | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MerklePathOutOfBounds() | 0x1c500385 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MsgValueTooLow(uint256,uint256) | 0xb385a3da | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NotInitializedReentrancyGuard() | 0xdd7e3621 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | OnlyEraSupported() | 0xf3ed9dfa | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PubdataGreaterThanLimit(uint256,uint256) | 0x959f26fb | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Reentrancy() | 0xab143c06 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TooManyFactoryDeps() | 0x76da24b9 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TooMuchGas() | 0xf0b4e88f | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TransactionNotAllowed() | 0x00c5a6a9 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TxnBodyGasLimitNotEnoughGas() | 0x2e311df8 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | Unauthorized(address) | 0x8e4a23d6 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ValidateTxnNotEnoughGas() | 0x47b3b145 | ++----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +IAdmin ++----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=====================================================================================================================================================================================+ +| Function | acceptAdmin() | 0x0e18b681 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | changeFeeParams((uint8,uint32,uint32,uint32,uint32,uint64)) | 0x64bf8d66 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | executeUpgrade(((address,uint8,bool,bytes4[])[],address,bytes)) | 0xa9f6d941 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeBurn(address,address,bytes) | 0x64b554ad | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeMint(bytes,bool) | 0x3f42d5dd | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeRecoverFailedTransfer(uint256,bytes32,address,bytes) | 0xb7846107 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | freezeDiamond() | 0x27ae4c16 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | genesisUpgrade(address,address,bytes,bytes[]) | 0x2878fe74 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getName() | 0x17d7de7c | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | prepareChainCommitment() | 0x41cf49bb | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setDAValidatorPair(address,address) | 0x6223258e | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPendingAdmin(address) | 0x4dd18bf5 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPorterAvailability(bool) | 0x1cc5d103 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPriorityTxMaxGasLimit(uint256) | 0xbe6f11cf | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPubdataPricingMode(uint8) | 0xe76db865 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setTokenMultiplier(uint128,uint128) | 0x235d9eb5 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setTransactionFilterer(address) | 0x21f603d7 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setValidator(address,bool) | 0x4623c91d | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | unfreezeDiamond() | 0x17338945 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | upgradeChainFromVersion(uint256,((address,uint8,bool,bytes4[])[],address,bytes)) | 0xfc57565f | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeInitialize(address,string,string,uint8) | 0x81e8e92e5873539605a102eddae7ed06d19bea042099a437cbc3644415eb7404 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeMint(address,uint256) | 0x397b33b307fc137878ebfc75b295289ec0ee25a31bb5bf034f33256fe8ea2aa6 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ExecuteUpgrade(((address,uint8,bool,bytes4[])[],address,bytes)) | 0x1dabfc3f4f6a4e74e19be10cc9d1d8e23db03e415d5d3547a1a7d5eb766513ba | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Freeze() | 0x615acbaede366d76a8b8cb2a9ada6a71495f0786513d71aa97aaf0c3910b78de | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | IsPorterAvailableStatusUpdate(bool) | 0x036b81a8a07344698cb5aa4142c5669a9317c9ce905264a08f0b9f9331883936 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | MigrationComplete() | 0x96e718f44bd77cb63370212c5aa24a0396d8f43e88e7ce175d160e371c8e2a6a | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewAdmin(address,address) | 0xf9ffabca9c8276e99321725bcb43fb076a6c66a54b7f21c4e8146d8519b417dc | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewBaseTokenMultiplier(uint128,uint128,uint128,uint128) | 0xc9cbdb110bd58a133e82c62b1488e57677be1f326df10a4d8096b5f170c370c8 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewFeeParams((uint8,uint32,uint32,uint32,uint32,uint64),(uint8,uint32,uint32,uint32,uint32,uint64)) | 0xc8b245ac8b138b17b6b1dbbbb8860adc66b373afa000d99f3cdc775d8ae0bbed | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewL1DAValidator(address,address) | 0x08b0b676d456a0431162145d2821f30c665b69ca626521c937ba7a77a29ed67c | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewL2DAValidator(address,address) | 0x866a71b85fb8625f74adc67901361962f0aa3730a1615400911ae610d65dc130 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewPendingAdmin(address,address) | 0xca4f2f25d0898edd99413412fb94012f9e54ec8142f9b093e7720646a95b16a9 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewPriorityTxMaxGasLimit(uint256,uint256) | 0x83dd728f7e76a849126c55ffabdc6e299eb8c85dccf12498701376d9f5c954d1 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewTransactionFilterer(address,address) | 0xa9b43a66c5d1c607986f49e932a0c08058d843210db03dd6e8a621b96b4770f4 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Unfreeze() | 0x2f05ba71d0df11bf5fa562a6569d70c4f80da84284badbe015ce1456063d0ded | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ValidatorStatusUpdate(address,bool) | 0x065b77b53864e46fda3d8986acb51696223d6dde7ced42441eb150bae6d48136 | +|----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | PubdataPricingModeUpdate(uint8) | 0xaa01146df0a628c6b214e79a281f7524b792de4a52ad6f07c78e6e035d58c896 | ++----------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +IDiamondInit ++----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+ +| Type | Signature | Selector | ++====================================================================================================================================================================================================================+ +| Function | initialize((uint256,address,address,uint256,address,address,bytes32,bytes32,address,(bytes32,bytes32,bytes32),bytes32,bytes32,uint256,(uint8,uint32,uint32,uint32,uint32,uint64),address)) | 0xadad4b1b | ++----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+ + +IGetters ++----------+-------------------------------------------+------------+ +| Type | Signature | Selector | ++===================================================================+ +| Function | baseTokenGasPriceMultiplierDenominator() | 0x1de72e34 | +|----------+-------------------------------------------+------------| +| Function | baseTokenGasPriceMultiplierNominator() | 0xea6c029c | +|----------+-------------------------------------------+------------| +| Function | facetAddress(bytes4) | 0xcdffacc6 | +|----------+-------------------------------------------+------------| +| Function | facetAddresses() | 0x52ef6b2c | +|----------+-------------------------------------------+------------| +| Function | facetFunctionSelectors(address) | 0xadfca15e | +|----------+-------------------------------------------+------------| +| Function | facets() | 0x7a0ed627 | +|----------+-------------------------------------------+------------| +| Function | getAdmin() | 0x6e9960c3 | +|----------+-------------------------------------------+------------| +| Function | getBaseToken() | 0x98acd7a6 | +|----------+-------------------------------------------+------------| +| Function | getBaseTokenAssetId() | 0x960dcf24 | +|----------+-------------------------------------------+------------| +| Function | getBridgehub() | 0x3591c1a0 | +|----------+-------------------------------------------+------------| +| Function | getChainId() | 0x3408e470 | +|----------+-------------------------------------------+------------| +| Function | getChainTypeManager() | 0x946ebad1 | +|----------+-------------------------------------------+------------| +| Function | getFirstUnprocessedPriorityTx() | 0x79823c9a | +|----------+-------------------------------------------+------------| +| Function | getL2BootloaderBytecodeHash() | 0xd86970d8 | +|----------+-------------------------------------------+------------| +| Function | getL2DefaultAccountBytecodeHash() | 0xfd791f3c | +|----------+-------------------------------------------+------------| +| Function | getL2SystemContractsUpgradeBatchNumber() | 0xe5355c75 | +|----------+-------------------------------------------+------------| +| Function | getL2SystemContractsUpgradeTxHash() | 0x7b30c8da | +|----------+-------------------------------------------+------------| +| Function | getName() | 0x17d7de7c | +|----------+-------------------------------------------+------------| +| Function | getPendingAdmin() | 0xd0468156 | +|----------+-------------------------------------------+------------| +| Function | getPriorityQueueSize() | 0x631f4bac | +|----------+-------------------------------------------+------------| +| Function | getPriorityTreeRoot() | 0x39d7d4aa | +|----------+-------------------------------------------+------------| +| Function | getPriorityTxMaxGasLimit() | 0x0ec6b0b7 | +|----------+-------------------------------------------+------------| +| Function | getProtocolVersion() | 0x33ce93fe | +|----------+-------------------------------------------+------------| +| Function | getPubdataPricingMode() | 0x06d49e5b | +|----------+-------------------------------------------+------------| +| Function | getSemverProtocolVersion() | 0xf5c1182c | +|----------+-------------------------------------------+------------| +| Function | getSettlementLayer() | 0x6a27e8b5 | +|----------+-------------------------------------------+------------| +| Function | getTotalBatchesCommitted() | 0xdb1f0bf9 | +|----------+-------------------------------------------+------------| +| Function | getTotalBatchesExecuted() | 0xb8c2f66f | +|----------+-------------------------------------------+------------| +| Function | getTotalBatchesVerified() | 0xef3f0bae | +|----------+-------------------------------------------+------------| +| Function | getTotalPriorityTxs() | 0xa1954fc5 | +|----------+-------------------------------------------+------------| +| Function | getTransactionFilterer() | 0x22c5cf23 | +|----------+-------------------------------------------+------------| +| Function | getVerifier() | 0x46657fe9 | +|----------+-------------------------------------------+------------| +| Function | getVerifierParams() | 0x18e3a941 | +|----------+-------------------------------------------+------------| +| Function | isDiamondStorageFrozen() | 0x29b98c67 | +|----------+-------------------------------------------+------------| +| Function | isEthWithdrawalFinalized(uint256,uint256) | 0xbd7c5412 | +|----------+-------------------------------------------+------------| +| Function | isFacetFreezable(address) | 0xc3bbd2d7 | +|----------+-------------------------------------------+------------| +| Function | isFunctionFreezable(bytes4) | 0xe81e0ba1 | +|----------+-------------------------------------------+------------| +| Function | isValidator(address) | 0xfacd743b | +|----------+-------------------------------------------+------------| +| Function | l2LogsRootHash(uint256) | 0x9cd939e4 | +|----------+-------------------------------------------+------------| +| Function | storedBatchHash(uint256) | 0xb22dd78e | ++----------+-------------------------------------------+------------+ + +IZKChain ++----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++===================================================================================================================================================================================================================================================================+ +| Function | acceptAdmin() | 0x0e18b681 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | baseTokenGasPriceMultiplierDenominator() | 0x1de72e34 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | baseTokenGasPriceMultiplierNominator() | 0xea6c029c | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgehubRequestL2Transaction((address,address,uint256,uint256,bytes,uint256,uint256,bytes[],address)) | 0x12f43dab | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | bridgehubRequestL2TransactionOnGateway(bytes32,uint64) | 0xddcc9eec | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | changeFeeParams((uint8,uint32,uint32,uint32,uint32,uint64)) | 0x64bf8d66 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | commitBatchesSharedBridge(uint256,uint256,uint256,bytes) | 0x98f81962 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | executeBatchesSharedBridge(uint256,uint256,uint256,bytes) | 0xcf02827d | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | executeUpgrade(((address,uint8,bool,bytes4[])[],address,bytes)) | 0xa9f6d941 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | facetAddress(bytes4) | 0xcdffacc6 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | facetAddresses() | 0x52ef6b2c | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | facetFunctionSelectors(address) | 0xadfca15e | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | facets() | 0x7a0ed627 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | finalizeEthWithdrawal(uint256,uint256,uint16,bytes,bytes32[]) | 0x6c0960f9 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeBurn(address,address,bytes) | 0x64b554ad | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeMint(bytes,bool) | 0x3f42d5dd | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | forwardedBridgeRecoverFailedTransfer(uint256,bytes32,address,bytes) | 0xb7846107 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | freezeDiamond() | 0x27ae4c16 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | genesisUpgrade(address,address,bytes,bytes[]) | 0x2878fe74 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getAdmin() | 0x6e9960c3 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getBaseToken() | 0x98acd7a6 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getBaseTokenAssetId() | 0x960dcf24 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getBridgehub() | 0x3591c1a0 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getChainId() | 0x3408e470 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getChainTypeManager() | 0x946ebad1 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getFirstUnprocessedPriorityTx() | 0x79823c9a | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getL2BootloaderBytecodeHash() | 0xd86970d8 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getL2DefaultAccountBytecodeHash() | 0xfd791f3c | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getL2SystemContractsUpgradeBatchNumber() | 0xe5355c75 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getL2SystemContractsUpgradeTxHash() | 0x7b30c8da | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getName() | 0x17d7de7c | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getPendingAdmin() | 0xd0468156 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getPriorityQueueSize() | 0x631f4bac | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getPriorityTreeRoot() | 0x39d7d4aa | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getPriorityTxMaxGasLimit() | 0x0ec6b0b7 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getProtocolVersion() | 0x33ce93fe | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getPubdataPricingMode() | 0x06d49e5b | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getSemverProtocolVersion() | 0xf5c1182c | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getSettlementLayer() | 0x6a27e8b5 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getTotalBatchesCommitted() | 0xdb1f0bf9 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getTotalBatchesExecuted() | 0xb8c2f66f | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getTotalBatchesVerified() | 0xef3f0bae | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getTotalPriorityTxs() | 0xa1954fc5 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getTransactionFilterer() | 0x22c5cf23 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getVerifier() | 0x46657fe9 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | getVerifierParams() | 0x18e3a941 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isDiamondStorageFrozen() | 0x29b98c67 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isEthWithdrawalFinalized(uint256,uint256) | 0xbd7c5412 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isFacetFreezable(address) | 0xc3bbd2d7 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isFunctionFreezable(bytes4) | 0xe81e0ba1 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isValidator(address) | 0xfacd743b | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2LogsRootHash(uint256) | 0x9cd939e4 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | l2TransactionBaseCost(uint256,uint256,uint256) | 0xb473318e | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | prepareChainCommitment() | 0x41cf49bb | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveBatchesSharedBridge(uint256,uint256,uint256,bytes) | 0xe12a6137 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL1ToL2TransactionStatus(bytes32,uint256,uint256,uint16,bytes32[],uint8) | 0x042901c7 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL2LeafInclusion(uint256,uint256,bytes32,bytes32[]) | 0x7efda2ae | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL2LogInclusion(uint256,uint256,(uint8,bool,uint16,address,bytes32,bytes32),bytes32[]) | 0x263b7f8e | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | proveL2MessageInclusion(uint256,uint256,(uint16,address,bytes),bytes32[]) | 0xe4948f43 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | requestL2Transaction(address,uint256,bytes,uint256,uint256,bytes[],address) | 0xeb672419 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | requestL2TransactionToGatewayMailbox(uint256,bytes32,uint64) | 0xd0772551 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | revertBatchesSharedBridge(uint256,uint256) | 0x0f23da43 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setDAValidatorPair(address,address) | 0x6223258e | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPendingAdmin(address) | 0x4dd18bf5 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPorterAvailability(bool) | 0x1cc5d103 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPriorityTxMaxGasLimit(uint256) | 0xbe6f11cf | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setPubdataPricingMode(uint8) | 0xe76db865 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setTokenMultiplier(uint128,uint128) | 0x235d9eb5 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setTransactionFilterer(address) | 0x21f603d7 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | setValidator(address,bool) | 0x4623c91d | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | storedBatchHash(uint256) | 0xb22dd78e | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | unfreezeDiamond() | 0x17338945 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | upgradeChainFromVersion(uint256,((address,uint8,bool,bytes4[])[],address,bytes)) | 0xfc57565f | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlockCommit(uint256,bytes32,bytes32) | 0x8f2916b2f2d78cc5890ead36c06c0f6d5d112c7e103589947e8e2f0d6eddb763 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlockExecution(uint256,bytes32,bytes32) | 0x2402307311a4d6604e4e7b4c8a15a7e1213edb39c16a31efa70afb06030d3165 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlocksRevert(uint256,uint256,uint256) | 0x8bd4b15ea7d1bc41ea9abc3fc487ccb89cd678a00786584714faa9d751c84ee5 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BlocksVerification(uint256,uint256) | 0x22c9005dd88c18b552a1cd7e8b3b937fcde9ca69213c1f658f54d572e4877a81 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeInitialize(address,string,string,uint8) | 0x81e8e92e5873539605a102eddae7ed06d19bea042099a437cbc3644415eb7404 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | BridgeMint(address,uint256) | 0x397b33b307fc137878ebfc75b295289ec0ee25a31bb5bf034f33256fe8ea2aa6 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ExecuteUpgrade(((address,uint8,bool,bytes4[])[],address,bytes)) | 0x1dabfc3f4f6a4e74e19be10cc9d1d8e23db03e415d5d3547a1a7d5eb766513ba | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Freeze() | 0x615acbaede366d76a8b8cb2a9ada6a71495f0786513d71aa97aaf0c3910b78de | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | IsPorterAvailableStatusUpdate(bool) | 0x036b81a8a07344698cb5aa4142c5669a9317c9ce905264a08f0b9f9331883936 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | MigrationComplete() | 0x96e718f44bd77cb63370212c5aa24a0396d8f43e88e7ce175d160e371c8e2a6a | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewAdmin(address,address) | 0xf9ffabca9c8276e99321725bcb43fb076a6c66a54b7f21c4e8146d8519b417dc | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewBaseTokenMultiplier(uint128,uint128,uint128,uint128) | 0xc9cbdb110bd58a133e82c62b1488e57677be1f326df10a4d8096b5f170c370c8 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewFeeParams((uint8,uint32,uint32,uint32,uint32,uint64),(uint8,uint32,uint32,uint32,uint32,uint64)) | 0xc8b245ac8b138b17b6b1dbbbb8860adc66b373afa000d99f3cdc775d8ae0bbed | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewL1DAValidator(address,address) | 0x08b0b676d456a0431162145d2821f30c665b69ca626521c937ba7a77a29ed67c | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewL2DAValidator(address,address) | 0x866a71b85fb8625f74adc67901361962f0aa3730a1615400911ae610d65dc130 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewPendingAdmin(address,address) | 0xca4f2f25d0898edd99413412fb94012f9e54ec8142f9b093e7720646a95b16a9 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewPriorityRequest(uint256,bytes32,uint64,(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[]) | 0x4531cd5795773d7101c17bdeb9f5ab7f47d7056017506f937083be5d6e77a382 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewPriorityTxMaxGasLimit(uint256,uint256) | 0x83dd728f7e76a849126c55ffabdc6e299eb8c85dccf12498701376d9f5c954d1 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewRelayedPriorityTransaction(uint256,bytes32,uint64) | 0x0137d2eaa6ec5b7e4f233f6d6f441410014535d0f3985367994c94bf15a2a564 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewTransactionFilterer(address,address) | 0xa9b43a66c5d1c607986f49e932a0c08058d843210db03dd6e8a621b96b4770f4 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ProposeTransparentUpgrade(((address,uint8,bool,bytes4[])[],address,bytes),uint256,bytes32) | 0x69115b49afe7a6101a2e7af17d421eda1dc153bd26d699f013c4fff0404646a6 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Unfreeze() | 0x2f05ba71d0df11bf5fa562a6569d70c4f80da84284badbe015ce1456063d0ded | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | ValidatorStatusUpdate(address,bool) | 0x065b77b53864e46fda3d8986acb51696223d6dde7ced42441eb150bae6d48136 | +|----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | PubdataPricingModeUpdate(uint8) | 0xaa01146df0a628c6b214e79a281f7524b792de4a52ad6f07c78e6e035d58c896 | ++----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +Diamond ++-------+-----------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++========================================================================================================================================+ +| Event | DiamondCut((address,uint8,bool,bytes4[])[],address,bytes) | 0x87b829356b3403d36217eff1f66ee48eacd0a69015153aba4f0de29fe5340c30 | ++-------+-----------------------------------------------------------+--------------------------------------------------------------------+ + +GatewayTransactionFilterer ++----------+---------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=====================================================================================================================================================+ +| Function | BRIDGE_HUB() | 0x5d4edca7 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | L1_ASSET_ROUTER() | 0xcdf25430 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | acceptOwnership() | 0x79ba5097 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | grantWhitelist(address) | 0x5b063622 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | initialize(address) | 0xc4d66de8 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | isTransactionAllowed(address,address,uint256,uint256,bytes,address) | 0xcbcf2e3c | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | owner() | 0x8da5cb5b | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | pendingOwner() | 0xe30c3978 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | renounceOwnership() | 0x715018a6 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | revokeWhitelist(address) | 0x9c7f3315 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | transferOwnership(address) | 0xf2fde38b | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | whitelistedSenders(address) | 0x272efc69 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | Initialized(uint8) | 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | WhitelistGranted(address) | 0xd5a6b3454d1aa211b5b7e99c94012bed9883c7695b5c58fd97e20f425ec0b5bf | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | WhitelistRevoked(address) | 0x79bc96e8fb893a32b4429ae9fd9084cdb98be910b9c7cdbb4307cc96732c7ec7 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AlreadyWhitelisted(address) | 0x0bfcef28 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidSelector(bytes4) | 0x12ba286f | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NotWhitelisted(address) | 0xdf17e316 | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SlotOccupied() | 0xdf3a8fdd | +|----------+---------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ZeroAddress() | 0xd92e233d | ++----------+---------------------------------------------------------------------+--------------------------------------------------------------------+ + +BaseZkSyncUpgrade ++----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=========================================================================================================================================================================================================================================================================================================================================+ +| Function | upgrade(((uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[],bytes32,bytes32,address,(bytes32,bytes32,bytes32),bytes,bytes,uint256,uint256)) | 0x08284e57 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewL2BootloaderBytecodeHash(bytes32,bytes32) | 0x271b33af94e3f065ecd8659833e6b1daf851f063700c36ddefefab35d4ce4746 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewL2DefaultAccountBytecodeHash(bytes32,bytes32) | 0x36df93a47cc02081d9d8208022ab736fdf98fac566e5fc6f5762bf7666e521f3 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewProtocolVersion(uint256,uint256) | 0x4235104f56661fe2e9d2f2a460b42766581bc45ce366c6a30a9f86c8a2b371a7 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewVerifier(address,address) | 0x2ff4895c300d6993c27f2bb507b4b59d29464dc640af727383451365631ba8b2 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewVerifierParams((bytes32,bytes32,bytes32),(bytes32,bytes32,bytes32)) | 0x4c055dbc5f14dcb6e081c9421d9657d950dcd6372f6db0a714b9135171cbc15d | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | UpgradeComplete(uint256,bytes32,((uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[],bytes32,bytes32,address,(bytes32,bytes32,bytes32),bytes,bytes,uint256,uint256)) | 0x56405fee20a4cf3c21d1b23cbbedc0f54921b0347dc19a7641c80645f6916798 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidTxType(uint256) | 0x5cb29523 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidUpgradeTxn(uint8) | 0x5f1aa154 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | L2BytecodeHashMismatch(bytes32,bytes32) | 0xcb5e4247 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | L2UpgradeNonceNotEqualToNewProtocolVersion(uint256,uint256) | 0xd2c011d6 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | LengthIsNotDivisibleBy32(uint256) | 0xe37d2c02 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MalformedBytecode(uint8) | 0x43e266b0 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NewProtocolMajorVersionNotZero() | 0x72ea85ad | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PatchCantSetUpgradeTxn() | 0xd7f50a9d | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PatchUpgradeCantSetBootloader() | 0x962fd7d0 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PatchUpgradeCantSetDefaultAccount() | 0x559cc34e | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PreviousProtocolMajorVersionNotZero() | 0x5c598b60 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PreviousUpgradeNotCleaned() | 0xa0f47245 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PreviousUpgradeNotFinalized(bytes32) | 0x101ba748 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ProtocolVersionMinorDeltaTooBig(uint256,uint256) | 0xd328c12a | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ProtocolVersionTooSmall() | 0x88d7b498 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PubdataGreaterThanLimit(uint256,uint256) | 0x959f26fb | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TimeNotReached(uint256,uint256) | 0x08753982 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TooManyFactoryDeps() | 0x76da24b9 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TooMuchGas() | 0xf0b4e88f | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TxnBodyGasLimitNotEnoughGas() | 0x2e311df8 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnexpectedNumberOfFactoryDeps() | 0x07218375 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ValidateTxnNotEnoughGas() | 0x47b3b145 | ++----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +BaseZkSyncUpgradeGenesis ++----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=========================================================================================================================================================================================================================================================================================================================================+ +| Function | upgrade(((uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[],bytes32,bytes32,address,(bytes32,bytes32,bytes32),bytes,bytes,uint256,uint256)) | 0x08284e57 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewL2BootloaderBytecodeHash(bytes32,bytes32) | 0x271b33af94e3f065ecd8659833e6b1daf851f063700c36ddefefab35d4ce4746 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewL2DefaultAccountBytecodeHash(bytes32,bytes32) | 0x36df93a47cc02081d9d8208022ab736fdf98fac566e5fc6f5762bf7666e521f3 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewProtocolVersion(uint256,uint256) | 0x4235104f56661fe2e9d2f2a460b42766581bc45ce366c6a30a9f86c8a2b371a7 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewVerifier(address,address) | 0x2ff4895c300d6993c27f2bb507b4b59d29464dc640af727383451365631ba8b2 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewVerifierParams((bytes32,bytes32,bytes32),(bytes32,bytes32,bytes32)) | 0x4c055dbc5f14dcb6e081c9421d9657d950dcd6372f6db0a714b9135171cbc15d | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | UpgradeComplete(uint256,bytes32,((uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[],bytes32,bytes32,address,(bytes32,bytes32,bytes32),bytes,bytes,uint256,uint256)) | 0x56405fee20a4cf3c21d1b23cbbedc0f54921b0347dc19a7641c80645f6916798 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidTxType(uint256) | 0x5cb29523 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidUpgradeTxn(uint8) | 0x5f1aa154 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | L2BytecodeHashMismatch(bytes32,bytes32) | 0xcb5e4247 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | L2UpgradeNonceNotEqualToNewProtocolVersion(uint256,uint256) | 0xd2c011d6 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | LengthIsNotDivisibleBy32(uint256) | 0xe37d2c02 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MalformedBytecode(uint8) | 0x43e266b0 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PatchCantSetUpgradeTxn() | 0xd7f50a9d | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PatchUpgradeCantSetBootloader() | 0x962fd7d0 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PatchUpgradeCantSetDefaultAccount() | 0x559cc34e | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PreviousUpgradeBatchNotCleared() | 0xd7f8c13e | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PreviousUpgradeNotFinalized(bytes32) | 0x101ba748 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ProtocolMajorVersionNotZero() | 0x3c43ccce | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ProtocolVersionDeltaTooLarge(uint256,uint256) | 0xe1a9736b | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ProtocolVersionTooSmall() | 0x88d7b498 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PubdataGreaterThanLimit(uint256,uint256) | 0x959f26fb | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TimeNotReached(uint256,uint256) | 0x08753982 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TooManyFactoryDeps() | 0x76da24b9 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TooMuchGas() | 0xf0b4e88f | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TxnBodyGasLimitNotEnoughGas() | 0x2e311df8 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnexpectedNumberOfFactoryDeps() | 0x07218375 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ValidateTxnNotEnoughGas() | 0x47b3b145 | ++----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +DefaultUpgrade ++----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=========================================================================================================================================================================================================================================================================================================================================+ +| Function | upgrade(((uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[],bytes32,bytes32,address,(bytes32,bytes32,bytes32),bytes,bytes,uint256,uint256)) | 0x08284e57 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewL2BootloaderBytecodeHash(bytes32,bytes32) | 0x271b33af94e3f065ecd8659833e6b1daf851f063700c36ddefefab35d4ce4746 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewL2DefaultAccountBytecodeHash(bytes32,bytes32) | 0x36df93a47cc02081d9d8208022ab736fdf98fac566e5fc6f5762bf7666e521f3 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewProtocolVersion(uint256,uint256) | 0x4235104f56661fe2e9d2f2a460b42766581bc45ce366c6a30a9f86c8a2b371a7 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewVerifier(address,address) | 0x2ff4895c300d6993c27f2bb507b4b59d29464dc640af727383451365631ba8b2 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewVerifierParams((bytes32,bytes32,bytes32),(bytes32,bytes32,bytes32)) | 0x4c055dbc5f14dcb6e081c9421d9657d950dcd6372f6db0a714b9135171cbc15d | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | UpgradeComplete(uint256,bytes32,((uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[],bytes32,bytes32,address,(bytes32,bytes32,bytes32),bytes,bytes,uint256,uint256)) | 0x56405fee20a4cf3c21d1b23cbbedc0f54921b0347dc19a7641c80645f6916798 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidTxType(uint256) | 0x5cb29523 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidUpgradeTxn(uint8) | 0x5f1aa154 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | L2BytecodeHashMismatch(bytes32,bytes32) | 0xcb5e4247 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | L2UpgradeNonceNotEqualToNewProtocolVersion(uint256,uint256) | 0xd2c011d6 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | LengthIsNotDivisibleBy32(uint256) | 0xe37d2c02 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MalformedBytecode(uint8) | 0x43e266b0 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NewProtocolMajorVersionNotZero() | 0x72ea85ad | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PatchCantSetUpgradeTxn() | 0xd7f50a9d | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PatchUpgradeCantSetBootloader() | 0x962fd7d0 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PatchUpgradeCantSetDefaultAccount() | 0x559cc34e | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PreviousProtocolMajorVersionNotZero() | 0x5c598b60 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PreviousUpgradeNotCleaned() | 0xa0f47245 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PreviousUpgradeNotFinalized(bytes32) | 0x101ba748 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ProtocolVersionMinorDeltaTooBig(uint256,uint256) | 0xd328c12a | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ProtocolVersionTooSmall() | 0x88d7b498 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PubdataGreaterThanLimit(uint256,uint256) | 0x959f26fb | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TimeNotReached(uint256,uint256) | 0x08753982 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TooManyFactoryDeps() | 0x76da24b9 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TooMuchGas() | 0xf0b4e88f | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TxnBodyGasLimitNotEnoughGas() | 0x2e311df8 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnexpectedNumberOfFactoryDeps() | 0x07218375 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ValidateTxnNotEnoughGas() | 0x47b3b145 | ++----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +GatewayUpgrade ++----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=========================================================================================================================================================================================================================================================================================================================================+ +| Function | THIS_ADDRESS() | 0x315fff4e | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | upgrade(((uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[],bytes32,bytes32,address,(bytes32,bytes32,bytes32),bytes,bytes,uint256,uint256)) | 0x08284e57 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | upgradeExternal(((uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[],bytes32,bytes32,address,(bytes32,bytes32,bytes32),bytes,bytes,uint256,uint256)) | 0xde7c5443 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewL2BootloaderBytecodeHash(bytes32,bytes32) | 0x271b33af94e3f065ecd8659833e6b1daf851f063700c36ddefefab35d4ce4746 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewL2DefaultAccountBytecodeHash(bytes32,bytes32) | 0x36df93a47cc02081d9d8208022ab736fdf98fac566e5fc6f5762bf7666e521f3 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewProtocolVersion(uint256,uint256) | 0x4235104f56661fe2e9d2f2a460b42766581bc45ce366c6a30a9f86c8a2b371a7 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewVerifier(address,address) | 0x2ff4895c300d6993c27f2bb507b4b59d29464dc640af727383451365631ba8b2 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewVerifierParams((bytes32,bytes32,bytes32),(bytes32,bytes32,bytes32)) | 0x4c055dbc5f14dcb6e081c9421d9657d950dcd6372f6db0a714b9135171cbc15d | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | UpgradeComplete(uint256,bytes32,((uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[],bytes32,bytes32,address,(bytes32,bytes32,bytes32),bytes,bytes,uint256,uint256)) | 0x56405fee20a4cf3c21d1b23cbbedc0f54921b0347dc19a7641c80645f6916798 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidTxType(uint256) | 0x5cb29523 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidUpgradeTxn(uint8) | 0x5f1aa154 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | L2BytecodeHashMismatch(bytes32,bytes32) | 0xcb5e4247 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | L2UpgradeNonceNotEqualToNewProtocolVersion(uint256,uint256) | 0xd2c011d6 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | LengthIsNotDivisibleBy32(uint256) | 0xe37d2c02 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MalformedBytecode(uint8) | 0x43e266b0 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NewProtocolMajorVersionNotZero() | 0x72ea85ad | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PatchCantSetUpgradeTxn() | 0xd7f50a9d | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PatchUpgradeCantSetBootloader() | 0x962fd7d0 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PatchUpgradeCantSetDefaultAccount() | 0x559cc34e | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PreviousProtocolMajorVersionNotZero() | 0x5c598b60 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PreviousUpgradeNotCleaned() | 0xa0f47245 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PreviousUpgradeNotFinalized(bytes32) | 0x101ba748 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ProtocolVersionMinorDeltaTooBig(uint256,uint256) | 0xd328c12a | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ProtocolVersionTooSmall() | 0x88d7b498 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PubdataGreaterThanLimit(uint256,uint256) | 0x959f26fb | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TimeNotReached(uint256,uint256) | 0x08753982 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TooManyFactoryDeps() | 0x76da24b9 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TooMuchGas() | 0xf0b4e88f | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TxnBodyGasLimitNotEnoughGas() | 0x2e311df8 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnexpectedNumberOfFactoryDeps() | 0x07218375 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ValidateTxnNotEnoughGas() | 0x47b3b145 | ++----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ + +IDefaultUpgrade ++----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+ +| Type | Signature | Selector | ++=========================================================================================================================================================================================================================================================+ +| Function | upgrade(((uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[],bytes32,bytes32,address,(bytes32,bytes32,bytes32),bytes,bytes,uint256,uint256)) | 0x08284e57 | ++----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+ + +IGatewayUpgrade ++----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+ +| Type | Signature | Selector | ++=================================================================================================================================================================================================================================================================+ +| Function | upgradeExternal(((uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[],bytes32,bytes32,address,(bytes32,bytes32,bytes32),bytes,bytes,uint256,uint256)) | 0xde7c5443 | ++----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+ + +L1GenesisUpgrade ++----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ +| Type | Signature | Selector | ++=========================================================================================================================================================================================================================================================================================================================================+ +| Function | genesisUpgrade(address,uint256,uint256,address,bytes,bytes[]) | 0xd83e4e03 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Function | upgrade(((uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[],bytes32,bytes32,address,(bytes32,bytes32,bytes32),bytes,bytes,uint256,uint256)) | 0x08284e57 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | DiamondCut((address,uint8,bool,bytes4[])[],address,bytes) | 0x87b829356b3403d36217eff1f66ee48eacd0a69015153aba4f0de29fe5340c30 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | GenesisUpgrade(address,(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),uint256,bytes[]) | 0xc5902263211386c797097c5eef7ce20567f0e13359623233314cfa01a55341da | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewL2BootloaderBytecodeHash(bytes32,bytes32) | 0x271b33af94e3f065ecd8659833e6b1daf851f063700c36ddefefab35d4ce4746 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewL2DefaultAccountBytecodeHash(bytes32,bytes32) | 0x36df93a47cc02081d9d8208022ab736fdf98fac566e5fc6f5762bf7666e521f3 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewProtocolVersion(uint256,uint256) | 0x4235104f56661fe2e9d2f2a460b42766581bc45ce366c6a30a9f86c8a2b371a7 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewVerifier(address,address) | 0x2ff4895c300d6993c27f2bb507b4b59d29464dc640af727383451365631ba8b2 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | NewVerifierParams((bytes32,bytes32,bytes32),(bytes32,bytes32,bytes32)) | 0x4c055dbc5f14dcb6e081c9421d9657d950dcd6372f6db0a714b9135171cbc15d | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Event | UpgradeComplete(uint256,bytes32,((uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[],bytes32,bytes32,address,(bytes32,bytes32,bytes32),bytes,bytes,uint256,uint256)) | 0x56405fee20a4cf3c21d1b23cbbedc0f54921b0347dc19a7641c80645f6916798 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | AddressHasNoCode(address) | 0x86bb51b8 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | DelegateCallFailed(bytes) | 0xf7a01e4d | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | FacetExists(bytes4,address) | 0xac4a3f98 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidTxType(uint256) | 0x5cb29523 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | InvalidUpgradeTxn(uint8) | 0x5f1aa154 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | L2BytecodeHashMismatch(bytes32,bytes32) | 0xcb5e4247 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | L2UpgradeNonceNotEqualToNewProtocolVersion(uint256,uint256) | 0xd2c011d6 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | LengthIsNotDivisibleBy32(uint256) | 0xe37d2c02 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | MalformedBytecode(uint8) | 0x43e266b0 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NoFunctionsForDiamondCut() | 0xa6fef710 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | NonEmptyCalldata() | 0xc21b1ab7 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PatchCantSetUpgradeTxn() | 0xd7f50a9d | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PatchUpgradeCantSetBootloader() | 0x962fd7d0 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PatchUpgradeCantSetDefaultAccount() | 0x559cc34e | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PreviousUpgradeBatchNotCleared() | 0xd7f8c13e | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PreviousUpgradeNotFinalized(bytes32) | 0x101ba748 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ProtocolMajorVersionNotZero() | 0x3c43ccce | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ProtocolVersionDeltaTooLarge(uint256,uint256) | 0xe1a9736b | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ProtocolVersionTooSmall() | 0x88d7b498 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | PubdataGreaterThanLimit(uint256,uint256) | 0x959f26fb | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | RemoveFunctionFacetAddressNotZero(address) | 0x667d17de | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | RemoveFunctionFacetAddressZero() | 0xa2d4b16c | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ReplaceFunctionFacetAddressZero() | 0x3580370c | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | SelectorsMustAllHaveSameFreezability() | 0xd3b6535b | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TimeNotReached(uint256,uint256) | 0x08753982 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TooManyFactoryDeps() | 0x76da24b9 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TooMuchGas() | 0xf0b4e88f | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | TxnBodyGasLimitNotEnoughGas() | 0x2e311df8 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | UndefinedDiamondCutAction() | 0xe52478c7 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | UnexpectedNumberOfFactoryDeps() | 0x07218375 | +|----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------| +| Error | ValidateTxnNotEnoughGas() | 0x47b3b145 | ++----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ diff --git a/l1-contracts/src.ts/deploy-process.ts b/l1-contracts/src.ts/deploy-process.ts index d30762a15..4415c8109 100644 --- a/l1-contracts/src.ts/deploy-process.ts +++ b/l1-contracts/src.ts/deploy-process.ts @@ -12,7 +12,13 @@ import type { FacetCut } from "./diamondCut"; import type { Deployer } from "./deploy"; import { getTokens } from "./deploy-token"; -import { ADDRESS_ONE } from "../src.ts/utils"; +import { + ADDRESS_ONE, + L2_BRIDGEHUB_ADDRESS, + L2_MESSAGE_ROOT_ADDRESS, + isCurrentNetworkLocal, + encodeNTVAssetId, +} from "../src.ts/utils"; export const L2_BOOTLOADER_BYTECODE_HASH = "0x1000100000000000000000000000000000000000000000000000000000000000"; export const L2_DEFAULT_ACCOUNT_BYTECODE_HASH = "0x1001000000000000000000000000000000000000000000000000000000000000"; @@ -25,16 +31,19 @@ export async function initialBridgehubDeployment( 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++; + if (isCurrentNetworkLocal()) { + if (!deployer.isZkMode()) { + await deployer.deployCreate2Factory({ gasPrice, nonce }); + nonce = nonce || nonce == 0 ? ++nonce : nonce; + } else { + await deployer.updateCreate2FactoryZkMode(); + } await deployer.deployMulticall3(create2Salt, { gasPrice, nonce }); - nonce++; + nonce = nonce || nonce == 0 ? ++nonce : nonce; } if (onlyVerifier) { @@ -44,36 +53,52 @@ export async function initialBridgehubDeployment( await deployer.deployDefaultUpgrade(create2Salt, { gasPrice, - nonce, }); - nonce++; + nonce = nonce ? ++nonce : nonce; await deployer.deployGenesisUpgrade(create2Salt, { gasPrice, - nonce, }); - nonce++; - - await deployer.deployValidatorTimelock(create2Salt, { gasPrice, nonce }); - nonce++; - - await deployer.deployGovernance(create2Salt, { gasPrice, nonce }); - nonce++; + nonce = nonce ? ++nonce : nonce; + + await deployer.deployDAValidators(create2Salt, { gasPrice }); + // Governance will be L1 governance, but we want to deploy it here for the init process. + await deployer.deployGovernance(create2Salt, { gasPrice }); + await deployer.deployChainAdmin(create2Salt, { gasPrice }); + await deployer.deployValidatorTimelock(create2Salt, { gasPrice }); + + if (!deployer.isZkMode()) { + // proxy admin is already deployed when SL's L2SharedBridge is registered + await deployer.deployTransparentProxyAdmin(create2Salt, { gasPrice }); + await deployer.deployBridgehubContract(create2Salt, gasPrice); + } else { + deployer.addresses.Bridgehub.BridgehubProxy = L2_BRIDGEHUB_ADDRESS; + deployer.addresses.Bridgehub.MessageRootProxy = L2_MESSAGE_ROOT_ADDRESS; + + console.log(`CONTRACTS_BRIDGEHUB_IMPL_ADDR=${L2_BRIDGEHUB_ADDRESS}`); + console.log(`CONTRACTS_BRIDGEHUB_PROXY_ADDR=${L2_BRIDGEHUB_ADDRESS}`); + console.log(`CONTRACTS_MESSAGE_ROOT_IMPL_ADDR=${L2_MESSAGE_ROOT_ADDRESS}`); + console.log(`CONTRACTS_MESSAGE_ROOT_PROXY_ADDR=${L2_MESSAGE_ROOT_ADDRESS}`); + } - await deployer.deployChainAdmin(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 }); + // L2 Asset Router Bridge already deployed + if (!deployer.isZkMode()) { + await deployer.deploySharedBridgeContracts(create2Salt, gasPrice); + await deployer.deployERC20BridgeImplementation(create2Salt, { gasPrice }); + await deployer.deployERC20BridgeProxy(create2Salt, { gasPrice }); + await deployer.setParametersSharedBridge(); + } - await deployer.deploySharedBridgeContracts(create2Salt, gasPrice); - await deployer.deployERC20BridgeImplementation(create2Salt, { gasPrice }); - await deployer.deployERC20BridgeProxy(create2Salt, { gasPrice }); - await deployer.setParametersSharedBridge(); + if (deployer.isZkMode()) { + await deployer.updateBlobVersionedHashRetrieverZkMode(); + } else { + await deployer.deployBlobVersionedHashRetriever(create2Salt, { gasPrice }); + } + await deployer.deployChainTypeManagerContract(create2Salt, extraFacets, gasPrice); + await deployer.setChainTypeManagerInValidatorTimelock({ gasPrice }); } -export async function registerHyperchain( +export async function registerZKChain( deployer: Deployer, validiumMode: boolean, extraFacets: FacetCut[], @@ -88,17 +113,22 @@ export async function registerHyperchain( ? testnetTokens.find((token: { symbol: string }) => token.symbol == baseTokenName).address : ADDRESS_ONE; - if (!(await deployer.bridgehubContract(deployer.deployWallet).tokenIsRegistered(baseTokenAddress))) { - await deployer.registerToken(baseTokenAddress, useGovernance); + const baseTokenAssetId = encodeNTVAssetId(deployer.l1ChainId, ethers.utils.hexZeroPad(baseTokenAddress, 32)); + if (!(await deployer.bridgehubContract(deployer.deployWallet).assetIdIsRegistered(baseTokenAssetId))) { + await deployer.registerTokenBridgehub(baseTokenAddress, useGovernance); + } + if (baseTokenAddress !== ADDRESS_ONE) { + await deployer.registerTokenInNativeTokenVault(baseTokenAddress); } - await deployer.registerHyperchain( - baseTokenAddress, + await deployer.registerZKChain( + encodeNTVAssetId(deployer.l1ChainId, ethers.utils.hexZeroPad(baseTokenAddress, 32)), validiumMode, extraFacets, gasPrice, false, null, chainId, - useGovernance + useGovernance, + true ); } diff --git a/l1-contracts/src.ts/deploy-test-process.ts b/l1-contracts/src.ts/deploy-test-process.ts index b8af27b34..07b3fcfb9 100644 --- a/l1-contracts/src.ts/deploy-test-process.ts +++ b/l1-contracts/src.ts/deploy-test-process.ts @@ -7,7 +7,6 @@ import * as ethers from "ethers"; import type { BigNumberish, Wallet } from "ethers"; import { Interface } from "ethers/lib/utils"; import * as zkethers from "zksync-ethers"; -import { ETH_ADDRESS_IN_CONTRACTS } from "zksync-ethers/build/utils"; import * as fs from "fs"; import type { FacetCut } from "./diamondCut"; @@ -16,7 +15,7 @@ import { L2_BOOTLOADER_BYTECODE_HASH, L2_DEFAULT_ACCOUNT_BYTECODE_HASH, initialBridgehubDeployment, - registerHyperchain, + registerZKChain, } from "./deploy-process"; import { deployTokens, getTokens } from "./deploy-token"; @@ -28,6 +27,9 @@ import { PubdataPricingMode, ADDRESS_ONE, EMPTY_STRING_KECCAK, + isCurrentNetworkLocal, + ETH_ADDRESS_IN_CONTRACTS, + encodeNTVAssetId, } from "./utils"; import { diamondCut, getCurrentFacetCutsForAdd, facetCut, Action } from "./diamondCut"; import { CONTRACTS_GENESIS_PROTOCOL_VERSION } from "../test/unit_tests/utils"; @@ -49,12 +51,14 @@ export async function loadDefaultEnvVarsForTests(deployWallet: Wallet) { // process.env.CONTRACTS_SHARED_BRIDGE_UPGRADE_STORAGE_SWITCH = "1"; process.env.ETH_CLIENT_CHAIN_ID = (await deployWallet.getChainId()).toString(); process.env.CONTRACTS_ERA_CHAIN_ID = "270"; + process.env.CONTRACTS_L1_CHAIN_ID = "31337"; process.env.CONTRACTS_ERA_DIAMOND_PROXY_ADDR = ADDRESS_ONE; // CONTRACTS_ERA_DIAMOND_PROXY_ADDR; process.env.CONTRACTS_L2_SHARED_BRIDGE_ADDR = ADDRESS_ONE; process.env.CONTRACTS_L2_SHARED_BRIDGE_IMPL_ADDR = ADDRESS_ONE; process.env.CONTRACTS_L2_ERC20_BRIDGE_ADDR = ADDRESS_ONE; process.env.CONTRACTS_BRIDGEHUB_PROXY_ADDR = ADDRESS_ONE; + process.env.CONTRACTS_L2_DA_VALIDATOR_ADDR = ADDRESS_ONE; } export async function defaultDeployerForTests(deployWallet: Wallet, ownerAddress: string): Promise { @@ -65,6 +69,7 @@ export async function defaultDeployerForTests(deployWallet: Wallet, ownerAddress addresses: addressConfig, bootloaderBytecodeHash: L2_BOOTLOADER_BYTECODE_HASH, defaultAccountBytecodeHash: L2_DEFAULT_ACCOUNT_BYTECODE_HASH, + l1ChainId: process.env.CONTRACTS_L1_CHAIN_ID, }); } @@ -76,6 +81,7 @@ export async function defaultEraDeployerForTests(deployWallet: Wallet, ownerAddr addresses: addressConfig, bootloaderBytecodeHash: L2_BOOTLOADER_BYTECODE_HASH, defaultAccountBytecodeHash: L2_DEFAULT_ACCOUNT_BYTECODE_HASH, + l1ChainId: process.env.CONTRACTS_L1_CHAIN_ID, }); const l2_rpc_addr = "http://localhost:3050"; const web3Provider = new zkethers.Provider(l2_rpc_addr); @@ -98,17 +104,44 @@ export async function initialTestnetDeploymentProcess( deployer.chainId = 9; const testnetTokens = getTokens(); - const result = await deployTokens(testnetTokens, deployer.deployWallet, null, false, deployer.verbose); + const result = await deployTokens(testnetTokens, deployer.deployWallet, null, true, deployer.verbose); + fs.writeFileSync(testnetTokenPath, JSON.stringify(result, null, 2)); // deploy the verifier first await initialBridgehubDeployment(deployer, extraFacets, gasPrice, true); await initialBridgehubDeployment(deployer, extraFacets, gasPrice, false); - await registerHyperchain(deployer, false, extraFacets, gasPrice, baseTokenName); + await registerZKChainWithBridgeRegistration(deployer, false, extraFacets, gasPrice, baseTokenName); + await registerTestDAValidators(deployer); + return deployer; } -// This is used to deploy the diamond and bridge such that they can be upgraded using UpgradeHyperchain.sol +export async function registerZKChainWithBridgeRegistration( + deployer: Deployer, + onlyVerifier: boolean, + extraFacets: FacetCut[], + gasPrice: BigNumberish, + baseTokenName?: string, + chainId?: string +) { + chainId = chainId ?? deployer.chainId.toString(); + await registerZKChain(deployer, onlyVerifier, extraFacets, gasPrice, baseTokenName, chainId, true); + await registerTestDAValidators(deployer); +} + +async function registerTestDAValidators(deployer: Deployer) { + const contract = await deployer.stateTransitionContract(deployer.deployWallet); + // The L2 DA validator must not be zero, but it can be any other value. It is not relevant for the tests. + await ( + await contract.setDAValidatorPair( + deployer.addresses.RollupL1DAValidator, + process.env.CONTRACTS_L2_DA_VALIDATOR_ADDR + ) + ).wait(); +} + +// This is used to deploy the diamond and bridge such that they can be upgraded using UpgradeZKChain.sol // This should be deleted after the migration export async function initialPreUpgradeContractsDeployment( deployWallet: Wallet, @@ -128,7 +161,7 @@ export async function initialPreUpgradeContractsDeployment( const 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") { + if (isCurrentNetworkLocal()) { await deployer.deployCreate2Factory({ gasPrice, nonce }); nonce++; @@ -154,8 +187,8 @@ export async function initialPreUpgradeContractsDeployment( // note we should also deploy the old ERC20Bridge here, but we can do that later. // // for Era we first deploy the DiamondProxy manually, set the vars manually, - // // and register it in the system via STM.registerAlreadyDeployedStateTransition and bridgehub.createNewChain(ERA_CHAIN_ID, ..) - // // note we just deploy the STM to get the storedBatchZero + // // and register it in the system via CTM.registerAlreadyDeployedStateTransition and bridgehub.createNewChain(ERA_CHAIN_ID, ..) + // // note we just deploy the CTM to get the storedBatchZero await deployer.deployDiamondProxy(extraFacets, {}); // we have to know the address of the diamond proxy in the mailbox so we separate the deployment @@ -165,7 +198,7 @@ export async function initialPreUpgradeContractsDeployment( ); await deployer.deployStateTransitionDiamondFacets(create2Salt); - await diamondAdminFacet.executeUpgradeNoOverlap(await deployer.upgradeZkSyncHyperchainDiamondCut()); + await diamondAdminFacet.executeUpgradeNoOverlap(await deployer.upgradeZKChainDiamondCut()); return deployer; } @@ -201,15 +234,9 @@ export async function initialEraTestnetDeploymentProcess( "DummyAdminFacetNoOverlap", deployer.addresses.StateTransition.DiamondProxy ); - await diamondAdminFacet.executeUpgradeNoOverlap(await deployer.upgradeZkSyncHyperchainDiamondCut()); - - const stateTransitionManager = deployer.stateTransitionManagerContract(deployer.deployWallet); - const registerData = stateTransitionManager.interface.encodeFunctionData("registerAlreadyDeployedHyperchain", [ - deployer.chainId, - deployer.addresses.StateTransition.DiamondProxy, - ]); - await deployer.executeUpgrade(deployer.addresses.StateTransition.StateTransitionProxy, 0, registerData); - await registerHyperchain(deployer, false, extraFacets, gasPrice, baseTokenName, deployer.chainId.toString()); + await diamondAdminFacet.executeUpgradeNoOverlap(await deployer.upgradeZKChainDiamondCut()); + + await registerZKChain(deployer, false, extraFacets, gasPrice, baseTokenName, deployer.chainId.toString(), true); return deployer; } @@ -252,7 +279,7 @@ export class EraDeployer extends Deployer { await tx.wait(); } - public async upgradeZkSyncHyperchainDiamondCut(extraFacets?: FacetCut[]) { + public async upgradeZKChainDiamondCut(extraFacets?: FacetCut[]) { let facetCuts: FacetCut[] = Object.values( await getCurrentFacetCutsForAdd( this.addresses.StateTransition.AdminFacet, @@ -304,11 +331,14 @@ export class EraDeployer extends Deployer { { chainId: this.chainId, // era chain Id bridgehub: this.addresses.Bridgehub.BridgehubProxy, - stateTransitionManager: this.addresses.StateTransition.StateTransitionProxy, + chainTypeManager: this.addresses.StateTransition.StateTransitionProxy, protocolVersion: CONTRACTS_GENESIS_PROTOCOL_VERSION, admin: this.ownerAddress, validatorTimelock: ADDRESS_ONE, - baseToken: ETH_ADDRESS_IN_CONTRACTS, + baseTokenAssetId: encodeNTVAssetId( + parseInt(process.env.CONTRACTS_L1_CHAIN_ID), + ethers.utils.hexZeroPad(ETH_ADDRESS_IN_CONTRACTS, 32) + ), baseTokenBridge: this.addresses.Bridges.SharedBridgeProxy, storedBatchZero, verifier: this.addresses.StateTransition.Verifier, diff --git a/l1-contracts/src.ts/deploy-token.ts b/l1-contracts/src.ts/deploy-token.ts index 4574db7dd..ea22d7029 100644 --- a/l1-contracts/src.ts/deploy-token.ts +++ b/l1-contracts/src.ts/deploy-token.ts @@ -5,6 +5,10 @@ import type { Contract } from "ethers"; import { parseEther } from "ethers/lib/utils"; import * as fs from "fs"; +import { isZKMode } from "./utils"; + +import { deployContractWithArgs as deployContractWithArgsEVM } from "./deploy-utils"; +import { deployContractWithArgs as deployContractWithArgsZK } from "./deploy-utils-zk"; const DEFAULT_ERC20 = "TestnetERC20Token"; @@ -25,10 +29,17 @@ export async function deployContracts(tokens: TokenDescription[], wallet: Wallet 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++ }); + if (isZKMode()) { + token.contract = await deployContractWithArgsZK(wallet, token.implementation, args, { nonce: nonce++ }); + } else { + token.contract = await deployContractWithArgsEVM(wallet, token.implementation, args, { + gasLimit: 5000000, + nonce: nonce++, + }); + } } await Promise.all(tokens.map(async (token) => token.contract.deployTransaction.wait())); @@ -55,16 +66,17 @@ export async function mintTokens( tokens: TokenDescription[], wallet: Wallet, nonce: number, - mnemonic: string + mnemonics: string[] ): Promise { - const targetAddresses = [wallet.address, ...getTestAddresses(mnemonic)]; + const addressArray = mnemonics.map(getTestAddresses).flat(); + const targetAddresses = [wallet.address, ...addressArray]; 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++ }); + const tx = await token.contract.mint(address, parseEther("300000000000000000000"), { nonce: nonce++ }); promises.push(tx.wait()); } } @@ -113,13 +125,15 @@ export async function deployTokens( } if (token.symbol !== "WETH" && mintTokens) { - await erc20.mint(wallet.address, parseEther("3000000000")); + await erc20.mint(wallet.address, parseEther("3000000000000")); } if (mintTokens) { for (let i = 0; i < 10; ++i) { - const testWalletAddress = Wallet.fromMnemonic(mnemonic as string, "m/44'/60'/0'/0/" + i).address; + const testWalletAddress = mnemonic + ? Wallet.fromMnemonic(mnemonic as string, "m/44'/60'/0'/0/" + i).address + : wallet.address; if (token.symbol !== "WETH") { - await erc20.mint(testWalletAddress, parseEther("3000000000")); + await erc20.mint(testWalletAddress, parseEther("3000000000000")); } } } diff --git a/l1-contracts/src.ts/deploy-utils-zk.ts b/l1-contracts/src.ts/deploy-utils-zk.ts new file mode 100644 index 000000000..de7287f90 --- /dev/null +++ b/l1-contracts/src.ts/deploy-utils-zk.ts @@ -0,0 +1,176 @@ +import * as hardhat from "hardhat"; +import "@nomiclabs/hardhat-ethers"; +import { Deployer as ZkDeployer } from "@matterlabs/hardhat-zksync-deploy"; +// import "@matterlabs/hardhat-zksync-ethers"; +import { ethers } from "ethers"; +import * as path from "path"; +import { IL2ContractDeployerFactory } from "../typechain/IL2ContractDeployerFactory"; +import type { Wallet as ZkWallet } from "zksync-ethers"; +import { utils as zkUtils, ContractFactory } from "zksync-ethers"; +// import { encode } from "querystring"; +// import { web3Provider, web3Url } from "../scripts/utils"; +import { ethersWalletToZkWallet, readBytecode, readContract, readInterface } from "./utils"; + +export const BUILT_IN_ZKSYNC_CREATE2_FACTORY = "0x0000000000000000000000000000000000010000"; + +const contractsHome = process.env.ZKSYNC_HOME ? path.join(process.env.ZKSYNC_HOME as string, "contracts/") : "../"; +const contractArtifactsPath = path.join(contractsHome, "l1-contracts/artifacts-zk/"); +const openzeppelinBeaconProxyArtifactsPath = path.join( + contractArtifactsPath, + "@openzeppelin/contracts-v4/proxy/beacon" +); +const L2_SHARED_BRIDGE_PATH = contractArtifactsPath + "contracts/bridge"; +export const L2_STANDARD_ERC20_PROXY_FACTORY = readContract(openzeppelinBeaconProxyArtifactsPath, "UpgradeableBeacon"); +export const L2_STANDARD_ERC20_IMPLEMENTATION = readContract(L2_SHARED_BRIDGE_PATH, "BridgedStandardERC20"); +export const L2_STANDARD_TOKEN_PROXY = readContract(openzeppelinBeaconProxyArtifactsPath, "BeaconProxy"); + +export const L2_SHARED_BRIDGE_IMPLEMENTATION = readContract(L2_SHARED_BRIDGE_PATH, "L2SharedBridgeLegacy"); +export const L2_SHARED_BRIDGE_PROXY = readContract( + contractArtifactsPath + "@openzeppelin/contracts-v4/proxy/transparent", + "TransparentUpgradeableProxy" +); + +export async function deployViaCreate2( + deployWallet: ZkWallet, + contractName: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + args: any[], + create2Salt: string, + ethTxOptions: ethers.providers.TransactionRequest, + verbose: boolean = true +): Promise<[string, string]> { + return await deployBytecodeViaCreate2(deployWallet, contractName, create2Salt, ethTxOptions, args, verbose); +} + +export async function deployBytecodeViaCreate2( + deployWallet: ZkWallet, + contractName: string, + create2Salt: string, + ethTxOptions: ethers.providers.TransactionRequest, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + args: any[], + verbose: boolean = true +): Promise<[string, string]> { + // [address, txHash] + + const log = (msg: string) => { + if (verbose) { + console.log(msg); + } + }; + log(`Deploying ${contractName}`); + + // @ts-ignore + const zkDeployer = new ZkDeployer(hardhat, deployWallet); + const artifact = await zkDeployer.loadArtifact(contractName); + const factoryDeps = await zkDeployer.extractFactoryDeps(artifact); + + const bytecodeHash = zkUtils.hashBytecode(artifact.bytecode); + const iface = new ethers.utils.Interface(artifact.abi); + const encodedArgs = iface.encodeDeploy(args); + + // The CREATE2Factory has the same interface as the contract deployer + const create2Factory = IL2ContractDeployerFactory.connect(BUILT_IN_ZKSYNC_CREATE2_FACTORY, deployWallet); + const expectedAddress = zkUtils.create2Address(create2Factory.address, bytecodeHash, create2Salt, encodedArgs); + + const deployedBytecodeBefore = await deployWallet.provider.getCode(expectedAddress); + if (ethers.utils.hexDataLength(deployedBytecodeBefore) > 0) { + log(`Contract ${contractName} already deployed`); + return [expectedAddress, ethers.constants.HashZero]; + } + + const encodedTx = create2Factory.interface.encodeFunctionData("create2", [create2Salt, bytecodeHash, encodedArgs]); + + const tx = await deployWallet.sendTransaction({ + data: encodedTx, + to: create2Factory.address, + ...ethTxOptions, + customData: { + factoryDeps: [artifact.bytecode, ...factoryDeps], + }, + }); + const receipt = await tx.wait(); + + const gasUsed = receipt.gasUsed; + log(`${contractName} deployed, gasUsed: ${gasUsed.toString()}`); + + const deployedBytecodeAfter = await deployWallet.provider.getCode(expectedAddress); + if (ethers.utils.hexDataLength(deployedBytecodeAfter) == 0) { + throw new Error(`Failed to deploy ${contractName} bytecode via create2 factory`); + } + + return [expectedAddress, tx.hash]; +} + +export async function deployBytecodeViaCreate2OnPath( + deployWallet: ZkWallet, + contractName: string, + contractPath: string, + create2Salt: string, + ethTxOptions: ethers.providers.TransactionRequest, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + args: any[], + factoryDeps: string[] = [], + verbose: boolean = true +): Promise<[string, string]> { + // [address, txHash] + + const log = (msg: string) => { + if (verbose) { + console.log(msg); + } + }; + + // @ts-ignore + // const zkDeployer = new ZkDeployer(hardhat, deployWallet); + const bytecode = readBytecode(contractPath, contractName); + + const bytecodeHash = zkUtils.hashBytecode(bytecode); + const iface = readInterface(contractPath, contractName); + const encodedArgs = iface.encodeDeploy(args); + + // The CREATE2Factory has the same interface as the contract deployer + const create2Factory = IL2ContractDeployerFactory.connect(BUILT_IN_ZKSYNC_CREATE2_FACTORY, deployWallet); + const expectedAddress = zkUtils.create2Address(create2Factory.address, bytecodeHash, create2Salt, encodedArgs); + + const deployedBytecodeBefore = await deployWallet.provider.getCode(expectedAddress); + if (ethers.utils.hexDataLength(deployedBytecodeBefore) > 0) { + log(`Contract ${contractName} already deployed`); + return [expectedAddress, ethers.constants.HashZero]; + } + + const encodedTx = create2Factory.interface.encodeFunctionData("create2", [create2Salt, bytecodeHash, encodedArgs]); + + const tx = await deployWallet.sendTransaction({ + data: encodedTx, + to: create2Factory.address, + ...ethTxOptions, + customData: { + factoryDeps: [bytecode, ...factoryDeps], + }, + }); + const receipt = await tx.wait(); + + const gasUsed = receipt.gasUsed; + log(`${contractName} deployed, gasUsed: ${gasUsed.toString()}`); + + const deployedBytecodeAfter = await deployWallet.provider.getCode(expectedAddress); + if (ethers.utils.hexDataLength(deployedBytecodeAfter) == 0) { + throw new Error(`Failed to deploy ${contractName} bytecode via create2 factory`); + } + + return [expectedAddress, tx.hash]; +} +export async function deployContractWithArgs( + wallet: ethers.Wallet, + contractName: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + args: any[], + ethTxOptions: ethers.providers.TransactionRequest +) { + const artifact = await hardhat.artifacts.readArtifact(contractName); + const zkWallet = ethersWalletToZkWallet(wallet); + const factory = new ContractFactory(artifact.abi, artifact.bytecode, zkWallet); + + return await factory.deploy(...args, ethTxOptions); +} diff --git a/l1-contracts/src.ts/deploy-utils.ts b/l1-contracts/src.ts/deploy-utils.ts index 71b4d0c31..dcec2c180 100644 --- a/l1-contracts/src.ts/deploy-utils.ts +++ b/l1-contracts/src.ts/deploy-utils.ts @@ -1,9 +1,19 @@ import * as hardhat from "hardhat"; import "@nomiclabs/hardhat-ethers"; import { ethers } from "ethers"; +import { Interface } from "ethers/lib/utils"; import { SingletonFactoryFactory } from "../typechain"; -import { getAddressFromEnv } from "./utils"; +import { + encodeNTVAssetId, + getAddressFromEnv, + getNumberFromEnv, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + DEPLOYER_SYSTEM_CONTRACT_ADDRESS, + ADDRESS_ONE, +} from "./utils"; +import { IBridgehubFactory } from "../typechain/IBridgehubFactory"; +import { IERC20Factory } from "../typechain/IERC20Factory"; export async function deployViaCreate2( deployWallet: ethers.Wallet, @@ -15,15 +25,18 @@ export async function deployViaCreate2( create2FactoryAddress: string, verbose: boolean = true, // eslint-disable-next-line @typescript-eslint/no-explicit-any - libraries?: any + libraries?: any, + bytecode?: ethers.utils.BytesLike ): Promise<[string, string]> { // [address, txHash] - const contractFactory = await hardhat.ethers.getContractFactory(contractName, { - signer: deployWallet, - libraries, - }); - const bytecode = contractFactory.getDeployTransaction(...args, ethTxOptions).data; + if (!bytecode) { + const contractFactory = await hardhat.ethers.getContractFactory(contractName, { + signer: deployWallet, + libraries, + }); + bytecode = contractFactory.getDeployTransaction(...args, ethTxOptions).data; + } return await deployBytecodeViaCreate2( deployWallet, @@ -83,10 +96,111 @@ export async function deployBytecodeViaCreate2( return [expectedAddress, tx.hash]; } +export async function deployContractWithArgs( + wallet: ethers.Wallet, + contractName: string, + // eslint-disable-next-line + args: any[], + ethTxOptions: ethers.providers.TransactionRequest +) { + const factory = await hardhat.ethers.getContractFactory(contractName, wallet); + + return await factory.deploy(...args, ethTxOptions); +} + +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 async function create2DeployFromL1( + chainId: ethers.BigNumberish, + wallet: ethers.Wallet, + bytecode: ethers.BytesLike, + constructor: ethers.BytesLike, + create2Salt: ethers.BytesLike, + l2GasLimit: ethers.BigNumberish, + gasPrice?: ethers.BigNumberish, + extraFactoryDeps?: ethers.BytesLike[], + bridgehubAddress?: string, + assetRouterAddress?: string +) { + bridgehubAddress = bridgehubAddress ?? deployedAddressesFromEnv().Bridgehub.BridgehubProxy; + const bridgehub = IBridgehubFactory.connect(bridgehubAddress, wallet); + + const deployerSystemContracts = new Interface(hardhat.artifacts.readArtifactSync("IContractDeployer").abi); + const bytecodeHash = hashL2Bytecode(bytecode); + const calldata = deployerSystemContracts.encodeFunctionData("create2", [create2Salt, bytecodeHash, constructor]); + gasPrice ??= await bridgehub.provider.getGasPrice(); + const expectedCost = await bridgehub.l2TransactionBaseCost( + chainId, + gasPrice, + l2GasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA + ); + + const baseTokenAddress = await bridgehub.baseToken(chainId); + const baseTokenBridge = assetRouterAddress ?? deployedAddressesFromEnv().Bridges.SharedBridgeProxy; + const ethIsBaseToken = ADDRESS_ONE == baseTokenAddress; + + if (!ethIsBaseToken) { + const baseToken = IERC20Factory.connect(baseTokenAddress, wallet); + const tx = await baseToken.approve(baseTokenBridge, expectedCost); + await tx.wait(); + } + 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 interface DeployedAddresses { Bridgehub: { BridgehubProxy: string; BridgehubImplementation: string; + CTMDeploymentTrackerImplementation: string; + CTMDeploymentTrackerProxy: string; + MessageRootImplementation: string; + MessageRootProxy: string; }; StateTransition: { StateTransitionProxy: string; @@ -103,27 +217,55 @@ export interface DeployedAddresses { DiamondProxy: string; }; Bridges: { + L1NullifierImplementation: string; + L1NullifierProxy: string; ERC20BridgeImplementation: string; ERC20BridgeProxy: string; SharedBridgeImplementation: string; SharedBridgeProxy: string; L2SharedBridgeProxy: string; L2SharedBridgeImplementation: string; + L2LegacySharedBridgeProxy: string; + L2LegacySharedBridgeImplementation: string; + L2NativeTokenVaultImplementation: string; + L2NativeTokenVaultProxy: string; + NativeTokenVaultImplementation: string; + NativeTokenVaultProxy: string; + BridgedStandardERC20Implementation: string; + BridgedTokenBeacon: string; }; + BaseTokenAssetId: string; BaseToken: string; TransparentProxyAdmin: string; + L2ProxyAdmin: string; Governance: string; ChainAdmin: string; BlobVersionedHashRetriever: string; ValidatorTimeLock: string; + RollupL1DAValidator: string; + ValidiumL1DAValidator: string; + RelayedSLDAValidator: string; Create2Factory: string; } export function deployedAddressesFromEnv(): DeployedAddresses { + let baseTokenAssetId = "0"; + try { + baseTokenAssetId = getAddressFromEnv("CONTRACTS_BASE_TOKEN_ASSET_ID"); + } catch (error) { + baseTokenAssetId = encodeNTVAssetId( + parseInt(getNumberFromEnv("ETH_CLIENT_CHAIN_ID")), + ethers.utils.hexZeroPad(getAddressFromEnv("CONTRACTS_BASE_TOKEN_ADDR"), 32) + ); + } return { Bridgehub: { BridgehubProxy: getAddressFromEnv("CONTRACTS_BRIDGEHUB_PROXY_ADDR"), BridgehubImplementation: getAddressFromEnv("CONTRACTS_BRIDGEHUB_IMPL_ADDR"), + CTMDeploymentTrackerImplementation: getAddressFromEnv("CONTRACTS_CTM_DEPLOYMENT_TRACKER_IMPL_ADDR"), + CTMDeploymentTrackerProxy: getAddressFromEnv("CONTRACTS_CTM_DEPLOYMENT_TRACKER_PROXY_ADDR"), + MessageRootImplementation: getAddressFromEnv("CONTRACTS_MESSAGE_ROOT_IMPL_ADDR"), + MessageRootProxy: getAddressFromEnv("CONTRACTS_MESSAGE_ROOT_PROXY_ADDR"), }, StateTransition: { StateTransitionProxy: getAddressFromEnv("CONTRACTS_STATE_TRANSITION_PROXY_ADDR"), @@ -140,15 +282,30 @@ export function deployedAddressesFromEnv(): DeployedAddresses { DiamondProxy: getAddressFromEnv("CONTRACTS_DIAMOND_PROXY_ADDR"), }, Bridges: { + L1NullifierImplementation: getAddressFromEnv("CONTRACTS_L1_NULLIFIER_IMPL_ADDR"), + L1NullifierProxy: getAddressFromEnv("CONTRACTS_L1_NULLIFIER_PROXY_ADDR"), 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"), + L2NativeTokenVaultImplementation: getAddressFromEnv("CONTRACTS_L2_NATIVE_TOKEN_VAULT_IMPL_ADDR"), + L2NativeTokenVaultProxy: getAddressFromEnv("CONTRACTS_L2_NATIVE_TOKEN_VAULT_PROXY_ADDR"), L2SharedBridgeImplementation: getAddressFromEnv("CONTRACTS_L2_SHARED_BRIDGE_IMPL_ADDR"), L2SharedBridgeProxy: getAddressFromEnv("CONTRACTS_L2_SHARED_BRIDGE_ADDR"), + L2LegacySharedBridgeProxy: getAddressFromEnv("CONTRACTS_L2_LEGACY_SHARED_BRIDGE_ADDR"), + L2LegacySharedBridgeImplementation: getAddressFromEnv("CONTRACTS_L2_LEGACY_SHARED_BRIDGE_IMPL_ADDR"), + NativeTokenVaultImplementation: getAddressFromEnv("CONTRACTS_L1_NATIVE_TOKEN_VAULT_IMPL_ADDR"), + NativeTokenVaultProxy: getAddressFromEnv("CONTRACTS_L1_NATIVE_TOKEN_VAULT_PROXY_ADDR"), + BridgedStandardERC20Implementation: getAddressFromEnv("CONTRACTS_L1_BRIDGED_STANDARD_ERC20_IMPL_ADDR"), + BridgedTokenBeacon: getAddressFromEnv("CONTRACTS_L1_BRIDGED_TOKEN_BEACON_ADDR"), }, + RollupL1DAValidator: getAddressFromEnv("CONTRACTS_L1_ROLLUP_DA_VALIDATOR"), + ValidiumL1DAValidator: getAddressFromEnv("CONTRACTS_L1_VALIDIUM_DA_VALIDATOR"), + RelayedSLDAValidator: getAddressFromEnv("CONTRACTS_L1_RELAYED_SL_DA_VALIDATOR"), BaseToken: getAddressFromEnv("CONTRACTS_BASE_TOKEN_ADDR"), + BaseTokenAssetId: baseTokenAssetId, TransparentProxyAdmin: getAddressFromEnv("CONTRACTS_TRANSPARENT_PROXY_ADMIN_ADDR"), + L2ProxyAdmin: getAddressFromEnv("CONTRACTS_L2_PROXY_ADMIN_ADDR"), Create2Factory: getAddressFromEnv("CONTRACTS_CREATE2_FACTORY_ADDR"), BlobVersionedHashRetriever: getAddressFromEnv("CONTRACTS_BLOB_VERSIONED_HASH_RETRIEVER_ADDR"), ValidatorTimeLock: getAddressFromEnv("CONTRACTS_VALIDATOR_TIMELOCK_ADDR"), diff --git a/l1-contracts/src.ts/deploy.ts b/l1-contracts/src.ts/deploy.ts index 3ee3bb07b..4747418c7 100644 --- a/l1-contracts/src.ts/deploy.ts +++ b/l1-contracts/src.ts/deploy.ts @@ -1,16 +1,38 @@ import * as hardhat from "hardhat"; import "@nomiclabs/hardhat-ethers"; +// import "@matterlabs/hardhat-zksync-ethers"; -import type { BigNumberish, providers, Signer, Wallet } from "ethers"; +import type { BigNumberish, providers, Signer, Wallet, Contract } from "ethers"; import { ethers } from "ethers"; import { hexlify, Interface } from "ethers/lib/utils"; +import { Wallet as ZkWallet, ContractFactory as ZkContractFactory } from "zksync-ethers"; + import type { DeployedAddresses } from "./deploy-utils"; -import { deployedAddressesFromEnv, deployBytecodeViaCreate2, deployViaCreate2 } from "./deploy-utils"; +import { + deployedAddressesFromEnv, + deployBytecodeViaCreate2 as deployBytecodeViaCreate2EVM, + deployViaCreate2 as deployViaCreate2EVM, + create2DeployFromL1, +} from "./deploy-utils"; +import { + deployViaCreate2 as deployViaCreate2Zk, + BUILT_IN_ZKSYNC_CREATE2_FACTORY, + L2_STANDARD_ERC20_PROXY_FACTORY, + L2_STANDARD_ERC20_IMPLEMENTATION, + L2_STANDARD_TOKEN_PROXY, + L2_SHARED_BRIDGE_IMPLEMENTATION, + L2_SHARED_BRIDGE_PROXY, + // deployBytecodeViaCreate2OnPath, + // L2_SHARED_BRIDGE_PATH, +} from "./deploy-utils-zk"; import { packSemver, readBatchBootloaderBytecode, readSystemContractsBytecode, unpackStringSemVer, + SYSTEM_CONFIG, + // web3Provider, + // web3Url, } from "../scripts/utils"; import { getTokens } from "./deploy-token"; import { @@ -21,16 +43,25 @@ import { PubdataPricingMode, hashL2Bytecode, DIAMOND_CUT_DATA_ABI_STRING, + FIXED_FORCE_DEPLOYMENTS_DATA_ABI_STRING, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA, compileInitialCutHash, + readBytecode, + applyL1ToL2Alias, + BRIDGEHUB_CTM_ASSET_DATA_ABI_STRING, + encodeNTVAssetId, + computeL2Create2Address, + priorityTxMaxGasLimit, + isCurrentNetworkLocal, } from "./utils"; -import { IBridgehubFactory } from "../typechain/IBridgehubFactory"; +import type { ChainAdminCall } from "./utils"; import { IGovernanceFactory } from "../typechain/IGovernanceFactory"; -import { IStateTransitionManagerFactory } from "../typechain/IStateTransitionManagerFactory"; import { ITransparentUpgradeableProxyFactory } from "../typechain/ITransparentUpgradeableProxyFactory"; import { ProxyAdminFactory } from "../typechain/ProxyAdminFactory"; -import { IZkSyncHyperchainFactory } from "../typechain/IZkSyncHyperchainFactory"; -import { L1SharedBridgeFactory } from "../typechain/L1SharedBridgeFactory"; +import { IZKChainFactory } from "../typechain/IZKChainFactory"; +import { L1AssetRouterFactory } from "../typechain/L1AssetRouterFactory"; +import { L1NullifierDevFactory } from "../typechain/L1NullifierDevFactory"; import { SingletonFactoryFactory } from "../typechain/SingletonFactoryFactory"; import { ValidatorTimelockFactory } from "../typechain/ValidatorTimelockFactory"; @@ -38,19 +69,29 @@ import { ValidatorTimelockFactory } from "../typechain/ValidatorTimelockFactory" import type { FacetCut } from "./diamondCut"; import { getCurrentFacetCutsForAdd } from "./diamondCut"; -import { ChainAdminFactory, ERC20Factory, StateTransitionManagerFactory } from "../typechain"; -import type { Contract, Overrides } from "@ethersproject/contracts"; +import { BridgehubFactory, ChainAdminFactory, ERC20Factory, ChainTypeManagerFactory } from "../typechain"; + +import { IL1AssetRouterFactory } from "../typechain/IL1AssetRouterFactory"; +import { IL1NativeTokenVaultFactory } from "../typechain/IL1NativeTokenVaultFactory"; +import { IL1NullifierFactory } from "../typechain/IL1NullifierFactory"; +import { ICTMDeploymentTrackerFactory } from "../typechain/ICTMDeploymentTrackerFactory"; +import { TestnetERC20TokenFactory } from "../typechain/TestnetERC20TokenFactory"; + +import { RollupL1DAValidatorFactory } from "../../da-contracts/typechain/RollupL1DAValidatorFactory"; let L2_BOOTLOADER_BYTECODE_HASH: string; let L2_DEFAULT_ACCOUNT_BYTECODE_HASH: string; export interface DeployerConfig { - deployWallet: Wallet; + deployWallet: Wallet | ZkWallet; addresses?: DeployedAddresses; ownerAddress?: string; verbose?: boolean; bootloaderBytecodeHash?: string; defaultAccountBytecodeHash?: string; + deployedLogPrefix?: string; + l1Deployer?: Deployer; + l1ChainId?: string; } export interface Operation { @@ -63,10 +104,16 @@ export type OperationOrString = Operation | string; export class Deployer { public addresses: DeployedAddresses; - public deployWallet: Wallet; + public deployWallet: Wallet | ZkWallet; public verbose: boolean; public chainId: number; + public l1ChainId: number; public ownerAddress: string; + public deployedLogPrefix: string; + + public isZkMode(): boolean { + return this.deployWallet instanceof ZkWallet; + } constructor(config: DeployerConfig) { this.deployWallet = config.deployWallet; @@ -80,9 +127,11 @@ export class Deployer { : hexlify(hashL2Bytecode(readSystemContractsBytecode("DefaultAccount"))); this.ownerAddress = config.ownerAddress != null ? config.ownerAddress : this.deployWallet.address; this.chainId = parseInt(process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID!); + this.l1ChainId = parseInt(config.l1ChainId || getNumberFromEnv("ETH_CLIENT_CHAIN_ID")); + this.deployedLogPrefix = config.deployedLogPrefix ?? "CONTRACTS"; } - public async initialZkSyncHyperchainDiamondCut(extraFacets?: FacetCut[], compareDiamondCutHash: boolean = false) { + public async initialZkSyncZKChainDiamondCut(extraFacets?: FacetCut[], compareDiamondCutHash: boolean = false) { let facetCuts: FacetCut[] = Object.values( await getCurrentFacetCutsForAdd( this.addresses.StateTransition.AdminFacet, @@ -118,25 +167,74 @@ export class Deployer { ); console.log(`Diamond cut hash: ${hash}`); - const stm = StateTransitionManagerFactory.connect( + const ctm = ChainTypeManagerFactory.connect( this.addresses.StateTransition.StateTransitionProxy, this.deployWallet ); - const hashFromSTM = await stm.initialCutHash(); - if (hash != hashFromSTM) { - throw new Error(`Has from STM ${hashFromSTM} does not match the computed hash ${hash}`); + const hashFromCTM = await ctm.initialCutHash(); + if (hash != hashFromCTM) { + throw new Error(`Has from CTM ${hashFromCTM} does not match the computed hash ${hash}`); } } return diamondCut; } + public async genesisForceDeploymentsData() { + let bridgehubZKBytecode = ethers.constants.HashZero; + let assetRouterZKBytecode = ethers.constants.HashZero; + let nativeTokenVaultZKBytecode = ethers.constants.HashZero; + let l2TokenProxyBytecodeHash = ethers.constants.HashZero; + let messageRootZKBytecode = ethers.constants.HashZero; + if (process.env.CHAIN_ETH_NETWORK != "hardhat") { + bridgehubZKBytecode = readBytecode("./artifacts-zk/contracts/bridgehub", "Bridgehub"); + assetRouterZKBytecode = readBytecode("./artifacts-zk/contracts/bridge/asset-router", "L2AssetRouter"); + nativeTokenVaultZKBytecode = readBytecode("./artifacts-zk/contracts/bridge/ntv", "L2NativeTokenVault"); + messageRootZKBytecode = readBytecode("./artifacts-zk/contracts/bridgehub", "MessageRoot"); + const l2TokenProxyBytecode = readBytecode( + "./artifacts-zk/@openzeppelin/contracts-v4/proxy/beacon", + "BeaconProxy" + ); + l2TokenProxyBytecodeHash = ethers.utils.hexlify(hashL2Bytecode(l2TokenProxyBytecode)); + } + const fixedForceDeploymentsData = { + l1ChainId: getNumberFromEnv("ETH_CLIENT_CHAIN_ID"), + eraChainId: getNumberFromEnv("CONTRACTS_ERA_CHAIN_ID"), + l1AssetRouter: this.addresses.Bridges.SharedBridgeProxy, + l2TokenProxyBytecodeHash: l2TokenProxyBytecodeHash, + aliasedL1Governance: applyL1ToL2Alias(this.addresses.Governance), + maxNumberOfZKChains: getNumberFromEnv("CONTRACTS_MAX_NUMBER_OF_ZK_CHAINS"), + bridgehubBytecodeHash: ethers.utils.hexlify(hashL2Bytecode(bridgehubZKBytecode)), + l2AssetRouterBytecodeHash: ethers.utils.hexlify(hashL2Bytecode(assetRouterZKBytecode)), + l2NtvBytecodeHash: ethers.utils.hexlify(hashL2Bytecode(nativeTokenVaultZKBytecode)), + messageRootBytecodeHash: ethers.utils.hexlify(hashL2Bytecode(messageRootZKBytecode)), + l2SharedBridgeLegacyImpl: ethers.constants.AddressZero, + l2BridgedStandardERC20Impl: ethers.constants.AddressZero, + }; + + return ethers.utils.defaultAbiCoder.encode([FIXED_FORCE_DEPLOYMENTS_DATA_ABI_STRING], [fixedForceDeploymentsData]); + } + + public async updateCreate2FactoryZkMode() { + if (!this.isZkMode()) { + throw new Error("`updateCreate2FactoryZkMode` should be only called in Zk mode"); + } + + console.log("Create2Factory is built into zkSync and so won't be deployed separately"); + console.log(`CONTRACTS_CREATE2_FACTORY_ADDR=${BUILT_IN_ZKSYNC_CREATE2_FACTORY}`); + this.addresses.Create2Factory = BUILT_IN_ZKSYNC_CREATE2_FACTORY; + } + public async deployCreate2Factory(ethTxOptions?: ethers.providers.TransactionRequest) { if (this.verbose) { console.log("Deploying Create2 factory"); } + if (this.isZkMode()) { + throw new Error("Create2Factory is built into zkSync and should not be deployed separately"); + } + const contractFactory = await hardhat.ethers.getContractFactory("SingletonFactory", { signer: this.deployWallet, }); @@ -146,7 +244,7 @@ export class Deployer { if (this.verbose) { console.log(`CONTRACTS_CREATE2_FACTORY_ADDR=${create2Factory.address}`); - console.log(`Create2 factory deployed, gasUsed: ${rec.gasUsed.toString()}`); + console.log(`Create2 factory deployed, gasUsed: ${rec.gasUsed.toString()}, ${rec.transactionHash}`); } this.addresses.Create2Factory = create2Factory.address; @@ -159,9 +257,28 @@ export class Deployer { create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest, // eslint-disable-next-line @typescript-eslint/no-explicit-any - libraries?: any + libraries?: any, + bytecode?: ethers.utils.BytesLike ) { - const result = await deployViaCreate2( + if (this.isZkMode()) { + if (bytecode != null) { + return ADDRESS_ONE; + // note providing bytecode is only for da-contracts on L1, we can skip it here + } + const result = await deployViaCreate2Zk( + this.deployWallet as ZkWallet, + contractName, + args, + create2Salt, + ethTxOptions, + this.verbose + ); + return result[0]; + } + + // For L1 deployments we try to use constant gas limit + ethTxOptions.gasLimit ??= 10_000_000; + const result = await deployViaCreate2EVM( this.deployWallet, contractName, args, @@ -169,18 +286,35 @@ export class Deployer { ethTxOptions, this.addresses.Create2Factory, this.verbose, - libraries + libraries, + bytecode ); return result[0]; } + public async loadFromDAFolder(contractName: string) { + let factory; + if (contractName == "RollupL1DAValidator") { + factory = new RollupL1DAValidatorFactory(this.deployWallet); + } else { + throw new Error(`Unknown DA contract name ${contractName}`); + } + return factory.getDeployTransaction().data; + } + private async deployBytecodeViaCreate2( contractName: string, bytecode: ethers.BytesLike, create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest ): Promise { - const result = await deployBytecodeViaCreate2( + if (this.isZkMode()) { + throw new Error("`deployBytecodeViaCreate2` not supported in zkMode"); + } + + ethTxOptions.gasLimit ??= 10_000_000; + + const result = await deployBytecodeViaCreate2EVM( this.deployWallet, contractName, bytecode, @@ -194,7 +328,6 @@ export class Deployer { } public async deployGovernance(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; const contractAddress = await this.deployViaCreate2( "Governance", // TODO: load parameters from config @@ -212,9 +345,21 @@ export class Deployer { public async deployChainAdmin(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { ethTxOptions.gasLimit ??= 10_000_000; + // Firstly, we deploy the access control restriction for the chain admin + const accessControlRestriction = await this.deployViaCreate2( + "AccessControlRestriction", + [0, this.ownerAddress], + create2Salt, + ethTxOptions + ); + if (this.verbose) { + console.log(`CONTRACTS_ACCESS_CONTROL_RESTRICTION_ADDR=${accessControlRestriction}`); + } + + // Then we deploy the ChainAdmin contract itself const contractAddress = await this.deployViaCreate2( "ChainAdmin", - [this.ownerAddress, ethers.constants.AddressZero], + [[accessControlRestriction]], create2Salt, ethTxOptions ); @@ -224,38 +369,44 @@ export class Deployer { this.addresses.ChainAdmin = contractAddress; } - public async deployBridgehubImplementation(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2("Bridgehub", [], create2Salt, ethTxOptions); - - if (this.verbose) { - 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"); } // Note: we cannot deploy using Create2, as the owner of the ProxyAdmin is msg.sender - const contractFactory = await hardhat.ethers.getContractFactory( - "@openzeppelin/contracts-v4/proxy/transparent/ProxyAdmin.sol:ProxyAdmin", - { - signer: this.deployWallet, - } - ); - - const proxyAdmin = await contractFactory.deploy(...[ethTxOptions]); - const rec = await proxyAdmin.deployTransaction.wait(); + let proxyAdmin; + let rec; + + if (this.isZkMode()) { + // @ts-ignore + // TODO try to make it work with zksync ethers + const zkWal = this.deployWallet as ZkWallet; + // TODO: this is a hack + const tmpContractFactory = await hardhat.ethers.getContractFactory( + "@openzeppelin/contracts-v4/proxy/transparent/ProxyAdmin.sol:ProxyAdmin", + { + signer: this.deployWallet, + } + ); + const contractFactory = new ZkContractFactory(tmpContractFactory.interface, tmpContractFactory.bytecode, zkWal); + proxyAdmin = await contractFactory.deploy(...[ethTxOptions]); + rec = await proxyAdmin.deployTransaction.wait(); + } else { + ethTxOptions.gasLimit ??= 10_000_000; + const contractFactory = await hardhat.ethers.getContractFactory( + "@openzeppelin/contracts-v4/proxy/transparent/ProxyAdmin.sol:ProxyAdmin", + { + signer: this.deployWallet, + } + ); + proxyAdmin = await contractFactory.deploy(...[ethTxOptions]); + rec = await proxyAdmin.deployTransaction.wait(); + } if (this.verbose) { console.log( - `Proxy admin deployed, gasUsed: ${rec.gasUsed.toString()}, tx hash ${rec.transactionHash}, expected address: ${ - proxyAdmin.address - }` + `Proxy admin deployed, gasUsed: ${rec.gasUsed.toString()}, tx hash ${rec.transactionHash}, expected address: + ${proxyAdmin.address}` ); console.log(`CONTRACTS_TRANSPARENT_PROXY_ADMIN_ADDR=${proxyAdmin.address}`); } @@ -267,19 +418,31 @@ export class Deployer { if (this.verbose) { console.log( - `ProxyAdmin ownership transferred to Governance in tx ${ - receipt.transactionHash - }, gas used: ${receipt.gasUsed.toString()}` + `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; + public async deployBridgehubImplementation(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + const contractAddress = await this.deployViaCreate2( + "Bridgehub", + [await this.getL1ChainId(), this.addresses.Governance, getNumberFromEnv("CONTRACTS_MAX_NUMBER_OF_ZK_CHAINS")], + create2Salt, + ethTxOptions + ); + + if (this.verbose) { + console.log(`CONTRACTS_BRIDGEHUB_IMPL_ADDR=${contractAddress}`); + } + + this.addresses.Bridgehub.BridgehubImplementation = contractAddress; + } + public async deployBridgehubProxy(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { const bridgehub = new Interface(hardhat.artifacts.readArtifactSync("Bridgehub").abi); - const initCalldata = bridgehub.encodeFunctionData("initialize", [this.ownerAddress]); + const initCalldata = bridgehub.encodeFunctionData("initialize", [this.addresses.Governance]); const contractAddress = await this.deployViaCreate2( "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol:TransparentUpgradeableProxy", @@ -295,16 +458,52 @@ export class Deployer { this.addresses.Bridgehub.BridgehubProxy = contractAddress; } - public async deployStateTransitionManagerImplementation( + public async deployMessageRootImplementation(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + const contractAddress = await this.deployViaCreate2( + "MessageRoot", + [this.addresses.Bridgehub.BridgehubProxy], + create2Salt, + ethTxOptions + ); + + if (this.verbose) { + console.log(`CONTRACTS_MESSAGE_ROOT_IMPL_ADDR=${contractAddress}`); + } + + this.addresses.Bridgehub.MessageRootImplementation = contractAddress; + } + + public async deployMessageRootProxy(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + const messageRoot = new Interface(hardhat.artifacts.readArtifactSync("MessageRoot").abi); + + const initCalldata = messageRoot.encodeFunctionData("initialize"); + + const contractAddress = await this.deployViaCreate2( + "TransparentUpgradeableProxy", + [this.addresses.Bridgehub.MessageRootImplementation, this.addresses.TransparentProxyAdmin, initCalldata], + create2Salt, + ethTxOptions + ); + + if (this.verbose) { + console.log(`CONTRACTS_MESSAGE_ROOT_PROXY_ADDR=${contractAddress}`); + } + + this.addresses.Bridgehub.MessageRootProxy = contractAddress; + } + + public async deployChainTypeManagerImplementation( create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest ) { - ethTxOptions.gasLimit ??= 10_000_000; const contractAddress = await this.deployViaCreate2( - "StateTransitionManager", - [this.addresses.Bridgehub.BridgehubProxy, getNumberFromEnv("CONTRACTS_MAX_NUMBER_OF_HYPERCHAINS")], + "ChainTypeManager", + [this.addresses.Bridgehub.BridgehubProxy], create2Salt, - ethTxOptions + { + ...ethTxOptions, + gasLimit: 20_000_000, + } ); if (this.verbose) { @@ -314,29 +513,29 @@ export class Deployer { this.addresses.StateTransition.StateTransitionImplementation = contractAddress; } - public async deployStateTransitionManagerProxy( + public async deployChainTypeManagerProxy( 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.initialZkSyncHyperchainDiamondCut(extraFacets); + const diamondCut = await this.initialZkSyncZKChainDiamondCut(extraFacets); const protocolVersion = packSemver(...unpackStringSemVer(process.env.CONTRACTS_GENESIS_PROTOCOL_SEMANTIC_VERSION)); - const stateTransitionManager = new Interface(hardhat.artifacts.readArtifactSync("StateTransitionManager").abi); - + const chainTypeManager = new Interface(hardhat.artifacts.readArtifactSync("ChainTypeManager").abi); + const forceDeploymentsData = await this.genesisForceDeploymentsData(); const chainCreationParams = { genesisUpgrade: this.addresses.StateTransition.GenesisUpgrade, genesisBatchHash, genesisIndexRepeatedStorageChanges: genesisRollupLeafIndex, genesisBatchCommitment, diamondCut, + forceDeploymentsData, }; - const initCalldata = stateTransitionManager.encodeFunctionData("initialize", [ + const initCalldata = chainTypeManager.encodeFunctionData("initialize", [ { owner: this.addresses.Governance, validatorTimelock: this.addresses.ValidatorTimeLock, @@ -357,7 +556,7 @@ export class Deployer { ); if (this.verbose) { - console.log(`StateTransitionManagerProxy deployed, with protocol version: ${protocolVersion}`); + console.log(`ChainTypeManagerProxy deployed, with protocol version: ${protocolVersion}`); console.log(`CONTRACTS_STATE_TRANSITION_PROXY_ADDR=${contractAddress}`); } @@ -365,8 +564,12 @@ export class Deployer { } public async deployAdminFacet(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2("AdminFacet", [], create2Salt, ethTxOptions); + const contractAddress = await this.deployViaCreate2( + "AdminFacet", + [await this.getL1ChainId(), ethers.constants.AddressZero], + create2Salt, + ethTxOptions + ); if (this.verbose) { console.log(`CONTRACTS_ADMIN_FACET_ADDR=${contractAddress}`); @@ -376,9 +579,13 @@ export class Deployer { } public async deployMailboxFacet(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; const eraChainId = getNumberFromEnv("CONTRACTS_ERA_CHAIN_ID"); - const contractAddress = await this.deployViaCreate2("MailboxFacet", [eraChainId], create2Salt, ethTxOptions); + const contractAddress = await this.deployViaCreate2( + "MailboxFacet", + [eraChainId, await this.getL1ChainId()], + create2Salt, + ethTxOptions + ); if (this.verbose) { console.log(`Mailbox deployed with era chain id: ${eraChainId}`); @@ -389,8 +596,12 @@ export class Deployer { } public async deployExecutorFacet(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2("ExecutorFacet", [], create2Salt, ethTxOptions); + const contractAddress = await this.deployViaCreate2( + "ExecutorFacet", + [await this.getL1ChainId()], + create2Salt, + ethTxOptions + ); if (this.verbose) { console.log(`CONTRACTS_EXECUTOR_FACET_ADDR=${contractAddress}`); @@ -400,7 +611,6 @@ export class Deployer { } public async deployGettersFacet(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; const contractAddress = await this.deployViaCreate2("GettersFacet", [], create2Salt, ethTxOptions); if (this.verbose) { @@ -411,8 +621,6 @@ export class Deployer { } public async deployVerifier(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; - let contractAddress: string; if (process.env.CHAIN_ETH_NETWORK === "mainnet") { @@ -433,10 +641,15 @@ export class Deployer { ethTxOptions: ethers.providers.TransactionRequest, dummy: boolean = false ) { - ethTxOptions.gasLimit ??= 10_000_000; + const eraChainId = getNumberFromEnv("CONTRACTS_ERA_CHAIN_ID"); const contractAddress = await this.deployViaCreate2( dummy ? "DummyL1ERC20Bridge" : "L1ERC20Bridge", - [this.addresses.Bridges.SharedBridgeProxy], + [ + this.addresses.Bridges.L1NullifierProxy, + this.addresses.Bridges.SharedBridgeProxy, + this.addresses.Bridges.NativeTokenVaultProxy, + eraChainId, + ], create2Salt, ethTxOptions ); @@ -449,24 +662,11 @@ export class Deployer { } public async setParametersSharedBridge() { - const sharedBridge = L1SharedBridgeFactory.connect(this.addresses.Bridges.SharedBridgeProxy, this.deployWallet); + const sharedBridge = L1AssetRouterFactory.connect(this.addresses.Bridges.SharedBridgeProxy, this.deployWallet); const data1 = sharedBridge.interface.encodeFunctionData("setL1Erc20Bridge", [ this.addresses.Bridges.ERC20BridgeProxy, ]); - const data2 = sharedBridge.interface.encodeFunctionData("setEraPostDiamondUpgradeFirstBatch", [ - process.env.CONTRACTS_ERA_POST_DIAMOND_UPGRADE_FIRST_BATCH ?? 1, - ]); - const data3 = sharedBridge.interface.encodeFunctionData("setEraPostLegacyBridgeUpgradeFirstBatch", [ - process.env.CONTRACTS_ERA_POST_LEGACY_BRIDGE_UPGRADE_FIRST_BATCH ?? 1, - ]); - const data4 = sharedBridge.interface.encodeFunctionData("setEraLegacyBridgeLastDepositTime", [ - process.env.CONTRACTS_ERA_LEGACY_UPGRADE_LAST_DEPOSIT_BATCH ?? 1, - process.env.CONTRACTS_ERA_LEGACY_UPGRADE_LAST_DEPOSIT_TX_NUMBER ?? 0, - ]); await this.executeUpgrade(this.addresses.Bridges.SharedBridgeProxy, 0, data1); - await this.executeUpgrade(this.addresses.Bridges.SharedBridgeProxy, 0, data2); - await this.executeUpgrade(this.addresses.Bridges.SharedBridgeProxy, 0, data3); - await this.executeUpgrade(this.addresses.Bridges.SharedBridgeProxy, 0, data4); if (this.verbose) { console.log("Shared bridge updated with ERC20Bridge address"); } @@ -479,14 +679,16 @@ export class Deployer { // eslint-disable-next-line @typescript-eslint/no-explicit-any fargs: any[], value: BigNumberish, - overrides?: Overrides, + overrides?: ethers.providers.TransactionRequest, printOperation: boolean = false ): Promise { if (useGovernance) { const cdata = contract.interface.encodeFunctionData(fname, fargs); - return this.executeUpgrade(contract.address, value, cdata, printOperation); + return this.executeUpgrade(contract.address, value, cdata, overrides, printOperation); } else { - const tx: ethers.ContractTransaction = await contract[fname](...fargs, ...(overrides ? [overrides] : [])); + overrides = overrides || {}; + overrides.value = value; + const tx: ethers.ContractTransaction = await contract[fname](...fargs, overrides); return await tx.wait(); } } @@ -496,6 +698,7 @@ export class Deployer { targetAddress: string, value: BigNumberish, callData: string, + ethTxOptions?: ethers.providers.TransactionRequest, printOperation: boolean = false ) { const governance = IGovernanceFactory.connect(this.addresses.Governance, this.deployWallet); @@ -521,7 +724,7 @@ export class Deployer { if (this.verbose) { console.log("Upgrade scheduled"); } - const executeTX = await governance.execute(operation, { value: value }); + const executeTX = await governance.execute(operation, { ...ethTxOptions, value: value }); const receipt = await executeTX.wait(); if (this.verbose) { console.log( @@ -534,10 +737,63 @@ export class Deployer { return receipt; } + /// this should be only use for local testing + public async executeUpgradeOnL2( + chainId: string, + targetAddress: string, + gasPrice: BigNumberish, + callData: string, + l2GasLimit: BigNumberish, + ethTxOptions?: ethers.providers.TransactionRequest, + printOperation: boolean = false + ) { + const bridgehub = this.bridgehubContract(this.deployWallet); + const value = await bridgehub.l2TransactionBaseCost( + chainId, + gasPrice, + l2GasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA + ); + const baseTokenAddress = await bridgehub.baseToken(chainId); + const ethIsBaseToken = baseTokenAddress == ADDRESS_ONE; + if (!ethIsBaseToken) { + const baseToken = TestnetERC20TokenFactory.connect(baseTokenAddress, this.deployWallet); + await (await baseToken.transfer(this.addresses.Governance, value)).wait(); + await this.executeUpgrade( + baseTokenAddress, + 0, + baseToken.interface.encodeFunctionData("approve", [this.addresses.Bridges.SharedBridgeProxy, value]) + ); + } + const l1Calldata = bridgehub.interface.encodeFunctionData("requestL2TransactionDirect", [ + { + chainId, + l2Contract: targetAddress, + mintValue: value, + l2Value: 0, + l2Calldata: callData, + l2GasLimit: l2GasLimit, + l2GasPerPubdataByteLimit: SYSTEM_CONFIG.requiredL2GasPricePerPubdata, + factoryDeps: [], + refundRecipient: this.deployWallet.address, + }, + ]); + const receipt = await this.executeUpgrade( + this.addresses.Bridgehub.BridgehubProxy, + ethIsBaseToken ? value : 0, + l1Calldata, + { + ...ethTxOptions, + gasPrice, + }, + printOperation + ); + return receipt; + } + // used for testing, mimics original deployment process. // we don't use the real implementation, as we need the address to be independent public async deployERC20BridgeProxy(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; const initCalldata = new Interface(hardhat.artifacts.readArtifactSync("L1ERC20Bridge").abi).encodeFunctionData( "initialize" ); @@ -554,18 +810,62 @@ export class Deployer { this.addresses.Bridges.ERC20BridgeProxy = contractAddress; } + public async deployL1NullifierImplementation(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + // const tokens = getTokens(); + // const l1WethToken = tokens.find((token: { symbol: string }) => token.symbol == "WETH")!.address; + const eraChainId = getNumberFromEnv("CONTRACTS_ERA_CHAIN_ID"); + const eraDiamondProxy = getAddressFromEnv("CONTRACTS_ERA_DIAMOND_PROXY_ADDR"); + const contractName = isCurrentNetworkLocal() ? "L1NullifierDev" : "L1Nullifier"; + const contractAddress = await this.deployViaCreate2( + contractName, + [this.addresses.Bridgehub.BridgehubProxy, eraChainId, eraDiamondProxy], + create2Salt, + ethTxOptions + ); + + if (this.verbose) { + console.log(`CONTRACTS_L1_NULLIFIER_IMPL_ADDR=${contractAddress}`); + } + + this.addresses.Bridges.L1NullifierImplementation = contractAddress; + } + + public async deployL1NullifierProxy(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + const initCalldata = new Interface(hardhat.artifacts.readArtifactSync("L1Nullifier").abi).encodeFunctionData( + "initialize", + [this.addresses.Governance, 1, 1, 1, 0] + ); + const contractAddress = await this.deployViaCreate2( + "TransparentUpgradeableProxy", + [this.addresses.Bridges.L1NullifierImplementation, this.addresses.TransparentProxyAdmin, initCalldata], + create2Salt, + ethTxOptions + ); + + if (this.verbose) { + console.log(`CONTRACTS_L1_NULLIFIER_PROXY_ADDR=${contractAddress}`); + } + + this.addresses.Bridges.L1NullifierProxy = contractAddress; + } + public async deploySharedBridgeImplementation( create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest ) { - ethTxOptions.gasLimit ??= 10_000_000; const tokens = getTokens(); const l1WethToken = tokens.find((token: { symbol: string }) => token.symbol == "WETH")!.address; const eraChainId = getNumberFromEnv("CONTRACTS_ERA_CHAIN_ID"); const eraDiamondProxy = getAddressFromEnv("CONTRACTS_ERA_DIAMOND_PROXY_ADDR"); const contractAddress = await this.deployViaCreate2( - "L1SharedBridge", - [l1WethToken, this.addresses.Bridgehub.BridgehubProxy, eraChainId, eraDiamondProxy], + "L1AssetRouter", + [ + l1WethToken, + this.addresses.Bridgehub.BridgehubProxy, + this.addresses.Bridges.L1NullifierProxy, + eraChainId, + eraDiamondProxy, + ], create2Salt, ethTxOptions ); @@ -579,8 +879,7 @@ export class Deployer { } public async deploySharedBridgeProxy(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; - const initCalldata = new Interface(hardhat.artifacts.readArtifactSync("L1SharedBridge").abi).encodeFunctionData( + const initCalldata = new Interface(hardhat.artifacts.readArtifactSync("L1AssetRouter").abi).encodeFunctionData( "initialize", [this.addresses.Governance] ); @@ -598,9 +897,163 @@ export class Deployer { this.addresses.Bridges.SharedBridgeProxy = contractAddress; } - public async sharedBridgeSetEraPostUpgradeFirstBatch(ethTxOptions: ethers.providers.TransactionRequest) { + public async deployBridgedStandardERC20Implementation( + create2Salt: string, + ethTxOptions: ethers.providers.TransactionRequest + ) { + const contractAddress = await this.deployViaCreate2("BridgedStandardERC20", [], create2Salt, ethTxOptions); + + if (this.verbose) { + // console.log(`With era chain id ${eraChainId}`); + console.log(`CONTRACTS_L1_BRIDGED_STANDARD_ERC20_IMPL_ADDR=${contractAddress}`); + } + + this.addresses.Bridges.BridgedStandardERC20Implementation = contractAddress; + } + + public async deployBridgedTokenBeacon(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + /// Note we cannot use create2 as the deployer is the owner. ethTxOptions.gasLimit ??= 10_000_000; - const sharedBridge = L1SharedBridgeFactory.connect(this.addresses.Bridges.SharedBridgeProxy, this.deployWallet); + const contractFactory = await hardhat.ethers.getContractFactory( + "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol:UpgradeableBeacon", + { + signer: this.deployWallet, + } + ); + const beacon = await contractFactory.deploy( + ...[this.addresses.Bridges.BridgedStandardERC20Implementation, ethTxOptions] + ); + const rec = await beacon.deployTransaction.wait(); + + if (this.verbose) { + console.log("Beacon deployed with tx hash", rec.transactionHash); + console.log(`CONTRACTS_L1_BRIDGED_TOKEN_BEACON_ADDR=${beacon.address}`); + } + + this.addresses.Bridges.BridgedTokenBeacon = beacon.address; + + await beacon.transferOwnership(this.addresses.Governance); + } + + public async deployNativeTokenVaultImplementation( + create2Salt: string, + ethTxOptions: ethers.providers.TransactionRequest + ) { + const tokens = getTokens(); + const l1WethToken = tokens.find((token: { symbol: string }) => token.symbol == "WETH")!.address; + const contractAddress = await this.deployViaCreate2( + "L1NativeTokenVault", + [l1WethToken, this.addresses.Bridges.SharedBridgeProxy, this.addresses.Bridges.L1NullifierProxy], + create2Salt, + ethTxOptions + ); + + if (this.verbose) { + // console.log(`With era chain id ${eraChainId}`); + console.log(`CONTRACTS_L1_NATIVE_TOKEN_VAULT_IMPL_ADDR=${contractAddress}`); + } + + this.addresses.Bridges.NativeTokenVaultImplementation = contractAddress; + } + + public async deployNativeTokenVaultProxy(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + const initCalldata = new Interface(hardhat.artifacts.readArtifactSync("L1NativeTokenVault").abi).encodeFunctionData( + "initialize", + [this.addresses.Governance, this.addresses.Bridges.BridgedTokenBeacon] + ); + const contractAddress = await this.deployViaCreate2( + "TransparentUpgradeableProxy", + [this.addresses.Bridges.NativeTokenVaultImplementation, this.addresses.TransparentProxyAdmin, initCalldata], + create2Salt, + ethTxOptions + ); + + if (this.verbose) { + console.log(`CONTRACTS_L1_NATIVE_TOKEN_VAULT_PROXY_ADDR=${contractAddress}`); + } + + this.addresses.Bridges.NativeTokenVaultProxy = contractAddress; + + const nullifier = this.l1NullifierContract(this.deployWallet); + const assetRouter = this.defaultSharedBridge(this.deployWallet); + const ntv = this.nativeTokenVault(this.deployWallet); + + const data = await assetRouter.interface.encodeFunctionData("setNativeTokenVault", [ + this.addresses.Bridges.NativeTokenVaultProxy, + ]); + await this.executeUpgrade(this.addresses.Bridges.SharedBridgeProxy, 0, data); + if (this.verbose) { + console.log("Native token vault set in shared bridge"); + } + + const data2 = await nullifier.interface.encodeFunctionData("setL1NativeTokenVault", [ + this.addresses.Bridges.NativeTokenVaultProxy, + ]); + await this.executeUpgrade(this.addresses.Bridges.L1NullifierProxy, 0, data2); + if (this.verbose) { + console.log("Native token vault set in nullifier"); + } + + const data3 = await nullifier.interface.encodeFunctionData("setL1AssetRouter", [ + this.addresses.Bridges.SharedBridgeProxy, + ]); + await this.executeUpgrade(this.addresses.Bridges.L1NullifierProxy, 0, data3); + if (this.verbose) { + console.log("Asset router set in nullifier"); + } + + await (await this.nativeTokenVault(this.deployWallet).registerEthToken()).wait(); + + await ntv.registerEthToken(); + } + + public async deployCTMDeploymentTrackerImplementation( + create2Salt: string, + ethTxOptions: ethers.providers.TransactionRequest + ) { + const contractAddress = await this.deployViaCreate2( + "CTMDeploymentTracker", + [this.addresses.Bridgehub.BridgehubProxy, this.addresses.Bridges.SharedBridgeProxy], + create2Salt, + ethTxOptions + ); + + if (this.verbose) { + console.log(`CONTRACTS_CTM_DEPLOYMENT_TRACKER_IMPL_ADDR=${contractAddress}`); + } + + this.addresses.Bridgehub.CTMDeploymentTrackerImplementation = contractAddress; + } + + public async deployCTMDeploymentTrackerProxy(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + const initCalldata = new Interface( + hardhat.artifacts.readArtifactSync("CTMDeploymentTracker").abi + ).encodeFunctionData("initialize", [this.addresses.Governance]); + const contractAddress = await this.deployViaCreate2( + "TransparentUpgradeableProxy", + [this.addresses.Bridgehub.CTMDeploymentTrackerImplementation, this.addresses.TransparentProxyAdmin, initCalldata], + create2Salt, + ethTxOptions + ); + + if (this.verbose) { + console.log(`CONTRACTS_CTM_DEPLOYMENT_TRACKER_PROXY_ADDR=${contractAddress}`); + } + + this.addresses.Bridgehub.CTMDeploymentTrackerProxy = contractAddress; + + // const bridgehub = this.bridgehubContract(this.deployWallet); + // const data0 = bridgehub.interface.encodeFunctionData("setCTMDeployer", [ + // this.addresses.Bridgehub.CTMDeploymentTrackerProxy, + // ]); + // await this.executeUpgrade(this.addresses.Bridgehub.BridgehubProxy, 0, data0); + // if (this.verbose) { + // console.log("CTM DT registered in Bridgehub"); + // } + } + + public async sharedBridgeSetEraPostUpgradeFirstBatch() { + const sharedBridge = L1AssetRouterFactory.connect(this.addresses.Bridges.SharedBridgeProxy, this.deployWallet); const storageSwitch = getNumberFromEnv("CONTRACTS_SHARED_BRIDGE_UPGRADE_STORAGE_SWITCH"); const tx = await sharedBridge.setEraPostUpgradeFirstBatch(storageSwitch); const receipt = await tx.wait(); @@ -609,20 +1062,43 @@ export class Deployer { } } - public async registerSharedBridge(ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; + public async registerAddresses() { 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 upgradeData1 = await bridgehub.interface.encodeFunctionData("setAddresses", [ + this.addresses.Bridges.SharedBridgeProxy, + this.addresses.Bridgehub.CTMDeploymentTrackerProxy, + this.addresses.Bridgehub.MessageRootProxy, + ]); + await this.executeUpgrade(this.addresses.Bridgehub.BridgehubProxy, 0, upgradeData1); + if (this.verbose) { + console.log("Shared bridge was registered in Bridgehub"); + } + } + + public async registerTokenBridgehub(tokenAddress: string, useGovernance: boolean = false) { + const bridgehub = this.bridgehubContract(this.deployWallet); + const baseTokenAssetId = encodeNTVAssetId(this.l1ChainId, tokenAddress); + const receipt = await this.executeDirectOrGovernance( + useGovernance, + bridgehub, + "addTokenAssetId", + [baseTokenAssetId], + 0 + ); - 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()}` - ); + console.log(`Token ${tokenAddress} was registered, gas used: ${receipt.gasUsed.toString()}`); + } + } + + public async registerTokenInNativeTokenVault(token: string) { + const nativeTokenVault = this.nativeTokenVault(this.deployWallet); + + const data = nativeTokenVault.interface.encodeFunctionData("registerToken", [token]); + await this.executeUpgrade(this.addresses.Bridges.NativeTokenVaultProxy, 0, data); + if (this.verbose) { + console.log("Native token vault registered with token", token); } } @@ -630,7 +1106,6 @@ export class Deployer { create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest ) { - ethTxOptions.gasLimit ??= 10_000_000; const contractAddress = await this.deployViaCreate2("DiamondInit", [], create2Salt, ethTxOptions); if (this.verbose) { @@ -641,7 +1116,6 @@ export class Deployer { } public async deployDefaultUpgrade(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; const contractAddress = await this.deployViaCreate2("DefaultUpgrade", [], create2Salt, ethTxOptions); if (this.verbose) { @@ -651,20 +1125,18 @@ export class Deployer { this.addresses.StateTransition.DefaultUpgrade = contractAddress; } - public async deployHyperchainsUpgrade(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2("UpgradeHyperchains", [], create2Salt, ethTxOptions); + public async deployZKChainsUpgrade(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + const contractAddress = await this.deployViaCreate2("UpgradeZKChains", [], create2Salt, ethTxOptions); if (this.verbose) { - console.log(`CONTRACTS_HYPERCHAIN_UPGRADE_ADDR=${contractAddress}`); + console.log(`CONTRACTS_ZK_CHAIN_UPGRADE_ADDR=${contractAddress}`); } this.addresses.StateTransition.DefaultUpgrade = contractAddress; } public async deployGenesisUpgrade(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2("GenesisUpgrade", [], create2Salt, ethTxOptions); + const contractAddress = await this.deployViaCreate2("L1GenesisUpgrade", [], create2Salt, ethTxOptions); if (this.verbose) { console.log(`CONTRACTS_GENESIS_UPGRADE_ADDR=${contractAddress}`); @@ -678,20 +1150,21 @@ export class Deployer { await this.deployBridgehubImplementation(create2Salt, { gasPrice, nonce }); await this.deployBridgehubProxy(create2Salt, { gasPrice }); + await this.deployMessageRootImplementation(create2Salt, { gasPrice }); + await this.deployMessageRootProxy(create2Salt, { gasPrice }); } - public async deployStateTransitionManagerContract( + public async deployChainTypeManagerContract( 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(); + await this.deployChainTypeManagerImplementation(create2Salt, { gasPrice }); + await this.deployChainTypeManagerProxy(create2Salt, { gasPrice }, extraFacets); + await this.registerChainTypeManager(); } public async deployStateTransitionDiamondFacets(create2Salt: string, gasPrice?: BigNumberish, nonce?) { @@ -704,41 +1177,206 @@ export class Deployer { await this.deployStateTransitionDiamondInit(create2Salt, { gasPrice, nonce: nonce + 4 }); } - public async registerStateTransitionManager() { + public async registerChainTypeManager() { const bridgehub = this.bridgehubContract(this.deployWallet); - if (!(await bridgehub.stateTransitionManagerIsRegistered(this.addresses.StateTransition.StateTransitionProxy))) { - const tx = await bridgehub.addStateTransitionManager(this.addresses.StateTransition.StateTransitionProxy); + if (!(await bridgehub.chainTypeManagerIsRegistered(this.addresses.StateTransition.StateTransitionProxy))) { + const upgradeData = bridgehub.interface.encodeFunctionData("addChainTypeManager", [ + this.addresses.StateTransition.StateTransitionProxy, + ]); - const receipt = await tx.wait(); - if (this.verbose) { - console.log(`StateTransition System registered, gas used: ${receipt.gasUsed.toString()}`); + let receipt1; + if (!this.isZkMode()) { + receipt1 = await this.executeUpgrade(this.addresses.Bridgehub.BridgehubProxy, 0, upgradeData); + if (this.verbose) { + console.log(`StateTransition System registered, gas used: ${receipt1.gasUsed.toString()}`); + } + + const ctmDeploymentTracker = this.ctmDeploymentTracker(this.deployWallet); + + const l1AssetRouter = this.defaultSharedBridge(this.deployWallet); + const whitelistData = l1AssetRouter.interface.encodeFunctionData("setAssetDeploymentTracker", [ + ethers.utils.hexZeroPad(this.addresses.StateTransition.StateTransitionProxy, 32), + ctmDeploymentTracker.address, + ]); + const receipt2 = await this.executeUpgrade(l1AssetRouter.address, 0, whitelistData); + if (this.verbose) { + console.log("CTM deployment tracker whitelisted in L1 Shared Bridge", receipt2.gasUsed.toString()); + console.log( + `CONTRACTS_CTM_ASSET_INFO=${await bridgehub.ctmAssetId(this.addresses.StateTransition.StateTransitionProxy)}` + ); + } + + const data1 = ctmDeploymentTracker.interface.encodeFunctionData("registerCTMAssetOnL1", [ + this.addresses.StateTransition.StateTransitionProxy, + ]); + const receipt3 = await this.executeUpgrade(this.addresses.Bridgehub.CTMDeploymentTrackerProxy, 0, data1); + if (this.verbose) { + console.log( + "CTM asset registered in L1 Shared Bridge via CTM Deployment Tracker", + receipt3.gasUsed.toString() + ); + console.log( + `CONTRACTS_CTM_ASSET_INFO=${await bridgehub.ctmAssetId(this.addresses.StateTransition.StateTransitionProxy)}` + ); + } + } else { + console.log(`CONTRACTS_CTM_ASSET_INFO=${getHashFromEnv("CONTRACTS_CTM_ASSET_INFO")}`); } } } - public async registerHyperchain( - baseTokenAddress: string, + public async registerSettlementLayer() { + const bridgehub = this.bridgehubContract(this.deployWallet); + const calldata = bridgehub.interface.encodeFunctionData("registerSettlementLayer", [this.chainId, true]); + await this.executeUpgrade(this.addresses.Bridgehub.BridgehubProxy, 0, calldata); + if (this.verbose) { + console.log("Gateway registered"); + } + } + + // Main function to move the current chain (that is hooked to l1), on top of the syncLayer chain. + public async moveChainToGateway(gatewayChainId: string, gasPrice: BigNumberish) { + const protocolVersion = packSemver(...unpackStringSemVer(process.env.CONTRACTS_GENESIS_PROTOCOL_SEMANTIC_VERSION)); + const chainData = ethers.utils.defaultAbiCoder.encode(["uint256"], [protocolVersion]); + const bridgehub = this.bridgehubContract(this.deployWallet); + // Just some large gas limit that should always be enough + const l2GasLimit = ethers.BigNumber.from(72_000_000); + const expectedCost = ( + await bridgehub.l2TransactionBaseCost(gatewayChainId, gasPrice, l2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA) + ).mul(5); + + // We are creating the new DiamondProxy for our chain, to be deployed on top of sync Layer. + const newAdmin = this.deployWallet.address; + const diamondCutData = await this.initialZkSyncZKChainDiamondCut(); + const initialDiamondCut = new ethers.utils.AbiCoder().encode([DIAMOND_CUT_DATA_ABI_STRING], [diamondCutData]); + + const ctmData = new ethers.utils.AbiCoder().encode(["uint256", "bytes"], [newAdmin, initialDiamondCut]); + const bridgehubData = new ethers.utils.AbiCoder().encode( + [BRIDGEHUB_CTM_ASSET_DATA_ABI_STRING], + [[this.chainId, ctmData, chainData]] + ); + + // console.log("bridgehubData", bridgehubData) + // console.log("this.addresses.ChainAssetInfo", this.addresses.ChainAssetInfo) + + // The ctmAssetIFromChainId gives us a unique 'asset' identifier for a given chain. + const chainAssetId = await bridgehub.ctmAssetIdFromChainId(this.chainId); + if (this.verbose) { + console.log("Chain asset id is: ", chainAssetId); + console.log(`CONTRACTS_CTM_ASSET_INFO=${chainAssetId}`); + } + + let sharedBridgeData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "bytes"], + + [chainAssetId, bridgehubData] + ); + // The 0x01 is the encoding for the L1AssetRouter. + sharedBridgeData = "0x01" + sharedBridgeData.slice(2); + + // And now we 'transfer' the chain through the bridge (it behaves like a 'regular' asset, where we 'freeze' it in L1 + // and then create on SyncLayer). You can see these methods in Admin.sol (part of DiamondProxy). + const receipt = await this.executeChainAdminMulticall([ + { + target: bridgehub.address, + data: bridgehub.interface.encodeFunctionData("requestL2TransactionTwoBridges", [ + // These arguments must match L2TransactionRequestTwoBridgesOuter struct. + { + chainId: gatewayChainId, + mintValue: expectedCost, + l2Value: 0, + l2GasLimit: l2GasLimit, + l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + refundRecipient: await this.deployWallet.getAddress(), + secondBridgeAddress: this.addresses.Bridges.SharedBridgeProxy, + secondBridgeValue: 0, + secondBridgeCalldata: sharedBridgeData, + }, + ]), + value: expectedCost, + }, + ]); + + return receipt; + } + + public async finishMoveChainToL1(synclayerChainId: number) { + const nullifier = this.l1NullifierContract(this.deployWallet); + // const baseTokenAmount = ethers.utils.parseEther("1"); + // const chainData = new ethers.utils.AbiCoder().encode(["uint256", "bytes"], [ADDRESS_ONE, "0x"]); // todo + // const bridgehubData = new ethers.utils.AbiCoder().encode(["uint256", "bytes"], [this.chainId, chainData]); + // console.log("bridgehubData", bridgehubData) + // console.log("this.addresses.ChainAssetInfo", this.addresses.ChainAssetInfo) + // const sharedBridgeData = ethers.utils.defaultAbiCoder.encode( + // ["bytes32", "bytes"], + + // [await bridgehub.ctmAssetInfoFromChainId(this.chainId), bridgehubData] + // ); + const l2BatchNumber = 1; + const l2MsgIndex = 1; + const l2TxNumberInBatch = 1; + const message = ethers.utils.defaultAbiCoder.encode(["bytes32", "bytes"], []); + const merkleProof = ["0x00"]; + const tx = await nullifier.finalizeWithdrawal( + synclayerChainId, + l2BatchNumber, + l2MsgIndex, + l2TxNumberInBatch, + message, + merkleProof + ); + const receipt = await tx.wait(); + if (this.verbose) { + console.log("Chain move to L1 finished", receipt.gasUsed.toString()); + } + } + + public async registerZKChain( + baseTokenAssetId: string, validiumMode: boolean, extraFacets?: FacetCut[], gasPrice?: BigNumberish, compareDiamondCutHash: boolean = false, nonce?, predefinedChainId?: string, - useGovernance: boolean = false + useGovernance: boolean = false, + l2LegacySharedBridge: boolean = false ) { - const gasLimit = 10_000_000; + const txOptions = this.isZkMode() ? {} : { gasLimit: 10_000_000 }; nonce = nonce ? parseInt(nonce) : await this.deployWallet.getTransactionCount(); const bridgehub = this.bridgehubContract(this.deployWallet); - const stateTransitionManager = this.stateTransitionManagerContract(this.deployWallet); + const chainTypeManager = this.chainTypeManagerContract(this.deployWallet); + const ntv = this.nativeTokenVault(this.deployWallet); + const baseTokenAddress = await ntv.tokenAddress(baseTokenAssetId); const inputChainId = predefinedChainId || getNumberFromEnv("CHAIN_ETH_ZKSYNC_NETWORK_ID"); + const alreadyRegisteredInCTM = (await chainTypeManager.getZKChain(inputChainId)) != ethers.constants.AddressZero; + + if (l2LegacySharedBridge) { + if (this.verbose) { + console.log("Setting L2 legacy shared bridge in L1Nullifier"); + } + await this.setL2LegacySharedBridgeInL1Nullifier(inputChainId); + nonce++; + } + const admin = process.env.CHAIN_ADMIN_ADDRESS || this.ownerAddress; - const diamondCutData = await this.initialZkSyncHyperchainDiamondCut(extraFacets, compareDiamondCutHash); + const diamondCutData = await this.initialZkSyncZKChainDiamondCut(extraFacets, compareDiamondCutHash); const initialDiamondCut = new ethers.utils.AbiCoder().encode([DIAMOND_CUT_DATA_ABI_STRING], [diamondCutData]); - + const forceDeploymentsData = await this.genesisForceDeploymentsData(); + const initData = ethers.utils.defaultAbiCoder.encode(["bytes", "bytes"], [initialDiamondCut, forceDeploymentsData]); + let factoryDeps = []; + if (process.env.CHAIN_ETH_NETWORK != "hardhat") { + factoryDeps = [ + L2_STANDARD_ERC20_PROXY_FACTORY.bytecode, + L2_STANDARD_ERC20_IMPLEMENTATION.bytecode, + L2_STANDARD_TOKEN_PROXY.bytecode, + ]; + } + // note the factory deps are provided at genesis const receipt = await this.executeDirectOrGovernance( useGovernance, bridgehub, @@ -746,19 +1384,18 @@ export class Deployer { [ inputChainId, this.addresses.StateTransition.StateTransitionProxy, - baseTokenAddress, + baseTokenAssetId, Date.now(), admin, - initialDiamondCut, + initData, + factoryDeps, ], 0, { gasPrice, - nonce, - gasLimit, + ...txOptions, } ); - const chainId = receipt.logs.find((log) => log.topics[0] == bridgehub.interface.getEventTopic("NewChain")) .topics[1]; @@ -769,20 +1406,24 @@ export class Deployer { } this.addresses.BaseToken = baseTokenAddress; + this.addresses.BaseTokenAssetId = baseTokenAssetId; 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(`ZK chain registered, gas used: ${receipt.gasUsed.toString()} and ${receipt.gasUsed.toString()}`); + console.log(`ZK chain registration tx hash: ${receipt.transactionHash}`); console.log(`CHAIN_ETH_ZKSYNC_NETWORK_ID=${parseInt(chainId, 16)}`); - + console.log( + `CONTRACTS_CTM_ASSET_INFO=${await bridgehub.ctmAssetId(this.addresses.StateTransition.StateTransitionProxy)}` + ); console.log(`CONTRACTS_BASE_TOKEN_ADDR=${baseTokenAddress}`); } - if (!predefinedChainId) { + + if (!alreadyRegisteredInCTM) { const diamondProxyAddress = "0x" + receipt.logs - .find((log) => log.topics[0] == stateTransitionManager.interface.getEventTopic("NewHyperchain")) + .find((log) => log.topics[0] == chainTypeManager.interface.getEventTopic("NewZKChain")) .topics[2].slice(26); this.addresses.StateTransition.DiamondProxy = diamondProxyAddress; if (this.verbose) { @@ -798,14 +1439,13 @@ export class Deployer { const txRegisterValidator = await validatorTimelock.addValidator(chainId, validatorOneAddress, { gasPrice, nonce, - gasLimit, + ...txOptions, }); const receiptRegisterValidator = await txRegisterValidator.wait(); if (this.verbose) { console.log( - `Validator registered, gas used: ${receiptRegisterValidator.gasUsed.toString()}, tx hash: ${ - txRegisterValidator.hash - }` + `Validator registered, gas used: ${receiptRegisterValidator.gasUsed.toString()}, tx hash: + ${txRegisterValidator.hash}` ); } @@ -814,7 +1454,7 @@ export class Deployer { const tx3 = await validatorTimelock.addValidator(chainId, validatorTwoAddress, { gasPrice, nonce, - gasLimit, + ...txOptions, }); const receipt3 = await tx3.wait(); if (this.verbose) { @@ -842,6 +1482,178 @@ export class Deployer { "BaseTokenMultiplier and Validium mode can't be set through the governance, please set it separately, using the admin account" ); } + + if (l2LegacySharedBridge) { + await this.deployL2LegacySharedBridge(inputChainId, gasPrice); + } + } + + public async setL2LegacySharedBridgeInL1Nullifier(inputChainId: string) { + const l1Nullifier = L1NullifierDevFactory.connect(this.addresses.Bridges.L1NullifierProxy, this.deployWallet); + const l1SharedBridge = this.defaultSharedBridge(this.deployWallet); + + if (isCurrentNetworkLocal()) { + const l2SharedBridgeImplementationBytecode = L2_SHARED_BRIDGE_IMPLEMENTATION.bytecode; + + const l2SharedBridgeImplAddress = computeL2Create2Address( + this.deployWallet.address, + l2SharedBridgeImplementationBytecode, + "0x", + ethers.constants.HashZero + ); + + const l2GovernorAddress = applyL1ToL2Alias(this.addresses.Governance); + + const l2SharedBridgeInterface = new Interface(L2_SHARED_BRIDGE_IMPLEMENTATION.abi); + const proxyInitializationParams = l2SharedBridgeInterface.encodeFunctionData("initialize", [ + l1SharedBridge.address, + hashL2Bytecode(L2_STANDARD_TOKEN_PROXY.bytecode), + l2GovernorAddress, + ]); + + const l2SharedBridgeProxyConstructorData = ethers.utils.arrayify( + new ethers.utils.AbiCoder().encode( + ["address", "address", "bytes"], + [l2SharedBridgeImplAddress, l2GovernorAddress, proxyInitializationParams] + ) + ); + + /// compute L2SharedBridgeProxy address + const l2SharedBridgeProxyAddress = computeL2Create2Address( + this.deployWallet.address, + L2_SHARED_BRIDGE_PROXY.bytecode, + l2SharedBridgeProxyConstructorData, + ethers.constants.HashZero + ); + + const tx = await l1Nullifier.setL2LegacySharedBridge(inputChainId, l2SharedBridgeProxyAddress); + const receipt8 = await tx.wait(); + if (this.verbose) { + console.log(`L2 legacy shared bridge set in L1 Nullifier, gas used: ${receipt8.gasUsed.toString()}`); + } + } + } + + public async deployL2LegacySharedBridge(inputChainId: string, gasPrice: BigNumberish) { + if (this.verbose) { + console.log("Deploying L2 legacy shared bridge"); + } + await this.deploySharedBridgeImplOnL2ThroughL1(inputChainId, gasPrice); + await this.deploySharedBridgeProxyOnL2ThroughL1(inputChainId, gasPrice); + } + + public async deploySharedBridgeImplOnL2ThroughL1(chainId: string, gasPrice: BigNumberish) { + if (this.verbose) { + console.log("Deploying L2SharedBridge Implementation"); + } + const eraChainId = getNumberFromEnv("CONTRACTS_ERA_CHAIN_ID"); + + const l2SharedBridgeImplementationBytecode = L2_SHARED_BRIDGE_IMPLEMENTATION.bytecode; + // localLegacyBridgeTesting + // ? L2_DEV_SHARED_BRIDGE_IMPLEMENTATION.bytecode + // : L2_SHARED_BRIDGE_IMPLEMENTATION.bytecode; + if (!l2SharedBridgeImplementationBytecode) { + throw new Error("l2SharedBridgeImplementationBytecode not found"); + } + + if (this.verbose) { + console.log("l2SharedBridgeImplementationBytecode loaded"); + + console.log("Computing L2SharedBridge Implementation Address"); + } + + const l2SharedBridgeImplAddress = computeL2Create2Address( + this.deployWallet.address, + l2SharedBridgeImplementationBytecode, + "0x", + ethers.constants.HashZero + ); + this.addresses.Bridges.L2LegacySharedBridgeImplementation = l2SharedBridgeImplAddress; + + if (this.verbose) { + console.log(`L2SharedBridge Implementation Address: ${l2SharedBridgeImplAddress}`); + + console.log("Deploying L2SharedBridge Implementation"); + } + // TODO: request from API how many L2 gas needs for the transaction. + const tx2 = await create2DeployFromL1( + chainId, + this.deployWallet, + l2SharedBridgeImplementationBytecode, + ethers.utils.defaultAbiCoder.encode(["uint256"], [eraChainId]), + ethers.constants.HashZero, + priorityTxMaxGasLimit, + gasPrice, + [L2_STANDARD_TOKEN_PROXY.bytecode], + this.addresses.Bridgehub.BridgehubProxy, + this.addresses.Bridges.SharedBridgeProxy + ); + await tx2.wait(); + + if (this.verbose) { + console.log("Deployed L2SharedBridge Implementation"); + console.log(`CONTRACTS_L2_LEGACY_SHARED_BRIDGE_IMPL_ADDR=${l2SharedBridgeImplAddress}`); + } + } + + public async deploySharedBridgeProxyOnL2ThroughL1(chainId: string, gasPrice: BigNumberish) { + const l1SharedBridge = this.defaultSharedBridge(this.deployWallet); + if (this.verbose) { + console.log("Deploying L2SharedBridge Proxy"); + } + const l2GovernorAddress = applyL1ToL2Alias(this.addresses.Governance); + + const l2SharedBridgeInterface = new Interface(L2_SHARED_BRIDGE_IMPLEMENTATION.abi); + const proxyInitializationParams = l2SharedBridgeInterface.encodeFunctionData("initialize", [ + l1SharedBridge.address, + hashL2Bytecode(L2_STANDARD_TOKEN_PROXY.bytecode), + l2GovernorAddress, + ]); + + /// prepare constructor data + const l2SharedBridgeProxyConstructorData = ethers.utils.arrayify( + new ethers.utils.AbiCoder().encode( + ["address", "address", "bytes"], + [this.addresses.Bridges.L2LegacySharedBridgeImplementation, l2GovernorAddress, proxyInitializationParams] + ) + ); + + /// compute L2SharedBridgeProxy address + const l2SharedBridgeProxyAddress = computeL2Create2Address( + this.deployWallet.address, + L2_SHARED_BRIDGE_PROXY.bytecode, + l2SharedBridgeProxyConstructorData, + ethers.constants.HashZero + ); + this.addresses.Bridges.L2LegacySharedBridgeProxy = l2SharedBridgeProxyAddress; + + /// deploy L2SharedBridgeProxy + // TODO: request from API how many L2 gas needs for the transaction. + const tx3 = await create2DeployFromL1( + chainId, + this.deployWallet, + L2_SHARED_BRIDGE_PROXY.bytecode, + l2SharedBridgeProxyConstructorData, + ethers.constants.HashZero, + priorityTxMaxGasLimit, + gasPrice, + undefined, + this.addresses.Bridgehub.BridgehubProxy, + this.addresses.Bridges.SharedBridgeProxy + ); + await tx3.wait(); + if (this.verbose) { + console.log(`CONTRACTS_L2_LEGACY_SHARED_BRIDGE_ADDR=${l2SharedBridgeProxyAddress}`); + } + } + + public async executeChainAdminMulticall(calls: ChainAdminCall[], requireSuccess: boolean = true) { + const chainAdmin = ChainAdminFactory.connect(this.addresses.ChainAdmin, this.deployWallet); + + const totalValue = calls.reduce((acc, call) => acc.add(call.value), ethers.BigNumber.from(0)); + + const multicallTx = await chainAdmin.multicall(calls, requireSuccess, { value: totalValue }); + return await multicallTx.wait(); } public async setTokenMultiplierSetterAddress(tokenMultiplierSetterAddress: string) { @@ -856,59 +1668,53 @@ export class Deployer { } public async transferAdminFromDeployerToChainAdmin() { - const stm = this.stateTransitionManagerContract(this.deployWallet); - const diamondProxyAddress = await stm.getHyperchain(this.chainId); - const hyperchain = IZkSyncHyperchainFactory.connect(diamondProxyAddress, this.deployWallet); + const ctm = this.chainTypeManagerContract(this.deployWallet); + const diamondProxyAddress = await ctm.getZKChain(this.chainId); + const zkChain = IZKChainFactory.connect(diamondProxyAddress, this.deployWallet); - const receipt = await (await hyperchain.setPendingAdmin(this.addresses.ChainAdmin)).wait(); + const receipt = await (await zkChain.setPendingAdmin(this.addresses.ChainAdmin)).wait(); if (this.verbose) { console.log(`ChainAdmin set as pending admin, gas used: ${receipt.gasUsed.toString()}`); } - const acceptAdminData = hyperchain.interface.encodeFunctionData("acceptAdmin"); - const chainAdmin = ChainAdminFactory.connect(this.addresses.ChainAdmin, this.deployWallet); - const multicallTx = await chainAdmin.multicall( - [ - { - target: hyperchain.address, - value: 0, - data: acceptAdminData, - }, - ], - true - ); - await multicallTx.wait(); + const acceptAdminData = zkChain.interface.encodeFunctionData("acceptAdmin"); + await this.executeChainAdminMulticall([ + { + target: zkChain.address, + value: 0, + data: acceptAdminData, + }, + ]); if (this.verbose) { console.log("Pending admin successfully accepted"); } } - public async registerToken(tokenAddress: string, useGovernance: boolean = false) { - const bridgehub = this.bridgehubContract(this.deployWallet); - - const receipt = await this.executeDirectOrGovernance(useGovernance, bridgehub, "addToken", [tokenAddress], 0); - - 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.deployL1NullifierImplementation(create2Salt, { gasPrice, nonce: nonce }); + await this.deployL1NullifierProxy(create2Salt, { gasPrice, nonce: nonce + 1 }); + + nonce = nonce + 2; await this.deploySharedBridgeImplementation(create2Salt, { gasPrice, nonce: nonce }); await this.deploySharedBridgeProxy(create2Salt, { gasPrice, nonce: nonce + 1 }); - await this.registerSharedBridge({ gasPrice, nonce: nonce + 2 }); + nonce = nonce + 2; + await this.deployBridgedStandardERC20Implementation(create2Salt, { gasPrice, nonce: nonce }); + await this.deployBridgedTokenBeacon(create2Salt, { gasPrice, nonce: nonce + 1 }); + await this.deployNativeTokenVaultImplementation(create2Salt, { gasPrice, nonce: nonce + 3 }); + await this.deployNativeTokenVaultProxy(create2Salt, { gasPrice }); + await this.deployCTMDeploymentTrackerImplementation(create2Salt, { gasPrice }); + await this.deployCTMDeploymentTrackerProxy(create2Salt, { gasPrice }); + await this.registerAddresses(); } public async deployValidatorTimelock(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; const executionDelay = getNumberFromEnv("CONTRACTS_VALIDATOR_TIMELOCK_EXECUTION_DELAY"); - const eraChainId = getNumberFromEnv("CONTRACTS_ERA_CHAIN_ID"); const contractAddress = await this.deployViaCreate2( "ValidatorTimelock", - [this.ownerAddress, executionDelay, eraChainId], + [this.ownerAddress, executionDelay], create2Salt, ethTxOptions ); @@ -919,20 +1725,19 @@ export class Deployer { this.addresses.ValidatorTimeLock = contractAddress; } - public async setStateTransitionManagerInValidatorTimelock(ethTxOptions: ethers.providers.TransactionRequest) { + public async setChainTypeManagerInValidatorTimelock(ethTxOptions: ethers.providers.TransactionRequest) { const validatorTimelock = this.validatorTimelock(this.deployWallet); - const tx = await validatorTimelock.setStateTransitionManager( + const tx = await validatorTimelock.setChainTypeManager( 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()}`); + console.log(`ChainTypeManager 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); if (this.verbose) { @@ -940,11 +1745,59 @@ export class Deployer { } } + public async deployDAValidators(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; + + // This address only makes sense on the L1, but we deploy it anyway to keep the script simple + const rollupValidatorBytecode = await this.loadFromDAFolder("RollupL1DAValidator"); + const rollupDAValidatorAddress = await this.deployViaCreate2( + "RollupL1DAValidator", + [], + create2Salt, + ethTxOptions, + undefined, + rollupValidatorBytecode + ); + if (this.verbose) { + console.log(`CONTRACTS_L1_ROLLUP_DA_VALIDATOR=${rollupDAValidatorAddress}`); + } + const validiumDAValidatorAddress = await this.deployViaCreate2( + "ValidiumL1DAValidator", + [], + create2Salt, + ethTxOptions, + undefined + ); + + if (this.verbose) { + console.log(`CONTRACTS_L1_VALIDIUM_DA_VALIDATOR=${validiumDAValidatorAddress}`); + } + // This address only makes sense on the Sync Layer, but we deploy it anyway to keep the script simple + const relayedSLDAValidator = await this.deployViaCreate2("RelayedSLDAValidator", [], create2Salt, ethTxOptions); + if (this.verbose) { + console.log(`CONTRACTS_L1_RELAYED_SL_DA_VALIDATOR=${relayedSLDAValidator}`); + } + this.addresses.RollupL1DAValidator = rollupDAValidatorAddress; + this.addresses.ValidiumL1DAValidator = validiumDAValidatorAddress; + this.addresses.RelayedSLDAValidator = relayedSLDAValidator; + } + + public async updateBlobVersionedHashRetrieverZkMode() { + if (!this.isZkMode()) { + throw new Error("`updateBlobVersionedHashRetrieverZk` should be only called when deploying on zkSync network"); + } + + console.log("BlobVersionedHashRetriever is not needed within zkSync network and won't be deployed"); + + // 0 is not allowed, we need to some random non-zero value. Let it be 0x1000000000000000000000000000000000000001 + console.log("CONTRACTS_BLOB_VERSIONED_HASH_RETRIEVER_ADDR=0x1000000000000000000000000000000000000001"); + this.addresses.BlobVersionedHashRetriever = "0x1000000000000000000000000000000000000001"; + } + public async deployBlobVersionedHashRetriever( create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest ) { - ethTxOptions.gasLimit ??= 10_000_000; // solc contracts/zksync/utils/blobVersionedHashRetriever.yul --strict-assembly --bin const bytecode = "0x600b600b5f39600b5ff3fe5f358049805f5260205ff3"; @@ -971,18 +1824,15 @@ export class Deployer { } public bridgehubContract(signerOrProvider: Signer | providers.Provider) { - return IBridgehubFactory.connect(this.addresses.Bridgehub.BridgehubProxy, signerOrProvider); + return BridgehubFactory.connect(this.addresses.Bridgehub.BridgehubProxy, signerOrProvider); } - public stateTransitionManagerContract(signerOrProvider: Signer | providers.Provider) { - return IStateTransitionManagerFactory.connect( - this.addresses.StateTransition.StateTransitionProxy, - signerOrProvider - ); + public chainTypeManagerContract(signerOrProvider: Signer | providers.Provider) { + return ChainTypeManagerFactory.connect(this.addresses.StateTransition.StateTransitionProxy, signerOrProvider); } public stateTransitionContract(signerOrProvider: Signer | providers.Provider) { - return IZkSyncHyperchainFactory.connect(this.addresses.StateTransition.DiamondProxy, signerOrProvider); + return IZKChainFactory.connect(this.addresses.StateTransition.DiamondProxy, signerOrProvider); } public governanceContract(signerOrProvider: Signer | providers.Provider) { @@ -994,7 +1844,19 @@ export class Deployer { } public defaultSharedBridge(signerOrProvider: Signer | providers.Provider) { - return L1SharedBridgeFactory.connect(this.addresses.Bridges.SharedBridgeProxy, signerOrProvider); + return IL1AssetRouterFactory.connect(this.addresses.Bridges.SharedBridgeProxy, signerOrProvider); + } + + public l1NullifierContract(signerOrProvider: Signer | providers.Provider) { + return IL1NullifierFactory.connect(this.addresses.Bridges.L1NullifierProxy, signerOrProvider); + } + + public nativeTokenVault(signerOrProvider: Signer | providers.Provider) { + return IL1NativeTokenVaultFactory.connect(this.addresses.Bridges.NativeTokenVaultProxy, signerOrProvider); + } + + public ctmDeploymentTracker(signerOrProvider: Signer | providers.Provider) { + return ICTMDeploymentTrackerFactory.connect(this.addresses.Bridgehub.CTMDeploymentTrackerProxy, signerOrProvider); } public baseTokenContract(signerOrProvider: Signer | providers.Provider) { @@ -1004,4 +1866,9 @@ export class Deployer { public proxyAdminContract(signerOrProvider: Signer | providers.Provider) { return ProxyAdminFactory.connect(this.addresses.TransparentProxyAdmin, signerOrProvider); } + + private async getL1ChainId(): Promise { + const l1ChainId = this.isZkMode() ? getNumberFromEnv("ETH_CLIENT_CHAIN_ID") : await this.deployWallet.getChainId(); + return +l1ChainId; + } } diff --git a/l1-contracts/src.ts/diamondCut.ts b/l1-contracts/src.ts/diamondCut.ts index c2a8e8728..ca44029bf 100644 --- a/l1-contracts/src.ts/diamondCut.ts +++ b/l1-contracts/src.ts/diamondCut.ts @@ -3,8 +3,8 @@ import type { Interface } from "ethers/lib/utils"; import "@nomiclabs/hardhat-ethers"; import type { Wallet, BigNumberish } from "ethers"; import { ethers } from "ethers"; -import { IZkSyncHyperchainFactory } from "../typechain/IZkSyncHyperchainFactory"; -import { IZkSyncHyperchainBaseFactory } from "../typechain/IZkSyncHyperchainBaseFactory"; +import { IZKChainFactory } from "../typechain/IZKChainFactory"; +import { IZKChainBaseFactory } from "../typechain/IZKChainBaseFactory"; export enum Action { Add = 0, @@ -98,12 +98,12 @@ export async function getCurrentFacetCutsForAdd( } export async function getDeployedFacetCutsForRemove(wallet: Wallet, zkSyncAddress: string, updatedFaceNames: string[]) { - const mainContract = IZkSyncHyperchainFactory.connect(zkSyncAddress, wallet); + const mainContract = IZKChainFactory.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 = IZkSyncHyperchainBaseFactory.connect(addr, wallet); + const facet = IZKChainBaseFactory.connect(addr, wallet); const facetName = await facet.getName(); if (updatedFaceNames.includes(facetName)) { result.push({ diff --git a/l1-contracts/src.ts/utils.ts b/l1-contracts/src.ts/utils.ts index ca18bc7e4..32eaf5f6e 100644 --- a/l1-contracts/src.ts/utils.ts +++ b/l1-contracts/src.ts/utils.ts @@ -9,7 +9,8 @@ import * as path from "path"; import { DiamondInitFactory } from "../typechain"; import type { DiamondCut, FacetCut } from "./diamondCut"; import { diamondCut } from "./diamondCut"; -import { SYSTEM_CONFIG } from "../scripts/utils"; +import { SYSTEM_CONFIG, web3Url } from "../scripts/utils"; +import { Wallet as ZkWallet, Provider } from "zksync-ethers"; export const testConfigPath = process.env.ZKSYNC_ENV ? path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant") @@ -21,13 +22,33 @@ export const REQUIRED_L2_GAS_PRICE_PER_PUBDATA = require("../../SystemConfig.jso export const SYSTEM_UPGRADE_L2_TX_TYPE = 254; export const ADDRESS_ONE = "0x0000000000000000000000000000000000000001"; +export const ETH_ADDRESS_IN_CONTRACTS = ADDRESS_ONE; export const L1_TO_L2_ALIAS_OFFSET = "0x1111000000000000000000000000000000001111"; +export const L2_BRIDGEHUB_ADDRESS = "0x0000000000000000000000000000000000010002"; +export const L2_ASSET_ROUTER_ADDRESS = "0x0000000000000000000000000000000000010003"; +export const L2_NATIVE_TOKEN_VAULT_ADDRESS = "0x0000000000000000000000000000000000010004"; +export const L2_MESSAGE_ROOT_ADDRESS = "0x0000000000000000000000000000000000010005"; +export const DEPLOYER_SYSTEM_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000008006"; export const EMPTY_STRING_KECCAK = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"; const CREATE2_PREFIX = ethers.utils.solidityKeccak256(["string"], ["zksyncCreate2"]); +export const priorityTxMaxGasLimit = getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT"); + const ADDRESS_MODULO = ethers.BigNumber.from(2).pow(160); +export const STORED_BATCH_INFO_ABI_STRING = + "tuple(uint64 batchNumber, bytes32 batchHash, uint64 indexRepeatedStorageChanges, uint256 numberOfLayer1Txs, bytes32 priorityOperationsHash, bytes32 l2LogsTreeRoot, uint256 timestamp, bytes32 commitment)"; +export const COMMIT_BATCH_INFO_ABI_STRING = + "tuple(uint64 batchNumber, uint64 timestamp, uint64 indexRepeatedStorageChanges, bytes32 newStateRoot, uint256 numberOfLayer1Txs, bytes32 priorityOperationsHash, bytes32 bootloaderHeapInitialContentsHash, bytes32 eventsQueueStateHash, bytes systemLogs, bytes operatorDAInput)"; +export const PRIORITY_OPS_BATCH_INFO_ABI_STRING = + "tuple(bytes32[] leftPath, bytes32[] rightPath, bytes32[] itemHashes)"; export const DIAMOND_CUT_DATA_ABI_STRING = "tuple(tuple(address facet, uint8 action, bool isFreezable, bytes4[] selectors)[] facetCuts, address initAddress, bytes initCalldata)"; +export const FORCE_DEPLOYMENT_ABI_STRING = + "tuple(bytes32 bytecodeHash, address newAddress, bool callConstructor, uint256 value, bytes input)[]"; +export const BRIDGEHUB_CTM_ASSET_DATA_ABI_STRING = "tuple(uint256 chainId, bytes ctmData, bytes chainData)"; +export const FIXED_FORCE_DEPLOYMENTS_DATA_ABI_STRING = + "tuple(uint256 l1ChainId, uint256 eraChainId, address l1AssetRouter, bytes32 l2TokenProxyBytecodeHash, address aliasedL1Governance, uint256 maxNumberOfZKChains, bytes32 bridgehubBytecodeHash, bytes32 l2AssetRouterBytecodeHash, bytes32 l2NtvBytecodeHash, bytes32 messageRootBytecodeHash, address l2SharedBridgeLegacyImpl, address l2BridgedStandardERC20Impl)"; +export const ADDITIONAL_FORCE_DEPLOYMENTS_DATA_ABI_STRING = "tuple(bytes32 baseTokenAssetId, address l2Weth)"; export function applyL1ToL2Alias(address: string): string { return ethers.utils.hexlify(ethers.BigNumber.from(address).add(L1_TO_L2_ALIAS_OFFSET).mod(ADDRESS_MODULO)); @@ -42,6 +63,10 @@ export function readInterface(path: string, fileName: string) { return new ethers.utils.Interface(abi); } +export function readContract(path: string, fileName: string) { + return JSON.parse(fs.readFileSync(`${path}/${fileName}.sol/${fileName}.json`, { encoding: "utf-8" })); +} + 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); @@ -92,6 +117,15 @@ export function computeL2Create2Address( return ethers.utils.hexDataSlice(data, 12); } +export function encodeNTVAssetId(chainId: number, tokenAddress: BytesLike) { + return ethers.utils.keccak256( + ethers.utils.defaultAbiCoder.encode( + ["uint256", "address", "bytes32"], + [chainId, L2_NATIVE_TOKEN_VAULT_ADDRESS, ethers.utils.hexZeroPad(tokenAddress, 32)] + ) + ); +} + export function getAddressFromEnv(envName: string): string { const address = process.env[envName]; if (!/^0x[a-fA-F0-9]{40}$/.test(address)) { @@ -133,7 +167,6 @@ export interface FeeParams { 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; @@ -179,6 +212,20 @@ export interface L2CanonicalTransaction { reservedDynamic: BytesLike; } +export function ethersWalletToZkWallet(wallet: ethers.Wallet): ZkWallet { + return new ZkWallet(wallet.privateKey, new Provider(web3Url())); +} + +export function isZKMode(): boolean { + return process.env.CONTRACTS_BASE_NETWORK_ZKSYNC === "true"; +} + +const LOCAL_NETWORKS = ["localhost", "hardhat", "localhostL2"]; + +export function isCurrentNetworkLocal(): boolean { + return LOCAL_NETWORKS.includes(process.env.CHAIN_ETH_NETWORK); +} + // Checks that the initial cut hash params are valid. // Sometimes it makes sense to allow dummy values for testing purposes, but in production // these values should be set correctly. @@ -268,12 +315,11 @@ export function compileInitialCutHash( { chainId: "0x0000000000000000000000000000000000000000000000000000000000000001", bridgehub: "0x0000000000000000000000000000000000001234", - stateTransitionManager: "0x0000000000000000000000000000000000002234", + chainTypeManager: "0x0000000000000000000000000000000000002234", protocolVersion: "0x0000000000000000000000000000000000002234", admin: "0x0000000000000000000000000000000000003234", validatorTimelock: "0x0000000000000000000000000000000000004234", - baseToken: "0x0000000000000000000000000000000000004234", - baseTokenBridge: "0x0000000000000000000000000000000000004234", + baseTokenAssetId: "0x0000000000000000000000000000000000000000000000000000000000004234", storedBatchZero: "0x0000000000000000000000000000000000000000000000000000000000005432", verifier, verifierParams, @@ -285,5 +331,16 @@ export function compileInitialCutHash( }, ]); - return diamondCut(facetCuts, diamondInit, "0x" + diamondInitCalldata.slice(2 + (4 + 9 * 32) * 2)); + return diamondCut(facetCuts, diamondInit, "0x" + diamondInitCalldata.slice(2 + (4 + 8 * 32) * 2)); +} + +export enum PubdataSource { + Rollup, + Validium, +} + +export interface ChainAdminCall { + target: string; + value: BigNumberish; + data: BytesLike; } diff --git a/l1-contracts/test/foundry/L1TestsErrors.sol b/l1-contracts/test/foundry/L1TestsErrors.sol new file mode 100644 index 000000000..0eabefc4c --- /dev/null +++ b/l1-contracts/test/foundry/L1TestsErrors.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +error AddressesAlreadyGenerated(); + +error DepositNotSet(); + +error FeeParamsWereNotChangedCorrectly(); + +error EventOnFallbackTargetExpected(); + +error OverheadForTransactionMustBeEqualToTxSlotOverhead(uint256 overheadForTransaction); + +error InvalidBlobCommitmentsLength(); + +error InvalidBlobHashesLength(); diff --git a/l1-contracts/test/foundry/l1/da-contracts-imports/CalldataDA.sol b/l1-contracts/test/foundry/l1/da-contracts-imports/CalldataDA.sol new file mode 100644 index 000000000..782718a65 --- /dev/null +++ b/l1-contracts/test/foundry/l1/da-contracts-imports/CalldataDA.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +// This file is intended to be a *subset* of `da-contracts/contracts/CalldataDA.sol`. +// We can not import the file directly due to issues during imports from folders outside of the project. + +/// @dev Total number of bytes in a blob. Blob = 4096 field elements * 31 bytes per field element +/// @dev EIP-4844 defines it as 131_072 but we use 4096 * 31 within our circuits to always fit within a field element +/// @dev Our circuits will prove that a EIP-4844 blob and our internal blob are the same. +uint256 constant BLOB_SIZE_BYTES = 126_976; + +/// @dev The state diff hash, hash of pubdata + the number of blobs. +uint256 constant BLOB_DATA_OFFSET = 65; + +/// @dev The size of the commitment for a single blob. +uint256 constant BLOB_COMMITMENT_SIZE = 32; diff --git a/l1-contracts/test/foundry/l1/da-contracts-imports/DAContractsErrors.sol b/l1-contracts/test/foundry/l1/da-contracts-imports/DAContractsErrors.sol new file mode 100644 index 000000000..c84e3206e --- /dev/null +++ b/l1-contracts/test/foundry/l1/da-contracts-imports/DAContractsErrors.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +// This file is intended to be an exact copy of `da-contracts/contracts/DAContractsErrors.sol`. +// We can not import the file directly due to issues during imports from folders outside of the project. + +// 0x53dee67b +error PubdataCommitmentsEmpty(); +// 0x53e6d04d +error InvalidPubdataCommitmentsSize(); +// 0xafd53e2f +error BlobHashCommitmentError(uint256 index, bool blobHashEmpty, bool blobCommitmentEmpty); +// 0xfc7ab1d3 +error EmptyBlobVersionHash(uint256 index); +// 0x92290acc +error NonEmptyBlobVersionHash(uint256 index); +// 0x8d5851de +error PointEvalCallFailed(bytes); +// 0x4daa985d +error PointEvalFailed(bytes); + +// 0xf4a3e629 +error OperatorDAInputTooSmall(uint256 operatorDAInputLength, uint256 minAllowedLength); + +// 0xbeb96791 +error InvalidNumberOfBlobs(uint256 blobsProvided, uint256 maxBlobsSupported); + +// 0xe9e79528 +error InvalidL2DAOutputHash(); + +// 0x04e05fd1 +error OnlyOneBlobWithCalldataAllowed(); + +// 0x2dc9747d +error PubdataInputTooSmall(uint256 pubdataInputLength, uint256 totalBlobsCommitmentSize); + +// 0x9044dff9 +error PubdataLengthTooBig(uint256 pubdataLength, uint256 totalBlobSizeBytes); + +// 0x5513177c +error InvalidPubdataHash(bytes32 fullPubdataHash, bytes32 providedPubdataHash); + +// 0xc771423e +error BlobCommitmentNotPublished(); + +// 0x5717f940 +error InvalidPubdataSource(uint8 pubdataSource); +// 0x52595598 +error ValL1DAWrongInputLength(uint256 inputLength, uint256 expectedLength); diff --git a/l1-contracts/test/foundry/l1/da-contracts-imports/DAUtils.sol b/l1-contracts/test/foundry/l1/da-contracts-imports/DAUtils.sol new file mode 100644 index 000000000..c4a53bc95 --- /dev/null +++ b/l1-contracts/test/foundry/l1/da-contracts-imports/DAUtils.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +// This file is intended to be an exact copy of `da-contracts/contracts/DAContractsErrors.sol`. +// We can not import the file directly due to issues during imports from folders outside of the project. + +/// @dev Total number of bytes in a blob. Blob = 4096 field elements * 31 bytes per field element +/// @dev EIP-4844 defines it as 131_072 but we use 4096 * 31 within our circuits to always fit within a field element +/// @dev Our circuits will prove that a EIP-4844 blob and our internal blob are the same. +uint256 constant BLOB_SIZE_BYTES = 126_976; + +/// @dev Enum used to determine the source of pubdata. At first we will support calldata and blobs but this can be extended. +enum PubdataSource { + Calldata, + Blob +} + +/// @dev BLS Modulus value defined in EIP-4844 and the magic value returned from a successful call to the +/// point evaluation precompile +uint256 constant BLS_MODULUS = 52435875175126190479447740508185965837690552500527637822603658699938581184513; + +/// @dev Packed pubdata commitments. +/// @dev Format: list of: opening point (16 bytes) || claimed value (32 bytes) || commitment (48 bytes) || proof (48 bytes)) = 144 bytes +uint256 constant PUBDATA_COMMITMENT_SIZE = 144; + +/// @dev Offset in pubdata commitment of blobs for claimed value +uint256 constant PUBDATA_COMMITMENT_CLAIMED_VALUE_OFFSET = 16; + +/// @dev Offset in pubdata commitment of blobs for kzg commitment +uint256 constant PUBDATA_COMMITMENT_COMMITMENT_OFFSET = 48; + +/// @dev For each blob we expect that the commitment is provided as well as the marker whether a blob with such commitment has been published before. +uint256 constant BLOB_DA_INPUT_SIZE = PUBDATA_COMMITMENT_SIZE + 32; + +/// @dev Address of the point evaluation precompile used for EIP-4844 blob verification. +address constant POINT_EVALUATION_PRECOMPILE_ADDR = address(0x0A); + +/// @dev The address of the special smart contract that can send arbitrary length message as an L2 log +address constant L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR = address(0x8008); diff --git a/l1-contracts/test/foundry/l1/integration/AssetRouterTest.t.sol b/l1-contracts/test/foundry/l1/integration/AssetRouterTest.t.sol new file mode 100644 index 000000000..512dd203a --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/AssetRouterTest.t.sol @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {console2 as console} from "forge-std/console2.sol"; + +import {L2TransactionRequestDirect, L2TransactionRequestTwoBridgesOuter} from "contracts/bridgehub/IBridgehub.sol"; +import {TestnetERC20Token} from "contracts/dev-contracts/TestnetERC20Token.sol"; +import {MailboxFacet} from "contracts/state-transition/chain-deps/facets/Mailbox.sol"; +import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; +import {IMailbox} from "contracts/state-transition/chain-interfaces/IMailbox.sol"; +import {IExecutor} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; +import {L1ContractDeployer} from "./_SharedL1ContractDeployer.t.sol"; +import {TokenDeployer} from "./_SharedTokenDeployer.t.sol"; +import {ZKChainDeployer} from "./_SharedZKChainDeployer.t.sol"; +import {L2TxMocker} from "./_SharedL2TxMocker.t.sol"; +import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; +import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, DEFAULT_L2_LOGS_TREE_ROOT_HASH, EMPTY_STRING_KECCAK} from "contracts/common/Config.sol"; +import {L2CanonicalTransaction, L2Message} from "contracts/common/Messaging.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {IL1ERC20Bridge} from "contracts/bridge/interfaces/IL1ERC20Bridge.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IL1NativeTokenVault} from "contracts/bridge/ntv/IL1NativeTokenVault.sol"; +import {INativeTokenVault} from "contracts/bridge/ntv/INativeTokenVault.sol"; +import {IL1Nullifier, FinalizeL1DepositParams} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IAssetRouterBase, LEGACY_ENCODING_VERSION, NEW_ENCODING_VERSION} from "contracts/bridge/asset-router/IAssetRouterBase.sol"; +import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; +import {BridgeHelper} from "contracts/bridge/BridgeHelper.sol"; +import {BridgedStandardERC20, NonSequentialVersion} from "contracts/bridge/BridgedStandardERC20.sol"; +import {IBridgedStandardToken} from "contracts/bridge/BridgedStandardERC20.sol"; +import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; + +contract AssetRouterTest is L1ContractDeployer, ZKChainDeployer, TokenDeployer, L2TxMocker { + uint256 constant TEST_USERS_COUNT = 10; + address[] public users; + address[] public l2ContractAddresses; + bytes32 public l2TokenAssetId; + address public tokenL1Address; + // generate MAX_USERS addresses and append it to users array + function _generateUserAddresses() internal { + require(users.length == 0, "Addresses already generated"); + + for (uint256 i = 0; i < TEST_USERS_COUNT; i++) { + address newAddress = makeAddr(string(abi.encode("account", i))); + users.push(newAddress); + } + } + + function prepare() public { + _generateUserAddresses(); + + _deployL1Contracts(); + _deployTokens(); + _registerNewTokens(tokens); + + _deployEra(); + // _deployHyperchain(ETH_TOKEN_ADDRESS); + // _deployHyperchain(ETH_TOKEN_ADDRESS); + // _deployHyperchain(tokens[0]); + // _deployHyperchain(tokens[0]); + // _deployHyperchain(tokens[1]); + // _deployHyperchain(tokens[1]); + + for (uint256 i = 0; i < zkChainIds.length; i++) { + address contractAddress = makeAddr(string(abi.encode("contract", i))); + l2ContractAddresses.push(contractAddress); + + _addL2ChainContract(zkChainIds[i], contractAddress); + } + } + + function setUp() public { + prepare(); + } + + function depositToL1(address _tokenAddress) public { + vm.mockCall( + address(bridgehub), + abi.encodeWithSelector(IBridgehub.proveL2MessageInclusion.selector), + abi.encode(true) + ); + uint256 chainId = eraZKChainId; + l2TokenAssetId = DataEncoding.encodeNTVAssetId(chainId, _tokenAddress); + bytes memory transferData = DataEncoding.encodeBridgeMintData({ + _originalCaller: ETH_TOKEN_ADDRESS, + _remoteReceiver: address(this), + _originToken: ETH_TOKEN_ADDRESS, + _amount: 100, + _erc20Metadata: BridgeHelper.getERC20Getters(_tokenAddress, chainId) + }); + l1Nullifier.finalizeDeposit( + FinalizeL1DepositParams({ + chainId: chainId, + l2BatchNumber: 1, + l2MessageIndex: 1, + l2Sender: L2_ASSET_ROUTER_ADDR, + l2TxNumberInBatch: 1, + message: abi.encodePacked( + IAssetRouterBase.finalizeDeposit.selector, + chainId, + l2TokenAssetId, + transferData + ), + merkleProof: new bytes32[](0) + }) + ); + tokenL1Address = l1NativeTokenVault.tokenAddress(l2TokenAssetId); + } + + function test_DepositToL1_Success() public { + depositToL1(ETH_TOKEN_ADDRESS); + } + + function test_BridgeTokenFunctions() public { + depositToL1(ETH_TOKEN_ADDRESS); + BridgedStandardERC20 bridgedToken = BridgedStandardERC20(l1NativeTokenVault.tokenAddress(l2TokenAssetId)); + assertEq(bridgedToken.name(), "Ether"); + assertEq(bridgedToken.symbol(), "ETH"); + assertEq(bridgedToken.decimals(), 18); + } + + function test_reinitBridgedToken_Success() public { + depositToL1(ETH_TOKEN_ADDRESS); + BridgedStandardERC20 bridgedToken = BridgedStandardERC20(l1NativeTokenVault.tokenAddress(l2TokenAssetId)); + address owner = l1NativeTokenVault.owner(); + vm.broadcast(owner); + bridgedToken.reinitializeToken( + BridgedStandardERC20.ERC20Getters({ignoreName: false, ignoreSymbol: false, ignoreDecimals: false}), + "TestnetERC20Token", + "TST", + 2 + ); + } + + function test_reinitBridgedToken_WrongVersion() public { + depositToL1(ETH_TOKEN_ADDRESS); + BridgedStandardERC20 bridgedToken = BridgedStandardERC20(l1NativeTokenVault.tokenAddress(l2TokenAssetId)); + vm.expectRevert(NonSequentialVersion.selector); + bridgedToken.reinitializeToken( + BridgedStandardERC20.ERC20Getters({ignoreName: false, ignoreSymbol: false, ignoreDecimals: false}), + "TestnetERC20Token", + "TST", + 3 + ); + } + + /// @dev We should not test this on the L1, but to get coverage we do. + function test_BridgeTokenBurn() public { + depositToL1(ETH_TOKEN_ADDRESS); + BridgedStandardERC20 bridgedToken = BridgedStandardERC20(l1NativeTokenVault.tokenAddress(l2TokenAssetId)); + // setting nativeTokenVault to zero address. + vm.store(address(bridgedToken), bytes32(uint256(207)), bytes32(0)); + vm.mockCall( + address(L2_NATIVE_TOKEN_VAULT_ADDR), + abi.encodeWithSelector(INativeTokenVault.L1_CHAIN_ID.selector), + abi.encode(block.chainid) + ); + vm.broadcast(L2_NATIVE_TOKEN_VAULT_ADDR); // kl todo call ntv, or even assetRouter/bridgehub + bridgedToken.bridgeBurn(address(this), 100); + } + + function test_DepositToL1AndWithdraw() public { + depositToL1(ETH_TOKEN_ADDRESS); + bytes memory secondBridgeCalldata = bytes.concat( + NEW_ENCODING_VERSION, + abi.encode(l2TokenAssetId, abi.encode(uint256(100), address(this), tokenL1Address)) + ); + IERC20(tokenL1Address).approve(address(l1NativeTokenVault), 100); + bridgehub.requestL2TransactionTwoBridges{value: 250000000000100}( + L2TransactionRequestTwoBridgesOuter({ + chainId: eraZKChainId, + mintValue: 250000000000100, + l2Value: 0, + l2GasLimit: 1000000, + l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + refundRecipient: address(0), + secondBridgeAddress: address(sharedBridge), + secondBridgeValue: 0, + secondBridgeCalldata: secondBridgeCalldata + }) + ); + } + + // add this to be excluded from coverage report + function test() internal override {} +} diff --git a/l1-contracts/test/foundry/l1/integration/BridgeHubInvariantTests.t.sol b/l1-contracts/test/foundry/l1/integration/BridgeHubInvariantTests.t.sol new file mode 100644 index 000000000..77c1aa2a3 --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/BridgeHubInvariantTests.t.sol @@ -0,0 +1,744 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {Vm} from "forge-std/Vm.sol"; + +import {L2TransactionRequestDirect, L2TransactionRequestTwoBridgesOuter} from "contracts/bridgehub/IBridgehub.sol"; +import {TestnetERC20Token} from "contracts/dev-contracts/TestnetERC20Token.sol"; +import {MailboxFacet} from "contracts/state-transition/chain-deps/facets/Mailbox.sol"; +import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; +import {IMailbox} from "contracts/state-transition/chain-interfaces/IMailbox.sol"; +import {IExecutor} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; +import {L1ContractDeployer} from "./_SharedL1ContractDeployer.t.sol"; +import {TokenDeployer} from "./_SharedTokenDeployer.t.sol"; +import {ZKChainDeployer} from "./_SharedZKChainDeployer.t.sol"; +import {L2TxMocker} from "./_SharedL2TxMocker.t.sol"; +import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; +import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, DEFAULT_L2_LOGS_TREE_ROOT_HASH, EMPTY_STRING_KECCAK} from "contracts/common/Config.sol"; +import {L2CanonicalTransaction} from "contracts/common/Messaging.sol"; +import {L2Message} from "contracts/common/Messaging.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {IL1ERC20Bridge} from "contracts/bridge/interfaces/IL1ERC20Bridge.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; +import {AddressesAlreadyGenerated} from "test/foundry/L1TestsErrors.sol"; + +contract BridgeHubInvariantTests is L1ContractDeployer, ZKChainDeployer, TokenDeployer, L2TxMocker { + uint256 constant TEST_USERS_COUNT = 10; + + bytes32 constant NEW_PRIORITY_REQUEST_HASH = + keccak256( + "NewPriorityRequest(uint256,bytes32,uint64,(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[])" + ); + + enum RequestType { + DIRECT, + TWO_BRIDGES + } + + struct NewPriorityRequest { + uint256 txId; + bytes32 txHash; + uint64 expirationTimestamp; + L2CanonicalTransaction transaction; + bytes[] factoryDeps; + } + + address[] public users; + address[] public l2ContractAddresses; + address[] public addressesToExclude; + address public currentUser; + uint256 public currentChainId; + address public currentChainAddress; + address public currentTokenAddress = ETH_TOKEN_ADDRESS; + TestnetERC20Token currentToken; + + // Amounts deposited by each user, mapped by user address and token address + mapping(address user => mapping(address token => uint256 deposited)) public depositsUsers; + // Amounts deposited into the bridge, mapped by ZK chain address and token address + mapping(address chain => mapping(address token => uint256 deposited)) public depositsBridge; + // Total sum of deposits into the bridge, mapped by token address + mapping(address token => uint256 deposited) public tokenSumDeposit; + // Total sum of withdrawn tokens, mapped by token address + mapping(address token => uint256 deposited) public tokenSumWithdrawal; + // Total sum of L2 values transferred to mock contracts, mapped by token address + mapping(address token => uint256 deposited) public l2ValuesSum; + // Deposits into the ZK chains contract, mapped by L2 contract address and token address + mapping(address l2contract => mapping(address token => uint256 balance)) public contractDeposits; + // Total sum of deposits into all L2 contracts, mapped by token address + mapping(address token => uint256 deposited) public contractDepositsSum; + + // gets random user from users array, set contract variables + modifier useUser(uint256 userIndexSeed) { + currentUser = users[bound(userIndexSeed, 0, users.length - 1)]; + vm.startPrank(currentUser); + _; + vm.stopPrank(); + } + + // gets random ZK chain from ZK chain ids, set contract variables + modifier useZKChain(uint256 chainIndexSeed) { + currentChainId = zkChainIds[bound(chainIndexSeed, 0, zkChainIds.length - 1)]; + currentChainAddress = getZKChainAddress(currentChainId); + _; + } + + // use token specified by address, set contract variables + modifier useGivenToken(address tokenAddress) { + currentToken = TestnetERC20Token(tokenAddress); + currentTokenAddress = tokenAddress; + _; + } + + // use random token from tokens array, set contract variables + modifier useRandomToken(uint256 tokenIndexSeed) { + currentTokenAddress = tokens[bound(tokenIndexSeed, 0, tokens.length - 1)]; + currentToken = TestnetERC20Token(currentTokenAddress); + _; + } + + // use base token as main token + // watch out, do not use with ETH + modifier useBaseToken() { + currentToken = TestnetERC20Token(getZKChainBaseToken(currentChainId)); + currentTokenAddress = address(currentToken); + _; + } + + // use ERC token by getting randomly token + // it keeps iterating while the token is ETH + modifier useERC20Token(uint256 tokenIndexSeed) { + currentTokenAddress = tokens[bound(tokenIndexSeed, 0, tokens.length - 1)]; + + while (currentTokenAddress == ETH_TOKEN_ADDRESS) { + tokenIndexSeed += 1; + currentTokenAddress = tokens[bound(tokenIndexSeed, 0, tokens.length - 1)]; + } + + currentToken = TestnetERC20Token(currentTokenAddress); + + _; + } + + // generate MAX_USERS addresses and append it to users array + function _generateUserAddresses() internal { + if (users.length != 0) { + revert AddressesAlreadyGenerated(); + } + + for (uint256 i = 0; i < TEST_USERS_COUNT; i++) { + address newAddress = makeAddr(string(abi.encode("account", i))); + users.push(newAddress); + } + } + + // TODO: consider what should be actually committed, do we need to simulate operator: + // blocks -> batches -> commits or just mock it. + function _commitBatchInfo(uint256 _chainId) internal { + //vm.warp(COMMIT_TIMESTAMP_NOT_OLDER + 1 + 1); + + GettersFacet zkChainGetters = GettersFacet(getZKChainAddress(_chainId)); + + IExecutor.StoredBatchInfo memory batchZero; + + batchZero.batchNumber = 0; + batchZero.timestamp = 0; + batchZero.numberOfLayer1Txs = 0; + batchZero.priorityOperationsHash = EMPTY_STRING_KECCAK; + batchZero.l2LogsTreeRoot = DEFAULT_L2_LOGS_TREE_ROOT_HASH; + batchZero.batchHash = vm.parseBytes32("0x0000000000000000000000000000000000000000000000000000000000000000"); //genesis root hash + batchZero.indexRepeatedStorageChanges = uint64(0); + batchZero.commitment = vm.parseBytes32("0x0000000000000000000000000000000000000000000000000000000000000000"); + + bytes32 hashedZeroBatch = keccak256(abi.encode(batchZero)); + assertEq(zkChainGetters.storedBatchHash(0), hashedZeroBatch); + } + + // use mailbox interface to return exact amount to use as a gas on l2 side, + // prevents from failing if mintValue < l2Value + required gas + function _getMinRequiredGasPriceForChain( + uint256 _chainId, + uint256 _gasPrice, + uint256 _l2GasLimit, + uint256 _l2GasPerPubdataByteLimit + ) public view returns (uint256) { + MailboxFacet chainMailBox = MailboxFacet(getZKChainAddress(_chainId)); + + return chainMailBox.l2TransactionBaseCost(_gasPrice, _l2GasLimit, _l2GasPerPubdataByteLimit); + } + + // decodes data encoded with encodeCall, this is just to decode information received from logs + // to deposit into mock l2 contract + function _getDecodedDepositL2Calldata( + bytes memory callData + ) internal view returns (address l1Sender, address l2Receiver, address l1Token, uint256 amount, bytes memory b) { + // UnsafeBytes approach doesn't work, because abi is not deterministic + bytes memory slicedData = new bytes(callData.length - 4); + + for (uint256 i = 4; i < callData.length; i++) { + slicedData[i - 4] = callData[i]; + } + + (l1Sender, l2Receiver, l1Token, amount, b) = abi.decode( + slicedData, + (address, address, address, uint256, bytes) + ); + } + + // handle event emitted from logs, just to ensure proper decoding to set mock contract balance + function _handleRequestByMockL2Contract(NewPriorityRequest memory request, RequestType requestType) internal { + address contractAddress = address(uint160(uint256(request.transaction.to))); + + address tokenAddress; + address receiver; + uint256 toSend; + address l1Sender; + uint256 balanceAfter; + bytes memory temp; + + if (requestType == RequestType.TWO_BRIDGES) { + (l1Sender, receiver, tokenAddress, toSend, temp) = _getDecodedDepositL2Calldata(request.transaction.data); + } else { + (tokenAddress, toSend, receiver) = abi.decode(request.transaction.data, (address, uint256, address)); + } + + assertEq(contractAddress, receiver); + + if (tokenAddress == ETH_TOKEN_ADDRESS) { + uint256 balanceBefore = contractAddress.balance; + vm.deal(contractAddress, toSend + balanceBefore); + + balanceAfter = contractAddress.balance; + } else { + TestnetERC20Token token = TestnetERC20Token(tokenAddress); + token.mint(contractAddress, toSend); + + balanceAfter = token.balanceOf(contractAddress); + } + + contractDeposits[contractAddress][tokenAddress] += toSend; + contractDepositsSum[tokenAddress] += toSend; + assertEq(balanceAfter, contractDeposits[contractAddress][tokenAddress]); + } + + // gets event from logs + function _getNewPriorityQueueFromLogs(Vm.Log[] memory logs) internal returns (NewPriorityRequest memory request) { + for (uint256 i = 0; i < logs.length; i++) { + Vm.Log memory log = logs[i]; + + if (log.topics[0] == NEW_PRIORITY_REQUEST_HASH) { + ( + request.txId, + request.txHash, + request.expirationTimestamp, + request.transaction, + request.factoryDeps + ) = abi.decode(log.data, (uint256, bytes32, uint64, L2CanonicalTransaction, bytes[])); + } + } + } + + // deposits ERC20 token to the ZK chain where base token is ETH + // this function use requestL2TransactionTwoBridges function from shared bridge. + // tokenAddress should be any ERC20 token, excluding ETH + function depositERC20ToEthChain(uint256 l2Value, address tokenAddress) private useGivenToken(tokenAddress) { + uint256 gasPrice = 10000000; + vm.txGasPrice(gasPrice); + + uint256 l2GasLimit = 1000000; + uint256 minRequiredGas = _getMinRequiredGasPriceForChain( + currentChainId, + gasPrice, + l2GasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA + ); + + uint256 mintValue = minRequiredGas; + vm.deal(currentUser, mintValue); + + currentToken.mint(currentUser, l2Value); + currentToken.approve(address(sharedBridge), l2Value); + + bytes memory secondBridgeCallData = abi.encode(currentTokenAddress, l2Value, chainContracts[currentChainId]); + L2TransactionRequestTwoBridgesOuter memory requestTx = _createL2TransactionRequestTwoBridges({ + _chainId: currentChainId, + _mintValue: mintValue, + _secondBridgeValue: 0, + _secondBridgeAddress: address(sharedBridge), + _l2Value: 0, + _l2GasLimit: l2GasLimit, + _l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + _secondBridgeCalldata: secondBridgeCallData + }); + + vm.recordLogs(); + bytes32 resultantHash = bridgehub.requestL2TransactionTwoBridges{value: mintValue}(requestTx); + Vm.Log[] memory logs = vm.getRecordedLogs(); + NewPriorityRequest memory request = _getNewPriorityQueueFromLogs(logs); + + assertNotEq(resultantHash, bytes32(0)); + assertNotEq(request.txHash, bytes32(0)); + _handleRequestByMockL2Contract(request, RequestType.TWO_BRIDGES); + + depositsUsers[currentUser][ETH_TOKEN_ADDRESS] += mintValue; + depositsBridge[currentChainAddress][ETH_TOKEN_ADDRESS] += mintValue; + tokenSumDeposit[ETH_TOKEN_ADDRESS] += mintValue; + + depositsUsers[currentUser][currentTokenAddress] += l2Value; + depositsBridge[currentChainAddress][currentTokenAddress] += l2Value; + tokenSumDeposit[currentTokenAddress] += l2Value; + l2ValuesSum[currentTokenAddress] += l2Value; + } + + // deposits ETH token to chain where base token is some ERC20 + // modifier prevents you from using some other token as base + function depositEthToERC20Chain(uint256 l2Value) private useBaseToken { + uint256 gasPrice = 10000000; + vm.txGasPrice(gasPrice); + + uint256 l2GasLimit = 1000000; + uint256 minRequiredGas = _getMinRequiredGasPriceForChain( + currentChainId, + gasPrice, + l2GasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA + ); + + vm.deal(currentUser, l2Value); + uint256 mintValue = minRequiredGas; + currentToken.mint(currentUser, mintValue); + currentToken.approve(address(sharedBridge), mintValue); + + bytes memory secondBridgeCallData = abi.encode(ETH_TOKEN_ADDRESS, uint256(0), chainContracts[currentChainId]); + L2TransactionRequestTwoBridgesOuter memory requestTx = _createL2TransactionRequestTwoBridges({ + _chainId: currentChainId, + _mintValue: mintValue, + _secondBridgeValue: l2Value, + _secondBridgeAddress: address(sharedBridge), + _l2Value: 0, + _l2GasLimit: l2GasLimit, + _l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + _secondBridgeCalldata: secondBridgeCallData + }); + + vm.recordLogs(); + bytes32 resultantHash = bridgehub.requestL2TransactionTwoBridges{value: l2Value}(requestTx); + Vm.Log[] memory logs = vm.getRecordedLogs(); + NewPriorityRequest memory request = _getNewPriorityQueueFromLogs(logs); + + assertNotEq(resultantHash, bytes32(0)); + assertNotEq(request.txHash, bytes32(0)); + _handleRequestByMockL2Contract(request, RequestType.TWO_BRIDGES); + + depositsUsers[currentUser][ETH_TOKEN_ADDRESS] += l2Value; + depositsBridge[currentChainAddress][ETH_TOKEN_ADDRESS] += l2Value; + tokenSumDeposit[ETH_TOKEN_ADDRESS] += l2Value; + l2ValuesSum[ETH_TOKEN_ADDRESS] += l2Value; + + depositsUsers[currentUser][currentTokenAddress] += mintValue; + depositsBridge[currentChainAddress][currentTokenAddress] += mintValue; + tokenSumDeposit[currentTokenAddress] += mintValue; + } + + // deposits ERC20 to token with base being also ERC20 + // there are no modifiers so watch out, baseTokenAddress should be base of ZK chain + // currentToken should be different from base + function depositERC20ToERC20Chain(uint256 l2Value, address baseTokenAddress) private { + uint256 gasPrice = 10000000; + vm.txGasPrice(gasPrice); + + uint256 l2GasLimit = 1000000; + uint256 minRequiredGas = _getMinRequiredGasPriceForChain( + currentChainId, + gasPrice, + l2GasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA + ); + + uint256 mintValue = minRequiredGas; + + TestnetERC20Token baseToken = TestnetERC20Token(baseTokenAddress); + baseToken.mint(currentUser, mintValue); + baseToken.approve(address(sharedBridge), mintValue); + + currentToken.mint(currentUser, l2Value); + currentToken.approve(address(sharedBridge), l2Value); + + bytes memory secondBridgeCallData = abi.encode(currentTokenAddress, l2Value, chainContracts[currentChainId]); + L2TransactionRequestTwoBridgesOuter memory requestTx = _createL2TransactionRequestTwoBridges({ + _chainId: currentChainId, + _mintValue: mintValue, + _secondBridgeValue: 0, + _secondBridgeAddress: address(sharedBridge), + _l2Value: 0, + _l2GasLimit: l2GasLimit, + _l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + _secondBridgeCalldata: secondBridgeCallData + }); + + vm.recordLogs(); + bytes32 resultantHash = bridgehub.requestL2TransactionTwoBridges(requestTx); + Vm.Log[] memory logs = vm.getRecordedLogs(); + NewPriorityRequest memory request = _getNewPriorityQueueFromLogs(logs); + + assertNotEq(resultantHash, bytes32(0)); + assertNotEq(request.txHash, bytes32(0)); + _handleRequestByMockL2Contract(request, RequestType.TWO_BRIDGES); + + depositsUsers[currentUser][baseTokenAddress] += mintValue; + depositsBridge[currentChainAddress][baseTokenAddress] += mintValue; + tokenSumDeposit[baseTokenAddress] += mintValue; + + depositsUsers[currentUser][currentTokenAddress] += l2Value; + depositsBridge[currentChainAddress][currentTokenAddress] += l2Value; + tokenSumDeposit[currentTokenAddress] += l2Value; + l2ValuesSum[currentTokenAddress] += l2Value; + } + + // deposits ETH to ZK chain where base is ETH + function depositEthBase(uint256 l2Value) private { + uint256 gasPrice = 10000000; + vm.txGasPrice(gasPrice); + + uint256 l2GasLimit = 1000000; // reverts with 8 + uint256 minRequiredGas = _getMinRequiredGasPriceForChain( + currentChainId, + gasPrice, + l2GasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA + ); + + uint256 mintValue = l2Value + minRequiredGas; + vm.deal(currentUser, mintValue); + + bytes memory callData = abi.encode(currentTokenAddress, l2Value, chainContracts[currentChainId]); + L2TransactionRequestDirect memory txRequest = _createL2TransactionRequestDirect({ + _chainId: currentChainId, + _mintValue: mintValue, + _l2Value: l2Value, + _l2GasLimit: l2GasLimit, + _l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + _l2CallData: callData + }); + + vm.recordLogs(); + bytes32 resultantHash = bridgehub.requestL2TransactionDirect{value: mintValue}(txRequest); + Vm.Log[] memory logs = vm.getRecordedLogs(); + + NewPriorityRequest memory request = _getNewPriorityQueueFromLogs(logs); + + assertNotEq(resultantHash, bytes32(0)); + assertNotEq(request.txHash, bytes32(0)); + _handleRequestByMockL2Contract(request, RequestType.DIRECT); + + depositsUsers[currentUser][ETH_TOKEN_ADDRESS] += mintValue; + depositsBridge[currentChainAddress][ETH_TOKEN_ADDRESS] += mintValue; + tokenSumDeposit[ETH_TOKEN_ADDRESS] += mintValue; + l2ValuesSum[ETH_TOKEN_ADDRESS] += l2Value; + } + + // deposits base ERC20 token to the bridge + function depositERC20Base(uint256 l2Value) private useBaseToken { + uint256 gasPrice = 10000000; + vm.txGasPrice(gasPrice); + vm.deal(currentUser, gasPrice); + + uint256 l2GasLimit = 1000000; + uint256 minRequiredGas = _getMinRequiredGasPriceForChain( + currentChainId, + gasPrice, + l2GasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA + ); + + uint256 mintValue = l2Value + minRequiredGas; + currentToken.mint(currentUser, mintValue); + currentToken.approve(address(sharedBridge), mintValue); + + bytes memory callData = abi.encode(currentTokenAddress, l2Value, chainContracts[currentChainId]); + L2TransactionRequestDirect memory txRequest = _createL2TransactionRequestDirect({ + _chainId: currentChainId, + _mintValue: mintValue, + _l2Value: l2Value, + _l2GasLimit: l2GasLimit, + _l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + _l2CallData: callData + }); + + vm.recordLogs(); + bytes32 resultantHash = bridgehub.requestL2TransactionDirect(txRequest); + Vm.Log[] memory logs = vm.getRecordedLogs(); + + NewPriorityRequest memory request = _getNewPriorityQueueFromLogs(logs); + + assertNotEq(resultantHash, bytes32(0)); + assertNotEq(request.txHash, bytes32(0)); + _handleRequestByMockL2Contract(request, RequestType.DIRECT); + + depositsUsers[currentUser][currentTokenAddress] += mintValue; + depositsBridge[currentChainAddress][currentTokenAddress] += mintValue; + tokenSumDeposit[currentTokenAddress] += mintValue; + l2ValuesSum[currentTokenAddress] += l2Value; + } + + function withdrawERC20Token(uint256 amountToWithdraw, address tokenAddress) private useGivenToken(tokenAddress) { + uint256 l2BatchNumber = uint256(uint160(makeAddr("l2BatchNumber"))); + uint256 l2MessageIndex = uint256(uint160(makeAddr("l2MessageIndex"))); + uint16 l2TxNumberInBatch = uint16(uint160(makeAddr("l2TxNumberInBatch"))); + bytes32[] memory merkleProof = new bytes32[](1); + + _setSharedBridgeIsWithdrawalFinalized(currentChainId, l2BatchNumber, l2MessageIndex, false); + uint256 beforeChainBalance = l1Nullifier.chainBalance(currentChainId, currentTokenAddress); + uint256 beforeBalance = currentToken.balanceOf(address(sharedBridge)); + + if (beforeChainBalance < amountToWithdraw) { + vm.expectRevert("L1AR: not enough funds 2"); + } else { + tokenSumWithdrawal[currentTokenAddress] += amountToWithdraw; + } + + bytes memory message = abi.encodePacked( + IL1ERC20Bridge.finalizeWithdrawal.selector, + currentUser, + currentTokenAddress, + amountToWithdraw + ); + + L2Message memory l2ToL1Message = L2Message({ + txNumberInBatch: l2TxNumberInBatch, + sender: L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, + data: message + }); + + vm.mockCall( + bridgehubProxyAddress, + // solhint-disable-next-line func-named-parameters + abi.encodeWithSelector( + IBridgehub.proveL2MessageInclusion.selector, + currentChainId, + l2BatchNumber, + l2MessageIndex, + l2ToL1Message, + merkleProof + ), + abi.encode(true) + ); + + sharedBridge.finalizeWithdrawal({ + _chainId: currentChainId, + _l2BatchNumber: l2BatchNumber, + _l2MessageIndex: l2MessageIndex, + _l2TxNumberInBatch: l2TxNumberInBatch, + _message: message, + _merkleProof: merkleProof + }); + + // check if the balance was updated correctly + if (beforeChainBalance > amountToWithdraw) { + assertEq( + beforeChainBalance - l1Nullifier.chainBalance(currentChainId, currentTokenAddress), + amountToWithdraw + ); + assertEq(beforeBalance - currentToken.balanceOf(address(sharedBridge)), amountToWithdraw); + } + } + + function withdrawETHToken(uint256 amountToWithdraw, address tokenAddress) private useGivenToken(tokenAddress) { + uint256 l2BatchNumber = uint256(uint160(makeAddr("l2BatchNumber"))); + uint256 l2MessageIndex = uint256(uint160(makeAddr("l2MessageIndex"))); + uint16 l2TxNumberInBatch = uint16(uint160(makeAddr("l2TxNumberInBatch"))); + bytes32[] memory merkleProof = new bytes32[](1); + + _setSharedBridgeIsWithdrawalFinalized(currentChainId, l2BatchNumber, l2MessageIndex, false); + uint256 beforeChainBalance = l1Nullifier.chainBalance(currentChainId, currentTokenAddress); + uint256 beforeBalance = address(sharedBridge).balance; + + if (beforeChainBalance < amountToWithdraw) { + vm.expectRevert("L1AR: not enough funds 2"); + } else { + tokenSumWithdrawal[currentTokenAddress] += amountToWithdraw; + } + + bytes memory message = abi.encodePacked(IMailbox.finalizeEthWithdrawal.selector, currentUser, amountToWithdraw); + L2Message memory l2ToL1Message = L2Message({ + txNumberInBatch: l2TxNumberInBatch, + sender: L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, + data: message + }); + + vm.mockCall( + bridgehubProxyAddress, + // solhint-disable-next-line func-named-parameters + abi.encodeWithSelector( + IBridgehub.proveL2MessageInclusion.selector, + currentChainId, + l2BatchNumber, + l2MessageIndex, + l2ToL1Message, + merkleProof + ), + abi.encode(true) + ); + + sharedBridge.finalizeWithdrawal({ + _chainId: currentChainId, + _l2BatchNumber: l2BatchNumber, + _l2MessageIndex: l2MessageIndex, + _l2TxNumberInBatch: l2TxNumberInBatch, + _message: message, + _merkleProof: merkleProof + }); + + // check if the balance was updated correctly + if (beforeChainBalance > amountToWithdraw) { + assertEq( + beforeChainBalance - l1Nullifier.chainBalance(currentChainId, currentTokenAddress), + amountToWithdraw + ); + assertEq(beforeBalance - address(sharedBridge).balance, amountToWithdraw); + } + } + + function depositEthToBridgeSuccess( + uint256 userIndexSeed, + uint256 chainIndexSeed, + uint256 l2Value + ) public virtual useUser(userIndexSeed) useZKChain(chainIndexSeed) useBaseToken { + if (currentTokenAddress == ETH_TOKEN_ADDRESS) { + depositEthBase(l2Value); + } else { + depositEthToERC20Chain(l2Value); + } + } + + function depositERC20ToBridgeSuccess( + uint256 userIndexSeed, + uint256 chainIndexSeed, + uint256 tokenIndexSeed, + uint256 l2Value + ) public virtual useUser(userIndexSeed) useZKChain(chainIndexSeed) useERC20Token(tokenIndexSeed) { + address chainBaseToken = getZKChainBaseToken(currentChainId); + + if (chainBaseToken == ETH_TOKEN_ADDRESS) { + depositERC20ToEthChain(l2Value, currentTokenAddress); + } else { + if (currentTokenAddress == chainBaseToken) { + depositERC20Base(l2Value); + } else { + depositERC20ToERC20Chain(l2Value, chainBaseToken); + } + } + } + + function withdrawSuccess( + uint256 userIndexSeed, + uint256 chainIndexSeed, + uint256 amountToWithdraw + ) public virtual useUser(userIndexSeed) useZKChain(chainIndexSeed) { + address token = getZKChainBaseToken(currentChainId); + + if (token != ETH_TOKEN_ADDRESS) { + withdrawERC20Token(amountToWithdraw, token); + } else if (token == ETH_TOKEN_ADDRESS) { + withdrawETHToken(amountToWithdraw, token); + } + } + + function getAddressesToExclude() public returns (address[] memory) { + addressesToExclude.push(bridgehubProxyAddress); + addressesToExclude.push(address(sharedBridge)); + + for (uint256 i = 0; i < users.length; i++) { + addressesToExclude.push(users[i]); + } + + for (uint256 i = 0; i < l2ContractAddresses.length; i++) { + addressesToExclude.push(l2ContractAddresses[i]); + } + + for (uint256 i = 0; i < zkChainIds.length; i++) { + addressesToExclude.push(getZKChainAddress(zkChainIds[i])); + } + + return addressesToExclude; + } + + function prepare() public { + _generateUserAddresses(); + + _deployL1Contracts(); + _deployTokens(); + _registerNewTokens(tokens); + + _deployEra(); + _deployZKChain(ETH_TOKEN_ADDRESS); + _deployZKChain(ETH_TOKEN_ADDRESS); + _deployZKChain(tokens[0]); + _deployZKChain(tokens[0]); + _deployZKChain(tokens[1]); + _deployZKChain(tokens[1]); + + for (uint256 i = 0; i < zkChainIds.length; i++) { + address contractAddress = makeAddr(string(abi.encode("contract", i))); + l2ContractAddresses.push(contractAddress); + + _addL2ChainContract(zkChainIds[i], contractAddress); + } + } + + // add this to be excluded from coverage report + function test() internal override {} +} + +contract BoundedBridgeHubInvariantTests is BridgeHubInvariantTests { + function depositEthSuccess(uint256 userIndexSeed, uint256 chainIndexSeed, uint256 l2Value) public { + uint64 MAX = 2 ** 64 - 1; + uint256 l2Value = bound(l2Value, 0.1 ether, MAX); + + emit log_string("DEPOSIT ETH"); + super.depositEthToBridgeSuccess(userIndexSeed, chainIndexSeed, l2Value); + } + + function depositERC20Success( + uint256 userIndexSeed, + uint256 chainIndexSeed, + uint256 tokenIndexSeed, + uint256 l2Value + ) public { + uint64 MAX = 2 ** 64 - 1; + uint256 l2Value = bound(l2Value, 0.1 ether, MAX); + + emit log_string("DEPOSIT ERC20"); + super.depositERC20ToBridgeSuccess(userIndexSeed, chainIndexSeed, tokenIndexSeed, l2Value); + } + + function withdrawERC20Success(uint256 userIndexSeed, uint256 chainIndexSeed, uint256 amountToWithdraw) public { + uint64 MAX = (2 ** 32 - 1) + 0.1 ether; + uint256 amountToWithdraw = bound(amountToWithdraw, 0.1 ether, MAX); + + emit log_string("WITHDRAW ERC20"); + super.withdrawSuccess(userIndexSeed, chainIndexSeed, amountToWithdraw); + } + + // add this to be excluded from coverage report + function testBoundedBridgeHubInvariant() internal {} +} + +contract InvariantTesterZKChains is Test { + BoundedBridgeHubInvariantTests tests; + + function setUp() public { + tests = new BoundedBridgeHubInvariantTests(); + // tests.prepare(); + } + + // // Check whether the sum of ETH deposits from tests, updated on each deposit and withdrawal, + // // equals the balance of L1Shared bridge. + // function invariant_ETHbalanceStaysEqual() public { + // require(1==1); + // } + + // add this to be excluded from coverage report + function test() internal {} +} diff --git a/l1-contracts/test/foundry/l1/integration/BridgehubTests.t.sol b/l1-contracts/test/foundry/l1/integration/BridgehubTests.t.sol new file mode 100644 index 000000000..1467d8535 --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/BridgehubTests.t.sol @@ -0,0 +1,744 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {Vm} from "forge-std/Vm.sol"; + +import {L2TransactionRequestDirect, L2TransactionRequestTwoBridgesOuter} from "contracts/bridgehub/IBridgehub.sol"; +import {TestnetERC20Token} from "contracts/dev-contracts/TestnetERC20Token.sol"; +import {MailboxFacet} from "contracts/state-transition/chain-deps/facets/Mailbox.sol"; +import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; +import {IMailbox} from "contracts/state-transition/chain-interfaces/IMailbox.sol"; +import {IExecutor} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; +import {L1ContractDeployer} from "./_SharedL1ContractDeployer.t.sol"; +import {TokenDeployer} from "./_SharedTokenDeployer.t.sol"; +import {ZKChainDeployer} from "./_SharedZKChainDeployer.t.sol"; +import {L2TxMocker} from "./_SharedL2TxMocker.t.sol"; +import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; +import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, DEFAULT_L2_LOGS_TREE_ROOT_HASH, EMPTY_STRING_KECCAK} from "contracts/common/Config.sol"; +import {L2CanonicalTransaction} from "contracts/common/Messaging.sol"; +import {L2Message} from "contracts/common/Messaging.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {IL1ERC20Bridge} from "contracts/bridge/interfaces/IL1ERC20Bridge.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; +import {AddressesAlreadyGenerated} from "test/foundry/L1TestsErrors.sol"; + +contract BridgeHubInvariantTests is L1ContractDeployer, ZKChainDeployer, TokenDeployer, L2TxMocker { + uint256 constant TEST_USERS_COUNT = 10; + + bytes32 constant NEW_PRIORITY_REQUEST_HASH = + keccak256( + "NewPriorityRequest(uint256,bytes32,uint64,(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[])" + ); + + enum RequestType { + DIRECT, + TWO_BRIDGES + } + + struct NewPriorityRequest { + uint256 txId; + bytes32 txHash; + uint64 expirationTimestamp; + L2CanonicalTransaction transaction; + bytes[] factoryDeps; + } + + address[] public users; + address[] public l2ContractAddresses; + address[] public addressesToExclude; + address public currentUser; + uint256 public currentChainId; + address public currentChainAddress; + address public currentTokenAddress = ETH_TOKEN_ADDRESS; + TestnetERC20Token currentToken; + + // Amounts deposited by each user, mapped by user address and token address + mapping(address user => mapping(address token => uint256 deposited)) public depositsUsers; + // Amounts deposited into the bridge, mapped by ZK chain address and token address + mapping(address chain => mapping(address token => uint256 deposited)) public depositsBridge; + // Total sum of deposits into the bridge, mapped by token address + mapping(address token => uint256 deposited) public tokenSumDeposit; + // Total sum of withdrawn tokens, mapped by token address + mapping(address token => uint256 deposited) public tokenSumWithdrawal; + // Total sum of L2 values transferred to mock contracts, mapped by token address + mapping(address token => uint256 deposited) public l2ValuesSum; + // Deposits into the ZK chains contract, mapped by L2 contract address and token address + mapping(address l2contract => mapping(address token => uint256 balance)) public contractDeposits; + // Total sum of deposits into all L2 contracts, mapped by token address + mapping(address token => uint256 deposited) public contractDepositsSum; + + // gets random user from users array, set contract variables + modifier useUser(uint256 userIndexSeed) { + currentUser = users[bound(userIndexSeed, 0, users.length - 1)]; + vm.startPrank(currentUser); + _; + vm.stopPrank(); + } + + // gets random ZK chain from ZK chain ids, set contract variables + modifier useZKChain(uint256 chainIndexSeed) { + currentChainId = zkChainIds[bound(chainIndexSeed, 0, zkChainIds.length - 1)]; + currentChainAddress = getZKChainAddress(currentChainId); + _; + } + + // use token specified by address, set contract variables + modifier useGivenToken(address tokenAddress) { + currentToken = TestnetERC20Token(tokenAddress); + currentTokenAddress = tokenAddress; + _; + } + + // use random token from tokens array, set contract variables + modifier useRandomToken(uint256 tokenIndexSeed) { + currentTokenAddress = tokens[bound(tokenIndexSeed, 0, tokens.length - 1)]; + currentToken = TestnetERC20Token(currentTokenAddress); + _; + } + + // use base token as main token + // watch out, do not use with ETH + modifier useBaseToken() { + currentToken = TestnetERC20Token(getZKChainBaseToken(currentChainId)); + currentTokenAddress = address(currentToken); + _; + } + + // use ERC token by getting randomly token + // it keeps iterating while the token is ETH + modifier useERC20Token(uint256 tokenIndexSeed) { + currentTokenAddress = tokens[bound(tokenIndexSeed, 0, tokens.length - 1)]; + + while (currentTokenAddress == ETH_TOKEN_ADDRESS) { + tokenIndexSeed += 1; + currentTokenAddress = tokens[bound(tokenIndexSeed, 0, tokens.length - 1)]; + } + + currentToken = TestnetERC20Token(currentTokenAddress); + + _; + } + + // generate MAX_USERS addresses and append it to users array + function _generateUserAddresses() internal { + if (users.length != 0) { + revert AddressesAlreadyGenerated(); + } + + for (uint256 i = 0; i < TEST_USERS_COUNT; i++) { + address newAddress = makeAddr(string(abi.encode("account", i))); + users.push(newAddress); + } + } + + // TODO: consider what should be actually committed, do we need to simulate operator: + // blocks -> batches -> commits or just mock it. + function _commitBatchInfo(uint256 _chainId) internal { + //vm.warp(COMMIT_TIMESTAMP_NOT_OLDER + 1 + 1); + + GettersFacet zkChainGetters = GettersFacet(getZKChainAddress(_chainId)); + + IExecutor.StoredBatchInfo memory batchZero; + + batchZero.batchNumber = 0; + batchZero.timestamp = 0; + batchZero.numberOfLayer1Txs = 0; + batchZero.priorityOperationsHash = EMPTY_STRING_KECCAK; + batchZero.l2LogsTreeRoot = DEFAULT_L2_LOGS_TREE_ROOT_HASH; + batchZero.batchHash = vm.parseBytes32("0x0000000000000000000000000000000000000000000000000000000000000000"); //genesis root hash + batchZero.indexRepeatedStorageChanges = uint64(0); + batchZero.commitment = vm.parseBytes32("0x0000000000000000000000000000000000000000000000000000000000000000"); + + bytes32 hashedZeroBatch = keccak256(abi.encode(batchZero)); + assertEq(zkChainGetters.storedBatchHash(0), hashedZeroBatch); + } + + // use mailbox interface to return exact amount to use as a gas on l2 side, + // prevents from failing if mintValue < l2Value + required gas + function _getMinRequiredGasPriceForChain( + uint256 _chainId, + uint256 _gasPrice, + uint256 _l2GasLimit, + uint256 _l2GasPerPubdataByteLimit + ) public view returns (uint256) { + MailboxFacet chainMailBox = MailboxFacet(getZKChainAddress(_chainId)); + + return chainMailBox.l2TransactionBaseCost(_gasPrice, _l2GasLimit, _l2GasPerPubdataByteLimit); + } + + // decodes data encoded with encodeCall, this is just to decode information received from logs + // to deposit into mock l2 contract + function _getDecodedDepositL2Calldata( + bytes memory callData + ) internal view returns (address l1Sender, address l2Receiver, address l1Token, uint256 amount, bytes memory b) { + // UnsafeBytes approach doesn't work, because abi is not deterministic + bytes memory slicedData = new bytes(callData.length - 4); + + for (uint256 i = 4; i < callData.length; i++) { + slicedData[i - 4] = callData[i]; + } + + (l1Sender, l2Receiver, l1Token, amount, b) = abi.decode( + slicedData, + (address, address, address, uint256, bytes) + ); + } + + // handle event emitted from logs, just to ensure proper decoding to set mock contract balance + function _handleRequestByMockL2Contract(NewPriorityRequest memory request, RequestType requestType) internal { + address contractAddress = address(uint160(uint256(request.transaction.to))); + + address tokenAddress; + address receiver; + uint256 toSend; + address l1Sender; + uint256 balanceAfter; + bytes memory temp; + + if (requestType == RequestType.TWO_BRIDGES) { + (l1Sender, receiver, tokenAddress, toSend, temp) = _getDecodedDepositL2Calldata(request.transaction.data); + } else { + (tokenAddress, toSend, receiver) = abi.decode(request.transaction.data, (address, uint256, address)); + } + + assertEq(contractAddress, receiver); + + if (tokenAddress == ETH_TOKEN_ADDRESS) { + uint256 balanceBefore = contractAddress.balance; + vm.deal(contractAddress, toSend + balanceBefore); + + balanceAfter = contractAddress.balance; + } else { + TestnetERC20Token token = TestnetERC20Token(tokenAddress); + token.mint(contractAddress, toSend); + + balanceAfter = token.balanceOf(contractAddress); + } + + contractDeposits[contractAddress][tokenAddress] += toSend; + contractDepositsSum[tokenAddress] += toSend; + assertEq(balanceAfter, contractDeposits[contractAddress][tokenAddress]); + } + + // gets event from logs + function _getNewPriorityQueueFromLogs(Vm.Log[] memory logs) internal returns (NewPriorityRequest memory request) { + for (uint256 i = 0; i < logs.length; i++) { + Vm.Log memory log = logs[i]; + + if (log.topics[0] == NEW_PRIORITY_REQUEST_HASH) { + ( + request.txId, + request.txHash, + request.expirationTimestamp, + request.transaction, + request.factoryDeps + ) = abi.decode(log.data, (uint256, bytes32, uint64, L2CanonicalTransaction, bytes[])); + } + } + } + + // deposits ERC20 token to the ZK chain where base token is ETH + // this function use requestL2TransactionTwoBridges function from shared bridge. + // tokenAddress should be any ERC20 token, excluding ETH + function depositERC20ToEthChain(uint256 l2Value, address tokenAddress) private useGivenToken(tokenAddress) { + uint256 gasPrice = 10000000; + vm.txGasPrice(gasPrice); + + uint256 l2GasLimit = 1000000; + uint256 minRequiredGas = _getMinRequiredGasPriceForChain( + currentChainId, + gasPrice, + l2GasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA + ); + + uint256 mintValue = minRequiredGas; + vm.deal(currentUser, mintValue); + + currentToken.mint(currentUser, l2Value); + currentToken.approve(address(sharedBridge), l2Value); + + bytes memory secondBridgeCallData = abi.encode(currentTokenAddress, l2Value, chainContracts[currentChainId]); + L2TransactionRequestTwoBridgesOuter memory requestTx = _createL2TransactionRequestTwoBridges({ + _chainId: currentChainId, + _mintValue: mintValue, + _secondBridgeValue: 0, + _secondBridgeAddress: address(sharedBridge), + _l2Value: 0, + _l2GasLimit: l2GasLimit, + _l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + _secondBridgeCalldata: secondBridgeCallData + }); + + vm.recordLogs(); + bytes32 resultantHash = bridgehub.requestL2TransactionTwoBridges{value: mintValue}(requestTx); + Vm.Log[] memory logs = vm.getRecordedLogs(); + NewPriorityRequest memory request = _getNewPriorityQueueFromLogs(logs); + + assertNotEq(resultantHash, bytes32(0)); + assertNotEq(request.txHash, bytes32(0)); + _handleRequestByMockL2Contract(request, RequestType.TWO_BRIDGES); + + depositsUsers[currentUser][ETH_TOKEN_ADDRESS] += mintValue; + depositsBridge[currentChainAddress][ETH_TOKEN_ADDRESS] += mintValue; + tokenSumDeposit[ETH_TOKEN_ADDRESS] += mintValue; + + depositsUsers[currentUser][currentTokenAddress] += l2Value; + depositsBridge[currentChainAddress][currentTokenAddress] += l2Value; + tokenSumDeposit[currentTokenAddress] += l2Value; + l2ValuesSum[currentTokenAddress] += l2Value; + } + + // deposits ETH token to chain where base token is some ERC20 + // modifier prevents you from using some other token as base + function depositEthToERC20Chain(uint256 l2Value) private useBaseToken { + uint256 gasPrice = 10000000; + vm.txGasPrice(gasPrice); + + uint256 l2GasLimit = 1000000; + uint256 minRequiredGas = _getMinRequiredGasPriceForChain( + currentChainId, + gasPrice, + l2GasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA + ); + + vm.deal(currentUser, l2Value); + uint256 mintValue = minRequiredGas; + currentToken.mint(currentUser, mintValue); + currentToken.approve(address(sharedBridge), mintValue); + + bytes memory secondBridgeCallData = abi.encode(ETH_TOKEN_ADDRESS, uint256(0), chainContracts[currentChainId]); + L2TransactionRequestTwoBridgesOuter memory requestTx = _createL2TransactionRequestTwoBridges({ + _chainId: currentChainId, + _mintValue: mintValue, + _secondBridgeValue: l2Value, + _secondBridgeAddress: address(sharedBridge), + _l2Value: 0, + _l2GasLimit: l2GasLimit, + _l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + _secondBridgeCalldata: secondBridgeCallData + }); + + vm.recordLogs(); + bytes32 resultantHash = bridgehub.requestL2TransactionTwoBridges{value: l2Value}(requestTx); + Vm.Log[] memory logs = vm.getRecordedLogs(); + NewPriorityRequest memory request = _getNewPriorityQueueFromLogs(logs); + + assertNotEq(resultantHash, bytes32(0)); + assertNotEq(request.txHash, bytes32(0)); + _handleRequestByMockL2Contract(request, RequestType.TWO_BRIDGES); + + depositsUsers[currentUser][ETH_TOKEN_ADDRESS] += l2Value; + depositsBridge[currentChainAddress][ETH_TOKEN_ADDRESS] += l2Value; + tokenSumDeposit[ETH_TOKEN_ADDRESS] += l2Value; + l2ValuesSum[ETH_TOKEN_ADDRESS] += l2Value; + + depositsUsers[currentUser][currentTokenAddress] += mintValue; + depositsBridge[currentChainAddress][currentTokenAddress] += mintValue; + tokenSumDeposit[currentTokenAddress] += mintValue; + } + + // deposits ERC20 to token with base being also ERC20 + // there are no modifiers so watch out, baseTokenAddress should be base of ZK chain + // currentToken should be different from base + function depositERC20ToERC20Chain(uint256 l2Value, address baseTokenAddress) private { + uint256 gasPrice = 10000000; + vm.txGasPrice(gasPrice); + + uint256 l2GasLimit = 1000000; + uint256 minRequiredGas = _getMinRequiredGasPriceForChain( + currentChainId, + gasPrice, + l2GasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA + ); + + uint256 mintValue = minRequiredGas; + + TestnetERC20Token baseToken = TestnetERC20Token(baseTokenAddress); + baseToken.mint(currentUser, mintValue); + baseToken.approve(address(sharedBridge), mintValue); + + currentToken.mint(currentUser, l2Value); + currentToken.approve(address(sharedBridge), l2Value); + + bytes memory secondBridgeCallData = abi.encode(currentTokenAddress, l2Value, chainContracts[currentChainId]); + L2TransactionRequestTwoBridgesOuter memory requestTx = _createL2TransactionRequestTwoBridges({ + _chainId: currentChainId, + _mintValue: mintValue, + _secondBridgeValue: 0, + _secondBridgeAddress: address(sharedBridge), + _l2Value: 0, + _l2GasLimit: l2GasLimit, + _l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + _secondBridgeCalldata: secondBridgeCallData + }); + + vm.recordLogs(); + bytes32 resultantHash = bridgehub.requestL2TransactionTwoBridges(requestTx); + Vm.Log[] memory logs = vm.getRecordedLogs(); + NewPriorityRequest memory request = _getNewPriorityQueueFromLogs(logs); + + assertNotEq(resultantHash, bytes32(0)); + assertNotEq(request.txHash, bytes32(0)); + _handleRequestByMockL2Contract(request, RequestType.TWO_BRIDGES); + + depositsUsers[currentUser][baseTokenAddress] += mintValue; + depositsBridge[currentChainAddress][baseTokenAddress] += mintValue; + tokenSumDeposit[baseTokenAddress] += mintValue; + + depositsUsers[currentUser][currentTokenAddress] += l2Value; + depositsBridge[currentChainAddress][currentTokenAddress] += l2Value; + tokenSumDeposit[currentTokenAddress] += l2Value; + l2ValuesSum[currentTokenAddress] += l2Value; + } + + // deposits ETH to ZK chain where base is ETH + function depositEthBase(uint256 l2Value) private { + uint256 gasPrice = 10000000; + vm.txGasPrice(gasPrice); + + uint256 l2GasLimit = 1000000; // reverts with 8 + uint256 minRequiredGas = _getMinRequiredGasPriceForChain( + currentChainId, + gasPrice, + l2GasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA + ); + + uint256 mintValue = l2Value + minRequiredGas; + vm.deal(currentUser, mintValue); + + bytes memory callData = abi.encode(currentTokenAddress, l2Value, chainContracts[currentChainId]); + L2TransactionRequestDirect memory txRequest = _createL2TransactionRequestDirect({ + _chainId: currentChainId, + _mintValue: mintValue, + _l2Value: l2Value, + _l2GasLimit: l2GasLimit, + _l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + _l2CallData: callData + }); + + vm.recordLogs(); + bytes32 resultantHash = bridgehub.requestL2TransactionDirect{value: mintValue}(txRequest); + Vm.Log[] memory logs = vm.getRecordedLogs(); + + NewPriorityRequest memory request = _getNewPriorityQueueFromLogs(logs); + + assertNotEq(resultantHash, bytes32(0)); + assertNotEq(request.txHash, bytes32(0)); + _handleRequestByMockL2Contract(request, RequestType.DIRECT); + + depositsUsers[currentUser][ETH_TOKEN_ADDRESS] += mintValue; + depositsBridge[currentChainAddress][ETH_TOKEN_ADDRESS] += mintValue; + tokenSumDeposit[ETH_TOKEN_ADDRESS] += mintValue; + l2ValuesSum[ETH_TOKEN_ADDRESS] += l2Value; + } + + // deposits base ERC20 token to the bridge + function depositERC20Base(uint256 l2Value) private useBaseToken { + uint256 gasPrice = 10000000; + vm.txGasPrice(gasPrice); + vm.deal(currentUser, gasPrice); + + uint256 l2GasLimit = 1000000; + uint256 minRequiredGas = _getMinRequiredGasPriceForChain( + currentChainId, + gasPrice, + l2GasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA + ); + + uint256 mintValue = l2Value + minRequiredGas; + currentToken.mint(currentUser, mintValue); + currentToken.approve(address(sharedBridge), mintValue); + + bytes memory callData = abi.encode(currentTokenAddress, l2Value, chainContracts[currentChainId]); + L2TransactionRequestDirect memory txRequest = _createL2TransactionRequestDirect({ + _chainId: currentChainId, + _mintValue: mintValue, + _l2Value: l2Value, + _l2GasLimit: l2GasLimit, + _l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + _l2CallData: callData + }); + + vm.recordLogs(); + bytes32 resultantHash = bridgehub.requestL2TransactionDirect(txRequest); + Vm.Log[] memory logs = vm.getRecordedLogs(); + + NewPriorityRequest memory request = _getNewPriorityQueueFromLogs(logs); + + assertNotEq(resultantHash, bytes32(0)); + assertNotEq(request.txHash, bytes32(0)); + _handleRequestByMockL2Contract(request, RequestType.DIRECT); + + depositsUsers[currentUser][currentTokenAddress] += mintValue; + depositsBridge[currentChainAddress][currentTokenAddress] += mintValue; + tokenSumDeposit[currentTokenAddress] += mintValue; + l2ValuesSum[currentTokenAddress] += l2Value; + } + + function withdrawERC20Token(uint256 amountToWithdraw, address tokenAddress) private useGivenToken(tokenAddress) { + uint256 l2BatchNumber = uint256(uint160(makeAddr("l2BatchNumber"))); + uint256 l2MessageIndex = uint256(uint160(makeAddr("l2MessageIndex"))); + uint16 l2TxNumberInBatch = uint16(uint160(makeAddr("l2TxNumberInBatch"))); + bytes32[] memory merkleProof = new bytes32[](1); + + _setSharedBridgeIsWithdrawalFinalized(currentChainId, l2BatchNumber, l2MessageIndex, false); + uint256 beforeChainBalance = l1Nullifier.chainBalance(currentChainId, currentTokenAddress); + uint256 beforeBalance = currentToken.balanceOf(address(sharedBridge)); + + if (beforeChainBalance < amountToWithdraw) { + vm.expectRevert("L1AR: not enough funds 2"); + } else { + tokenSumWithdrawal[currentTokenAddress] += amountToWithdraw; + } + + bytes memory message = abi.encodePacked( + IL1ERC20Bridge.finalizeWithdrawal.selector, + currentUser, + currentTokenAddress, + amountToWithdraw + ); + + L2Message memory l2ToL1Message = L2Message({ + txNumberInBatch: l2TxNumberInBatch, + sender: L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, + data: message + }); + + vm.mockCall( + bridgehubProxyAddress, + // solhint-disable-next-line func-named-parameters + abi.encodeWithSelector( + IBridgehub.proveL2MessageInclusion.selector, + currentChainId, + l2BatchNumber, + l2MessageIndex, + l2ToL1Message, + merkleProof + ), + abi.encode(true) + ); + + sharedBridge.finalizeWithdrawal({ + _chainId: currentChainId, + _l2BatchNumber: l2BatchNumber, + _l2MessageIndex: l2MessageIndex, + _l2TxNumberInBatch: l2TxNumberInBatch, + _message: message, + _merkleProof: merkleProof + }); + + // check if the balance was updated correctly + if (beforeChainBalance > amountToWithdraw) { + assertEq( + beforeChainBalance - l1Nullifier.chainBalance(currentChainId, currentTokenAddress), + amountToWithdraw + ); + assertEq(beforeBalance - currentToken.balanceOf(address(sharedBridge)), amountToWithdraw); + } + } + + function withdrawETHToken(uint256 amountToWithdraw, address tokenAddress) private useGivenToken(tokenAddress) { + uint256 l2BatchNumber = uint256(uint160(makeAddr("l2BatchNumber"))); + uint256 l2MessageIndex = uint256(uint160(makeAddr("l2MessageIndex"))); + uint16 l2TxNumberInBatch = uint16(uint160(makeAddr("l2TxNumberInBatch"))); + bytes32[] memory merkleProof = new bytes32[](1); + + _setSharedBridgeIsWithdrawalFinalized(currentChainId, l2BatchNumber, l2MessageIndex, false); + uint256 beforeChainBalance = l1Nullifier.chainBalance(currentChainId, currentTokenAddress); + uint256 beforeBalance = address(sharedBridge).balance; + + if (beforeChainBalance < amountToWithdraw) { + vm.expectRevert("L1AR: not enough funds 2"); + } else { + tokenSumWithdrawal[currentTokenAddress] += amountToWithdraw; + } + + bytes memory message = abi.encodePacked(IMailbox.finalizeEthWithdrawal.selector, currentUser, amountToWithdraw); + L2Message memory l2ToL1Message = L2Message({ + txNumberInBatch: l2TxNumberInBatch, + sender: L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, + data: message + }); + + vm.mockCall( + bridgehubProxyAddress, + // solhint-disable-next-line func-named-parameters + abi.encodeWithSelector( + IBridgehub.proveL2MessageInclusion.selector, + currentChainId, + l2BatchNumber, + l2MessageIndex, + l2ToL1Message, + merkleProof + ), + abi.encode(true) + ); + + sharedBridge.finalizeWithdrawal({ + _chainId: currentChainId, + _l2BatchNumber: l2BatchNumber, + _l2MessageIndex: l2MessageIndex, + _l2TxNumberInBatch: l2TxNumberInBatch, + _message: message, + _merkleProof: merkleProof + }); + + // check if the balance was updated correctly + if (beforeChainBalance > amountToWithdraw) { + assertEq( + beforeChainBalance - l1Nullifier.chainBalance(currentChainId, currentTokenAddress), + amountToWithdraw + ); + assertEq(beforeBalance - address(sharedBridge).balance, amountToWithdraw); + } + } + + function depositEthToBridgeSuccess( + uint256 userIndexSeed, + uint256 chainIndexSeed, + uint256 l2Value + ) public virtual useUser(userIndexSeed) useZKChain(chainIndexSeed) useBaseToken { + if (currentTokenAddress == ETH_TOKEN_ADDRESS) { + depositEthBase(l2Value); + } else { + depositEthToERC20Chain(l2Value); + } + } + + function depositERC20ToBridgeSuccess( + uint256 userIndexSeed, + uint256 chainIndexSeed, + uint256 tokenIndexSeed, + uint256 l2Value + ) public virtual useUser(userIndexSeed) useZKChain(chainIndexSeed) useERC20Token(tokenIndexSeed) { + address chainBaseToken = getZKChainBaseToken(currentChainId); + + if (chainBaseToken == ETH_TOKEN_ADDRESS) { + depositERC20ToEthChain(l2Value, currentTokenAddress); + } else { + if (currentTokenAddress == chainBaseToken) { + depositERC20Base(l2Value); + } else { + depositERC20ToERC20Chain(l2Value, chainBaseToken); + } + } + } + + function withdrawSuccess( + uint256 userIndexSeed, + uint256 chainIndexSeed, + uint256 amountToWithdraw + ) public virtual useUser(userIndexSeed) useZKChain(chainIndexSeed) { + address token = getZKChainBaseToken(currentChainId); + + if (token != ETH_TOKEN_ADDRESS) { + withdrawERC20Token(amountToWithdraw, token); + } else if (token == ETH_TOKEN_ADDRESS) { + withdrawETHToken(amountToWithdraw, token); + } + } + + function getAddressesToExclude() public returns (address[] memory) { + addressesToExclude.push(bridgehubProxyAddress); + addressesToExclude.push(address(sharedBridge)); + + for (uint256 i = 0; i < users.length; i++) { + addressesToExclude.push(users[i]); + } + + for (uint256 i = 0; i < l2ContractAddresses.length; i++) { + addressesToExclude.push(l2ContractAddresses[i]); + } + + for (uint256 i = 0; i < zkChainIds.length; i++) { + addressesToExclude.push(getZKChainAddress(zkChainIds[i])); + } + + return addressesToExclude; + } + + function prepare() public { + _generateUserAddresses(); + + _deployL1Contracts(); + _deployTokens(); + _registerNewTokens(tokens); + + _deployEra(); + _deployZKChain(ETH_TOKEN_ADDRESS); + _deployZKChain(ETH_TOKEN_ADDRESS); + _deployZKChain(tokens[0]); + _deployZKChain(tokens[0]); + _deployZKChain(tokens[1]); + _deployZKChain(tokens[1]); + + for (uint256 i = 0; i < zkChainIds.length; i++) { + address contractAddress = makeAddr(string(abi.encode("contract", i))); + l2ContractAddresses.push(contractAddress); + + _addL2ChainContract(zkChainIds[i], contractAddress); + } + } + + // add this to be excluded from coverage report + function test() internal override {} +} + +contract BoundedBridgeHubInvariantTests is BridgeHubInvariantTests { + function depositEthSuccess(uint256 userIndexSeed, uint256 chainIndexSeed, uint256 l2Value) public { + uint64 MAX = 2 ** 64 - 1; + uint256 l2Value = bound(l2Value, 0.1 ether, MAX); + + emit log_string("DEPOSIT ETH"); + super.depositEthToBridgeSuccess(userIndexSeed, chainIndexSeed, l2Value); + } + + function depositERC20Success( + uint256 userIndexSeed, + uint256 chainIndexSeed, + uint256 tokenIndexSeed, + uint256 l2Value + ) public { + uint64 MAX = 2 ** 64 - 1; + uint256 l2Value = bound(l2Value, 0.1 ether, MAX); + + emit log_string("DEPOSIT ERC20"); + super.depositERC20ToBridgeSuccess(userIndexSeed, chainIndexSeed, tokenIndexSeed, l2Value); + } + + function withdrawERC20Success(uint256 userIndexSeed, uint256 chainIndexSeed, uint256 amountToWithdraw) public { + uint64 MAX = (2 ** 32 - 1) + 0.1 ether; + uint256 amountToWithdraw = bound(amountToWithdraw, 0.1 ether, MAX); + + emit log_string("WITHDRAW ERC20"); + super.withdrawSuccess(userIndexSeed, chainIndexSeed, amountToWithdraw); + } + + // add this to be excluded from coverage report + function testBoundedBridgeHubInvariant() internal {} +} + +// contract InvariantTesterZKChains is Test { +// BoundedBridgeHubInvariantTests tests; + +// function setUp() public { +// tests = new BoundedBridgeHubInvariantTests(); +// tests.prepare(); +// } + +// // Check whether the sum of ETH deposits from tests, updated on each deposit and withdrawal, +// // equals the balance of L1Shared bridge. +// function test_ETHbalanceStaysEqual() public { +// require(1 == 1); +// } + +// // add this to be excluded from coverage report +// function test() internal {} +// } diff --git a/l1-contracts/test/foundry/l1/integration/DeploymentTest.t.sol b/l1-contracts/test/foundry/l1/integration/DeploymentTest.t.sol new file mode 100644 index 000000000..4f307a3db --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/DeploymentTest.t.sol @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {Vm} from "forge-std/Vm.sol"; + +import {Ownable} from "@openzeppelin/contracts-v4/access/Ownable.sol"; + +import {L2TransactionRequestDirect, L2TransactionRequestTwoBridgesOuter} from "contracts/bridgehub/IBridgehub.sol"; +import {TestnetERC20Token} from "contracts/dev-contracts/TestnetERC20Token.sol"; +import {MailboxFacet} from "contracts/state-transition/chain-deps/facets/Mailbox.sol"; +import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; +import {IMailbox} from "contracts/state-transition/chain-interfaces/IMailbox.sol"; +import {IExecutor} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; +import {L1ContractDeployer} from "./_SharedL1ContractDeployer.t.sol"; +import {TokenDeployer} from "./_SharedTokenDeployer.t.sol"; +import {ZKChainDeployer} from "./_SharedZKChainDeployer.t.sol"; +import {L2TxMocker} from "./_SharedL2TxMocker.t.sol"; +import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; +import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, DEFAULT_L2_LOGS_TREE_ROOT_HASH, EMPTY_STRING_KECCAK} from "contracts/common/Config.sol"; +import {L2CanonicalTransaction, L2Message} from "contracts/common/Messaging.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {IL1ERC20Bridge} from "contracts/bridge/interfaces/IL1ERC20Bridge.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {AddressesAlreadyGenerated} from "test/foundry/L1TestsErrors.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; +import {IncorrectBridgeHubAddress} from "contracts/common/L1ContractErrors.sol"; +import {MessageRoot} from "contracts/bridgehub/MessageRoot.sol"; + +contract DeploymentTests is L1ContractDeployer, ZKChainDeployer, TokenDeployer, L2TxMocker { + uint256 constant TEST_USERS_COUNT = 10; + address[] public users; + address[] public l2ContractAddresses; + + // generate MAX_USERS addresses and append it to users array + function _generateUserAddresses() internal { + if (users.length != 0) { + revert AddressesAlreadyGenerated(); + } + + for (uint256 i = 0; i < TEST_USERS_COUNT; i++) { + address newAddress = makeAddr(string(abi.encode("account", i))); + users.push(newAddress); + } + } + + function prepare() public { + _generateUserAddresses(); + + _deployL1Contracts(); + _deployTokens(); + _registerNewTokens(tokens); + + _deployEra(); + // _deployZKChain(ETH_TOKEN_ADDRESS); + // _deployZKChain(ETH_TOKEN_ADDRESS); + // _deployZKChain(tokens[0]); + // _deployZKChain(tokens[0]); + // _deployZKChain(tokens[1]); + // _deployZKChain(tokens[1]); + + for (uint256 i = 0; i < zkChainIds.length; i++) { + address contractAddress = makeAddr(string(abi.encode("contract", i))); + l2ContractAddresses.push(contractAddress); + + _addL2ChainContract(zkChainIds[i], contractAddress); + } + } + + function setUp() public { + prepare(); + } + + // Check whether the sum of ETH deposits from tests, updated on each deposit and withdrawal, + // equals the balance of L1Shared bridge. + function test_initialDeployment() public { + uint256 chainId = zkChainIds[0]; + address newChainAddress = bridgehub.getZKChain(chainId); + address admin = IZKChain(bridgehub.getZKChain(chainId)).getAdmin(); + + assertNotEq(admin, address(0)); + assertNotEq(newChainAddress, address(0)); + + address[] memory chainAddresses = bridgehub.getAllZKChains(); + assertEq(chainAddresses.length, 1); + assertEq(chainAddresses[0], newChainAddress); + + uint256[] memory chainIds = bridgehub.getAllZKChainChainIDs(); + assertEq(chainIds.length, 1); + assertEq(chainIds[0], chainId); + + uint256 protocolVersion = chainTypeManager.getProtocolVersion(chainId); + assertEq(protocolVersion, 25); + } + + function test_bridgehubSetter() public { + uint256 chainId = zkChainIds[0]; + uint256 randomChainId = 123456; + + vm.mockCall( + address(chainTypeManager), + abi.encodeWithSelector(IChainTypeManager.getZKChainLegacy.selector, randomChainId), + abi.encode(address(0x01)) + ); + vm.store(address(bridgehub), keccak256(abi.encode(randomChainId, 205)), bytes32(uint256(uint160(1)))); + vm.store( + address(bridgehub), + keccak256(abi.encode(randomChainId, 204)), + bytes32(uint256(uint160(address(chainTypeManager)))) + ); + bridgehub.registerLegacyChain(randomChainId); + + assertEq(bridgehub.settlementLayer(randomChainId), block.chainid); + + address messageRoot = address(bridgehub.messageRoot()); + assertTrue(MessageRoot(messageRoot).chainIndex(randomChainId) != 0); + } + + function test_registerAlreadyDeployedZKChain() public { + address owner = Ownable(address(bridgehub)).owner(); + + { + uint256 chainId = currentZKChainId++; + bytes32 baseTokenAssetId = DataEncoding.encodeNTVAssetId(chainId, ETH_TOKEN_ADDRESS); + + address chain = _deployZkChain( + chainId, + baseTokenAssetId, + owner, + chainTypeManager.protocolVersion(), + chainTypeManager.storedBatchZero(), + address(bridgehub) + ); + + address stmAddr = IZKChain(chain).getChainTypeManager(); + + vm.startBroadcast(owner); + bridgehub.addChainTypeManager(stmAddr); + bridgehub.addTokenAssetId(baseTokenAssetId); + bridgehub.registerAlreadyDeployedZKChain(chainId, chain); + vm.stopBroadcast(); + + address bridgehubStmForChain = bridgehub.chainTypeManager(chainId); + bytes32 bridgehubBaseAssetIdForChain = bridgehub.baseTokenAssetId(chainId); + address bridgehubChainAddressForChain = bridgehub.getZKChain(chainId); + address bhAddr = IZKChain(chain).getBridgehub(); + + assertEq(bridgehubStmForChain, stmAddr); + assertEq(bridgehubBaseAssetIdForChain, baseTokenAssetId); + assertEq(bridgehubChainAddressForChain, chain); + assertEq(bhAddr, address(bridgehub)); + } + + { + uint256 chainId = currentZKChainId++; + bytes32 baseTokenAssetId = DataEncoding.encodeNTVAssetId(chainId, ETH_TOKEN_ADDRESS); + address chain = _deployZkChain( + chainId, + baseTokenAssetId, + owner, + chainTypeManager.protocolVersion(), + chainTypeManager.storedBatchZero(), + address(bridgehub.sharedBridge()) + ); + + address stmAddr = IZKChain(chain).getChainTypeManager(); + + vm.startBroadcast(owner); + bridgehub.addTokenAssetId(baseTokenAssetId); + vm.expectRevert( + abi.encodeWithSelector(IncorrectBridgeHubAddress.selector, address(bridgehub.sharedBridge())) + ); + bridgehub.registerAlreadyDeployedZKChain(chainId, chain); + vm.stopBroadcast(); + } + } + + // add this to be excluded from coverage report + function test() internal override {} +} diff --git a/l1-contracts/test/foundry/l1/integration/L1GatewayTests.t.sol b/l1-contracts/test/foundry/l1/integration/L1GatewayTests.t.sol new file mode 100644 index 000000000..561a7827a --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/L1GatewayTests.t.sol @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {Vm} from "forge-std/Vm.sol"; +import "forge-std/console.sol"; + +import {Ownable} from "@openzeppelin/contracts-v4/access/Ownable.sol"; + +import {L2TransactionRequestDirect, L2TransactionRequestTwoBridgesOuter, BridgehubMintCTMAssetData, BridgehubBurnCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; +import {TestnetERC20Token} from "contracts/dev-contracts/TestnetERC20Token.sol"; +import {MailboxFacet} from "contracts/state-transition/chain-deps/facets/Mailbox.sol"; +import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; +import {IMailbox} from "contracts/state-transition/chain-interfaces/IMailbox.sol"; +import {IExecutor} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; +import {L1ContractDeployer} from "./_SharedL1ContractDeployer.t.sol"; +import {TokenDeployer} from "./_SharedTokenDeployer.t.sol"; +import {ZKChainDeployer} from "./_SharedZKChainDeployer.t.sol"; +import {GatewayDeployer} from "./_SharedGatewayDeployer.t.sol"; +import {L2TxMocker} from "./_SharedL2TxMocker.t.sol"; +import {ETH_TOKEN_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "contracts/common/Config.sol"; +import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, DEFAULT_L2_LOGS_TREE_ROOT_HASH, EMPTY_STRING_KECCAK} from "contracts/common/Config.sol"; +import {L2CanonicalTransaction} from "contracts/common/Messaging.sol"; +import {L2Message} from "contracts/common/Messaging.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, L2_ASSET_ROUTER_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {IL1ERC20Bridge} from "contracts/bridge/interfaces/IL1ERC20Bridge.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IAssetRouterBase} from "contracts/bridge/asset-router/IAssetRouterBase.sol"; +import {L1Nullifier} from "contracts/bridge/L1Nullifier.sol"; +import {FinalizeL1DepositParams} from "contracts/bridge/L1Nullifier.sol"; + +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol"; +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {AddressesAlreadyGenerated} from "test/foundry/L1TestsErrors.sol"; +import {TxStatus} from "contracts/common/Messaging.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; +import {IncorrectBridgeHubAddress} from "contracts/common/L1ContractErrors.sol"; +import {ChainAdmin} from "contracts/governance/ChainAdmin.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; + +contract L1GatewayTests is L1ContractDeployer, ZKChainDeployer, TokenDeployer, L2TxMocker, GatewayDeployer { + uint256 constant TEST_USERS_COUNT = 10; + address[] public users; + address[] public l2ContractAddresses; + + uint256 migratingChainId = 10; + IZKChain migratingChain; + + uint256 gatewayChainId = 11; + IZKChain gatewayChain; + + uint256 mintChainId = 12; + + // generate MAX_USERS addresses and append it to users array + function _generateUserAddresses() internal { + if (users.length != 0) { + revert AddressesAlreadyGenerated(); + } + + for (uint256 i = 0; i < TEST_USERS_COUNT; i++) { + address newAddress = makeAddr(string(abi.encode("account", i))); + users.push(newAddress); + } + } + + function prepare() public { + _generateUserAddresses(); + + _deployL1Contracts(); + _deployTokens(); + _registerNewTokens(tokens); + + _deployEra(); + _deployZKChain(ETH_TOKEN_ADDRESS); + acceptPendingAdmin(); + _deployZKChain(ETH_TOKEN_ADDRESS); + acceptPendingAdmin(); + // _deployZKChain(tokens[0]); + // _deployZKChain(tokens[0]); + // _deployZKChain(tokens[1]); + // _deployZKChain(tokens[1]); + + for (uint256 i = 0; i < zkChainIds.length; i++) { + address contractAddress = makeAddr(string(abi.encode("contract", i))); + l2ContractAddresses.push(contractAddress); + + _addL2ChainContract(zkChainIds[i], contractAddress); + // _registerL2SharedBridge(zkChainIds[i], contractAddress); + } + + _initializeGatewayScript(); + + vm.deal(ecosystemConfig.ownerAddress, 100000000000000000000000000000000000); + migratingChain = IZKChain(IBridgehub(bridgehub).getZKChain(migratingChainId)); + gatewayChain = IZKChain(IBridgehub(bridgehub).getZKChain(gatewayChainId)); + vm.deal(migratingChain.getAdmin(), 100000000000000000000000000000000000); + vm.deal(gatewayChain.getAdmin(), 100000000000000000000000000000000000); + + // vm.deal(msg.sender, 100000000000000000000000000000000000); + // vm.deal(bridgehub, 100000000000000000000000000000000000); + } + + // This is a method to simplify porting the tests for now. + // Here we rely that the first restriction is the AccessControlRestriction + // TODO(EVM-924): this function is not used. + function _extractAccessControlRestriction(address admin) internal returns (address) { + return ChainAdmin(payable(admin)).getRestrictions()[0]; + } + + function setUp() public { + prepare(); + } + + function _setUpGatewayWithFilterer() internal { + gatewayScript.governanceRegisterGateway(); + gatewayScript.deployAndSetGatewayTransactionFilterer(); + } + + // + function test_registerGateway() public { + _setUpGatewayWithFilterer(); + } + + // + function test_moveChainToGateway() public { + _setUpGatewayWithFilterer(); + gatewayScript.migrateChainToGateway(migratingChain.getAdmin(), address(1), address(0), migratingChainId); + require(bridgehub.settlementLayer(migratingChainId) == gatewayChainId, "Migration failed"); + } + + function test_l2Registration() public { + _setUpGatewayWithFilterer(); + gatewayScript.migrateChainToGateway(migratingChain.getAdmin(), address(1), address(0), migratingChainId); + gatewayScript.governanceSetCTMAssetHandler(bytes32(0)); + gatewayScript.registerAssetIdInBridgehub(address(0x01), bytes32(0)); + } + + function test_startMessageToL3() public { + _setUpGatewayWithFilterer(); + gatewayScript.migrateChainToGateway(migratingChain.getAdmin(), address(1), address(0), migratingChainId); + IBridgehub bridgehub = IBridgehub(bridgehub); + uint256 expectedValue = 1000000000000000000000; + + L2TransactionRequestDirect memory request = _createL2TransactionRequestDirect( + migratingChainId, + expectedValue, + 0, + 72000000, + 800, + "0x" + ); + bridgehub.requestL2TransactionDirect{value: expectedValue}(request); + } + + function test_recoverFromFailedChainMigration() public { + _setUpGatewayWithFilterer(); + gatewayScript.migrateChainToGateway(migratingChain.getAdmin(), address(1), address(0), migratingChainId); + + // Setup + IBridgehub bridgehub = IBridgehub(bridgehub); + bytes32 assetId = bridgehub.ctmAssetIdFromChainId(migratingChainId); + bytes memory transferData; + + { + IZKChain chain = IZKChain(bridgehub.getZKChain(migratingChainId)); + bytes memory chainData = abi.encode(chain.getProtocolVersion()); + bytes memory ctmData = abi.encode( + address(1), + msg.sender, + chainTypeManager.protocolVersion(), + ecosystemConfig.contracts.diamondCutData + ); + BridgehubBurnCTMAssetData memory data = BridgehubBurnCTMAssetData({ + chainId: migratingChainId, + ctmData: ctmData, + chainData: chainData + }); + transferData = abi.encode(data); + } + + address chainAdmin = IZKChain(bridgehub.getZKChain(migratingChainId)).getAdmin(); + IL1AssetRouter assetRouter = IL1AssetRouter(address(bridgehub.sharedBridge())); + bytes32 l2TxHash = keccak256("l2TxHash"); + uint256 l2BatchNumber = 5; + uint256 l2MessageIndex = 0; + uint16 l2TxNumberInBatch = 0; + bytes32[] memory merkleProof = new bytes32[](1); + bytes32 txDataHash = keccak256(bytes.concat(bytes1(0x01), abi.encode(chainAdmin, assetId, transferData))); + + // Mock Call for Msg Inclusion + vm.mockCall( + address(bridgehub), + abi.encodeWithSelector( + IBridgehub.proveL1ToL2TransactionStatus.selector, + migratingChainId, + l2TxHash, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + merkleProof, + TxStatus.Failure + ), + abi.encode(true) + ); + + // Set Deposit Happened + vm.startBroadcast(address(bridgehub)); + assetRouter.bridgehubConfirmL2Transaction({ + _chainId: migratingChainId, + _txDataHash: txDataHash, + _txHash: l2TxHash + }); + vm.stopBroadcast(); + + vm.startBroadcast(); + l1Nullifier.bridgeRecoverFailedTransfer({ + _chainId: migratingChainId, + _depositSender: chainAdmin, + _assetId: assetId, + _assetData: transferData, + _l2TxHash: l2TxHash, + _l2BatchNumber: l2BatchNumber, + _l2MessageIndex: l2MessageIndex, + _l2TxNumberInBatch: l2TxNumberInBatch, + _merkleProof: merkleProof + }); + vm.stopBroadcast(); + } + + function test_finishMigrateBackChain() public { + _setUpGatewayWithFilterer(); + gatewayScript.migrateChainToGateway(migratingChain.getAdmin(), address(1), address(0), migratingChainId); + migrateBackChain(); + } + + function migrateBackChain() public { + IBridgehub bridgehub = IBridgehub(bridgehub); + IZKChain migratingChain = IZKChain(bridgehub.getZKChain(migratingChainId)); + bytes32 assetId = bridgehub.ctmAssetIdFromChainId(migratingChainId); + + vm.startBroadcast(Ownable(address(bridgehub)).owner()); + bridgehub.registerSettlementLayer(gatewayChainId, true); + vm.stopBroadcast(); + + bytes32 baseTokenAssetId = eraConfig.baseTokenAssetId; + + uint256 currentChainId = block.chainid; + // we are already on L1, so we have to set another chain id, it cannot be GW or mintChainId. + vm.chainId(migratingChainId); + vm.mockCall( + address(bridgehub), + abi.encodeWithSelector(IBridgehub.proveL2MessageInclusion.selector), + abi.encode(true) + ); + vm.mockCall( + address(bridgehub), + abi.encodeWithSelector(IBridgehub.ctmAssetIdFromChainId.selector), + abi.encode(assetId) + ); + vm.mockCall( + address(chainTypeManager), + abi.encodeWithSelector(IChainTypeManager.protocolVersion.selector), + abi.encode(chainTypeManager.protocolVersion()) + ); + + uint256 protocolVersion = chainTypeManager.getProtocolVersion(migratingChainId); + + bytes memory chainData = abi.encode(IAdmin(address(migratingChain)).prepareChainCommitment()); + bytes memory ctmData = abi.encode( + baseTokenAssetId, + msg.sender, + protocolVersion, + ecosystemConfig.contracts.diamondCutData + ); + BridgehubMintCTMAssetData memory data = BridgehubMintCTMAssetData({ + chainId: migratingChainId, + baseTokenAssetId: baseTokenAssetId, + ctmData: ctmData, + chainData: chainData + }); + bytes memory bridgehubMintData = abi.encode(data); + bytes memory message = abi.encodePacked( + IAssetRouterBase.finalizeDeposit.selector, + gatewayChainId, + assetId, + bridgehubMintData + ); + gatewayScript.finishMigrateChainFromGateway( + migratingChainId, + gatewayChainId, + 0, + 0, + 0, + message, + new bytes32[](0) + ); + + vm.chainId(currentChainId); + + assertEq(bridgehub.baseTokenAssetId(migratingChainId), baseTokenAssetId); + IZKChain migratingChainContract = IZKChain(bridgehub.getZKChain(migratingChainId)); + assertEq(migratingChainContract.getBaseTokenAssetId(), baseTokenAssetId); + } + + /// to increase coverage, properly tested in L2GatewayTests + function test_forwardToL3OnGateway() public { + _setUpGatewayWithFilterer(); + vm.chainId(12345); + vm.startBroadcast(SETTLEMENT_LAYER_RELAY_SENDER); + bridgehub.forwardTransactionOnGateway(migratingChainId, bytes32(0), 0); + vm.stopBroadcast(); + } + + // add this to be excluded from coverage report + function test() internal override {} +} diff --git a/l1-contracts/test/foundry/l1/integration/UpgradeTest.t.sol b/l1-contracts/test/foundry/l1/integration/UpgradeTest.t.sol new file mode 100644 index 000000000..6434ce976 --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/UpgradeTest.t.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +// solhint-disable no-console, gas-custom-errors + +import {Script, console2 as console} from "forge-std/Script.sol"; +import {stdToml} from "forge-std/StdToml.sol"; + +import {EcosystemUpgrade} from "deploy-scripts/upgrade/EcosystemUpgrade.s.sol"; +import {ChainUpgrade} from "deploy-scripts/upgrade/ChainUpgrade.s.sol"; +import {Call} from "contracts/governance/Common.sol"; +import {Test} from "forge-std/Test.sol"; + +string constant ECOSYSTEM_INPUT = "/test/foundry/l1/integration/upgrade-envs/script-config/mainnet.toml"; +string constant ECOSYSTEM_OUTPUT = "/test/foundry/l1/integration/upgrade-envs/script-out/mainnet.toml"; +string constant CHAIN_INPUT = "/test/foundry/l1/integration/upgrade-envs/script-config/mainnet-era.toml"; +string constant CHAIN_OUTPUT = "/test/foundry/l1/integration/upgrade-envs/script-out/mainnet-era.toml"; + +contract UpgradeTest is Test { + EcosystemUpgrade generateUpgradeData; + ChainUpgrade chainUpgrade; + + function setUp() public { + generateUpgradeData = new EcosystemUpgrade(); + chainUpgrade = new ChainUpgrade(); + } + + function test_MainnetFork() public { + console.log("Preparing ecosystem contracts"); + // Firstly, we deploy all the contracts. + generateUpgradeData.prepareEcosystemContracts(ECOSYSTEM_INPUT, ECOSYSTEM_OUTPUT); + + // For chain, we have deployed the DA validator contracts + // and also updated the chain admin. + console.log("Preparing chain for the upgrade"); + chainUpgrade.prepareChain(ECOSYSTEM_INPUT, ECOSYSTEM_OUTPUT, CHAIN_INPUT, CHAIN_OUTPUT); + + console.log("Starting stage1 of the upgrade!"); + // Now, some time has passed and we are ready to start the upgrade of the + // ecosystem. + Call[] memory stage1Calls = generateUpgradeData.getStage1UpgradeCalls(); + + governanceMulticall(generateUpgradeData.getOwnerAddress(), stage1Calls); + + console.log("Stage1 is done, now all the chains have to upgrade to the new version"); + + console.log("Upgrading Era"); + + // Now, the admin of the Era needs to call the upgrade function. + // Note, that the step below also updated ValidatorTimelock so the server needs to be ready for that. + // TODO: We do not include calls that ensure that the server is ready for the sake of brevity. + chainUpgrade.upgradeChain( + generateUpgradeData.getOldProtocolVersion(), + generateUpgradeData.getChainUpgradeInfo() + ); + + // TODO: here we should include tests that depoists work for upgraded chains + // including era specific deposit/withdraw functions + // We also may need to test that normal flow of block commit / verify / execute works (but it is hard) + // so it was tested in e2e local environment. + + console.log("Starting stage2 of the upgrade!"); + governanceMulticall(generateUpgradeData.getOwnerAddress(), generateUpgradeData.getStage2UpgradeCalls()); + + // TODO: here we should have tests that the bridging works for the previously deployed chains + // and that it does not work for those that did not upgrade. + // TODO: test that creation of new chains works under new conditions. + // TODO: if not hard, include test for deploying a gateway and migrating Era to it. + } + + /// @dev This is a contract that is used for additional visibility of transactions + /// that the decentralized governance should do. + function governanceMulticall(address governanceAddr, Call[] memory calls) internal { + // How the governance is implemented is out of scope here + vm.startBroadcast(governanceAddr); + + for (uint256 i = 0; i < calls.length; i++) { + Call memory call = calls[i]; + + (bool success, bytes memory data) = payable(call.target).call{value: call.value}(call.data); + require(success, "Multicall failed"); + } + + vm.stopBroadcast(); + } +} diff --git a/l1-contracts/test/foundry/l1/integration/_GatewayPreparationForTests.sol b/l1-contracts/test/foundry/l1/integration/_GatewayPreparationForTests.sol new file mode 100644 index 000000000..26e754d9f --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/_GatewayPreparationForTests.sol @@ -0,0 +1,49 @@ +import {stdToml} from "forge-std/StdToml.sol"; +import {Script, console2 as console} from "forge-std/Script.sol"; + +import {GatewayPreparation} from "deploy-scripts/GatewayPreparation.s.sol"; + +contract GatewayPreparationForTests is GatewayPreparation { + using stdToml for string; + + function initializeConfig() internal override { + // Grab config from output of l1 deployment + string memory root = vm.projectRoot(); + string memory path = string.concat(root, vm.envString("L1_OUTPUT")); + string memory toml = vm.readFile(path); + + config.bridgehub = toml.readAddress("$.deployed_addresses.bridgehub.bridgehub_proxy_addr"); + config.chainTypeManagerProxy = toml.readAddress( + "$.deployed_addresses.state_transition.state_transition_proxy_addr" + ); + config.sharedBridgeProxy = toml.readAddress("$.deployed_addresses.bridges.shared_bridge_proxy_addr"); + config.ctmDeploymentTracker = toml.readAddress( + "$.deployed_addresses.bridgehub.ctm_deployment_tracker_proxy_addr" + ); + config.governance = toml.readAddress("$.deployed_addresses.governance_addr"); + + path = string.concat(root, vm.envString("GATEWAY_AS_CHAIN_CONFIG")); + toml = vm.readFile(path); + + config.gatewayChainId = toml.readUint("$.chain.chain_chain_id"); + + path = string.concat(root, vm.envString("GATEWAY_AS_CHAIN_OUTPUT")); + toml = vm.readFile(path); + + config.gatewayChainAdmin = toml.readAddress("$.chain_admin_addr"); + config.gatewayChainProxyAdmin = toml.readAddress("$.chain_proxy_admin_addr"); + config.gatewayAccessControlRestriction = toml.readAddress( + "$.deployed_addresses.access_control_restriction_addr" + ); + config.l1NullifierProxy = toml.readAddress("$.deployed_addresses.bridges.l1_nullifier_proxy_addr"); + + console.log("chain chain id = ", config.gatewayChainId); + + // This value is never checked in the integration tests + config.gatewayDiamondCutData = hex""; + } + + function _getL1GasPrice() internal view override returns (uint256) { + return 10; + } +} diff --git a/l1-contracts/test/foundry/l1/integration/_SharedGatewayDeployer.t.sol b/l1-contracts/test/foundry/l1/integration/_SharedGatewayDeployer.t.sol new file mode 100644 index 000000000..bd72835af --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/_SharedGatewayDeployer.t.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {L1ContractDeployer} from "./_SharedL1ContractDeployer.t.sol"; +import {GatewayPreparationForTests} from "./_GatewayPreparationForTests.sol"; +import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; +import "@openzeppelin/contracts-v4/utils/Strings.sol"; + +contract GatewayDeployer is L1ContractDeployer { + GatewayPreparationForTests gatewayScript; + + function _initializeGatewayScript() internal { + vm.setEnv("L1_CONFIG", "/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-l1.toml"); + vm.setEnv("L1_OUTPUT", "/test/foundry/l1/integration/deploy-scripts/script-out/output-deploy-l1.toml"); + vm.setEnv( + "ZK_CHAIN_CONFIG", + "/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-zk-chain-10.toml" + ); + vm.setEnv( + "GATEWAY_AS_CHAIN_CONFIG", + "/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-zk-chain-11.toml" + ); + vm.setEnv( + "GATEWAY_AS_CHAIN_OUTPUT", + "/test/foundry/l1/integration/deploy-scripts/script-out/output-deploy-zk-chain-11.toml" + ); + + gatewayScript = new GatewayPreparationForTests(); + gatewayScript.run(); + } +} diff --git a/l1-contracts/test/foundry/l1/integration/_SharedL1ContractDeployer.t.sol b/l1-contracts/test/foundry/l1/integration/_SharedL1ContractDeployer.t.sol new file mode 100644 index 000000000..e4cb7e690 --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/_SharedL1ContractDeployer.t.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {StdStorage, stdStorage} from "forge-std/Test.sol"; + +import {DeployL1Script} from "deploy-scripts/DeployL1.s.sol"; +import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; +import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol"; +import {L1Nullifier} from "contracts/bridge/L1Nullifier.sol"; +import {L1NativeTokenVault} from "contracts/bridge/ntv/L1NativeTokenVault.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; +import {CTMDeploymentTracker} from "contracts/bridgehub/CTMDeploymentTracker.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {DeployedAddresses, Config} from "deploy-scripts/DeployUtils.s.sol"; + +contract L1ContractDeployer is Test { + using stdStorage for StdStorage; + + DeployL1Script l1Script; + DeployedAddresses public ecosystemAddresses; + Config public ecosystemConfig; + + address bridgehubProxyAddress; + address bridgehubOwnerAddress; + Bridgehub bridgehub; + + CTMDeploymentTracker ctmDeploymentTracker; + + L1AssetRouter public sharedBridge; + L1Nullifier public l1Nullifier; + L1NativeTokenVault public l1NativeTokenVault; + + IChainTypeManager chainTypeManager; + + function _deployL1Contracts() internal { + vm.setEnv("L1_CONFIG", "/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-l1.toml"); + vm.setEnv("L1_OUTPUT", "/test/foundry/l1/integration/deploy-scripts/script-out/output-deploy-l1.toml"); + vm.setEnv( + "ZK_CHAIN_CONFIG", + "/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-zk-chain-era.toml" + ); + vm.setEnv( + "ZK_CHAIN_OUT", + "/test/foundry/l1/integration/deploy-scripts/script-out/output-deploy-zk-chain-era.toml" + ); + vm.setEnv( + "GATEWAY_PREPARATION_L1_CONFIG", + "/test/foundry/l1/integration/deploy-scripts/script-config/gateway-preparation-l1.toml" + ); + + l1Script = new DeployL1Script(); + l1Script.runForTest(); + + ecosystemAddresses = l1Script.getAddresses(); + ecosystemConfig = l1Script.getConfig(); + + bridgehub = Bridgehub(ecosystemAddresses.bridgehub.bridgehubProxy); + chainTypeManager = IChainTypeManager(ecosystemAddresses.stateTransition.chainTypeManagerProxy); + ctmDeploymentTracker = CTMDeploymentTracker(ecosystemAddresses.bridgehub.ctmDeploymentTrackerProxy); + + sharedBridge = L1AssetRouter(ecosystemAddresses.bridges.sharedBridgeProxy); + l1Nullifier = L1Nullifier(ecosystemAddresses.bridges.l1NullifierProxy); + l1NativeTokenVault = L1NativeTokenVault(payable(ecosystemAddresses.vaults.l1NativeTokenVaultProxy)); + + _acceptOwnership(); + _setEraBatch(); + + bridgehubOwnerAddress = bridgehub.owner(); + } + + function _acceptOwnership() private { + vm.startPrank(bridgehub.pendingOwner()); + bridgehub.acceptOwnership(); + sharedBridge.acceptOwnership(); + ctmDeploymentTracker.acceptOwnership(); + vm.stopPrank(); + } + + function _setEraBatch() private { + vm.startPrank(sharedBridge.owner()); + // sharedBridge.setEraPostLegacyBridgeUpgradeFirstBatch(1); + // sharedBridge.setEraPostDiamondUpgradeFirstBatch(1); + vm.stopPrank(); + } + + function _registerNewToken(address _tokenAddress) internal { + bytes32 tokenAssetId = DataEncoding.encodeNTVAssetId(block.chainid, _tokenAddress); + if (!bridgehub.assetIdIsRegistered(tokenAssetId)) { + vm.prank(bridgehubOwnerAddress); + bridgehub.addTokenAssetId(tokenAssetId); + } + } + + function _registerNewTokens(address[] memory _tokens) internal { + for (uint256 i = 0; i < _tokens.length; i++) { + _registerNewToken(_tokens[i]); + } + } + + function _setSharedBridgeChainBalance(uint256 _chainId, address _token, uint256 _value) internal { + stdstore + .target(address(l1Nullifier)) + .sig(l1Nullifier.chainBalance.selector) + .with_key(_chainId) + .with_key(_token) + .checked_write(_value); + } + + function _setSharedBridgeIsWithdrawalFinalized( + uint256 _chainId, + uint256 _l2BatchNumber, + uint256 _l2ToL1MessageNumber, + bool _isFinalized + ) internal { + stdstore + .target(address(l1Nullifier)) + .sig(l1Nullifier.isWithdrawalFinalized.selector) + .with_key(_chainId) + .with_key(_l2BatchNumber) + .with_key(_l2ToL1MessageNumber) + .checked_write(_isFinalized); + } + + // add this to be excluded from coverage report + function test() internal virtual {} +} diff --git a/l1-contracts/test/foundry/l1/integration/_SharedL2TxMocker.t.sol b/l1-contracts/test/foundry/l1/integration/_SharedL2TxMocker.t.sol new file mode 100644 index 000000000..488811850 --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/_SharedL2TxMocker.t.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {L2TransactionRequestDirect, L2TransactionRequestTwoBridgesOuter} from "contracts/bridgehub/IBridgehub.sol"; +import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA} from "contracts/common/Config.sol"; + +contract L2TxMocker is Test { + address mockRefundRecipient; + address mockL2Contract; + address mockL2SharedBridge; + + uint256 mockL2GasLimit = 10000000; + uint256 mockL2GasPerPubdataByteLimit = REQUIRED_L2_GAS_PRICE_PER_PUBDATA; + + bytes mockL2Calldata; + bytes[] mockFactoryDeps; + + mapping(uint256 chainId => address l2MockContract) public chainContracts; + + constructor() { + mockRefundRecipient = makeAddr("refundrecipient"); + mockL2Contract = makeAddr("mockl2contract"); + mockL2SharedBridge = makeAddr("mockl2sharedbridge"); + + mockL2Calldata = ""; + mockFactoryDeps = new bytes[](1); + mockFactoryDeps[0] = "11111111111111111111111111111111"; + } + + function _addL2ChainContract(uint256 _chainId, address _chainContract) internal { + chainContracts[_chainId] = _chainContract; + } + + function _createL2TransactionRequestDirect( + uint256 _chainId, + uint256 _mintValue, + uint256 _l2Value, + uint256 _l2GasLimit, + uint256 _l2GasPerPubdataByteLimit, + bytes memory _l2CallData + ) internal returns (L2TransactionRequestDirect memory request) { + request.chainId = _chainId; + request.mintValue = _mintValue; + request.l2Value = _l2Value; + request.l2GasLimit = _l2GasLimit; + request.l2GasPerPubdataByteLimit = _l2GasPerPubdataByteLimit; + request.l2Contract = chainContracts[_chainId]; + request.l2Calldata = _l2CallData; + + //mocked + request.factoryDeps = mockFactoryDeps; + request.refundRecipient = mockRefundRecipient; + } + + function _createL2TransactionRequestTwoBridges( + uint256 _chainId, + uint256 _mintValue, + uint256 _secondBridgeValue, + address _secondBridgeAddress, + uint256 _l2Value, + uint256 _l2GasLimit, + uint256 _l2GasPerPubdataByteLimit, + bytes memory _secondBridgeCalldata + ) internal returns (L2TransactionRequestTwoBridgesOuter memory request) { + request.chainId = _chainId; + request.mintValue = _mintValue; + request.secondBridgeAddress = _secondBridgeAddress; + request.secondBridgeValue = _secondBridgeValue; + request.l2Value = _l2Value; + request.l2GasLimit = _l2GasLimit; + request.l2GasPerPubdataByteLimit = _l2GasPerPubdataByteLimit; + request.secondBridgeCalldata = _secondBridgeCalldata; + + //mocks + request.refundRecipient = mockRefundRecipient; + } + + // add this to be excluded from coverage report + function testL2TxMocker() internal {} +} diff --git a/l1-contracts/test/foundry/l1/integration/_SharedTokenDeployer.t.sol b/l1-contracts/test/foundry/l1/integration/_SharedTokenDeployer.t.sol new file mode 100644 index 000000000..8696fd2a6 --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/_SharedTokenDeployer.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {DeployErc20Script} from "deploy-scripts/DeployErc20.s.sol"; + +contract TokenDeployer is Test { + address[] tokens; + DeployErc20Script private deployScript; + + function _deployTokens() internal { + vm.setEnv( + "TOKENS_CONFIG", + "/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-erc20.toml" + ); + + deployScript = new DeployErc20Script(); + deployScript.run(); + tokens = deployScript.getTokensAddresses(); + } + + // add this to be excluded from coverage report + function testTokenDeployer() internal {} +} diff --git a/l1-contracts/test/foundry/l1/integration/_SharedZKChainDeployer.t.sol b/l1-contracts/test/foundry/l1/integration/_SharedZKChainDeployer.t.sol new file mode 100644 index 000000000..8836fec99 --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/_SharedZKChainDeployer.t.sol @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {StdStorage, stdStorage} from "forge-std/Test.sol"; + +import {L1ContractDeployer} from "./_SharedL1ContractDeployer.t.sol"; +import {RegisterZKChainScript} from "deploy-scripts/RegisterZKChain.s.sol"; +import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; +import "@openzeppelin/contracts-v4/utils/Strings.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; +import {DiamondProxy} from "contracts/state-transition/chain-deps/DiamondProxy.sol"; +import {IDiamondInit} from "contracts/state-transition/chain-interfaces/IDiamondInit.sol"; + +import {Config as ChainConfig} from "deploy-scripts/RegisterZKChain.s.sol"; + +contract ZKChainDeployer is L1ContractDeployer { + using stdStorage for StdStorage; + + RegisterZKChainScript deployScript; + + struct ZKChainDescription { + uint256 zkChainChainId; + address baseToken; + uint256 bridgehubCreateNewChainSalt; + bool validiumMode; + address validatorSenderOperatorCommitEth; + address validatorSenderOperatorBlobsEth; + uint128 baseTokenGasPriceMultiplierNominator; + uint128 baseTokenGasPriceMultiplierDenominator; + } + + ChainConfig internal eraConfig; + + uint256 currentZKChainId = 10; + uint256 eraZKChainId = 9; + uint256[] public zkChainIds; + + function _deployEra() internal { + vm.setEnv( + "ZK_CHAIN_CONFIG", + "/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-zk-chain-era.toml" + ); + vm.setEnv( + "ZK_CHAIN_OUT", + "/test/foundry/l1/integration/deploy-scripts/script-out/output-deploy-zk-chain-era.toml" + ); + deployScript = new RegisterZKChainScript(); + saveZKChainConfig(_getDefaultDescription(eraZKChainId, ETH_TOKEN_ADDRESS, eraZKChainId)); + vm.warp(100); + deployScript.runForTest(); + zkChainIds.push(eraZKChainId); + eraConfig = deployScript.getConfig(); + } + + function _deployZKChain(address _baseToken) internal { + vm.setEnv( + "ZK_CHAIN_CONFIG", + string.concat( + "/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-zk-chain-", + Strings.toString(currentZKChainId), + ".toml" + ) + ); + vm.setEnv( + "ZK_CHAIN_OUT", + string.concat( + "/test/foundry/l1/integration/deploy-scripts/script-out/output-deploy-zk-chain-", + Strings.toString(currentZKChainId), + ".toml" + ) + ); + zkChainIds.push(currentZKChainId); + saveZKChainConfig(_getDefaultDescription(currentZKChainId, _baseToken, currentZKChainId)); + currentZKChainId++; + deployScript.runForTest(); + } + + function _getDefaultDescription( + uint256 __chainId, + address __baseToken, + uint256 __salt + ) internal returns (ZKChainDescription memory description) { + description = ZKChainDescription({ + zkChainChainId: __chainId, + baseToken: __baseToken, + bridgehubCreateNewChainSalt: __salt, + validiumMode: false, + validatorSenderOperatorCommitEth: address(0), + validatorSenderOperatorBlobsEth: address(1), + baseTokenGasPriceMultiplierNominator: uint128(1), + baseTokenGasPriceMultiplierDenominator: uint128(1) + }); + } + + function saveZKChainConfig(ZKChainDescription memory description) public { + string memory serialized; + + vm.serializeAddress("toml1", "owner_address", 0x70997970C51812dc3A010C7d01b50e0d17dc79C8); + vm.serializeUint("chain", "chain_chain_id", description.zkChainChainId); + vm.serializeAddress("chain", "base_token_addr", description.baseToken); + vm.serializeUint("chain", "bridgehub_create_new_chain_salt", description.bridgehubCreateNewChainSalt); + + uint256 validiumMode = 0; + + if (description.validiumMode) { + validiumMode = 1; + } + + vm.serializeUint("chain", "validium_mode", validiumMode); + vm.serializeAddress( + "chain", + "validator_sender_operator_commit_eth", + description.validatorSenderOperatorCommitEth + ); + vm.serializeAddress( + "chain", + "validator_sender_operator_blobs_eth", + description.validatorSenderOperatorBlobsEth + ); + vm.serializeUint( + "chain", + "base_token_gas_price_multiplier_nominator", + description.baseTokenGasPriceMultiplierNominator + ); + vm.serializeUint("chain", "governance_min_delay", 0); + vm.serializeAddress("chain", "governance_security_council_address", address(0)); + + string memory single_serialized = vm.serializeUint( + "chain", + "base_token_gas_price_multiplier_denominator", + description.baseTokenGasPriceMultiplierDenominator + ); + + string memory toml = vm.serializeString("toml1", "chain", single_serialized); + string memory path = string.concat(vm.projectRoot(), vm.envString("ZK_CHAIN_CONFIG")); + vm.writeToml(toml, path); + } + + function getZKChainAddress(uint256 _chainId) public view returns (address) { + return bridgehub.getZKChain(_chainId); + } + + function getZKChainBaseToken(uint256 _chainId) public view returns (address) { + return bridgehub.baseToken(_chainId); + } + + function acceptPendingAdmin() public { + IZKChain chain = IZKChain(bridgehub.getZKChain(currentZKChainId - 1)); + address admin = chain.getPendingAdmin(); + vm.startBroadcast(admin); + chain.acceptAdmin(); + vm.stopBroadcast(); + vm.deal(admin, 10000000000000000000000000); + } + + // add this to be excluded from coverage report + function testZKChainDeployer() internal {} + + function _deployZkChain( + uint256 _chainId, + bytes32 _baseTokenAssetId, + address _admin, + uint256 _protocolVersion, + bytes32 _storedBatchZero, + address _bridgehub + ) internal returns (address) { + Diamond.DiamondCutData memory diamondCut = abi.decode( + ecosystemConfig.contracts.diamondCutData, + (Diamond.DiamondCutData) + ); + bytes memory initData; + + { + initData = bytes.concat( + IDiamondInit.initialize.selector, + bytes32(_chainId), + bytes32(uint256(uint160(address(_bridgehub)))), + bytes32(uint256(uint160(address(this)))), + bytes32(_protocolVersion), + bytes32(uint256(uint160(_admin))), + bytes32(uint256(uint160(address(0x1337)))), + _baseTokenAssetId, + _storedBatchZero, + diamondCut.initCalldata + ); + } + diamondCut.initCalldata = initData; + DiamondProxy hyperchainContract = new DiamondProxy{salt: bytes32(0)}(block.chainid, diamondCut); + return address(hyperchainContract); + } +} diff --git a/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-erc20.toml b/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-erc20.toml new file mode 100644 index 000000000..dacf8865e --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-erc20.toml @@ -0,0 +1,15 @@ +additional_addresses_for_minting = [] + +[tokens.DAI] +name = "DAI" +symbol = "DAI" +decimals = 18 +implementation = "TestnetERC20Token.sol" +mint = "10000000000" + +[tokens.USDC] +name = "USDC" +symbol = "USDC" +decimals = 18 +implementation = "TestnetERC20Token.sol" +mint = "10000000000" diff --git a/l1-contracts/test/foundry/integration/deploy-scripts/script-out/output-deploy-l1.toml b/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-l1.toml similarity index 69% rename from l1-contracts/test/foundry/integration/deploy-scripts/script-out/output-deploy-l1.toml rename to l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-l1.toml index fa3301825..8bcd27fa6 100644 --- a/l1-contracts/test/foundry/integration/deploy-scripts/script-out/output-deploy-l1.toml +++ b/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-l1.toml @@ -1,54 +1,33 @@ -create2_factory_salt = "0x00000000000000000000000000000000000000000000000000000000000000ff" -deployer_addr = "0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496" era_chain_id = 9 -l1_chain_id = 31337 -multicall3_addr = "0x9735C424DEa176DC4304D1A240C824783D841f20" owner_address = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" +testnet_verifier = true +support_l2_legacy_shared_bridge_test = false -[contracts_config] -diamond_cut_data = "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000009a5d498f9fdab5d26090b68aec33363ac3706c5e0000000000000000000000000000000000000000000000000000000000000de00000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000009c00000000000000000000000000000000000000000000000000000000000000bc0000000000000000000000000c49e34de76847b6ce4933cae561d6ae7c72b1c2500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000140e18b681000000000000000000000000000000000000000000000000000000001733894500000000000000000000000000000000000000000000000000000000fc57565f000000000000000000000000000000000000000000000000000000001cc5d1030000000000000000000000000000000000000000000000000000000021f603d700000000000000000000000000000000000000000000000000000000235d9eb50000000000000000000000000000000000000000000000000000000027ae4c160000000000000000000000000000000000000000000000000000000043dc2951000000000000000000000000000000000000000000000000000000004623c91d000000000000000000000000000000000000000000000000000000004dd18bf5000000000000000000000000000000000000000000000000000000006223258e0000000000000000000000000000000000000000000000000000000064b554ad0000000000000000000000000000000000000000000000000000000064bf8d660000000000000000000000000000000000000000000000000000000082b57749000000000000000000000000000000000000000000000000000000008c564cc100000000000000000000000000000000000000000000000000000000a37dc1d400000000000000000000000000000000000000000000000000000000a3bd011200000000000000000000000000000000000000000000000000000000a9f6d94100000000000000000000000000000000000000000000000000000000be6f11cf00000000000000000000000000000000000000000000000000000000e76db86500000000000000000000000000000000000000000000000000000000000000000000000000000000ab0aa7c14904459f4bcde7f3b465546f022ec22a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000002c06d49e5b00000000000000000000000000000000000000000000000000000000086a56f8000000000000000000000000000000000000000000000000000000000ec6b0b700000000000000000000000000000000000000000000000000000000fe26699e0000000000000000000000000000000000000000000000000000000018e3a941000000000000000000000000000000000000000000000000000000001de72e340000000000000000000000000000000000000000000000000000000029b98c670000000000000000000000000000000000000000000000000000000033ce93fe000000000000000000000000000000000000000000000000000000003408e470000000000000000000000000000000000000000000000000000000003591c1a000000000000000000000000000000000000000000000000000000000396073820000000000000000000000000000000000000000000000000000000039d7d4aa0000000000000000000000000000000000000000000000000000000046657fe90000000000000000000000000000000000000000000000000000000052ef6b2c000000000000000000000000000000000000000000000000000000005518c73b0000000000000000000000000000000000000000000000000000000056142d7a00000000000000000000000000000000000000000000000000000000631f4bac000000000000000000000000000000000000000000000000000000006e9960c30000000000000000000000000000000000000000000000000000000074f4d30d0000000000000000000000000000000000000000000000000000000079823c9a000000000000000000000000000000000000000000000000000000007a0ed627000000000000000000000000000000000000000000000000000000007b30c8da0000000000000000000000000000000000000000000000000000000098acd7a6000000000000000000000000000000000000000000000000000000009cd939e4000000000000000000000000000000000000000000000000000000009d1b5a8100000000000000000000000000000000000000000000000000000000a1954fc500000000000000000000000000000000000000000000000000000000a7358efb00000000000000000000000000000000000000000000000000000000adfca15e00000000000000000000000000000000000000000000000000000000af6a2dcd00000000000000000000000000000000000000000000000000000000b22dd78e00000000000000000000000000000000000000000000000000000000b8c2f66f00000000000000000000000000000000000000000000000000000000bd7c541200000000000000000000000000000000000000000000000000000000c3bbd2d700000000000000000000000000000000000000000000000000000000cdffacc600000000000000000000000000000000000000000000000000000000d046815600000000000000000000000000000000000000000000000000000000d86970d800000000000000000000000000000000000000000000000000000000db1f0bf900000000000000000000000000000000000000000000000000000000e5355c7500000000000000000000000000000000000000000000000000000000e81e0ba100000000000000000000000000000000000000000000000000000000ea6c029c00000000000000000000000000000000000000000000000000000000ef3f0bae00000000000000000000000000000000000000000000000000000000f5c1182c00000000000000000000000000000000000000000000000000000000facd743b00000000000000000000000000000000000000000000000000000000fd791f3c0000000000000000000000000000000000000000000000000000000000000000000000000000000066825543c5c82b711de855426946f6929573cf55000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000b042901c70000000000000000000000000000000000000000000000000000000008522c300000000000000000000000000000000000000000000000000000000012f43dab00000000000000000000000000000000000000000000000000000000eb67241900000000000000000000000000000000000000000000000000000000263b7f8e000000000000000000000000000000000000000000000000000000006c0960f9000000000000000000000000000000000000000000000000000000007efda2ae00000000000000000000000000000000000000000000000000000000b473318e00000000000000000000000000000000000000000000000000000000d06b26e200000000000000000000000000000000000000000000000000000000dcabb98200000000000000000000000000000000000000000000000000000000e4948f430000000000000000000000000000000000000000000000000000000000000000000000000000000049c0372a531af3cf3fe715c1918c278c07affce5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000800a22e22000000000000000000000000000000000000000000000000000000000f23da4300000000000000000000000000000000000000000000000000000000c37533bb000000000000000000000000000000000000000000000000000000006edd4f1200000000000000000000000000000000000000000000000000000000701f58c5000000000000000000000000000000000000000000000000000000007f61885c0000000000000000000000000000000000000000000000000000000097c09d3400000000000000000000000000000000000000000000000000000000bd6db4990000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000032c101edc4d322abd5da779f1a5376e412e21160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c4b400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f4240000000000000000000000000000000000000000000000000000000000001d4c00000000000000000000000000000000000000000000000000000000004c4b40000000000000000000000000000000000000000000000000000000000000182b8000000000000000000000000000000000000000000000000000000000ee6b280000000000000000000000000a4cb26d6933d2c3e76718d30de8547bcdf8dd241" +[contracts] +governance_security_council_address = "0x0000000000000000000000000000000000000000" +governance_min_delay = 0 +max_number_of_chains = 100 +create2_factory_salt = "0x00000000000000000000000000000000000000000000000000000000000000ff" +create2_factory_addr = "0x0000000000000000000000000000000000000000" +validator_timelock_execution_delay = 0 +genesis_root = "0x1000000000000000000000000000000000000000000000000000000000000000" +genesis_rollup_leaf_index = 1 +genesis_batch_commitment = "0x1000000000000000000000000000000000000000000000000000000000000000" +latest_protocol_version = 25 +recursion_node_level_vk_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +recursion_leaf_level_vk_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +recursion_circuits_set_vks_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +priority_tx_max_gas_limit = 80000000 +diamond_init_pubdata_pricing_mode = 0 diamond_init_batch_overhead_l1_gas = 1000000 -diamond_init_max_l2_gas_per_batch = 80000000 diamond_init_max_pubdata_per_batch = 120000 -diamond_init_minimal_l2_gas_price = 250000000 +diamond_init_max_l2_gas_per_batch = 80000000 diamond_init_priority_tx_max_pubdata = 99000 -diamond_init_pubdata_pricing_mode = 0 +diamond_init_minimal_l2_gas_price = 250000000 +bootloader_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +default_aa_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" force_deployments_data = "0x00" -priority_tx_max_gas_limit = 80000000 -recursion_circuits_set_vks_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" -recursion_leaf_level_vk_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" -recursion_node_level_vk_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" - -[deployed_addresses] -blob_versioned_hash_retriever_addr = "0xA4cB26d6933D2c3E76718D30de8547bCDF8dD241" -governance_addr = "0x6ba327EAE385c52A861b1cacAc60021F03489413" -native_token_vault_addr = "0x153e4040C649Fe562cAa0A71Fd79f79BCA2593aB" -transparent_proxy_admin_addr = "0xDEb1E9a6Be7Baf84208BB6E10aC9F9bbE1D70809" -validator_timelock_addr = "0xDb50CefBF1F40e85951dAbd0194c477D6270Fe5E" - -[deployed_addresses.bridgehub] -bridgehub_implementation_addr = "0xa53970305e11ac9eD420Ec7C7AABb59fC3a64B0e" -bridgehub_proxy_addr = "0xC6585692481e509DDD11Eb2033535c6FF6e89B99" -message_root_implementation_addr = "0x67c321b17102Cbd39068B8bAeC3fF925FEc76C46" -message_root_proxy_addr = "0x88001933Ff48C53181cf1b11935AC2126954cb9e" -stm_deployment_tracker_implementation_addr = "0x205CEF369839dF59C016b02e4ECb45fB706576d0" -stm_deployment_tracker_proxy_addr = "0x8D9731582480f2CB74BC93168D86fB26788986b2" - -[deployed_addresses.bridges] -erc20_bridge_implementation_addr = "0xD23dF92Df88AF35d9f804BCd576B12C212A8BbD9" -erc20_bridge_proxy_addr = "0x6DDEFe6C5B5068347E278D5Be9B2a8a81c9C4F23" -shared_bridge_implementation_addr = "0xA66087143CEBcd6859aEd08420B1228De567Cd88" -shared_bridge_proxy_addr = "0x1e1314a32AaE641325b6BEfC625f499f1d7c7B2a" +diamond_cut_data = "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060000000000000000000000000103aa417efc38582cbb322d23b86342cb3bca4a40000000000000000000000000000000000000000000000000000000000000de00000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000009c00000000000000000000000000000000000000000000000000000000000000bc00000000000000000000000005d8d8112ce7c189c3df7e80fce3cd96863acbbb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000014a37dc1d400000000000000000000000000000000000000000000000000000000a3bd011200000000000000000000000000000000000000000000000000000000a9f6d94100000000000000000000000000000000000000000000000000000000be6f11cf00000000000000000000000000000000000000000000000000000000e76db86500000000000000000000000000000000000000000000000000000000fc57565f000000000000000000000000000000000000000000000000000000006223258e0000000000000000000000000000000000000000000000000000000064b554ad0000000000000000000000000000000000000000000000000000000064bf8d660000000000000000000000000000000000000000000000000000000082b57749000000000000000000000000000000000000000000000000000000008c564cc100000000000000000000000000000000000000000000000000000000235d9eb50000000000000000000000000000000000000000000000000000000027ae4c160000000000000000000000000000000000000000000000000000000043dc2951000000000000000000000000000000000000000000000000000000004623c91d000000000000000000000000000000000000000000000000000000004dd18bf5000000000000000000000000000000000000000000000000000000000e18b68100000000000000000000000000000000000000000000000000000000173389450000000000000000000000000000000000000000000000000000000021f603d7000000000000000000000000000000000000000000000000000000001cc5d103000000000000000000000000000000000000000000000000000000000000000000000000000000007684a3fd9f61c7c4d396177cc92384b076c6164a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000002c98acd7a6000000000000000000000000000000000000000000000000000000009cd939e4000000000000000000000000000000000000000000000000000000009d1b5a8100000000000000000000000000000000000000000000000000000000a1954fc500000000000000000000000000000000000000000000000000000000a7358efb00000000000000000000000000000000000000000000000000000000adfca15e00000000000000000000000000000000000000000000000000000000af6a2dcd00000000000000000000000000000000000000000000000000000000b22dd78e00000000000000000000000000000000000000000000000000000000b8c2f66f00000000000000000000000000000000000000000000000000000000bd7c541200000000000000000000000000000000000000000000000000000000c3bbd2d700000000000000000000000000000000000000000000000000000000cdffacc600000000000000000000000000000000000000000000000000000000d046815600000000000000000000000000000000000000000000000000000000d86970d800000000000000000000000000000000000000000000000000000000db1f0bf900000000000000000000000000000000000000000000000000000000e5355c7500000000000000000000000000000000000000000000000000000000e81e0ba100000000000000000000000000000000000000000000000000000000ea6c029c00000000000000000000000000000000000000000000000000000000ef3f0bae00000000000000000000000000000000000000000000000000000000f5c1182c00000000000000000000000000000000000000000000000000000000facd743b00000000000000000000000000000000000000000000000000000000fd791f3c00000000000000000000000000000000000000000000000000000000fe26699e00000000000000000000000000000000000000000000000000000000631f4bac000000000000000000000000000000000000000000000000000000006e9960c30000000000000000000000000000000000000000000000000000000074f4d30d0000000000000000000000000000000000000000000000000000000079823c9a000000000000000000000000000000000000000000000000000000007a0ed627000000000000000000000000000000000000000000000000000000007b30c8da0000000000000000000000000000000000000000000000000000000039d7d4aa0000000000000000000000000000000000000000000000000000000046657fe90000000000000000000000000000000000000000000000000000000052ef6b2c000000000000000000000000000000000000000000000000000000005518c73b0000000000000000000000000000000000000000000000000000000056142d7a000000000000000000000000000000000000000000000000000000001de72e340000000000000000000000000000000000000000000000000000000029b98c670000000000000000000000000000000000000000000000000000000033ce93fe000000000000000000000000000000000000000000000000000000003408e470000000000000000000000000000000000000000000000000000000003591c1a000000000000000000000000000000000000000000000000000000000396073820000000000000000000000000000000000000000000000000000000006d49e5b00000000000000000000000000000000000000000000000000000000086a56f8000000000000000000000000000000000000000000000000000000000ec6b0b70000000000000000000000000000000000000000000000000000000018e3a94100000000000000000000000000000000000000000000000000000000000000000000000000000000a36f72a317fa180d945efb5fe4383c9fbfe8c8be000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000b7efda2ae00000000000000000000000000000000000000000000000000000000b473318e00000000000000000000000000000000000000000000000000000000d06b26e200000000000000000000000000000000000000000000000000000000dcabb98200000000000000000000000000000000000000000000000000000000e4948f4300000000000000000000000000000000000000000000000000000000eb67241900000000000000000000000000000000000000000000000000000000042901c70000000000000000000000000000000000000000000000000000000008522c300000000000000000000000000000000000000000000000000000000012f43dab000000000000000000000000000000000000000000000000000000006c0960f900000000000000000000000000000000000000000000000000000000263b7f8e00000000000000000000000000000000000000000000000000000000000000000000000000000000340e73f0092fd6faec522a3b8a6b4b0d7242dbff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000008701f58c5000000000000000000000000000000000000000000000000000000007f61885c0000000000000000000000000000000000000000000000000000000097c09d3400000000000000000000000000000000000000000000000000000000bd6db49900000000000000000000000000000000000000000000000000000000c37533bb0000000000000000000000000000000000000000000000000000000000a22e22000000000000000000000000000000000000000000000000000000000f23da43000000000000000000000000000000000000000000000000000000006edd4f120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000ec2597d47a5416270d475b67e2b6cc20f35f4f50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c4b400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f4240000000000000000000000000000000000000000000000000000000000001d4c00000000000000000000000000000000000000000000000000000000004c4b40000000000000000000000000000000000000000000000000000000000000182b8000000000000000000000000000000000000000000000000000000000ee6b280000000000000000000000000a4cb26d6933d2c3e76718d30de8547bcdf8dd241" -[deployed_addresses.state_transition] -admin_facet_addr = "0xC49E34dE76847b6Ce4933caE561d6aE7C72B1c25" -default_upgrade_addr = "0x7C0213Ecf479fE20b03B9e0d5a62B6D1602fe9a5" -diamond_init_addr = "0x9a5D498f9FdAB5D26090B68AEc33363ac3706C5e" -diamond_proxy_addr = "0x0000000000000000000000000000000000000000" -executor_facet_addr = "0x49C0372A531aF3cF3Fe715c1918c278c07aFfCe5" -genesis_upgrade_addr = "0xAFAb4F3F4B7984A3A93A5eC3B1028aC6e7194602" -getters_facet_addr = "0xab0AA7c14904459F4bCDe7f3B465546f022ec22A" -mailbox_facet_addr = "0x66825543c5c82b711de855426946f6929573cF55" -state_transition_implementation_addr = "0x2051075b03d1F2E0902C9cFd349fbdD4c73bB2d4" -state_transition_proxy_addr = "0x236e89885449f7ef4650743Ba350Fd557060905E" -verifier_addr = "0x32C101EDC4D322AbD5da779f1A5376e412E21160" +[tokens] +token_weth_address = "0x0000000000000000000000000000000000000000" diff --git a/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/generate-force-deployments-data.toml b/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/generate-force-deployments-data.toml new file mode 100644 index 000000000..15a0d7d43 --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/generate-force-deployments-data.toml @@ -0,0 +1,7 @@ +era_chain_id = 9 +chain_id = 270 +l1_shared_bridge = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" +governance = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" +l2_legacy_shared_bridge = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" +l2_token_beacon = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" +l2_contracts_deployed_already = false diff --git a/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-out/.gitkeep b/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-out/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2Erc20L1Test.t.sol b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2Erc20L1Test.t.sol new file mode 100644 index 000000000..b6346a09d --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2Erc20L1Test.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +// solhint-disable gas-custom-errors + +import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; +import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {IL2NativeTokenVault} from "contracts/bridge/ntv/IL2NativeTokenVault.sol"; + +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; +import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; + +import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR, L2_BRIDGEHUB_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {ETH_TOKEN_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "contracts/common/Config.sol"; + +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {BridgehubMintCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {IL2AssetRouter} from "contracts/bridge/asset-router/IL2AssetRouter.sol"; +import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; + +import {SharedL2ContractDeployer} from "./_SharedL2ContractDeployer.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {SystemContractsArgs} from "./_SharedL2ContractL1DeployerUtils.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; +import {L2Erc20TestAbstract} from "./L2Erc20TestAbstract.t.sol"; +import {SharedL2ContractL1DeployerUtils} from "./_SharedL2ContractL1DeployerUtils.sol"; + +contract L2Erc20L1Test is Test, SharedL2ContractL1DeployerUtils, SharedL2ContractDeployer, L2Erc20TestAbstract { + function test() internal virtual override(DeployUtils, SharedL2ContractL1DeployerUtils) {} + + function initSystemContracts( + SystemContractsArgs memory _args + ) internal virtual override(SharedL2ContractDeployer, SharedL2ContractL1DeployerUtils) { + super.initSystemContracts(_args); + } + + function deployL2Contracts( + uint256 _l1ChainId + ) public virtual override(SharedL2ContractDeployer, SharedL2ContractL1DeployerUtils) { + super.deployL2Contracts(_l1ChainId); + } +} diff --git a/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2Erc20TestAbstract.t.sol b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2Erc20TestAbstract.t.sol new file mode 100644 index 000000000..c7e4d5ba5 --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2Erc20TestAbstract.t.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; +// solhint-disable gas-custom-errors + +import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; +import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {IL2NativeTokenVault} from "contracts/bridge/ntv/IL2NativeTokenVault.sol"; + +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; +import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; + +import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR, L2_BRIDGEHUB_ADDR, L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {ETH_TOKEN_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "contracts/common/Config.sol"; + +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {BridgehubMintCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {IL2AssetRouter} from "contracts/bridge/asset-router/IL2AssetRouter.sol"; +import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; + +import {SharedL2ContractDeployer} from "./_SharedL2ContractDeployer.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {SystemContractsArgs} from "./_SharedL2ContractL1DeployerUtils.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; +import {TestnetERC20Token} from "contracts/dev-contracts/TestnetERC20Token.sol"; + +abstract contract L2Erc20TestAbstract is Test, SharedL2ContractDeployer { + function performDeposit(address depositor, address receiver, uint256 amount) internal { + vm.prank(aliasedL1AssetRouter); + L2AssetRouter(L2_ASSET_ROUTER_ADDR).finalizeDeposit({ + _l1Sender: depositor, + _l2Receiver: receiver, + _l1Token: L1_TOKEN_ADDRESS, + _amount: amount, + _data: encodeTokenData(TOKEN_DEFAULT_NAME, TOKEN_DEFAULT_SYMBOL, TOKEN_DEFAULT_DECIMALS) + }); + } + + function initializeTokenByDeposit() internal returns (address l2TokenAddress) { + performDeposit(makeAddr("someDepositor"), makeAddr("someReeiver"), 1); + + l2TokenAddress = IL2NativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR).l2TokenAddress(L1_TOKEN_ADDRESS); + if (l2TokenAddress == address(0)) { + revert("Token not initialized"); + } + } + + function test_shouldFinalizeERC20Deposit() public { + address depositor = makeAddr("depositor"); + address receiver = makeAddr("receiver"); + + performDeposit(depositor, receiver, 100); + + address l2TokenAddress = IL2NativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR).l2TokenAddress(L1_TOKEN_ADDRESS); + + assertEq(BridgedStandardERC20(l2TokenAddress).balanceOf(receiver), 100); + assertEq(BridgedStandardERC20(l2TokenAddress).totalSupply(), 100); + assertEq(BridgedStandardERC20(l2TokenAddress).name(), TOKEN_DEFAULT_NAME); + assertEq(BridgedStandardERC20(l2TokenAddress).symbol(), TOKEN_DEFAULT_SYMBOL); + assertEq(BridgedStandardERC20(l2TokenAddress).decimals(), TOKEN_DEFAULT_DECIMALS); + } + + function test_governanceShouldBeAbleToReinitializeToken() public { + address l2TokenAddress = initializeTokenByDeposit(); + + BridgedStandardERC20.ERC20Getters memory getters = BridgedStandardERC20.ERC20Getters({ + ignoreName: false, + ignoreSymbol: false, + ignoreDecimals: false + }); + + vm.prank(ownerWallet); + BridgedStandardERC20(l2TokenAddress).reinitializeToken(getters, "TestTokenNewName", "TTN", 2); + assertEq(BridgedStandardERC20(l2TokenAddress).name(), "TestTokenNewName"); + assertEq(BridgedStandardERC20(l2TokenAddress).symbol(), "TTN"); + // The decimals should stay the same + assertEq(BridgedStandardERC20(l2TokenAddress).decimals(), 18); + } + + function test_governanceShouldlNotBeAbleToSkipInitializerVersions() public { + address l2TokenAddress = initializeTokenByDeposit(); + + BridgedStandardERC20.ERC20Getters memory getters = BridgedStandardERC20.ERC20Getters({ + ignoreName: false, + ignoreSymbol: false, + ignoreDecimals: false + }); + + vm.expectRevert(); + vm.prank(ownerWallet); + BridgedStandardERC20(l2TokenAddress).reinitializeToken(getters, "TestTokenNewName", "TTN", 20); + } + + function test_withdrawTokenNoRegistration() public { + TestnetERC20Token l2NativeToken = new TestnetERC20Token("token", "T", 18); + + l2NativeToken.mint(address(this), 100); + l2NativeToken.approve(L2_NATIVE_TOKEN_VAULT_ADDR, 100); + + // Basically we want all L2->L1 transactions to pass + vm.mockCall( + L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, + abi.encodeWithSignature("sendToL1(bytes)"), + abi.encode(bytes32(uint256(1))) + ); + + bytes32 assetId = DataEncoding.encodeNTVAssetId(block.chainid, address(l2NativeToken)); + + IL2AssetRouter(L2_ASSET_ROUTER_ADDR).withdraw( + assetId, + DataEncoding.encodeBridgeBurnData(100, address(1), address(l2NativeToken)) + ); + } +} diff --git a/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2GatewayL1Test.t.sol b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2GatewayL1Test.t.sol new file mode 100644 index 000000000..3e8b04e42 --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2GatewayL1Test.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +// solhint-disable gas-custom-errors + +import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; +import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {IL2NativeTokenVault} from "contracts/bridge/ntv/IL2NativeTokenVault.sol"; + +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; +import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; + +import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR, L2_BRIDGEHUB_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {ETH_TOKEN_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "contracts/common/Config.sol"; + +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {BridgehubMintCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {IL2AssetRouter} from "contracts/bridge/asset-router/IL2AssetRouter.sol"; +import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; + +import {SharedL2ContractDeployer} from "./_SharedL2ContractDeployer.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {SystemContractsArgs} from "./_SharedL2ContractL1DeployerUtils.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; +import {L2GatewayTestAbstract} from "./L2GatewayTestAbstract.t.sol"; +import {SharedL2ContractL1DeployerUtils} from "./_SharedL2ContractL1DeployerUtils.sol"; + +contract L2GatewayL1Test is Test, SharedL2ContractL1DeployerUtils, SharedL2ContractDeployer, L2GatewayTestAbstract { + function test() internal virtual override(DeployUtils, SharedL2ContractL1DeployerUtils) {} + + function initSystemContracts( + SystemContractsArgs memory _args + ) internal virtual override(SharedL2ContractDeployer, SharedL2ContractL1DeployerUtils) { + super.initSystemContracts(_args); + } + + function deployL2Contracts( + uint256 _l1ChainId + ) public virtual override(SharedL2ContractDeployer, SharedL2ContractL1DeployerUtils) { + super.deployL2Contracts(_l1ChainId); + } +} diff --git a/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2GatewayTestAbstract.t.sol b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2GatewayTestAbstract.t.sol new file mode 100644 index 000000000..600d3d346 --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2GatewayTestAbstract.t.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +// solhint-disable gas-custom-errors + +import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; +import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {IL2NativeTokenVault} from "contracts/bridge/ntv/IL2NativeTokenVault.sol"; + +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; +import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; + +import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR, L2_BRIDGEHUB_ADDR, L2_MESSENGER} from "contracts/common/L2ContractAddresses.sol"; +import {ETH_TOKEN_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "contracts/common/Config.sol"; + +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {BridgehubMintCTMAssetData, BridgehubBurnCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {IL2AssetRouter} from "contracts/bridge/asset-router/IL2AssetRouter.sol"; +import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; + +import {SharedL2ContractDeployer} from "./_SharedL2ContractDeployer.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {SystemContractsArgs} from "./_SharedL2ContractL1DeployerUtils.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; +import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; +import {ZKChainCommitment} from "contracts/common/Config.sol"; + +abstract contract L2GatewayTestAbstract is Test, SharedL2ContractDeployer { + function test_gatewayShouldFinalizeDeposit() public { + finalizeDeposit(); + require(l2Bridgehub.ctmAssetIdFromAddress(address(chainTypeManager)) == ctmAssetId, "ctmAssetId mismatch"); + require(l2Bridgehub.ctmAssetIdFromChainId(mintChainId) == ctmAssetId, "ctmAssetIdFromChainId mismatch"); + + address diamondProxy = l2Bridgehub.getZKChain(mintChainId); + require(!GettersFacet(diamondProxy).isPriorityQueueActive(), "Priority queue must not be active"); + } + + function test_gatewayNonEmptyPriorityQueueMigration() public { + ZKChainCommitment memory commitment = abi.decode(exampleChainCommitment, (ZKChainCommitment)); + + // Some non-zero value which would be the case if a chain existed before the + // priority tree was added + commitment.priorityTree.startIndex = 101; + commitment.priorityTree.nextLeafIndex = 102; + + finalizeDepositWithCustomCommitment(abi.encode(commitment)); + + address diamondProxy = l2Bridgehub.getZKChain(mintChainId); + require(!GettersFacet(diamondProxy).isPriorityQueueActive(), "Priority queue must not be active"); + } + + function test_forwardToL3OnGateway() public { + // todo fix this test + finalizeDeposit(); + vm.prank(SETTLEMENT_LAYER_RELAY_SENDER); + l2Bridgehub.forwardTransactionOnGateway(mintChainId, bytes32(0), 0); + } + + function test_withdrawFromGateway() public { + // todo fix this test + finalizeDeposit(); + address newAdmin = address(0x1); + bytes memory newDiamondCut = abi.encode(); + BridgehubBurnCTMAssetData memory data = BridgehubBurnCTMAssetData({ + chainId: mintChainId, + ctmData: abi.encode(newAdmin, config.contracts.diamondCutData), + chainData: abi.encode(chainTypeManager.protocolVersion()) + }); + vm.prank(ownerWallet); + vm.mockCall( + address(L2_MESSENGER), + abi.encodeWithSelector(L2_MESSENGER.sendToL1.selector), + abi.encode(bytes("")) + ); + l2AssetRouter.withdraw(ctmAssetId, abi.encode(data)); + } + + function finalizeDeposit() public { + finalizeDepositWithCustomCommitment(exampleChainCommitment); + } + + function finalizeDepositWithCustomCommitment(bytes memory chainCommitment) public { + bytes memory chainData = chainCommitment; + bytes memory ctmData = abi.encode( + baseTokenAssetId, + ownerWallet, + chainTypeManager.protocolVersion(), + config.contracts.diamondCutData + ); + BridgehubMintCTMAssetData memory data = BridgehubMintCTMAssetData({ + chainId: mintChainId, + baseTokenAssetId: baseTokenAssetId, + ctmData: ctmData, + chainData: chainData + }); + vm.prank(aliasedL1AssetRouter); + l2AssetRouter.finalizeDeposit(L1_CHAIN_ID, ctmAssetId, abi.encode(data)); + } +} diff --git a/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2NativeTokenVaultL1Test.t.sol b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2NativeTokenVaultL1Test.t.sol new file mode 100644 index 000000000..c84db6349 --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2NativeTokenVaultL1Test.t.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +// solhint-disable gas-custom-errors + +import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; +import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {IL2NativeTokenVault} from "contracts/bridge/ntv/IL2NativeTokenVault.sol"; + +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; +import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; + +import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR, L2_BRIDGEHUB_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {ETH_TOKEN_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "contracts/common/Config.sol"; + +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {BridgehubMintCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {IL2AssetRouter} from "contracts/bridge/asset-router/IL2AssetRouter.sol"; +import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; + +import {SharedL2ContractDeployer} from "./_SharedL2ContractDeployer.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {SystemContractsArgs} from "./_SharedL2ContractL1DeployerUtils.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; +import {L2NativeTokenVaultTestAbstract} from "./L2NativeTokenVaultTestAbstract.t.sol"; +import {SharedL2ContractL1DeployerUtils} from "./_SharedL2ContractL1DeployerUtils.sol"; + +contract L2GatewayL1Test is + Test, + SharedL2ContractL1DeployerUtils, + SharedL2ContractDeployer, + L2NativeTokenVaultTestAbstract +{ + function test() internal virtual override(DeployUtils, SharedL2ContractL1DeployerUtils) {} + + function initSystemContracts( + SystemContractsArgs memory _args + ) internal virtual override(SharedL2ContractDeployer, SharedL2ContractL1DeployerUtils) { + super.initSystemContracts(_args); + } + + function deployL2Contracts( + uint256 _l1ChainId + ) public virtual override(SharedL2ContractDeployer, SharedL2ContractL1DeployerUtils) { + super.deployL2Contracts(_l1ChainId); + } +} diff --git a/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2NativeTokenVaultTestAbstract.t.sol b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2NativeTokenVaultTestAbstract.t.sol new file mode 100644 index 000000000..7a2507d13 --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2NativeTokenVaultTestAbstract.t.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +// solhint-disable gas-custom-errors + +import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; +import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {IL2NativeTokenVault} from "contracts/bridge/ntv/IL2NativeTokenVault.sol"; + +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; +import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; + +import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR, L2_BRIDGEHUB_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {ETH_TOKEN_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "contracts/common/Config.sol"; + +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {BridgehubMintCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {IL2AssetRouter} from "contracts/bridge/asset-router/IL2AssetRouter.sol"; +import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; + +import {SharedL2ContractDeployer} from "./_SharedL2ContractDeployer.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {SystemContractsArgs} from "./_SharedL2ContractL1DeployerUtils.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; +import {TokenIsLegacy, TokenIsNotLegacy, Unauthorized, BridgeMintNotImplemented} from "contracts/common/L1ContractErrors.sol"; + +import {IL2SharedBridgeLegacy} from "contracts/bridge/interfaces/IL2SharedBridgeLegacy.sol"; +import {L2NativeTokenVault} from "contracts/bridge/ntv/L2NativeTokenVault.sol"; + +abstract contract L2NativeTokenVaultTestAbstract is Test, SharedL2ContractDeployer { + function test_registerLegacyToken() external { + address l2Token = makeAddr("l2Token"); + address l1Token = makeAddr("l1Token"); + vm.mockCall( + sharedBridgeLegacy, + abi.encodeCall(IL2SharedBridgeLegacy.l1TokenAddress, (l2Token)), + abi.encode(l1Token) + ); + L2NativeTokenVault(addresses.vaults.l1NativeTokenVaultProxy).setLegacyTokenAssetId(l2Token); + } + + function test_registerLegacyTokenRevertNotLegacy() external { + address l2Token = makeAddr("l2Token"); + vm.expectRevert(TokenIsNotLegacy.selector); + L2NativeTokenVault(addresses.vaults.l1NativeTokenVaultProxy).setLegacyTokenAssetId(l2Token); + } + + function test_registerTokenRevertIsLegacy() external { + address l2Token = makeAddr("l2Token"); + address l1Token = makeAddr("l1Token"); + vm.mockCall( + sharedBridgeLegacy, + abi.encodeCall(IL2SharedBridgeLegacy.l1TokenAddress, (l2Token)), + abi.encode(l1Token) + ); + + vm.expectRevert(TokenIsLegacy.selector); + L2NativeTokenVault(addresses.vaults.l1NativeTokenVaultProxy).registerToken(l2Token); + } +} diff --git a/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2WethTestAbstract.t.sol b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2WethTestAbstract.t.sol new file mode 100644 index 000000000..3bc89b3ef --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2WethTestAbstract.t.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +// solhint-disable gas-custom-errors + +import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; +import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {IL2NativeTokenVault} from "contracts/bridge/ntv/IL2NativeTokenVault.sol"; + +import {Unauthorized, BridgeMintNotImplemented} from "contracts/common/L1ContractErrors.sol"; +import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR, L2_BRIDGEHUB_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {ETH_TOKEN_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "contracts/common/Config.sol"; + +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {BridgehubMintCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {IL2AssetRouter} from "contracts/bridge/asset-router/IL2AssetRouter.sol"; +import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; + +import {SharedL2ContractDeployer} from "./_SharedL2ContractDeployer.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {SystemContractsArgs} from "./_SharedL2ContractL1DeployerUtils.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; + +abstract contract L2WethTestAbstract is Test, SharedL2ContractDeployer { + function test_shouldDepositWethByCallingDeposit() public { + uint256 amount = 100; + weth.deposit{value: amount}(); + assertEq(weth.balanceOf(address(this)), amount); + } + + function test_shouldDepositWethBySendingEth() public { + uint256 amount = 100; + address(weth).call{value: amount}(""); + assertEq(weth.balanceOf(address(this)), amount); + } + + function test_revertWhenDepositingWithRandomCalldata() public { + (bool success, ) = address(weth).call{value: 100}(hex"00000000"); + assertEq(success, false); + } + + function test_shouldWithdrawWethToL2Eth() public { + address sender = makeAddr("sender"); + uint256 amount = 100; + + vm.deal(sender, amount); + + vm.prank(sender); + weth.deposit{value: amount}(); + + vm.prank(sender); + weth.withdraw(amount); + + assertEq(weth.balanceOf(sender), 0); + assertEq(address(sender).balance, amount); + } + + function test_shouldDepositWethToAnotherAccount() public { + address sender = makeAddr("sender"); + address receiver = makeAddr("receiver"); + + uint256 amount = 100; + + vm.deal(sender, amount); + + vm.prank(sender); + weth.depositTo{value: amount}(receiver); + + assertEq(weth.balanceOf(receiver), amount); + assertEq(weth.balanceOf(sender), 0); + } + + function test_shouldWithdrawWethToAnotherAccount() public { + address sender = makeAddr("sender"); + address receiver = makeAddr("receiver"); + + uint256 amount = 100; + + vm.deal(sender, amount); + + vm.prank(sender); + weth.deposit{value: amount}(); + + vm.prank(sender); + weth.withdrawTo(receiver, amount); + + assertEq(receiver.balance, amount); + assertEq(sender.balance, 0); + } + + function test_revertWhenWithdrawingMoreThanBalance() public { + vm.expectRevert(); + weth.withdraw(1); + } + + function test_revertWhenCallingBridgeMint() public { + vm.expectRevert(abi.encodeWithSelector(BridgeMintNotImplemented.selector)); + vm.prank(L2_ASSET_ROUTER_ADDR); + weth.bridgeMint(address(1), 1); + } + + function test_revertWhenCallingBridgeMintDirectly() public { + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, address(this))); + weth.bridgeMint(address(1), 1); + } + + function test_revertWhenCallingBridgeBurnDirectly() public { + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, address(this))); + weth.bridgeBurn(address(1), 1); + } +} diff --git a/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/_SharedL2ContractDeployer.sol b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/_SharedL2ContractDeployer.sol new file mode 100644 index 000000000..8e1bfcf3e --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/_SharedL2ContractDeployer.sol @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +// solhint-disable gas-custom-errors + +import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; +import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {IL2NativeTokenVault} from "contracts/bridge/ntv/IL2NativeTokenVault.sol"; + +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; +import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; + +import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR, L2_BRIDGEHUB_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {ETH_TOKEN_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "contracts/common/Config.sol"; + +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {BridgehubMintCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {IL2AssetRouter} from "contracts/bridge/asset-router/IL2AssetRouter.sol"; +import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {L2WrappedBaseToken} from "contracts/bridge/L2WrappedBaseToken.sol"; +import {L2SharedBridgeLegacy} from "contracts/bridge/L2SharedBridgeLegacy.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; +import {MailboxFacet} from "contracts/state-transition/chain-deps/facets/Mailbox.sol"; +import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol"; +import {BridgehubL2TransactionRequest} from "contracts/common/Messaging.sol"; + +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {SystemContractsArgs} from "./_SharedL2ContractL1DeployerUtils.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; + +abstract contract SharedL2ContractDeployer is Test, DeployUtils { + L2WrappedBaseToken internal weth; + address internal l1WethAddress = address(4); + + // The owner of the beacon and the native token vault + address internal ownerWallet = address(2); + + BridgedStandardERC20 internal standardErc20Impl; + + UpgradeableBeacon internal beacon; + BeaconProxy internal proxy; + + IL2AssetRouter l2AssetRouter = IL2AssetRouter(L2_ASSET_ROUTER_ADDR); + IBridgehub l2Bridgehub = IBridgehub(L2_BRIDGEHUB_ADDR); + + uint256 internal constant L1_CHAIN_ID = 10; // it cannot be 9, the default block.chainid + uint256 internal ERA_CHAIN_ID = 270; + uint256 internal mintChainId = 300; + address internal l1AssetRouter = makeAddr("l1AssetRouter"); + address internal aliasedL1AssetRouter = AddressAliasHelper.applyL1ToL2Alias(l1AssetRouter); + + // We won't actually deploy an L1 token in these tests, but we need some address for it. + address internal L1_TOKEN_ADDRESS = 0x1111100000000000000000000000000000011111; + + string internal constant TOKEN_DEFAULT_NAME = "TestnetERC20Token"; + string internal constant TOKEN_DEFAULT_SYMBOL = "TET"; + uint8 internal constant TOKEN_DEFAULT_DECIMALS = 18; + address internal l1CTMDeployer = makeAddr("l1CTMDeployer"); + address internal l1CTM = makeAddr("l1CTM"); + bytes32 internal ctmAssetId = keccak256(abi.encode(L1_CHAIN_ID, l1CTMDeployer, bytes32(uint256(uint160(l1CTM))))); + + bytes32 internal baseTokenAssetId = + keccak256(abi.encode(L1_CHAIN_ID, L2_NATIVE_TOKEN_VAULT_ADDR, abi.encode(ETH_TOKEN_ADDRESS))); + + bytes internal exampleChainCommitment; + + address internal sharedBridgeLegacy; + + IChainTypeManager internal chainTypeManager; + + function setUp() public { + standardErc20Impl = new BridgedStandardERC20(); + beacon = new UpgradeableBeacon(address(standardErc20Impl)); + beacon.transferOwnership(ownerWallet); + + // One of the purposes of deploying it here is to publish its bytecode + BeaconProxy beaconProxy = new BeaconProxy(address(beacon), new bytes(0)); + proxy = beaconProxy; + bytes32 beaconProxyBytecodeHash; + assembly { + beaconProxyBytecodeHash := extcodehash(beaconProxy) + } + + sharedBridgeLegacy = deployL2SharedBridgeLegacy( + L1_CHAIN_ID, + ERA_CHAIN_ID, + ownerWallet, + l1AssetRouter, + beaconProxyBytecodeHash + ); + + L2WrappedBaseToken weth = deployL2Weth(); + + initSystemContracts( + SystemContractsArgs({ + l1ChainId: L1_CHAIN_ID, + eraChainId: ERA_CHAIN_ID, + l1AssetRouter: l1AssetRouter, + legacySharedBridge: sharedBridgeLegacy, + l2TokenBeacon: address(beacon), + l2TokenProxyBytecodeHash: beaconProxyBytecodeHash, + aliasedOwner: ownerWallet, + contractsDeployedAlready: false, + l1CtmDeployer: l1CTMDeployer + }) + ); + deployL2Contracts(L1_CHAIN_ID); + + vm.prank(aliasedL1AssetRouter); + l2AssetRouter.setAssetHandlerAddress(L1_CHAIN_ID, ctmAssetId, L2_BRIDGEHUB_ADDR); + vm.prank(ownerWallet); + l2Bridgehub.addChainTypeManager(address(addresses.stateTransition.chainTypeManagerProxy)); + vm.prank(AddressAliasHelper.applyL1ToL2Alias(l1CTMDeployer)); + l2Bridgehub.setCTMAssetAddress( + bytes32(uint256(uint160(l1CTM))), + address(addresses.stateTransition.chainTypeManagerProxy) + ); + chainTypeManager = IChainTypeManager(address(addresses.stateTransition.chainTypeManagerProxy)); + getExampleChainCommitment(); + } + + function getExampleChainCommitment() internal returns (bytes memory) { + address chainAdmin = makeAddr("chainAdmin"); + + vm.mockCall( + L2_ASSET_ROUTER_ADDR, + abi.encodeWithSelector(IL1AssetRouter.L1_NULLIFIER.selector), + abi.encode(L2_ASSET_ROUTER_ADDR) + ); + vm.mockCall( + L2_ASSET_ROUTER_ADDR, + abi.encodeWithSelector(IL1Nullifier.l2BridgeAddress.selector), + abi.encode(address(0)) + ); + vm.mockCall( + L2_BRIDGEHUB_ADDR, + abi.encodeWithSelector(IBridgehub.baseToken.selector, ERA_CHAIN_ID + 1), + abi.encode(address(uint160(1))) + ); + + vm.prank(L2_BRIDGEHUB_ADDR); + address chainAddress = chainTypeManager.createNewChain( + ERA_CHAIN_ID + 1, + baseTokenAssetId, + chainAdmin, + abi.encode(config.contracts.diamondCutData, generatedData.forceDeploymentsData), + new bytes[](0) + ); + + uint256 currentChainId = block.chainid; + + // This function is available only on L1 (and it is correct), + // but inside testing we need to call this function to recreate commitment + vm.chainId(L1_CHAIN_ID); + vm.prank(chainAdmin); + AdminFacet(chainAddress).setTokenMultiplier(1, 1); + + vm.chainId(currentChainId); + + // Now, let's also append a priority transaction for a more representative example + bytes[] memory deps = new bytes[](0); + + vm.prank(address(l2Bridgehub)); + MailboxFacet(chainAddress).bridgehubRequestL2Transaction( + BridgehubL2TransactionRequest({ + sender: address(0), + contractL2: address(0), + // Just a giant number so it is always enough + mintValue: 1 ether, + l2Value: 10, + l2Calldata: hex"", + l2GasLimit: 72_000_000, + l2GasPerPubdataByteLimit: 800, + factoryDeps: deps, + refundRecipient: address(0) + }) + ); + + exampleChainCommitment = abi.encode(IZKChain(chainAddress).prepareChainCommitment()); + } + + /// @notice Encodes the token data. + /// @param name The name of the token. + /// @param symbol The symbol of the token. + /// @param decimals The decimals of the token. + function encodeTokenData( + string memory name, + string memory symbol, + uint8 decimals + ) internal pure returns (bytes memory) { + bytes memory encodedName = abi.encode(name); + bytes memory encodedSymbol = abi.encode(symbol); + bytes memory encodedDecimals = abi.encode(decimals); + + return abi.encode(encodedName, encodedSymbol, encodedDecimals); + } + + function deployL2SharedBridgeLegacy( + uint256 _l1ChainId, + uint256 _eraChainId, + address _aliasedOwner, + address _l1SharedBridge, + bytes32 _l2TokenProxyBytecodeHash + ) internal returns (address) { + bytes32 ethAssetId = DataEncoding.encodeNTVAssetId(_l1ChainId, ETH_TOKEN_ADDRESS); + + L2SharedBridgeLegacy bridge = new L2SharedBridgeLegacy(); + console.log("bridge", address(bridge)); + address proxyAdmin = address(0x1); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(bridge), + proxyAdmin, + abi.encodeWithSelector( + L2SharedBridgeLegacy.initialize.selector, + _l1SharedBridge, + _l2TokenProxyBytecodeHash, + _aliasedOwner + ) + ); + console.log("proxy", address(proxy)); + return address(proxy); + } + + function deployL2Weth() internal returns (L2WrappedBaseToken) { + L2WrappedBaseToken wethImpl = new L2WrappedBaseToken(); + TransparentUpgradeableProxy wethProxy = new TransparentUpgradeableProxy(address(wethImpl), ownerWallet, ""); + weth = L2WrappedBaseToken(payable(wethProxy)); + weth.initializeV3("Wrapped Ether", "WETH", L2_ASSET_ROUTER_ADDR, l1WethAddress, baseTokenAssetId); + return weth; + } + + function initSystemContracts(SystemContractsArgs memory _args) internal virtual; + function deployL2Contracts(uint256 _l1ChainId) public virtual; +} diff --git a/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/_SharedL2ContractL1DeployerUtils.sol b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/_SharedL2ContractL1DeployerUtils.sol new file mode 100644 index 000000000..6768f5ddc --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/_SharedL2ContractL1DeployerUtils.sol @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {StdStorage, stdStorage, stdToml} from "forge-std/Test.sol"; +import {Script, console2 as console} from "forge-std/Script.sol"; + +import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; +import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol"; +import {L1Nullifier} from "contracts/bridge/L1Nullifier.sol"; +import {L1NativeTokenVault} from "contracts/bridge/ntv/L1NativeTokenVault.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; +import {CTMDeploymentTracker} from "contracts/bridgehub/CTMDeploymentTracker.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {DeployedAddresses, Config} from "deploy-scripts/DeployUtils.s.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; + +import {L2_MESSAGE_ROOT_ADDR, L2_BRIDGEHUB_ADDR, L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR} from "contracts/common/L2ContractAddresses.sol"; + +import {MessageRoot} from "contracts/bridgehub/MessageRoot.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {L2NativeTokenVault} from "contracts/bridge/ntv/L2NativeTokenVault.sol"; +import {L2NativeTokenVaultDev} from "contracts/dev-contracts/test/L2NativeTokenVaultDev.sol"; +import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; +import {IMessageRoot} from "contracts/bridgehub/IMessageRoot.sol"; +import {ICTMDeploymentTracker} from "contracts/bridgehub/ICTMDeploymentTracker.sol"; + +struct SystemContractsArgs { + uint256 l1ChainId; + uint256 eraChainId; + address l1AssetRouter; + address legacySharedBridge; + address l2TokenBeacon; + bytes32 l2TokenProxyBytecodeHash; + address aliasedOwner; + bool contractsDeployedAlready; + address l1CtmDeployer; +} + +contract SharedL2ContractL1DeployerUtils is DeployUtils { + using stdToml for string; + using stdStorage for StdStorage; + + /// @dev We provide a fast form of debugging the L2 contracts using L1 foundry. We also test using zk foundry. + function initSystemContracts(SystemContractsArgs memory _args) internal virtual { + bytes32 baseTokenAssetId = DataEncoding.encodeNTVAssetId(_args.l1ChainId, ETH_TOKEN_ADDRESS); + address wethToken = address(0x1); + // we deploy the code to get the contract code with immutables which we then vm.etch + address messageRoot = address(new MessageRoot(IBridgehub(L2_BRIDGEHUB_ADDR))); + address bridgehub = address(new Bridgehub(_args.l1ChainId, _args.aliasedOwner, 100)); + address assetRouter = address( + new L2AssetRouter( + _args.l1ChainId, + _args.eraChainId, + _args.l1AssetRouter, + _args.legacySharedBridge, + baseTokenAssetId, + _args.aliasedOwner + ) + ); + address ntv = address( + new L2NativeTokenVaultDev( + _args.l1ChainId, + _args.aliasedOwner, + _args.l2TokenProxyBytecodeHash, + _args.legacySharedBridge, + _args.l2TokenBeacon, + _args.contractsDeployedAlready, + wethToken, + baseTokenAssetId + ) + ); + + vm.etch(L2_MESSAGE_ROOT_ADDR, messageRoot.code); + MessageRoot(L2_MESSAGE_ROOT_ADDR).initialize(); + + vm.etch(L2_BRIDGEHUB_ADDR, bridgehub.code); + uint256 prevChainId = block.chainid; + vm.chainId(_args.l1ChainId); + Bridgehub(L2_BRIDGEHUB_ADDR).initialize(_args.aliasedOwner); + vm.chainId(prevChainId); + vm.prank(_args.aliasedOwner); + Bridgehub(L2_BRIDGEHUB_ADDR).setAddresses( + L2_ASSET_ROUTER_ADDR, + ICTMDeploymentTracker(_args.l1CtmDeployer), + IMessageRoot(L2_MESSAGE_ROOT_ADDR) + ); + + vm.etch(L2_ASSET_ROUTER_ADDR, assetRouter.code); + // Initializing reentrancy guard + vm.store( + L2_ASSET_ROUTER_ADDR, + bytes32(0x8e94fed44239eb2314ab7a406345e6c5a8f0ccedf3b600de3d004e672c33abf4), + bytes32(uint256(1)) + ); + + stdstore + .target(L2_ASSET_ROUTER_ADDR) + .sig("assetHandlerAddress(bytes32)") + .with_key(baseTokenAssetId) + .checked_write(bytes32(uint256(uint160(L2_NATIVE_TOKEN_VAULT_ADDR)))); + + vm.etch(L2_NATIVE_TOKEN_VAULT_ADDR, ntv.code); + + vm.store(L2_NATIVE_TOKEN_VAULT_ADDR, bytes32(uint256(251)), bytes32(uint256(_args.l2TokenProxyBytecodeHash))); + L2NativeTokenVaultDev(L2_NATIVE_TOKEN_VAULT_ADDR).deployBridgedStandardERC20(_args.aliasedOwner); + } + + function deployL2Contracts(uint256 _l1ChainId) public virtual { + string memory root = vm.projectRoot(); + string memory inputPath = string.concat( + root, + "/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-l1.toml" + ); + initializeConfig(inputPath); + addresses.transparentProxyAdmin = address(0x1); + addresses.bridgehub.bridgehubProxy = L2_BRIDGEHUB_ADDR; + addresses.bridges.sharedBridgeProxy = L2_ASSET_ROUTER_ADDR; + addresses.vaults.l1NativeTokenVaultProxy = L2_NATIVE_TOKEN_VAULT_ADDR; + addresses.blobVersionedHashRetriever = address(0x1); + config.l1ChainId = _l1ChainId; + console.log("Deploying L2 contracts"); + instantiateCreate2Factory(); + deployGenesisUpgrade(); + deployVerifier(); + deployValidatorTimelock(); + deployChainTypeManagerContract(); + } + + // add this to be excluded from coverage report + function test() internal virtual override {} +} diff --git a/l1-contracts/test/foundry/l1/integration/upgrade-envs/script-config/mainnet-era.toml b/l1-contracts/test/foundry/l1/integration/upgrade-envs/script-config/mainnet-era.toml new file mode 100644 index 000000000..d48013384 --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/upgrade-envs/script-config/mainnet-era.toml @@ -0,0 +1,7 @@ +owner_address = "0x4e4943346848c4867f81dfb37c4ca9c5715a7828" + +[chain] +chain_id = 324 +diamond_proxy_address = "0x32400084c286cf3e17e7b677ea9583e60a000324" +validium_mode = false +permanent_rollup = true diff --git a/l1-contracts/test/foundry/l1/integration/upgrade-envs/script-config/mainnet.toml b/l1-contracts/test/foundry/l1/integration/upgrade-envs/script-config/mainnet.toml new file mode 100644 index 000000000..0c77be87e --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/upgrade-envs/script-config/mainnet.toml @@ -0,0 +1,35 @@ +era_chain_id = 324 +owner_address = "8f7a9912416e8adc4d9c21fae1415d3318a11897" +testnet_verifier = false +support_l2_legacy_shared_bridge_test = false + +[contracts] +max_number_of_chains = 100 +create2_factory_salt = "0xde6b9c610417de5c775c1601c947f482e4f4e30c0f7b848c6d2b0554d76f607e" +validator_timelock_execution_delay = 0 +genesis_root = "0xf9030b78c5bf5ac997a76962aa32c90a6d8e8ebce9838c8eeb388d73e1f7659a" +genesis_rollup_leaf_index = 64 +genesis_batch_commitment = "0x34c1b220363e0cde7eaf10fe95754d61de097e0f9d9a1dc56c8026562e395259" +recursion_node_level_vk_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +recursion_leaf_level_vk_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +recursion_circuits_set_vks_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +priority_tx_max_gas_limit = 72000000 +diamond_init_pubdata_pricing_mode = 0 +diamond_init_batch_overhead_l1_gas = 1000000 +diamond_init_max_pubdata_per_batch = 120000 +diamond_init_max_l2_gas_per_batch = 80000000 +diamond_init_priority_tx_max_pubdata = 99000 +diamond_init_minimal_l2_gas_price = 250000000 +bootloader_hash = "0x010008e5b883de8897598e83d383e332b87d09164363816c15f22353c3cd910d" +default_aa_hash = "0x0100052307b3b66ef67935255483d39b3c8dcdb47fdf94dddca11ebe8271afe6" +bridgehub_proxy_address = "0x303a465B659cBB0ab36eE643eA362c509EEb5213" +old_shared_bridge_proxy_address = "0xD7f9f54194C633F36CCD5F3da84ad4a1c38cB2cB" +state_transition_manager_address = "0xc2eE6b6af7d616f6e27ce7F4A451Aedc2b0F5f5C" +transparent_proxy_admin = "0xC2a36181fB524a6bEfE639aFEd37A67e77d62cf1" +era_diamond_proxy = "0x32400084c286cf3e17e7b677ea9583e60a000324" +blob_versioned_hash_retriever = "0x0000000000000000000000000000000000000001" +legacy_erc20_bridge_address = "0x57891966931eb4bb6fb81430e6ce0a03aabde063" +old_validator_timelock = "0x5D8ba173Dc6C3c90C8f7C04C9288BeF5FDbAd06E" + +[tokens] +token_weth_address = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" diff --git a/l1-contracts/test/foundry/l1/integration/upgrade-envs/script-out/.gitkeep b/l1-contracts/test/foundry/l1/integration/upgrade-envs/script-out/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/l1-contracts/test/foundry/unit/concrete/AddressAliasHelper/_AddressAliasHelper_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/AddressAliasHelper/_AddressAliasHelper_Shared.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/AddressAliasHelper/_AddressAliasHelper_Shared.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/AddressAliasHelper/_AddressAliasHelper_Shared.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/AddressAliasHelper/applyL1ToL2Alias.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/AddressAliasHelper/applyL1ToL2Alias.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/AddressAliasHelper/applyL1ToL2Alias.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/AddressAliasHelper/applyL1ToL2Alias.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/AddressAliasHelper/undoL1ToL2Alias.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/AddressAliasHelper/undoL1ToL2Alias.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/AddressAliasHelper/undoL1ToL2Alias.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/AddressAliasHelper/undoL1ToL2Alias.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/Initialize.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridgehub/Initialize.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/Bridgehub/Initialize.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Bridgehub/Initialize.t.sol diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Bridgehub/MessageRoot.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridgehub/MessageRoot.t.sol new file mode 100644 index 000000000..a3c917d56 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/Bridgehub/MessageRoot.t.sol @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {MessageRoot} from "contracts/bridgehub/MessageRoot.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {OnlyBridgehub, MessageRootNotRegistered} from "contracts/bridgehub/L1BridgehubErrors.sol"; +import {Merkle} from "contracts/common/libraries/Merkle.sol"; +import {MessageHashing} from "contracts/common/libraries/MessageHashing.sol"; + +// Chain tree consists of batch commitments as their leaves. We use hash of "new bytes(96)" as the hash of an empty leaf. +bytes32 constant CHAIN_TREE_EMPTY_ENTRY_HASH = bytes32( + 0x46700b4d40ac5c35af2c22dda2787a91eb567b06c924a8fb8ae9a05b20c08c21 +); + +// Chain tree consists of batch commitments as their leaves. We use hash of "new bytes(96)" as the hash of an empty leaf. +bytes32 constant SHARED_ROOT_TREE_EMPTY_HASH = bytes32( + 0x46700b4d40ac5c35af2c22dda2787a91eb567b06c924a8fb8ae9a05b20c08c21 +); + +contract MessageRootTest is Test { + address bridgeHub; + MessageRoot messageRoot; + + function setUp() public { + bridgeHub = makeAddr("bridgeHub"); + messageRoot = new MessageRoot(IBridgehub(bridgeHub)); + } + + function test_init() public { + assertEq(messageRoot.getAggregatedRoot(), (MessageHashing.chainIdLeafHash(0x00, block.chainid))); + } + + function test_RevertWhen_addChainNotBridgeHub() public { + uint256 alphaChainId = uint256(uint160(makeAddr("alphaChainId"))); + uint256 betaChainId = uint256(uint160(makeAddr("betaChainId"))); + + assertFalse(messageRoot.chainRegistered(alphaChainId), "alpha chain 1"); + + vm.expectRevert(abi.encodeWithSelector(OnlyBridgehub.selector, address(this), bridgeHub)); + messageRoot.addNewChain(alphaChainId); + + assertFalse(messageRoot.chainRegistered(alphaChainId), "alpha chain 2"); + } + + function test_addNewChain() public { + uint256 alphaChainId = uint256(uint160(makeAddr("alphaChainId"))); + uint256 betaChainId = uint256(uint160(makeAddr("betaChainId"))); + + assertFalse(messageRoot.chainRegistered(alphaChainId), "alpha chain 1"); + assertFalse(messageRoot.chainRegistered(betaChainId), "beta chain 1"); + + vm.prank(bridgeHub); + vm.expectEmit(true, false, false, false); + emit MessageRoot.AddedChain(alphaChainId, 0); + messageRoot.addNewChain(alphaChainId); + + assertTrue(messageRoot.chainRegistered(alphaChainId), "alpha chain 2"); + assertFalse(messageRoot.chainRegistered(betaChainId), "beta chain 2"); + + assertEq(messageRoot.getChainRoot(alphaChainId), bytes32(0)); + } + + function test_RevertWhen_ChainNotRegistered() public { + address alphaChainSender = makeAddr("alphaChainSender"); + uint256 alphaChainId = uint256(uint160(makeAddr("alphaChainId"))); + vm.mockCall( + bridgeHub, + abi.encodeWithSelector(IBridgehub.getZKChain.selector, alphaChainId), + abi.encode(alphaChainSender) + ); + + vm.prank(alphaChainSender); + vm.expectRevert(MessageRootNotRegistered.selector); + messageRoot.addChainBatchRoot(alphaChainId, 1, bytes32(alphaChainId)); + } + + function test_addChainBatchRoot() public { + address alphaChainSender = makeAddr("alphaChainSender"); + uint256 alphaChainId = uint256(uint160(makeAddr("alphaChainId"))); + vm.mockCall( + bridgeHub, + abi.encodeWithSelector(IBridgehub.getZKChain.selector, alphaChainId), + abi.encode(alphaChainSender) + ); + + vm.prank(bridgeHub); + messageRoot.addNewChain(alphaChainId); + + vm.prank(alphaChainSender); + vm.expectEmit(true, false, false, false); + emit MessageRoot.Preimage(bytes32(0), bytes32(0)); + vm.expectEmit(true, false, false, false); + emit MessageRoot.AppendedChainBatchRoot(alphaChainId, 1, bytes32(alphaChainId)); + messageRoot.addChainBatchRoot(alphaChainId, 1, bytes32(alphaChainId)); + } + + function test_updateFullTree() public { + address alphaChainSender = makeAddr("alphaChainSender"); + uint256 alphaChainId = uint256(uint160(makeAddr("alphaChainId"))); + vm.mockCall( + bridgeHub, + abi.encodeWithSelector(IBridgehub.getZKChain.selector, alphaChainId), + abi.encode(alphaChainSender) + ); + + vm.prank(bridgeHub); + messageRoot.addNewChain(alphaChainId); + + vm.prank(alphaChainSender); + messageRoot.addChainBatchRoot(alphaChainId, 1, bytes32(alphaChainId)); + + messageRoot.updateFullTree(); + + assertEq(messageRoot.getAggregatedRoot(), 0x0ef1ac67d77f177a33449c47a8f05f0283300a81adca6f063c92c774beed140c); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/_Bridgehub_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridgehub/_Bridgehub_Shared.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/Bridgehub/_Bridgehub_Shared.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Bridgehub/_Bridgehub_Shared.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridgehub/experimental_bridge.t.sol similarity index 66% rename from l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Bridgehub/experimental_bridge.t.sol index 43826e6ac..c61584112 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Bridgehub/experimental_bridge.t.sol @@ -3,34 +3,67 @@ pragma solidity 0.8.24; import {stdStorage, StdStorage, Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {TestnetERC20Token} from "contracts/dev-contracts/TestnetERC20Token.sol"; import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; -import {ChainCreationParams} from "contracts/state-transition/IStateTransitionManager.sol"; +import {ChainCreationParams} from "contracts/state-transition/IChainTypeManager.sol"; import {L2TransactionRequestDirect, L2TransactionRequestTwoBridgesOuter} from "contracts/bridgehub/IBridgehub.sol"; -import {DummyStateTransitionManagerWBH} from "contracts/dev-contracts/test/DummyStateTransitionManagerWithBridgeHubAddress.sol"; -import {DummyHyperchain} from "contracts/dev-contracts/test/DummyHyperchain.sol"; -import {IL1SharedBridge} from "contracts/bridge/interfaces/IL1SharedBridge.sol"; -import {L1SharedBridge} from "contracts/bridge/L1SharedBridge.sol"; -import {L2TransactionRequestTwoBridgesInner} from "contracts/bridgehub/IBridgehub.sol"; +import {DummyChainTypeManagerWBH} from "contracts/dev-contracts/test/DummyChainTypeManagerWithBridgeHubAddress.sol"; +import {DummyZKChain} from "contracts/dev-contracts/test/DummyZKChain.sol"; +import {DummySharedBridge} from "contracts/dev-contracts/test/DummySharedBridge.sol"; +import {DummyBridgehubSetter} from "contracts/dev-contracts/test/DummyBridgehubSetter.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol"; +import {L1NativeTokenVault} from "contracts/bridge/ntv/L1NativeTokenVault.sol"; +import {L1Nullifier} from "contracts/bridge/L1Nullifier.sol"; +import {IL1Nullifier} from "contracts/bridge/L1Nullifier.sol"; + import {L2Message, L2Log, TxStatus, BridgehubL2TransactionRequest} from "contracts/common/Messaging.sol"; -import {ETH_TOKEN_ADDRESS, REQUIRED_L2_GAS_PRICE_PER_PUBDATA, MAX_NEW_FACTORY_DEPS, TWO_BRIDGES_MAGIC_VALUE} from "contracts/common/Config.sol"; +import {L2_NATIVE_TOKEN_VAULT_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; + +import {ICTMDeploymentTracker} from "contracts/bridgehub/ICTMDeploymentTracker.sol"; +import {IMessageRoot} from "contracts/bridgehub/IMessageRoot.sol"; +import {MessageRoot} from "contracts/bridgehub/MessageRoot.sol"; +import {L2TransactionRequestTwoBridgesInner} from "contracts/bridgehub/IBridgehub.sol"; +import {ETH_TOKEN_ADDRESS, REQUIRED_L2_GAS_PRICE_PER_PUBDATA, MAX_NEW_FACTORY_DEPS, TWO_BRIDGES_MAGIC_VALUE, BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS} from "contracts/common/Config.sol"; import {L1ERC20Bridge} from "contracts/bridge/L1ERC20Bridge.sol"; -import {ZeroChainId, AddressTooLow, ChainIdTooBig, WrongMagicValue, SharedBridgeNotSet, TokenNotRegistered, BridgeHubAlreadyRegistered, MsgValueMismatch, SlotOccupied, STMAlreadyRegistered, TokenAlreadyRegistered, Unauthorized, NonEmptyMsgValue, STMNotRegistered, InvalidChainId} from "contracts/common/L1ContractErrors.sol"; +import {IAssetRouterBase} from "contracts/bridge/asset-router/IAssetRouterBase.sol"; +import {SecondBridgeAddressTooLow} from "contracts/bridgehub/L1BridgehubErrors.sol"; +import {AssetIdNotSupported, ZeroChainId, AssetIdAlreadyRegistered, ChainIdTooBig, WrongMagicValue, SharedBridgeNotSet, BridgeHubAlreadyRegistered, MsgValueMismatch, SlotOccupied, CTMAlreadyRegistered, Unauthorized, NonEmptyMsgValue, CTMNotRegistered} from "contracts/common/L1ContractErrors.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; contract ExperimentalBridgeTest is Test { using stdStorage for StdStorage; + address weth; Bridgehub bridgeHub; + DummyBridgehubSetter dummyBridgehub; address public bridgeOwner; - DummyStateTransitionManagerWBH mockSTM; - DummyHyperchain mockChainContract; - L1SharedBridge sharedBridge; + address public testTokenAddress; + DummyChainTypeManagerWBH mockCTM; + DummyZKChain mockChainContract; + DummySharedBridge mockSharedBridge; + DummySharedBridge mockSecondSharedBridge; + L1AssetRouter sharedBridge; address sharedBridgeAddress; address secondBridgeAddress; - L1SharedBridge secondBridge; + address l1NullifierAddress; + L1AssetRouter secondBridge; TestnetERC20Token testToken; + L1NativeTokenVault ntv; + IMessageRoot messageRoot; + L1Nullifier l1Nullifier; + + bytes32 tokenAssetId; + + bytes32 private constant LOCK_FLAG_ADDRESS = 0x8e94fed44239eb2314ab7a406345e6c5a8f0ccedf3b600de3d004e672c33abf4; + + bytes32 ETH_TOKEN_ASSET_ID = + keccak256(abi.encode(block.chainid, L2_NATIVE_TOKEN_VAULT_ADDR, bytes32(uint256(uint160(ETH_TOKEN_ADDRESS))))); + TestnetERC20Token testToken6; TestnetERC20Token testToken8; TestnetERC20Token testToken18; @@ -39,7 +72,9 @@ contract ExperimentalBridgeTest is Test { uint256 eraChainId; - event NewChain(uint256 indexed chainId, address stateTransitionManager, address indexed chainGovernance); + address deployerAddress; + + event NewChain(uint256 indexed chainId, address chainTypeManager, address indexed chainGovernance); modifier useRandomToken(uint256 randomValue) { _setRandomToken(randomValue); @@ -57,28 +92,63 @@ contract ExperimentalBridgeTest is Test { } else { testToken = testToken8; } + + tokenAssetId = DataEncoding.encodeNTVAssetId(block.chainid, address(testToken)); } function setUp() public { - eraChainId = 9; - bridgeHub = new Bridgehub(); + deployerAddress = makeAddr("DEPLOYER_ADDRESS"); + eraChainId = 320; + uint256 l1ChainId = block.chainid; bridgeOwner = makeAddr("BRIDGE_OWNER"); - mockSTM = new DummyStateTransitionManagerWBH(address(bridgeHub)); - mockChainContract = new DummyHyperchain(address(bridgeHub), eraChainId); - mockL2Contract = makeAddr("mockL2Contract"); + dummyBridgehub = new DummyBridgehubSetter(l1ChainId, bridgeOwner, type(uint256).max); + bridgeHub = Bridgehub(address(dummyBridgehub)); + weth = makeAddr("WETH"); + mockCTM = new DummyChainTypeManagerWBH(address(bridgeHub)); + mockChainContract = new DummyZKChain(address(bridgeHub), eraChainId, block.chainid); + mockL2Contract = makeAddr("mockL2Contract"); // mocks to use in bridges instead of using a dummy one address mockL1WethAddress = makeAddr("Weth"); address eraDiamondProxy = makeAddr("eraDiamondProxy"); - sharedBridge = new L1SharedBridge(mockL1WethAddress, bridgeHub, eraChainId, eraDiamondProxy); + l1Nullifier = new L1Nullifier(bridgeHub, eraChainId, eraDiamondProxy); + l1NullifierAddress = address(l1Nullifier); + + mockSharedBridge = new DummySharedBridge(keccak256("0xabc")); + mockSecondSharedBridge = new DummySharedBridge(keccak256("0xdef")); + + ntv = _deployNTV(address(mockSharedBridge)); + + mockSecondSharedBridge.setNativeTokenVault(ntv); + + testToken = new TestnetERC20Token("ZKSTT", "ZkSync Test Token", 18); + testTokenAddress = address(testToken); + ntv.registerToken(address(testToken)); + tokenAssetId = DataEncoding.encodeNTVAssetId(block.chainid, address(testToken)); + + messageRoot = new MessageRoot(bridgeHub); + + sharedBridge = new L1AssetRouter( + mockL1WethAddress, + address(bridgeHub), + l1NullifierAddress, + eraChainId, + eraDiamondProxy + ); address defaultOwner = sharedBridge.owner(); vm.prank(defaultOwner); sharedBridge.transferOwnership(bridgeOwner); vm.prank(bridgeOwner); sharedBridge.acceptOwnership(); - secondBridge = new L1SharedBridge(mockL1WethAddress, bridgeHub, eraChainId, eraDiamondProxy); + secondBridge = new L1AssetRouter( + mockL1WethAddress, + address(bridgeHub), + l1NullifierAddress, + eraChainId, + eraDiamondProxy + ); defaultOwner = secondBridge.owner(); vm.prank(defaultOwner); secondBridge.transferOwnership(bridgeOwner); @@ -98,11 +168,7 @@ contract ExperimentalBridgeTest is Test { vm.expectRevert(SlotOccupied.selector); bridgeHub.initialize(bridgeOwner); - vm.store( - address(mockChainContract), - 0x8e94fed44239eb2314ab7a406345e6c5a8f0ccedf3b600de3d004e672c33abf4, - bytes32(uint256(1)) - ); + vm.store(address(mockChainContract), LOCK_FLAG_ADDRESS, bytes32(uint256(1))); bytes32 bridgehubLocation = bytes32(uint256(36)); vm.store(address(mockChainContract), bridgehubLocation, bytes32(uint256(uint160(address(bridgeHub))))); bytes32 baseTokenGasPriceNominatorLocation = bytes32(uint256(40)); @@ -123,6 +189,49 @@ contract ExperimentalBridgeTest is Test { assertEq(bridgeHub.owner(), bridgeOwner); } + function _deployNTV(address _sharedBridgeAddr) internal returns (L1NativeTokenVault addr) { + L1NativeTokenVault ntvImpl = new L1NativeTokenVault(weth, _sharedBridgeAddr, l1Nullifier); + TransparentUpgradeableProxy ntvProxy = new TransparentUpgradeableProxy( + address(ntvImpl), + address(bridgeOwner), + abi.encodeCall(ntvImpl.initialize, (bridgeOwner, address(0))) + ); + addr = L1NativeTokenVault(payable(ntvProxy)); + + vm.prank(bridgeOwner); + L1AssetRouter(_sharedBridgeAddr).setNativeTokenVault(addr); + + addr.registerEthToken(); + } + + function _useFullSharedBridge() internal { + ntv = _deployNTV(address(sharedBridge)); + + secondBridgeAddress = address(sharedBridge); + } + + function _useMockSharedBridge() internal { + sharedBridgeAddress = address(mockSharedBridge); + } + + function _initializeBridgehub() internal { + vm.prank(bridgeOwner); + bridgeHub.setPendingAdmin(deployerAddress); + vm.prank(deployerAddress); + bridgeHub.acceptAdmin(); + + vm.startPrank(bridgeOwner); + bridgeHub.addChainTypeManager(address(mockCTM)); + bridgeHub.addTokenAssetId(tokenAssetId); + bridgeHub.setAddresses(sharedBridgeAddress, ICTMDeploymentTracker(address(0)), messageRoot); + vm.stopPrank(); + + vm.prank(l1Nullifier.owner()); + l1Nullifier.setL1NativeTokenVault(ntv); + vm.prank(l1Nullifier.owner()); + l1Nullifier.setL1AssetRouter(sharedBridgeAddress); + } + function test_newPendingAdminReplacesPrevious(address randomDeployer, address otherRandomDeployer) public { vm.assume(randomDeployer != address(0)); vm.assume(otherRandomDeployer != address(0)); @@ -180,235 +289,244 @@ contract ExperimentalBridgeTest is Test { } } - function test_addStateTransitionManager(address randomAddressWithoutTheCorrectInterface) public { + function test_addChainTypeManager(address randomAddressWithoutTheCorrectInterface) public { vm.assume(randomAddressWithoutTheCorrectInterface != address(0)); - bool isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); - assertTrue(!isSTMRegistered); + bool isCTMRegistered = bridgeHub.chainTypeManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(!isCTMRegistered); vm.prank(bridgeOwner); - bridgeHub.addStateTransitionManager(randomAddressWithoutTheCorrectInterface); + bridgeHub.addChainTypeManager(randomAddressWithoutTheCorrectInterface); - isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); - assertTrue(isSTMRegistered); + isCTMRegistered = bridgeHub.chainTypeManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(isCTMRegistered); - // An address that has already been registered, cannot be registered again (at least not before calling `removeStateTransitionManager`). + // An address that has already been registered, cannot be registered again (at least not before calling `removeChainTypeManager`). vm.prank(bridgeOwner); - vm.expectRevert(STMAlreadyRegistered.selector); - bridgeHub.addStateTransitionManager(randomAddressWithoutTheCorrectInterface); + vm.expectRevert(CTMAlreadyRegistered.selector); + bridgeHub.addChainTypeManager(randomAddressWithoutTheCorrectInterface); - isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); - assertTrue(isSTMRegistered); + isCTMRegistered = bridgeHub.chainTypeManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(isCTMRegistered); } - function test_addStateTransitionManager_cannotBeCalledByRandomAddress( + function test_addChainTypeManager_cannotBeCalledByRandomAddress( address randomCaller, address randomAddressWithoutTheCorrectInterface ) public { vm.assume(randomAddressWithoutTheCorrectInterface != address(0)); - bool isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); - assertTrue(!isSTMRegistered); + bool isCTMRegistered = bridgeHub.chainTypeManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(!isCTMRegistered); if (randomCaller != bridgeOwner) { vm.prank(randomCaller); vm.expectRevert(bytes("Ownable: caller is not the owner")); - bridgeHub.addStateTransitionManager(randomAddressWithoutTheCorrectInterface); + bridgeHub.addChainTypeManager(randomAddressWithoutTheCorrectInterface); } vm.prank(bridgeOwner); - bridgeHub.addStateTransitionManager(randomAddressWithoutTheCorrectInterface); + bridgeHub.addChainTypeManager(randomAddressWithoutTheCorrectInterface); - isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); - assertTrue(isSTMRegistered); + isCTMRegistered = bridgeHub.chainTypeManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(isCTMRegistered); - // An address that has already been registered, cannot be registered again (at least not before calling `removeStateTransitionManager`). + // An address that has already been registered, cannot be registered again (at least not before calling `removeChainTypeManager`). vm.prank(bridgeOwner); - vm.expectRevert(STMAlreadyRegistered.selector); - bridgeHub.addStateTransitionManager(randomAddressWithoutTheCorrectInterface); + vm.expectRevert(CTMAlreadyRegistered.selector); + bridgeHub.addChainTypeManager(randomAddressWithoutTheCorrectInterface); // Definitely not by a random caller if (randomCaller != bridgeOwner) { vm.prank(randomCaller); vm.expectRevert("Ownable: caller is not the owner"); - bridgeHub.addStateTransitionManager(randomAddressWithoutTheCorrectInterface); + bridgeHub.addChainTypeManager(randomAddressWithoutTheCorrectInterface); } - isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); - assertTrue(isSTMRegistered); + isCTMRegistered = bridgeHub.chainTypeManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(isCTMRegistered); } - function test_removeStateTransitionManager(address randomAddressWithoutTheCorrectInterface) public { + function test_removeChainTypeManager(address randomAddressWithoutTheCorrectInterface) public { vm.assume(randomAddressWithoutTheCorrectInterface != address(0)); - bool isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); - assertTrue(!isSTMRegistered); + bool isCTMRegistered = bridgeHub.chainTypeManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(!isCTMRegistered); - // A non-existent STM cannot be removed + // A non-existent CTM cannot be removed vm.prank(bridgeOwner); - vm.expectRevert(STMNotRegistered.selector); - bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); + vm.expectRevert(CTMNotRegistered.selector); + bridgeHub.removeChainTypeManager(randomAddressWithoutTheCorrectInterface); - // Let's first register our particular stateTransitionManager + // Let's first register our particular chainTypeManager vm.prank(bridgeOwner); - bridgeHub.addStateTransitionManager(randomAddressWithoutTheCorrectInterface); + bridgeHub.addChainTypeManager(randomAddressWithoutTheCorrectInterface); - isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); - assertTrue(isSTMRegistered); + isCTMRegistered = bridgeHub.chainTypeManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(isCTMRegistered); // Only an address that has already been registered, can be removed. vm.prank(bridgeOwner); - bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); + bridgeHub.removeChainTypeManager(randomAddressWithoutTheCorrectInterface); - isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); - assertTrue(!isSTMRegistered); + isCTMRegistered = bridgeHub.chainTypeManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(!isCTMRegistered); - // An already removed STM cannot be removed again + // An already removed CTM cannot be removed again vm.prank(bridgeOwner); - vm.expectRevert(STMNotRegistered.selector); - bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); + vm.expectRevert(CTMNotRegistered.selector); + bridgeHub.removeChainTypeManager(randomAddressWithoutTheCorrectInterface); } - function test_removeStateTransitionManager_cannotBeCalledByRandomAddress( + function test_removeChainTypeManager_cannotBeCalledByRandomAddress( address randomAddressWithoutTheCorrectInterface, address randomCaller ) public { vm.assume(randomAddressWithoutTheCorrectInterface != address(0)); - bool isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); - assertTrue(!isSTMRegistered); + bool isCTMRegistered = bridgeHub.chainTypeManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(!isCTMRegistered); if (randomCaller != bridgeOwner) { vm.prank(randomCaller); vm.expectRevert(bytes("Ownable: caller is not the owner")); - bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); + bridgeHub.removeChainTypeManager(randomAddressWithoutTheCorrectInterface); } - // A non-existent STM cannot be removed + // A non-existent CTM cannot be removed vm.prank(bridgeOwner); - vm.expectRevert(STMNotRegistered.selector); - bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); + vm.expectRevert(CTMNotRegistered.selector); + bridgeHub.removeChainTypeManager(randomAddressWithoutTheCorrectInterface); - // Let's first register our particular stateTransitionManager + // Let's first register our particular chainTypeManager vm.prank(bridgeOwner); - bridgeHub.addStateTransitionManager(randomAddressWithoutTheCorrectInterface); + bridgeHub.addChainTypeManager(randomAddressWithoutTheCorrectInterface); - isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); - assertTrue(isSTMRegistered); + isCTMRegistered = bridgeHub.chainTypeManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(isCTMRegistered); // Only an address that has already been registered, can be removed. vm.prank(bridgeOwner); - bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); + bridgeHub.removeChainTypeManager(randomAddressWithoutTheCorrectInterface); - isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); - assertTrue(!isSTMRegistered); + isCTMRegistered = bridgeHub.chainTypeManagerIsRegistered(randomAddressWithoutTheCorrectInterface); + assertTrue(!isCTMRegistered); - // An already removed STM cannot be removed again + // An already removed CTM cannot be removed again vm.prank(bridgeOwner); - vm.expectRevert(STMNotRegistered.selector); - bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); + vm.expectRevert(CTMNotRegistered.selector); + bridgeHub.removeChainTypeManager(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); + bridgeHub.removeChainTypeManager(randomAddressWithoutTheCorrectInterface); } } - function test_addToken(address, address randomAddress, uint256 randomValue) public useRandomToken(randomValue) { - assertTrue(!bridgeHub.tokenIsRegistered(randomAddress), "This random address is not registered as a token"); + function test_addAssetId(address randomAddress) public { + vm.startPrank(bridgeOwner); + bridgeHub.setAddresses(address(mockSharedBridge), ICTMDeploymentTracker(address(0)), IMessageRoot(address(0))); + vm.stopPrank(); + + bytes32 assetId = DataEncoding.encodeNTVAssetId(block.chainid, testTokenAddress); + assertTrue(!bridgeHub.assetIdIsRegistered(assetId), "This random address is not registered as a token"); vm.prank(bridgeOwner); - bridgeHub.addToken(randomAddress); + bridgeHub.addTokenAssetId(assetId); assertTrue( - bridgeHub.tokenIsRegistered(randomAddress), + bridgeHub.assetIdIsRegistered(assetId), "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 + if (randomAddress != address(testTokenAddress)) { + assetId = DataEncoding.encodeNTVAssetId(block.chainid, address(randomAddress)); + vm.assume(!bridgeHub.assetIdIsRegistered(assetId)); + // Testing to see if a random address can also be added or not vm.prank(bridgeOwner); - bridgeHub.addToken(address(testToken)); - - assertTrue(bridgeHub.tokenIsRegistered(address(testToken))); + bridgeHub.addTokenAssetId(assetId); + assertTrue(bridgeHub.assetIdIsRegistered(assetId)); } // An already registered token cannot be registered again vm.prank(bridgeOwner); - vm.expectRevert(abi.encodeWithSelector(TokenAlreadyRegistered.selector, address(randomAddress))); - bridgeHub.addToken(randomAddress); + vm.expectRevert(AssetIdAlreadyRegistered.selector); + bridgeHub.addTokenAssetId(assetId); } - function test_addToken_cannotBeCalledByRandomAddress(address randomAddress, address randomCaller) public { + function test_addAssetId_cannotBeCalledByRandomAddress( + address randomCaller, + uint256 randomValue + ) public useRandomToken(randomValue) { + vm.startPrank(bridgeOwner); + bridgeHub.setAddresses(address(mockSharedBridge), ICTMDeploymentTracker(address(0)), IMessageRoot(address(0))); + vm.stopPrank(); + + bytes32 assetId = DataEncoding.encodeNTVAssetId(block.chainid, testTokenAddress); + vm.assume(randomCaller != bridgeOwner); vm.assume(randomCaller != bridgeHub.admin()); - vm.prank(randomCaller); vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, randomCaller)); - bridgeHub.addToken(randomAddress); + bridgeHub.addTokenAssetId(assetId); - assertTrue(!bridgeHub.tokenIsRegistered(randomAddress), "This random address is not registered as a token"); + assertTrue(!bridgeHub.assetIdIsRegistered(assetId), "This random address is not registered as a token"); vm.prank(bridgeOwner); - bridgeHub.addToken(randomAddress); + bridgeHub.addTokenAssetId(assetId); assertTrue( - bridgeHub.tokenIsRegistered(randomAddress), - "after call from the bridgeowner, this randomAddress should be a registered token" + bridgeHub.assetIdIsRegistered(assetId), + "after call from the bridgeowner, this testTokenAddress 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(abi.encodeWithSelector(TokenAlreadyRegistered.selector, address(randomAddress))); - bridgeHub.addToken(randomAddress); + vm.expectRevert(AssetIdAlreadyRegistered.selector); + bridgeHub.addTokenAssetId(assetId); } } - function test_setSharedBridge(address randomAddress) public { - vm.assume(randomAddress != address(0)); - assertTrue( - bridgeHub.sharedBridge() == IL1SharedBridge(address(0)), - "This random address is not registered as sharedBridge" - ); + function test_setAddresses(address randomAssetRouter, address randomCTMDeployer, address randomMessageRoot) public { + assertTrue(bridgeHub.sharedBridge() == address(0), "Shared bridge is already there"); + assertTrue(bridgeHub.l1CtmDeployer() == ICTMDeploymentTracker(address(0)), "L1 CTM deployer is already there"); + assertTrue(bridgeHub.messageRoot() == IMessageRoot(address(0)), "Message root is already there"); vm.prank(bridgeOwner); - bridgeHub.setSharedBridge(randomAddress); + bridgeHub.setAddresses( + randomAssetRouter, + ICTMDeploymentTracker(randomCTMDeployer), + IMessageRoot(randomMessageRoot) + ); + assertTrue(bridgeHub.sharedBridge() == randomAssetRouter, "Shared bridge is already there"); assertTrue( - bridgeHub.sharedBridge() == IL1SharedBridge(randomAddress), - "after call from the bridgeowner, this randomAddress should be the registered sharedBridge" + bridgeHub.l1CtmDeployer() == ICTMDeploymentTracker(randomCTMDeployer), + "L1 CTM deployer is already there" ); + assertTrue(bridgeHub.messageRoot() == IMessageRoot(randomMessageRoot), "Message root is already there"); } - function test_setSharedBridge_cannotBeCalledByRandomAddress(address randomCaller, address randomAddress) public { - vm.assume(randomAddress != address(0)); - if (randomCaller != bridgeOwner) { - vm.prank(randomCaller); - vm.expectRevert(bytes("Ownable: caller is not the owner")); - bridgeHub.setSharedBridge(randomAddress); - } + function test_setAddresses_cannotBeCalledByRandomAddress( + address randomCaller, + address randomAssetRouter, + address randomCTMDeployer, + address randomMessageRoot + ) public { + vm.assume(randomCaller != bridgeOwner); - assertTrue( - bridgeHub.sharedBridge() == IL1SharedBridge(address(0)), - "This random address is not registered as sharedBridge" + vm.prank(randomCaller); + vm.expectRevert(bytes("Ownable: caller is not the owner")); + bridgeHub.setAddresses( + randomAssetRouter, + ICTMDeploymentTracker(randomCTMDeployer), + IMessageRoot(randomMessageRoot) ); - vm.prank(bridgeOwner); - bridgeHub.setSharedBridge(randomAddress); - - assertTrue( - bridgeHub.sharedBridge() == IL1SharedBridge(randomAddress), - "after call from the bridgeowner, this randomAddress should be the registered sharedBridge" - ); + assertTrue(bridgeHub.sharedBridge() == address(0), "Shared bridge is already there"); + assertTrue(bridgeHub.l1CtmDeployer() == ICTMDeploymentTracker(address(0)), "L1 CTM deployer is already there"); + assertTrue(bridgeHub.messageRoot() == IMessageRoot(address(0)), "Message root is already there"); } uint256 newChainId; @@ -420,7 +538,8 @@ contract ExperimentalBridgeTest is Test { uint256 randomValue ) public useRandomToken(randomValue) { chainId = bound(chainId, 1, type(uint48).max); - address deployerAddress = makeAddr("DEPLOYER_ADDRESS"); + vm.assume(chainId != block.chainid); + admin = makeAddr("NEW_CHAIN_ADMIN"); vm.prank(bridgeOwner); @@ -430,39 +549,49 @@ contract ExperimentalBridgeTest is Test { vm.prank(deployerAddress); bridgeHub.acceptAdmin(); + // ntv.registerToken(address(testToken)); + + // bytes32 tokenAssetId = DataEncoding.encodeNTVAssetId(block.chainid, address(testToken)); + + // vm.prank(deployerAddress); + // bridgehub.addTokenAssetId(tokenAssetId); + vm.expectRevert("Pausable: paused"); vm.prank(deployerAddress); bridgeHub.createNewChain({ _chainId: chainId, - _stateTransitionManager: address(mockSTM), - _baseToken: address(testToken), + _chainTypeManager: address(mockCTM), + _baseTokenAssetId: tokenAssetId, _salt: salt, _admin: admin, - _initData: bytes("") + _initData: bytes(""), + _factoryDeps: new bytes[](0) }); vm.prank(bridgeOwner); bridgeHub.unpause(); - vm.expectRevert(STMNotRegistered.selector); + vm.expectRevert(CTMNotRegistered.selector); vm.prank(deployerAddress); bridgeHub.createNewChain({ - _chainId: 1, - _stateTransitionManager: address(mockSTM), - _baseToken: address(testToken), - _salt: uint256(123), + _chainId: chainId, + _chainTypeManager: address(mockCTM), + _baseTokenAssetId: tokenAssetId, + _salt: salt, _admin: admin, - _initData: bytes("") + _initData: bytes(""), + _factoryDeps: new bytes[](0) }); } - function test_RevertWhen_STMNotRegisteredOnCreate( + function test_RevertWhen_CTMNotRegisteredOnCreate( uint256 chainId, uint256 salt, uint256 randomValue ) public useRandomToken(randomValue) { chainId = bound(chainId, 1, type(uint48).max); - address deployerAddress = makeAddr("DEPLOYER_ADDRESS"); + vm.assume(chainId != block.chainid); + admin = makeAddr("NEW_CHAIN_ADMIN"); vm.prank(bridgeOwner); @@ -471,15 +600,16 @@ contract ExperimentalBridgeTest is Test { bridgeHub.acceptAdmin(); chainId = bound(chainId, 1, type(uint48).max); - vm.expectRevert(STMNotRegistered.selector); + vm.expectRevert(CTMNotRegistered.selector); vm.prank(deployerAddress); bridgeHub.createNewChain({ _chainId: chainId, - _stateTransitionManager: address(mockSTM), - _baseToken: address(testToken), + _chainTypeManager: address(mockCTM), + _baseTokenAssetId: tokenAssetId, _salt: salt, _admin: admin, - _initData: bytes("") + _initData: bytes(""), + _factoryDeps: new bytes[](0) }); } @@ -489,7 +619,8 @@ contract ExperimentalBridgeTest is Test { uint256 randomValue ) public useRandomToken(randomValue) { chainId = bound(chainId, 1, type(uint48).max); - address deployerAddress = makeAddr("DEPLOYER_ADDRESS"); + vm.assume(chainId != block.chainid); + admin = makeAddr("NEW_CHAIN_ADMIN"); vm.prank(bridgeOwner); @@ -502,11 +633,12 @@ contract ExperimentalBridgeTest is Test { vm.prank(deployerAddress); bridgeHub.createNewChain({ _chainId: chainId, - _stateTransitionManager: address(mockSTM), - _baseToken: address(testToken), + _chainTypeManager: address(mockCTM), + _baseTokenAssetId: tokenAssetId, _salt: salt, _admin: admin, - _initData: bytes("") + _initData: bytes(""), + _factoryDeps: new bytes[](0) }); chainId = 0; @@ -514,21 +646,23 @@ contract ExperimentalBridgeTest is Test { vm.prank(deployerAddress); bridgeHub.createNewChain({ _chainId: chainId, - _stateTransitionManager: address(mockSTM), - _baseToken: address(testToken), + _chainTypeManager: address(mockCTM), + _baseTokenAssetId: tokenAssetId, _salt: salt, _admin: admin, - _initData: bytes("") + _initData: bytes(""), + _factoryDeps: new bytes[](0) }); } - function test_RevertWhen_tokenNotRegistered( + function test_RevertWhen_assetIdNotRegistered( uint256 chainId, uint256 salt, uint256 randomValue ) public useRandomToken(randomValue) { chainId = bound(chainId, 1, type(uint48).max); - address deployerAddress = makeAddr("DEPLOYER_ADDRESS"); + vm.assume(chainId != block.chainid); + admin = makeAddr("NEW_CHAIN_ADMIN"); vm.prank(bridgeOwner); @@ -537,18 +671,19 @@ contract ExperimentalBridgeTest is Test { bridgeHub.acceptAdmin(); vm.startPrank(bridgeOwner); - bridgeHub.addStateTransitionManager(address(mockSTM)); + bridgeHub.addChainTypeManager(address(mockCTM)); vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(TokenNotRegistered.selector, address(testToken))); + vm.expectRevert(abi.encodeWithSelector(AssetIdNotSupported.selector, tokenAssetId)); vm.prank(deployerAddress); bridgeHub.createNewChain({ _chainId: chainId, - _stateTransitionManager: address(mockSTM), - _baseToken: address(testToken), + _chainTypeManager: address(mockCTM), + _baseTokenAssetId: tokenAssetId, _salt: salt, _admin: admin, - _initData: bytes("") + _initData: bytes(""), + _factoryDeps: new bytes[](0) }); } @@ -558,7 +693,7 @@ contract ExperimentalBridgeTest is Test { uint256 randomValue ) public useRandomToken(randomValue) { chainId = bound(chainId, 1, type(uint48).max); - address deployerAddress = makeAddr("DEPLOYER_ADDRESS"); + vm.assume(chainId != block.chainid); admin = makeAddr("NEW_CHAIN_ADMIN"); vm.prank(bridgeOwner); @@ -567,19 +702,20 @@ contract ExperimentalBridgeTest is Test { bridgeHub.acceptAdmin(); vm.startPrank(bridgeOwner); - bridgeHub.addStateTransitionManager(address(mockSTM)); - bridgeHub.addToken(address(testToken)); + bridgeHub.addChainTypeManager(address(mockCTM)); + bridgeHub.addTokenAssetId(tokenAssetId); vm.stopPrank(); vm.expectRevert(SharedBridgeNotSet.selector); vm.prank(deployerAddress); bridgeHub.createNewChain({ _chainId: chainId, - _stateTransitionManager: address(mockSTM), - _baseToken: address(testToken), + _chainTypeManager: address(mockCTM), + _baseTokenAssetId: tokenAssetId, _salt: salt, _admin: admin, - _initData: bytes("") + _initData: bytes(""), + _factoryDeps: new bytes[](0) }); } @@ -588,129 +724,97 @@ contract ExperimentalBridgeTest is Test { uint256 salt, uint256 randomValue ) public useRandomToken(randomValue) { - address deployerAddress = makeAddr("DEPLOYER_ADDRESS"); admin = makeAddr("NEW_CHAIN_ADMIN"); - vm.prank(bridgeOwner); - bridgeHub.setPendingAdmin(deployerAddress); - vm.prank(deployerAddress); - bridgeHub.acceptAdmin(); - vm.startPrank(bridgeOwner); - bridgeHub.addStateTransitionManager(address(mockSTM)); - bridgeHub.addToken(address(testToken)); - bridgeHub.setSharedBridge(sharedBridgeAddress); - vm.stopPrank(); + _initializeBridgehub(); chainId = bound(chainId, 1, type(uint48).max); - stdstore.target(address(bridgeHub)).sig("stateTransitionManager(uint256)").with_key(chainId).checked_write( - address(mockSTM) + vm.assume(chainId != block.chainid); + stdstore.target(address(bridgeHub)).sig("chainTypeManager(uint256)").with_key(chainId).checked_write( + address(mockCTM) ); vm.expectRevert(BridgeHubAlreadyRegistered.selector); vm.prank(deployerAddress); bridgeHub.createNewChain({ _chainId: chainId, - _stateTransitionManager: address(mockSTM), - _baseToken: address(testToken), + _chainTypeManager: address(mockCTM), + _baseTokenAssetId: tokenAssetId, _salt: salt, _admin: admin, - _initData: bytes("") + _initData: bytes(""), + _factoryDeps: new bytes[](0) }); } function test_createNewChain( address randomCaller, uint256 chainId, - bool isFreezable, - bytes4[] memory mockSelectors, - address mockInitAddress, bytes memory mockInitCalldata, + bytes[] memory factoryDeps, uint256 salt, - uint256 randomValue + uint256 randomValue, + address newChainAddress ) public useRandomToken(randomValue) { - address deployerAddress = makeAddr("DEPLOYER_ADDRESS"); admin = makeAddr("NEW_CHAIN_ADMIN"); chainId = bound(chainId, 1, type(uint48).max); + vm.assume(chainId != block.chainid); + vm.assume(randomCaller != deployerAddress && randomCaller != bridgeOwner); - vm.prank(bridgeOwner); - bridgeHub.setPendingAdmin(deployerAddress); - vm.prank(deployerAddress); - bridgeHub.acceptAdmin(); - - vm.startPrank(bridgeOwner); - bridgeHub.addStateTransitionManager(address(mockSTM)); - bridgeHub.addToken(address(testToken)); - bridgeHub.setSharedBridge(sharedBridgeAddress); - vm.stopPrank(); + _initializeBridgehub(); - if (randomCaller != deployerAddress && randomCaller != bridgeOwner) { - vm.prank(randomCaller); - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, randomCaller)); - bridgeHub.createNewChain({ - _chainId: chainId, - _stateTransitionManager: address(mockSTM), - _baseToken: address(testToken), - _salt: salt, - _admin: admin, - _initData: bytes("") - }); - } + vm.prank(randomCaller); + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, randomCaller)); + bridgeHub.createNewChain({ + _chainId: chainId, + _chainTypeManager: address(mockCTM), + _baseTokenAssetId: tokenAssetId, + _salt: salt, + _admin: admin, + _initData: bytes(""), + _factoryDeps: factoryDeps + }); - vm.prank(mockSTM.owner()); - bytes memory _newChainInitData = _createNewChainInitData( - isFreezable, - mockSelectors, - mockInitAddress, - mockInitCalldata - ); + vm.prank(mockCTM.owner()); - // 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.setHyperchain(chainId, address(mockChainContract)); - assertTrue(mockSTM.getHyperchain(chainId) == address(mockChainContract)); + // bridgeHub.createNewChain => chainTypeManager.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 mockCTM + mockCTM.setZKChain(chainId, address(mockChainContract)); vm.startPrank(deployerAddress); vm.mockCall( - address(mockSTM), + address(mockCTM), // solhint-disable-next-line func-named-parameters abi.encodeWithSelector( - mockSTM.createNewChain.selector, + mockCTM.createNewChain.selector, chainId, - address(testToken), - sharedBridgeAddress, + tokenAssetId, admin, - _newChainInitData + mockInitCalldata, + factoryDeps ), - bytes("") + abi.encode(newChainAddress) ); vm.expectEmit(true, true, true, true, address(bridgeHub)); - emit NewChain(chainId, address(mockSTM), admin); + emit NewChain(chainId, address(mockCTM), admin); - newChainId = bridgeHub.createNewChain({ + bridgeHub.createNewChain({ _chainId: chainId, - _stateTransitionManager: address(mockSTM), - _baseToken: address(testToken), + _chainTypeManager: address(mockCTM), + _baseTokenAssetId: tokenAssetId, _salt: uint256(chainId * 2), _admin: admin, - _initData: _newChainInitData + _initData: mockInitCalldata, + _factoryDeps: factoryDeps }); vm.stopPrank(); vm.clearMockedCalls(); - assertTrue(bridgeHub.stateTransitionManager(newChainId) == address(mockSTM)); - assertTrue(bridgeHub.baseToken(newChainId) == address(testToken)); - } - - function test_getHyperchain(uint256 mockChainId) public { - mockChainId = _setUpHyperchainForChainId(mockChainId); - - // Now the following statements should be true as well: - assertTrue(bridgeHub.stateTransitionManager(mockChainId) == address(mockSTM)); - address returnedHyperchain = bridgeHub.getHyperchain(mockChainId); - - assertEq(returnedHyperchain, address(mockChainContract)); + assertTrue(bridgeHub.chainTypeManager(chainId) == address(mockCTM)); + assertTrue(bridgeHub.baseTokenAssetId(chainId) == tokenAssetId); + assertTrue(bridgeHub.getZKChain(chainId) == newChainAddress); } function test_proveL2MessageInclusion( @@ -722,11 +826,11 @@ contract ExperimentalBridgeTest is Test { address randomSender, bytes memory randomData ) public { - mockChainId = _setUpHyperchainForChainId(mockChainId); + mockChainId = _setUpZKChainForChainId(mockChainId); // Now the following statements should be true as well: - assertTrue(bridgeHub.stateTransitionManager(mockChainId) == address(mockSTM)); - assertTrue(bridgeHub.getHyperchain(mockChainId) == address(mockChainContract)); + assertTrue(bridgeHub.chainTypeManager(mockChainId) == address(mockCTM)); + assertTrue(bridgeHub.getZKChain(mockChainId) == address(mockChainContract)); // Creating a random L2Message::l2Message so that we pass the correct parameters to `proveL2MessageInclusion` L2Message memory l2Message = _createMockL2Message(randomTxNumInBatch, randomSender, randomData); @@ -770,11 +874,11 @@ contract ExperimentalBridgeTest is Test { bytes32 randomKey, bytes32 randomValue ) public { - mockChainId = _setUpHyperchainForChainId(mockChainId); + mockChainId = _setUpZKChainForChainId(mockChainId); // Now the following statements should be true as well: - assertTrue(bridgeHub.stateTransitionManager(mockChainId) == address(mockSTM)); - assertTrue(bridgeHub.getHyperchain(mockChainId) == address(mockChainContract)); + assertTrue(bridgeHub.chainTypeManager(mockChainId) == address(mockCTM)); + assertTrue(bridgeHub.getZKChain(mockChainId) == address(mockChainContract)); // Creating a random L2Log::l2Log so that we pass the correct parameters to `proveL2LogInclusion` L2Log memory l2Log = _createMockL2Log({ @@ -823,7 +927,7 @@ contract ExperimentalBridgeTest is Test { bool randomResultantBool, bool txStatusBool ) public { - randomChainId = _setUpHyperchainForChainId(randomChainId); + randomChainId = _setUpZKChainForChainId(randomChainId); TxStatus txStatus; @@ -868,7 +972,7 @@ contract ExperimentalBridgeTest is Test { uint256 mockL2GasPerPubdataByteLimit, uint256 mockL2TxnCost ) public { - mockChainId = _setUpHyperchainForChainId(mockChainId); + mockChainId = _setUpZKChainForChainId(mockChainId); vm.mockCall( address(mockChainContract), @@ -899,10 +1003,8 @@ contract ExperimentalBridgeTest is Test { uint256 mockL2GasPerPubdataByteLimit, bytes[] memory mockFactoryDeps, address randomCaller - ) internal returns (L2TransactionRequestDirect memory l2TxnReqDirect) { - if (mockFactoryDeps.length > MAX_NEW_FACTORY_DEPS) { - mockFactoryDeps = _restrictArraySize(mockFactoryDeps, MAX_NEW_FACTORY_DEPS); - } + ) internal returns (L2TransactionRequestDirect memory l2TxnReqDirect, bytes32 canonicalHash) { + vm.assume(mockFactoryDeps.length <= MAX_NEW_FACTORY_DEPS); l2TxnReqDirect = _createMockL2TransactionRequestDirect({ mockChainId: mockChainId, @@ -916,17 +1018,17 @@ contract ExperimentalBridgeTest is Test { mockRefundRecipient: address(0) }); - l2TxnReqDirect.chainId = _setUpHyperchainForChainId(l2TxnReqDirect.chainId); + l2TxnReqDirect.chainId = _setUpZKChainForChainId(l2TxnReqDirect.chainId); - assertTrue(!(bridgeHub.baseToken(l2TxnReqDirect.chainId) == ETH_TOKEN_ADDRESS)); + assertTrue(bridgeHub.baseTokenAssetId(l2TxnReqDirect.chainId) != ETH_TOKEN_ASSET_ID); _setUpBaseTokenForChainId(l2TxnReqDirect.chainId, true, address(0)); + + assertTrue(bridgeHub.baseTokenAssetId(l2TxnReqDirect.chainId) == ETH_TOKEN_ASSET_ID); + console.log(IL1AssetRouter(bridgeHub.sharedBridge()).assetHandlerAddress(ETH_TOKEN_ASSET_ID)); assertTrue(bridgeHub.baseToken(l2TxnReqDirect.chainId) == ETH_TOKEN_ADDRESS); - _setUpSharedBridge(); - _setUpSharedBridgeL2(mockChainId); - - assertTrue(bridgeHub.getHyperchain(l2TxnReqDirect.chainId) == address(mockChainContract)); - bytes32 canonicalHash = keccak256(abi.encode("CANONICAL_TX_HASH")); + assertTrue(bridgeHub.getZKChain(l2TxnReqDirect.chainId) == address(mockChainContract)); + canonicalHash = keccak256(abi.encode("CANONICAL_TX_HASH")); vm.mockCall( address(mockChainContract), @@ -951,10 +1053,13 @@ contract ExperimentalBridgeTest is Test { uint256 mockL2GasPerPubdataByteLimit, bytes[] memory mockFactoryDeps ) public { + _useMockSharedBridge(); + _initializeBridgehub(); + address randomCaller = makeAddr("RANDOM_CALLER"); vm.assume(msgValue != mockMintValue); - L2TransactionRequestDirect memory l2TxnReqDirect = _prepareETHL2TransactionDirectRequest({ + (L2TransactionRequestDirect memory l2TxnReqDirect, bytes32 hash) = _prepareETHL2TransactionDirectRequest({ mockChainId: mockChainId, mockMintValue: mockMintValue, mockL2Contract: mockL2Contract, @@ -983,10 +1088,13 @@ contract ExperimentalBridgeTest is Test { bytes[] memory mockFactoryDeps, uint256 gasPrice ) public { + _useMockSharedBridge(); + _initializeBridgehub(); + address randomCaller = makeAddr("RANDOM_CALLER"); mockChainId = bound(mockChainId, 1, type(uint48).max); - L2TransactionRequestDirect memory l2TxnReqDirect = _prepareETHL2TransactionDirectRequest({ + (L2TransactionRequestDirect memory l2TxnReqDirect, bytes32 hash) = _prepareETHL2TransactionDirectRequest({ mockChainId: mockChainId, mockMintValue: mockMintValue, mockL2Contract: mockL2Contract, @@ -1002,10 +1110,9 @@ contract ExperimentalBridgeTest is Test { gasPrice = bound(gasPrice, 1_000, 50_000_000); vm.txGasPrice(gasPrice * 1 gwei); vm.prank(randomCaller); - bytes32 canonicalHash = keccak256(abi.encode("CANONICAL_TX_HASH")); bytes32 resultantHash = bridgeHub.requestL2TransactionDirect{value: randomCaller.balance}(l2TxnReqDirect); - assertTrue(resultantHash == canonicalHash); + assertTrue(resultantHash == hash); } function test_requestL2TransactionDirect_NonETHCase( @@ -1020,12 +1127,14 @@ contract ExperimentalBridgeTest is Test { uint256 gasPrice, uint256 randomValue ) public useRandomToken(randomValue) { + _useFullSharedBridge(); + _initializeBridgehub(); + address randomCaller = makeAddr("RANDOM_CALLER"); mockChainId = bound(mockChainId, 1, type(uint48).max); - if (mockFactoryDeps.length > MAX_NEW_FACTORY_DEPS) { - mockFactoryDeps = _restrictArraySize(mockFactoryDeps, MAX_NEW_FACTORY_DEPS); - } + vm.assume(mockFactoryDeps.length <= MAX_NEW_FACTORY_DEPS); + vm.assume(mockMintValue > 0); L2TransactionRequestDirect memory l2TxnReqDirect = _createMockL2TransactionRequestDirect({ mockChainId: mockChainId, @@ -1039,13 +1148,11 @@ contract ExperimentalBridgeTest is Test { mockRefundRecipient: address(0) }); - l2TxnReqDirect.chainId = _setUpHyperchainForChainId(l2TxnReqDirect.chainId); + l2TxnReqDirect.chainId = _setUpZKChainForChainId(l2TxnReqDirect.chainId); _setUpBaseTokenForChainId(l2TxnReqDirect.chainId, false, address(testToken)); - _setUpSharedBridge(); - _setUpSharedBridgeL2(mockChainId); - assertTrue(bridgeHub.getHyperchain(l2TxnReqDirect.chainId) == address(mockChainContract)); + assertTrue(bridgeHub.getZKChain(l2TxnReqDirect.chainId) == address(mockChainContract)); bytes32 canonicalHash = keccak256(abi.encode("CANONICAL_TX_HASH")); vm.mockCall( @@ -1092,6 +1199,11 @@ contract ExperimentalBridgeTest is Test { bytes memory secondBridgeCalldata, bytes32 magicValue ) public { + _useMockSharedBridge(); + _initializeBridgehub(); + + vm.assume(magicValue != TWO_BRIDGES_MAGIC_VALUE); + chainId = bound(chainId, 1, type(uint48).max); L2TransactionRequestTwoBridgesOuter memory l2TxnReq2BridgeOut = _createMockL2TransactionRequestTwoBridgesOuter({ @@ -1105,39 +1217,34 @@ contract ExperimentalBridgeTest is Test { secondBridgeCalldata: secondBridgeCalldata }); - l2TxnReq2BridgeOut.chainId = _setUpHyperchainForChainId(l2TxnReq2BridgeOut.chainId); + l2TxnReq2BridgeOut.chainId = _setUpZKChainForChainId(l2TxnReq2BridgeOut.chainId); _setUpBaseTokenForChainId(l2TxnReq2BridgeOut.chainId, true, address(0)); assertTrue(bridgeHub.baseToken(l2TxnReq2BridgeOut.chainId) == ETH_TOKEN_ADDRESS); - _setUpSharedBridge(); - _setUpSharedBridgeL2(chainId); - - assertTrue(bridgeHub.getHyperchain(l2TxnReq2BridgeOut.chainId) == address(mockChainContract)); + assertTrue(bridgeHub.getZKChain(l2TxnReq2BridgeOut.chainId) == address(mockChainContract)); uint256 callerMsgValue = l2TxnReq2BridgeOut.mintValue + l2TxnReq2BridgeOut.secondBridgeValue; address randomCaller = makeAddr("RANDOM_CALLER"); vm.deal(randomCaller, callerMsgValue); - if (magicValue != TWO_BRIDGES_MAGIC_VALUE) { - L2TransactionRequestTwoBridgesInner memory request = L2TransactionRequestTwoBridgesInner({ - magicValue: magicValue, - l2Contract: makeAddr("L2_CONTRACT"), - l2Calldata: new bytes(0), - factoryDeps: new bytes[](0), - txDataHash: bytes32(0) - }); + L2TransactionRequestTwoBridgesInner memory request = L2TransactionRequestTwoBridgesInner({ + magicValue: magicValue, + l2Contract: makeAddr("L2_CONTRACT"), + l2Calldata: new bytes(0), + factoryDeps: new bytes[](0), + txDataHash: bytes32(0) + }); - vm.mockCall( - secondBridgeAddress, - abi.encodeWithSelector(IL1SharedBridge.bridgehubDeposit.selector), - abi.encode(request) - ); + vm.mockCall( + secondBridgeAddress, + abi.encodeWithSelector(IL1AssetRouter.bridgehubDeposit.selector), + abi.encode(request) + ); - vm.expectRevert(abi.encodeWithSelector(WrongMagicValue.selector, TWO_BRIDGES_MAGIC_VALUE, magicValue)); - vm.prank(randomCaller); - bridgeHub.requestL2TransactionTwoBridges{value: randomCaller.balance}(l2TxnReq2BridgeOut); - } + vm.expectRevert(abi.encodeWithSelector(WrongMagicValue.selector, TWO_BRIDGES_MAGIC_VALUE, magicValue)); + vm.prank(randomCaller); + bridgeHub.requestL2TransactionTwoBridges{value: randomCaller.balance}(l2TxnReq2BridgeOut); } function test_requestL2TransactionTwoBridgesWrongBridgeAddress( @@ -1152,6 +1259,9 @@ contract ExperimentalBridgeTest is Test { uint160 secondBridgeAddressValue, bytes memory secondBridgeCalldata ) public { + _useMockSharedBridge(); + _initializeBridgehub(); + chainId = bound(chainId, 1, type(uint48).max); L2TransactionRequestTwoBridgesOuter memory l2TxnReq2BridgeOut = _createMockL2TransactionRequestTwoBridgesOuter({ @@ -1165,15 +1275,12 @@ contract ExperimentalBridgeTest is Test { secondBridgeCalldata: secondBridgeCalldata }); - l2TxnReq2BridgeOut.chainId = _setUpHyperchainForChainId(l2TxnReq2BridgeOut.chainId); + l2TxnReq2BridgeOut.chainId = _setUpZKChainForChainId(l2TxnReq2BridgeOut.chainId); _setUpBaseTokenForChainId(l2TxnReq2BridgeOut.chainId, true, address(0)); assertTrue(bridgeHub.baseToken(l2TxnReq2BridgeOut.chainId) == ETH_TOKEN_ADDRESS); - _setUpSharedBridge(); - _setUpSharedBridgeL2(chainId); - - assertTrue(bridgeHub.getHyperchain(l2TxnReq2BridgeOut.chainId) == address(mockChainContract)); + assertTrue(bridgeHub.getZKChain(l2TxnReq2BridgeOut.chainId) == address(mockChainContract)); uint256 callerMsgValue = l2TxnReq2BridgeOut.mintValue + l2TxnReq2BridgeOut.secondBridgeValue; address randomCaller = makeAddr("RANDOM_CALLER"); @@ -1203,7 +1310,7 @@ contract ExperimentalBridgeTest is Test { address(secondBridgeAddressValue), l2TxnReq2BridgeOut.secondBridgeValue, abi.encodeWithSelector( - IL1SharedBridge.bridgehubDeposit.selector, + IL1AssetRouter.bridgehubDeposit.selector, l2TxnReq2BridgeOut.chainId, randomCaller, l2TxnReq2BridgeOut.l2Value, @@ -1213,7 +1320,13 @@ contract ExperimentalBridgeTest is Test { ); l2TxnReq2BridgeOut.secondBridgeAddress = address(secondBridgeAddressValue); - vm.expectRevert(abi.encodeWithSelector(AddressTooLow.selector, secondBridgeAddress)); + vm.expectRevert( + abi.encodeWithSelector( + SecondBridgeAddressTooLow.selector, + secondBridgeAddress, + BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS + ) + ); vm.prank(randomCaller); bridgeHub.requestL2TransactionTwoBridges{value: randomCaller.balance}(l2TxnReq2BridgeOut); } @@ -1227,13 +1340,17 @@ contract ExperimentalBridgeTest is Test { address l2Receiver, uint256 randomValue ) public useRandomToken(randomValue) { + _useFullSharedBridge(); + _initializeBridgehub(); + vm.assume(mintValue > 0); + // create another token, to avoid base token TestnetERC20Token erc20Token = new TestnetERC20Token("ZKESTT", "ZkSync ERC Test Token", 18); address erc20TokenAddress = address(erc20Token); l2Value = bound(l2Value, 1, type(uint256).max); bytes memory secondBridgeCalldata = abi.encode(erc20TokenAddress, l2Value, l2Receiver); - chainId = _setUpHyperchainForChainId(chainId); + chainId = _setUpZKChainForChainId(chainId); L2TransactionRequestTwoBridgesOuter memory l2TxnReq2BridgeOut = _createMockL2TransactionRequestTwoBridgesOuter({ chainId: chainId, @@ -1251,10 +1368,7 @@ contract ExperimentalBridgeTest is Test { _setUpBaseTokenForChainId(l2TxnReq2BridgeOut.chainId, false, address(testToken)); assertTrue(bridgeHub.baseToken(l2TxnReq2BridgeOut.chainId) == address(testToken)); - _setUpSharedBridge(); - - _setUpSharedBridgeL2(chainId); - assertTrue(bridgeHub.getHyperchain(l2TxnReq2BridgeOut.chainId) == address(mockChainContract)); + assertTrue(bridgeHub.getZKChain(l2TxnReq2BridgeOut.chainId) == address(mockChainContract)); mockChainContract.setBridgeHubAddress(address(bridgeHub)); vm.mockCall( @@ -1277,10 +1391,10 @@ contract ExperimentalBridgeTest is Test { bytes32 resultHash = bridgeHub.requestL2TransactionTwoBridges(l2TxnReq2BridgeOut); assertEq(resultHash, canonicalHash); - assert(erc20Token.balanceOf(randomCaller) == 0); - assert(testToken.balanceOf(randomCaller) == 0); - assert(erc20Token.balanceOf(secondBridgeAddress) == l2Value); - assert(testToken.balanceOf(sharedBridgeAddress) == l2TxnReq2BridgeOut.mintValue); + assertEq(erc20Token.balanceOf(randomCaller), 0); + assertEq(testToken.balanceOf(randomCaller), 0); + assertEq(erc20Token.balanceOf(address(ntv)), l2Value); + assertEq(testToken.balanceOf(address(ntv)), l2TxnReq2BridgeOut.mintValue); l2TxnReq2BridgeOut.secondBridgeValue = 1; testToken.mint(randomCaller, l2TxnReq2BridgeOut.mintValue); @@ -1295,7 +1409,6 @@ contract ExperimentalBridgeTest is Test { uint256 chainId, uint256 mintValue, uint256 msgValue, - uint256 l2Value, uint256 l2GasLimit, uint256 l2GasPerPubdataByteLimit, address refundRecipient, @@ -1303,15 +1416,19 @@ contract ExperimentalBridgeTest is Test { address l2Receiver, uint256 randomValue ) public useRandomToken(randomValue) { + _useFullSharedBridge(); + _initializeBridgehub(); + vm.assume(mintValue > 0); + secondBridgeValue = bound(secondBridgeValue, 1, type(uint256).max); bytes memory secondBridgeCalldata = abi.encode(ETH_TOKEN_ADDRESS, 0, l2Receiver); - chainId = _setUpHyperchainForChainId(chainId); + chainId = _setUpZKChainForChainId(chainId); L2TransactionRequestTwoBridgesOuter memory l2TxnReq2BridgeOut = _createMockL2TransactionRequestTwoBridgesOuter({ chainId: chainId, mintValue: mintValue, - l2Value: l2Value, + l2Value: 0, l2GasLimit: l2GasLimit, l2GasPerPubdataByteLimit: l2GasPerPubdataByteLimit, refundRecipient: refundRecipient, @@ -1321,10 +1438,7 @@ contract ExperimentalBridgeTest is Test { _setUpBaseTokenForChainId(l2TxnReq2BridgeOut.chainId, false, address(testToken)); assertTrue(bridgeHub.baseToken(l2TxnReq2BridgeOut.chainId) == address(testToken)); - - _setUpSharedBridge(); - _setUpSharedBridgeL2(chainId); - assertTrue(bridgeHub.getHyperchain(l2TxnReq2BridgeOut.chainId) == address(mockChainContract)); + assertTrue(bridgeHub.getZKChain(l2TxnReq2BridgeOut.chainId) == address(mockChainContract)); address randomCaller = makeAddr("RANDOM_CALLER"); @@ -1460,56 +1574,45 @@ contract ExperimentalBridgeTest is Test { genesisUpgrade: address(0x01), genesisBatchHash: bytes32(uint256(0x01)), genesisIndexRepeatedStorageChanges: uint64(0x01), - genesisBatchCommitment: bytes32(uint256(0x01)) + genesisBatchCommitment: bytes32(uint256(0x01)), + forceDeploymentsData: bytes("") }); - mockSTM.setChainCreationParams(params); + mockCTM.setChainCreationParams(params); - return abi.encode(diamondCutData); + return abi.encode(abi.encode(diamondCutData), bytes("")); } - function _setUpHyperchainForChainId(uint256 mockChainId) internal returns (uint256 mockChainIdInRange) { + function _setUpZKChainForChainId(uint256 mockChainId) internal returns (uint256 mockChainIdInRange) { mockChainId = bound(mockChainId, 1, 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))); + if (!bridgeHub.chainTypeManagerIsRegistered(address(mockCTM))) { + vm.prank(bridgeOwner); + bridgeHub.addChainTypeManager(address(mockCTM)); + } - stdstore.target(address(bridgeHub)).sig("stateTransitionManager(uint256)").with_key(mockChainId).checked_write( - address(mockSTM) - ); + // We need to set the chainTypeManager of the mockChainId to mockCTM + // There is no function to do that in the bridgeHub + // So, perhaps we will have to manually set the values in the chainTypeManager mapping via a foundry cheatcode + assertTrue(!(bridgeHub.chainTypeManager(mockChainId) == address(mockCTM))); - // Now in the StateTransitionManager that has been set for our mockChainId, we set the hyperchain contract as our mockChainContract - mockSTM.setHyperchain(mockChainId, address(mockChainContract)); + dummyBridgehub.setCTM(mockChainId, address(mockCTM)); + dummyBridgehub.setZKChain(mockChainId, address(mockChainContract)); } function _setUpBaseTokenForChainId(uint256 mockChainId, bool tokenIsETH, address token) internal { - address baseToken = tokenIsETH ? ETH_TOKEN_ADDRESS : token; - - stdstore.target(address(bridgeHub)).sig("baseToken(uint256)").with_key(mockChainId).checked_write(baseToken); - } - - function _setUpSharedBridge() internal { - vm.prank(bridgeOwner); - bridgeHub.setSharedBridge(sharedBridgeAddress); - } - - function _setUpSharedBridgeL2(uint256 _chainId) internal { - _chainId = bound(_chainId, 1, type(uint48).max); - - vm.prank(bridgeOwner); - sharedBridge.initializeChainGovernance(_chainId, mockL2Contract); + if (tokenIsETH) { + token = ETH_TOKEN_ADDRESS; + } else { + ntv.registerToken(token); + } - assertEq(sharedBridge.l2BridgeAddress(_chainId), mockL2Contract); + bytes32 baseTokenAssetId = DataEncoding.encodeNTVAssetId(block.chainid, token); - vm.prank(bridgeOwner); - secondBridge.initializeChainGovernance(_chainId, mockL2Contract); - - assertEq(secondBridge.l2BridgeAddress(_chainId), mockL2Contract); + stdstore.target(address(bridgeHub)).sig("baseTokenAssetId(uint256)").with_key(mockChainId).checked_write( + baseTokenAssetId + ); } function _createMockL2TransactionRequestDirect( @@ -1581,7 +1684,7 @@ contract ExperimentalBridgeTest is Test { bytes memory randomData ) public { vm.startPrank(bridgeOwner); - bridgeHub.addStateTransitionManager(address(mockSTM)); + bridgeHub.addChainTypeManager(address(mockCTM)); vm.stopPrank(); L2Message memory l2Message = _createMockL2Message(randomTxNumInBatch, randomSender, randomData); @@ -1624,7 +1727,7 @@ contract ExperimentalBridgeTest is Test { bytes32 randomValue ) public { vm.startPrank(bridgeOwner); - bridgeHub.addStateTransitionManager(address(mockSTM)); + bridgeHub.addChainTypeManager(address(mockCTM)); vm.stopPrank(); L2Log memory l2Log = _createMockL2Log({ @@ -1671,7 +1774,7 @@ contract ExperimentalBridgeTest is Test { bool randomResultantBool ) public { vm.startPrank(bridgeOwner); - bridgeHub.addStateTransitionManager(address(mockSTM)); + bridgeHub.addChainTypeManager(address(mockCTM)); vm.stopPrank(); TxStatus txStatus; diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1Erc20Bridge/ClaimFailedDeposit.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1Erc20Bridge/ClaimFailedDeposit.t.sol new file mode 100644 index 000000000..aecde91f8 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1Erc20Bridge/ClaimFailedDeposit.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {L1Erc20BridgeTest} from "./_L1Erc20Bridge_Shared.t.sol"; +import {StdStorage, stdStorage} from "forge-std/Test.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {EmptyDeposit} from "contracts/common/L1ContractErrors.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(EmptyDeposit.selector); + bytes32[] memory merkleProof; + + bridge.claimFailedDeposit({ + _depositSender: randomSigner, + _l1Token: address(token), + _l2TxHash: bytes32(""), + _l2BatchNumber: 0, + _l2MessageIndex: 0, + _l2TxNumberInBatch: 0, + _merkleProof: merkleProof + }); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Deposit.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1Erc20Bridge/Deposit.t.sol similarity index 80% rename from l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Deposit.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1Erc20Bridge/Deposit.t.sol index ee679d0e1..63e47dc0e 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Deposit.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1Erc20Bridge/Deposit.t.sol @@ -2,9 +2,10 @@ pragma solidity 0.8.24; +import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; import {L1Erc20BridgeTest} from "./_L1Erc20Bridge_Shared.t.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; import {EmptyDeposit, ValueMismatch, TokensWithFeesNotSupported} from "contracts/common/L1ContractErrors.sol"; -import {IL1SharedBridge} from "contracts/bridge/interfaces/IL1SharedBridge.sol"; contract DepositTest is L1Erc20BridgeTest { event DepositInitiated( @@ -84,6 +85,11 @@ contract DepositTest is L1Erc20BridgeTest { function test_RevertWhen_depositTransferAmountIsDifferent() public { uint256 amount = 2; + vm.mockCall( + address(feeOnTransferToken), + abi.encodeWithSelector(IERC20.balanceOf.selector), + abi.encode(amount + 1) + ); vm.prank(alice); feeOnTransferToken.approve(address(bridge), amount); vm.expectRevert(TokensWithFeesNotSupported.selector); @@ -99,6 +105,11 @@ contract DepositTest is L1Erc20BridgeTest { function test_RevertWhen_legacyDepositTransferAmountIsDifferent() public { uint256 amount = 4; + vm.mockCall( + address(feeOnTransferToken), + abi.encodeWithSelector(IERC20.balanceOf.selector), + abi.encode(amount + 1) + ); vm.prank(alice); feeOnTransferToken.approve(address(bridge), amount); vm.expectRevert(TokensWithFeesNotSupported.selector); @@ -114,22 +125,6 @@ contract DepositTest is L1Erc20BridgeTest { function test_depositSuccessfully() public { uint256 amount = 8; - bytes32 l2TxHash = keccak256("txHash"); - - vm.mockCall( - sharedBridgeAddress, - abi.encodeWithSelector( - IL1SharedBridge.depositLegacyErc20Bridge.selector, - alice, - randomSigner, - address(token), - amount, - 0, - 0, - address(0) - ), - abi.encode(l2TxHash) - ); vm.prank(alice); token.approve(address(bridge), amount); @@ -137,7 +132,7 @@ contract DepositTest is L1Erc20BridgeTest { // solhint-disable-next-line func-named-parameters vm.expectEmit(true, true, true, true, address(bridge)); // solhint-disable-next-line func-named-parameters - emit DepositInitiated(l2TxHash, alice, randomSigner, address(token), amount); + emit DepositInitiated(dummyL2DepositTxHash, alice, randomSigner, address(token), amount); bytes32 txHash = bridge.deposit({ _l2Receiver: randomSigner, _l1Token: address(token), @@ -146,41 +141,25 @@ contract DepositTest is L1Erc20BridgeTest { _l2TxGasPerPubdataByte: 0, _refundRecipient: address(0) }); - assertEq(txHash, l2TxHash); + assertEq(txHash, dummyL2DepositTxHash); - uint256 depositedAmount = bridge.depositAmount(alice, address(token), l2TxHash); + uint256 depositedAmount = bridge.depositAmount(alice, address(token), dummyL2DepositTxHash); assertEq(amount, depositedAmount); } function test_legacyDepositSuccessfully() public { uint256 amount = 8; - bytes32 l2TxHash = keccak256("txHash"); - uint256 depositedAmountBefore = bridge.depositAmount(alice, address(token), l2TxHash); + uint256 depositedAmountBefore = bridge.depositAmount(alice, address(token), dummyL2DepositTxHash); assertEq(depositedAmountBefore, 0); - vm.mockCall( - sharedBridgeAddress, - abi.encodeWithSelector( - IL1SharedBridge.depositLegacyErc20Bridge.selector, - alice, - randomSigner, - address(token), - amount, - 0, - 0, - address(0) - ), - abi.encode(l2TxHash) - ); - vm.prank(alice); token.approve(address(bridge), amount); vm.prank(alice); // solhint-disable-next-line func-named-parameters vm.expectEmit(true, true, true, true, address(bridge)); // solhint-disable-next-line func-named-parameters - emit DepositInitiated(l2TxHash, alice, randomSigner, address(token), amount); + emit DepositInitiated(dummyL2DepositTxHash, alice, randomSigner, address(token), amount); bytes32 txHash = bridge.deposit({ _l2Receiver: randomSigner, _l1Token: address(token), @@ -188,9 +167,9 @@ contract DepositTest is L1Erc20BridgeTest { _l2TxGasLimit: 0, _l2TxGasPerPubdataByte: 0 }); - assertEq(txHash, l2TxHash); + assertEq(txHash, dummyL2DepositTxHash); - uint256 depositedAmount = bridge.depositAmount(alice, address(token), l2TxHash); + 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/l1/unit/concrete/Bridges/L1Erc20Bridge/FinalizeWithdrawal.sol similarity index 64% rename from l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/FinalizeWithdrawal.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1Erc20Bridge/FinalizeWithdrawal.sol index 59cc80323..e5a86bc2d 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/FinalizeWithdrawal.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1Erc20Bridge/FinalizeWithdrawal.sol @@ -3,9 +3,12 @@ pragma solidity 0.8.24; import {L1Erc20BridgeTest} from "./_L1Erc20Bridge_Shared.t.sol"; -import {IL1SharedBridge} from "contracts/bridge/interfaces/IL1SharedBridge.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; import {StdStorage, stdStorage} from "forge-std/Test.sol"; import {WithdrawalAlreadyFinalized} from "contracts/common/L1ContractErrors.sol"; +import {IL1Nullifier} from "contracts/bridge/L1Nullifier.sol"; +import {FinalizeL1DepositParams} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {L2_ASSET_ROUTER_ADDR} from "contracts/common/L2ContractAddresses.sol"; contract FinalizeWithdrawalTest is L1Erc20BridgeTest { using stdStorage for StdStorage; @@ -43,24 +46,31 @@ contract FinalizeWithdrawalTest is L1Erc20BridgeTest { uint256 amount = 999; assertFalse(bridge.isWithdrawalFinalized(l2BatchNumber, l2MessageIndex)); - + FinalizeL1DepositParams memory finalizeWithdrawalParams = FinalizeL1DepositParams({ + chainId: eraChainId, + l2BatchNumber: l2BatchNumber, + l2MessageIndex: l2MessageIndex, + l2Sender: L2_ASSET_ROUTER_ADDR, + l2TxNumberInBatch: uint16(txNumberInBatch), + message: "", + merkleProof: merkleProof + }); vm.mockCall( - sharedBridgeAddress, - abi.encodeWithSelector( - IL1SharedBridge.finalizeWithdrawalLegacyErc20Bridge.selector, - l2BatchNumber, - l2MessageIndex, - txNumberInBatch, - "", - merkleProof - ), + l1NullifierAddress, + abi.encodeWithSelector(IL1Nullifier.finalizeDeposit.selector, finalizeWithdrawalParams), abi.encode(alice, address(token), amount) ); + address l2BridgeAddress = address(12); + vm.mockCall( + l1NullifierAddress, + abi.encodeWithSelector(IL1Nullifier.l2BridgeAddress.selector, eraChainId), + abi.encode(l2BridgeAddress) + ); vm.prank(alice); // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(bridge)); - emit WithdrawalFinalized(alice, address(token), amount); + // vm.expectEmit(true, true, true, true, address(bridge)); + // emit WithdrawalFinalized(alice, address(token), amount); bridge.finalizeWithdrawal({ _l2BatchNumber: l2BatchNumber, diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Getters.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1Erc20Bridge/Getters.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Getters.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1Erc20Bridge/Getters.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Initialization.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1Erc20Bridge/Initialization.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Initialization.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1Erc20Bridge/Initialization.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Reentrancy.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1Erc20Bridge/Reentrancy.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Reentrancy.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1Erc20Bridge/Reentrancy.t.sol diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1Erc20Bridge/_L1Erc20Bridge_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1Erc20Bridge/_L1Erc20Bridge_Shared.t.sol new file mode 100644 index 000000000..60f6bef4c --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1Erc20Bridge/_L1Erc20Bridge_Shared.t.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.21; + +import {StdStorage, stdStorage} from "forge-std/Test.sol"; +import {Test} from "forge-std/Test.sol"; + +import {L1ERC20Bridge} from "contracts/bridge/L1ERC20Bridge.sol"; +import {L1NativeTokenVault} from "contracts/bridge/ntv/L1NativeTokenVault.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {TestnetERC20Token} from "contracts/dev-contracts/TestnetERC20Token.sol"; +import {FeeOnTransferToken} from "contracts/dev-contracts/FeeOnTransferToken.sol"; +import {ReenterL1ERC20Bridge} from "contracts/dev-contracts/test/ReenterL1ERC20Bridge.sol"; +import {DummySharedBridge} from "contracts/dev-contracts/test/DummySharedBridge.sol"; +import {Utils} from "../../Utils/Utils.sol"; +import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; +import {IL1NativeTokenVault} from "contracts/bridge/ntv/IL1NativeTokenVault.sol"; +import {IL1Nullifier} from "contracts/bridge/L1Nullifier.sol"; + +contract L1Erc20BridgeTest is Test { + L1ERC20Bridge internal bridge; + + ReenterL1ERC20Bridge internal reenterL1ERC20Bridge; + L1ERC20Bridge internal bridgeReenterItself; + + TestnetERC20Token internal token; + TestnetERC20Token internal feeOnTransferToken; + address internal randomSigner; + address internal alice; + address sharedBridgeAddress; + address l1NullifierAddress; + bytes32 internal dummyL2DepositTxHash; + uint256 eraChainId = 9; + + constructor() { + randomSigner = makeAddr("randomSigner"); + dummyL2DepositTxHash = Utils.randomBytes32("dummyL2DepositTxHash"); + sharedBridgeAddress = address(new DummySharedBridge(dummyL2DepositTxHash)); + alice = makeAddr("alice"); + l1NullifierAddress = makeAddr("l1NullifierAddress"); + + bridge = new L1ERC20Bridge( + IL1Nullifier(l1NullifierAddress), + IL1AssetRouter(sharedBridgeAddress), + IL1NativeTokenVault(address(1)), + eraChainId + ); + + address weth = makeAddr("weth"); + L1NativeTokenVault ntv = new L1NativeTokenVault(weth, sharedBridgeAddress, IL1Nullifier(l1NullifierAddress)); + + vm.store(address(bridge), bytes32(uint256(212)), bytes32(0)); + + reenterL1ERC20Bridge = new ReenterL1ERC20Bridge(); + bridgeReenterItself = new L1ERC20Bridge( + IL1Nullifier(address(reenterL1ERC20Bridge)), + IL1AssetRouter(address(reenterL1ERC20Bridge)), + ntv, + eraChainId + ); + 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/L1SharedBridgeBase.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeBase.t.sol similarity index 51% rename from l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeBase.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeBase.t.sol index c16ab2898..e253d28ec 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeBase.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeBase.t.sol @@ -1,86 +1,122 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -import {L1SharedBridgeTest} from "./_L1SharedBridge_Shared.t.sol"; +import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; + +import {L1AssetRouterTest} from "./_L1SharedBridge_Shared.t.sol"; import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; -import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {IBridgehub, L2TransactionRequestTwoBridgesInner} from "contracts/bridgehub/IBridgehub.sol"; import {L2Message, TxStatus} from "contracts/common/Messaging.sol"; import {IMailbox} from "contracts/state-transition/chain-interfaces/IMailbox.sol"; -import {IL1ERC20Bridge} from "contracts/bridge/interfaces/IL1ERC20Bridge.sol"; -import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IAssetRouterBase, LEGACY_ENCODING_VERSION} from "contracts/bridge/asset-router/IAssetRouterBase.sol"; +import {IL1AssetHandler} from "contracts/bridge/interfaces/IL1AssetHandler.sol"; +import {IL1BaseTokenAssetHandler} from "contracts/bridge/interfaces/IL1BaseTokenAssetHandler.sol"; +import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, L2_ASSET_ROUTER_ADDR} from "contracts/common/L2ContractAddresses.sol"; import {IGetters} from "contracts/state-transition/chain-interfaces/IGetters.sol"; +import {L1NativeTokenVault} from "contracts/bridge/ntv/L1NativeTokenVault.sol"; +import {StdStorage, stdStorage} from "forge-std/Test.sol"; +import {DepositNotSet} from "test/foundry/L1TestsErrors.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; + +contract L1AssetRouterTestBase is L1AssetRouterTest { + using stdStorage for StdStorage; + + function test_bridgehubPause() public { + vm.prank(owner); + sharedBridge.pause(); + assertEq(sharedBridge.paused(), true, "Shared Bridge Not Paused"); + } + + function test_bridgehubUnpause() public { + vm.prank(owner); + sharedBridge.pause(); + assertEq(sharedBridge.paused(), true, "Shared Bridge Not Paused"); + vm.prank(owner); + sharedBridge.unpause(); + assertEq(sharedBridge.paused(), false, "Shared Bridge Remains Paused"); + } -contract L1SharedBridgeTestBase is L1SharedBridgeTest { function test_bridgehubDepositBaseToken_Eth() public { - vm.deal(bridgehubAddress, amount); vm.prank(bridgehubAddress); // solhint-disable-next-line func-named-parameters 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); + emit BridgehubDepositBaseTokenInitiated(chainId, alice, ETH_TOKEN_ASSET_ID, amount); + sharedBridge.bridgehubDepositBaseToken{value: amount}(chainId, ETH_TOKEN_ASSET_ID, alice, amount); } function test_bridgehubDepositBaseToken_Erc() public { - token.mint(alice, amount); + vm.prank(bridgehubAddress); + // solhint-disable-next-line func-named-parameters + vm.expectEmit(true, true, true, true, address(sharedBridge)); + emit BridgehubDepositBaseTokenInitiated(chainId, alice, tokenAssetId, amount); + sharedBridge.bridgehubDepositBaseToken(chainId, tokenAssetId, alice, amount); + } + + function test_bridgehubDepositBaseToken_Erc_NoApproval() public { vm.prank(alice); - token.approve(address(sharedBridge), amount); + token.approve(address(nativeTokenVault), 0); vm.prank(bridgehubAddress); // solhint-disable-next-line func-named-parameters vm.expectEmit(true, true, true, true, address(sharedBridge)); - emit BridgehubDepositBaseTokenInitiated(chainId, alice, address(token), amount); - sharedBridge.bridgehubDepositBaseToken(chainId, alice, address(token), amount); + emit BridgehubDepositBaseTokenInitiated(chainId, alice, tokenAssetId, amount); + sharedBridge.bridgehubDepositBaseToken(chainId, tokenAssetId, alice, 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)) - ); + _setBaseTokenAssetId(tokenAssetId); + bytes32 txDataHash = keccak256(abi.encode(alice, ETH_TOKEN_ADDRESS, amount)); + bytes memory mintCalldata = abi.encode( + alice, + bob, + address(ETH_TOKEN_ADDRESS), + amount, + nativeTokenVault.getERC20Getters(address(ETH_TOKEN_ADDRESS), chainId) + ); // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); + vm.expectEmit(true, true, true, false, address(sharedBridge)); + vm.prank(bridgehubAddress); emit BridgehubDepositInitiated({ chainId: chainId, txDataHash: txDataHash, from: alice, - to: zkSync, - l1Token: ETH_TOKEN_ADDRESS, - amount: amount + assetId: ETH_TOKEN_ASSET_ID, + bridgeMintCalldata: mintCalldata }); - sharedBridge.bridgehubDeposit{value: amount}(chainId, alice, 0, abi.encode(ETH_TOKEN_ADDRESS, 0, bob)); + sharedBridge.bridgehubDeposit{value: amount}(chainId, alice, 0, abi.encode(ETH_TOKEN_ADDRESS, amount, bob)); } function test_bridgehubDeposit_Erc() public { - token.mint(alice, amount); - vm.prank(alice); - token.approve(address(sharedBridge), amount); vm.prank(bridgehubAddress); // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - vm.mockCall( - bridgehubAddress, - abi.encodeWithSelector(IBridgehub.baseToken.selector), - abi.encode(ETH_TOKEN_ADDRESS) - ); + vm.expectEmit(true, true, true, false, address(sharedBridge)); bytes32 txDataHash = keccak256(abi.encode(alice, address(token), amount)); emit BridgehubDepositInitiated({ chainId: chainId, txDataHash: txDataHash, from: alice, - to: zkSync, - l1Token: address(token), - amount: amount + assetId: tokenAssetId, + bridgeMintCalldata: abi.encode(amount, bob) }); sharedBridge.bridgehubDeposit(chainId, alice, 0, abi.encode(address(token), amount, bob)); } + function test_bridgehubDeposit_Erc_CustomAssetHandler() public { + // ToDo: remove the mock call and register custom asset handler? + vm.mockCall( + address(nativeTokenVault), + abi.encodeWithSelector(IL1BaseTokenAssetHandler.tokenAddress.selector, tokenAssetId), + abi.encode(address(token)) + ); + vm.prank(bridgehubAddress); + sharedBridge.bridgehubDeposit(chainId, alice, 0, abi.encode(address(token), amount, bob)); + } + function test_bridgehubConfirmL2Transaction() public { // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); + vm.expectEmit(true, true, true, false, address(l1Nullifier)); bytes32 txDataHash = keccak256(abi.encode(alice, address(token), amount)); emit BridgehubDepositFinalized(chainId, txDataHash, txHash); vm.prank(bridgehubAddress); @@ -88,11 +124,9 @@ contract L1SharedBridgeTestBase is L1SharedBridgeTest { } function test_claimFailedDeposit_Erc() public { - token.mint(address(sharedBridge), amount); bytes32 txDataHash = keccak256(abi.encode(alice, address(token), amount)); _setSharedBridgeDepositHappened(chainId, txHash, txDataHash); - require(sharedBridge.depositHappened(chainId, txHash) == txDataHash, "Deposit not set"); - _setSharedBridgeChainBalance(chainId, address(token), amount); + require(l1Nullifier.depositHappened(chainId, txHash) == txDataHash, "Deposit not set"); vm.mockCall( bridgehubAddress, @@ -111,9 +145,13 @@ contract L1SharedBridgeTestBase is L1SharedBridgeTest { ); // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - emit ClaimedFailedDepositSharedBridge({chainId: chainId, to: alice, l1Token: address(token), amount: amount}); - sharedBridge.claimFailedDeposit({ + vm.expectEmit(true, true, true, false, address(sharedBridge)); + emit ClaimedFailedDepositAssetRouter({ + chainId: chainId, + assetId: tokenAssetId, + assetData: abi.encode(bytes32(0)) + }); + l1Nullifier.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, _l1Token: address(token), @@ -127,12 +165,9 @@ contract L1SharedBridgeTestBase is L1SharedBridgeTest { } function test_claimFailedDeposit_Eth() public { - vm.deal(address(sharedBridge), amount); - bytes32 txDataHash = keccak256(abi.encode(alice, ETH_TOKEN_ADDRESS, amount)); _setSharedBridgeDepositHappened(chainId, txHash, txDataHash); - require(sharedBridge.depositHappened(chainId, txHash) == txDataHash, "Deposit not set"); - _setSharedBridgeChainBalance(chainId, ETH_TOKEN_ADDRESS, amount); + require(l1Nullifier.depositHappened(chainId, txHash) == txDataHash, "Deposit not set"); vm.mockCall( bridgehubAddress, @@ -151,14 +186,13 @@ contract L1SharedBridgeTestBase is L1SharedBridgeTest { ); // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - emit ClaimedFailedDepositSharedBridge({ + vm.expectEmit(true, true, true, false, address(sharedBridge)); + emit ClaimedFailedDepositAssetRouter({ chainId: chainId, - to: alice, - l1Token: ETH_TOKEN_ADDRESS, - amount: amount + assetId: ETH_TOKEN_ASSET_ID, + assetData: abi.encode(bytes32(0)) }); - sharedBridge.claimFailedDeposit({ + l1Nullifier.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, _l1Token: ETH_TOKEN_ADDRESS, @@ -171,16 +205,49 @@ contract L1SharedBridgeTestBase is L1SharedBridgeTest { }); } - function test_finalizeWithdrawal_EthOnEth() public { - vm.deal(address(sharedBridge), amount); + function test_bridgeRecoverFailedTransfer_Eth() public { + bytes memory transferData = abi.encode(amount, alice, ETH_TOKEN_ADDRESS); + bytes32 txDataHash = keccak256(abi.encode(alice, ETH_TOKEN_ADDRESS, amount)); + _setSharedBridgeDepositHappened(chainId, txHash, txDataHash); + require(l1Nullifier.depositHappened(chainId, txHash) == txDataHash, "Deposit not set"); - _setSharedBridgeChainBalance(chainId, ETH_TOKEN_ADDRESS, amount); vm.mockCall( bridgehubAddress, - abi.encodeWithSelector(IBridgehub.baseToken.selector), - abi.encode(ETH_TOKEN_ADDRESS) + // solhint-disable-next-line func-named-parameters + abi.encodeWithSelector( + IBridgehub.proveL1ToL2TransactionStatus.selector, + chainId, + txHash, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + merkleProof, + TxStatus.Failure + ), + abi.encode(true) ); + // solhint-disable-next-line func-named-parameters + vm.expectEmit(true, true, true, false, address(sharedBridge)); + emit ClaimedFailedDepositAssetRouter({ + chainId: chainId, + assetId: ETH_TOKEN_ASSET_ID, + assetData: abi.encode(bytes32(0)) + }); + l1Nullifier.bridgeRecoverFailedTransfer({ + _chainId: chainId, + _depositSender: alice, + _assetId: ETH_TOKEN_ASSET_ID, + _assetData: transferData, + _l2TxHash: txHash, + _l2BatchNumber: l2BatchNumber, + _l2MessageIndex: l2MessageIndex, + _l2TxNumberInBatch: l2TxNumberInBatch, + _merkleProof: merkleProof + }); + } + + function test_finalizeWithdrawal_EthOnEth() public { bytes memory message = abi.encodePacked(IMailbox.finalizeEthWithdrawal.selector, alice, amount); L2Message memory l2ToL1Message = L2Message({ txNumberInBatch: l2TxNumberInBatch, @@ -203,8 +270,8 @@ contract L1SharedBridgeTestBase is L1SharedBridgeTest { ); // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - emit WithdrawalFinalizedSharedBridge(chainId, alice, ETH_TOKEN_ADDRESS, amount); + vm.expectEmit(true, true, true, false, address(sharedBridge)); + emit DepositFinalizedAssetRouter(chainId, ETH_TOKEN_ASSET_ID, message); sharedBridge.finalizeWithdrawal({ _chainId: chainId, _l2BatchNumber: l2BatchNumber, @@ -216,44 +283,32 @@ contract L1SharedBridgeTestBase is L1SharedBridgeTest { } function test_finalizeWithdrawal_ErcOnEth() public { - token.mint(address(sharedBridge), amount); - - _setSharedBridgeChainBalance(chainId, address(token), amount); - vm.mockCall( - bridgehubAddress, - abi.encodeWithSelector(IBridgehub.baseToken.selector), - abi.encode(ETH_TOKEN_ADDRESS) - ); - + _setNativeTokenVaultChainBalance(chainId, address(token), amount); bytes memory message = abi.encodePacked( - IL1ERC20Bridge.finalizeWithdrawal.selector, - alice, - address(token), - amount + IAssetRouterBase.finalizeDeposit.selector, + chainId, + tokenAssetId, + abi.encode(0, alice, 0, amount, new bytes(0)) ); L2Message memory l2ToL1Message = L2Message({ txNumberInBatch: l2TxNumberInBatch, - sender: l2SharedBridge, + sender: l2LegacySharedBridgeAddr, data: message }); vm.mockCall( bridgehubAddress, // solhint-disable-next-line func-named-parameters - abi.encodeWithSelector( - IBridgehub.proveL2MessageInclusion.selector, - chainId, - l2BatchNumber, - l2MessageIndex, - l2ToL1Message, - merkleProof + abi.encodeCall( + IBridgehub.proveL2MessageInclusion, + (chainId, l2BatchNumber, l2MessageIndex, l2ToL1Message, merkleProof) ), abi.encode(true) ); // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - emit WithdrawalFinalizedSharedBridge(chainId, alice, address(token), amount); + vm.expectEmit(true, true, true, false, address(sharedBridge)); + emit DepositFinalizedAssetRouter(chainId, tokenAssetId, message); sharedBridge.finalizeWithdrawal({ _chainId: chainId, _l2BatchNumber: l2BatchNumber, @@ -265,24 +320,21 @@ contract L1SharedBridgeTestBase is L1SharedBridgeTest { } function test_finalizeWithdrawal_EthOnErc() public { - vm.deal(address(sharedBridge), amount); + // vm.deal(address(sharedBridge), amount); - _setSharedBridgeChainBalance(chainId, ETH_TOKEN_ADDRESS, amount); - vm.mockCall( - bridgehubAddress, - abi.encodeWithSelector(IBridgehub.baseToken.selector), - abi.encode(address(token)) - ); + // _setNativeTokenVaultChainBalance(chainId, ETH_TOKEN_ADDRESS, amount); + _setBaseTokenAssetId(tokenAssetId); + vm.prank(bridgehubAddress); bytes memory message = abi.encodePacked( - IL1ERC20Bridge.finalizeWithdrawal.selector, - alice, - ETH_TOKEN_ADDRESS, - amount + IAssetRouterBase.finalizeDeposit.selector, + chainId, + ETH_TOKEN_ASSET_ID, + abi.encode(0, alice, 0, amount, new bytes(0)) ); L2Message memory l2ToL1Message = L2Message({ txNumberInBatch: l2TxNumberInBatch, - sender: l2SharedBridge, + sender: l2LegacySharedBridgeAddr, data: message }); @@ -301,8 +353,8 @@ contract L1SharedBridgeTestBase is L1SharedBridgeTest { ); // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - emit WithdrawalFinalizedSharedBridge(chainId, alice, ETH_TOKEN_ADDRESS, amount); + vm.expectEmit(true, true, true, false, address(sharedBridge)); + emit DepositFinalizedAssetRouter(chainId, ETH_TOKEN_ASSET_ID, message); sharedBridge.finalizeWithdrawal({ _chainId: chainId, _l2BatchNumber: l2BatchNumber, @@ -314,20 +366,14 @@ contract L1SharedBridgeTestBase is L1SharedBridgeTest { } function test_finalizeWithdrawal_BaseErcOnErc() public { - token.mint(address(sharedBridge), amount); - - _setSharedBridgeChainBalance(chainId, address(token), amount); - vm.mockCall( - bridgehubAddress, - abi.encodeWithSelector(IBridgehub.baseToken.selector), - abi.encode(address(token)) - ); + _setBaseTokenAssetId(tokenAssetId); + vm.prank(bridgehubAddress); bytes memory message = abi.encodePacked( - IL1ERC20Bridge.finalizeWithdrawal.selector, - alice, - address(token), - amount + IAssetRouterBase.finalizeDeposit.selector, + chainId, + tokenAssetId, + abi.encode(0, alice, 0, amount, new bytes(0)) ); L2Message memory l2ToL1Message = L2Message({ txNumberInBatch: l2TxNumberInBatch, @@ -340,18 +386,18 @@ contract L1SharedBridgeTestBase is L1SharedBridgeTest { // solhint-disable-next-line func-named-parameters abi.encodeWithSelector( IBridgehub.proveL2MessageInclusion.selector, - chainId, - l2BatchNumber, - l2MessageIndex, - l2ToL1Message, - merkleProof + chainId + // l2BatchNumber, + // l2MessageIndex, + // l2ToL1Message, + // merkleProof ), abi.encode(true) ); // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - emit WithdrawalFinalizedSharedBridge(chainId, alice, address(token), amount); + vm.expectEmit(true, true, true, false, address(sharedBridge)); + emit DepositFinalizedAssetRouter(chainId, tokenAssetId, abi.encode(amount, alice)); sharedBridge.finalizeWithdrawal({ _chainId: chainId, _l2BatchNumber: l2BatchNumber, @@ -363,20 +409,21 @@ contract L1SharedBridgeTestBase is L1SharedBridgeTest { } function test_finalizeWithdrawal_NonBaseErcOnErc() public { - token.mint(address(sharedBridge), amount); - - _setSharedBridgeChainBalance(chainId, address(token), amount); - bytes memory message = abi.encodePacked( - IL1ERC20Bridge.finalizeWithdrawal.selector, - alice, - address(token), - amount + IAssetRouterBase.finalizeDeposit.selector, + chainId, + tokenAssetId, + abi.encode(0, alice, 0, amount, new bytes(0)) ); - vm.mockCall(bridgehubAddress, abi.encodeWithSelector(IBridgehub.baseToken.selector), abi.encode(address(2))); //alt base token + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseTokenAssetId.selector), + abi.encode(bytes32(uint256(2))) + ); + //alt base token L2Message memory l2ToL1Message = L2Message({ txNumberInBatch: l2TxNumberInBatch, - sender: l2SharedBridge, + sender: l2LegacySharedBridgeAddr, data: message }); @@ -395,8 +442,8 @@ contract L1SharedBridgeTestBase is L1SharedBridgeTest { ); // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - emit WithdrawalFinalizedSharedBridge(chainId, alice, address(token), amount); + vm.expectEmit(true, true, true, false, address(sharedBridge)); + emit DepositFinalizedAssetRouter(chainId, tokenAssetId, message); sharedBridge.finalizeWithdrawal({ _chainId: chainId, _l2BatchNumber: l2BatchNumber, @@ -407,60 +454,53 @@ contract L1SharedBridgeTestBase is L1SharedBridgeTest { }); } - 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) - ); + function test_safeTransferFundsFromSharedBridge_Erc() public { + bytes32 assetId = DataEncoding.encodeNTVAssetId(block.chainid, address(token)); + uint256 startBalanceNtv = nativeTokenVault.chainBalance(chainId, assetId); + // solhint-disable-next-line func-named-parameters + vm.expectEmit(true, true, false, true, address(token)); + emit IERC20.Transfer(address(l1Nullifier), address(nativeTokenVault), amount); + nativeTokenVault.transferFundsFromSharedBridge(address(token)); + nativeTokenVault.updateChainBalancesFromSharedBridge(address(token), chainId); + uint256 endBalanceNtv = nativeTokenVault.chainBalance(chainId, assetId); + assertEq(endBalanceNtv - startBalanceNtv, amount); + } - vm.mockCall( - eraDiamondProxy, - abi.encodeWithSelector(IGetters.isEthWithdrawalFinalized.selector), - abi.encode(false) - ); + function test_safeTransferFundsFromSharedBridge_Eth() public { + uint256 startEthBalanceNtv = address(nativeTokenVault).balance; + uint256 startBalanceNtv = nativeTokenVault.chainBalance(chainId, ETH_TOKEN_ASSET_ID); + nativeTokenVault.transferFundsFromSharedBridge(ETH_TOKEN_ADDRESS); + nativeTokenVault.updateChainBalancesFromSharedBridge(ETH_TOKEN_ADDRESS, chainId); + uint256 endBalanceNtv = nativeTokenVault.chainBalance(chainId, ETH_TOKEN_ASSET_ID); + uint256 endEthBalanceNtv = address(nativeTokenVault).balance; + assertEq(endBalanceNtv - startBalanceNtv, amount); + assertEq(endEthBalanceNtv - startEthBalanceNtv, amount); + } - _setSharedBridgeChainBalance(eraChainId, ETH_TOKEN_ADDRESS, amount); + function test_bridgehubDeposit_Eth_storesCorrectTxHash() public { + _setBaseTokenAssetId(tokenAssetId); + vm.prank(bridgehubAddress); vm.mockCall( bridgehubAddress, - abi.encodeWithSelector(IBridgehub.baseToken.selector), - abi.encode(ETH_TOKEN_ADDRESS) + abi.encodeWithSelector(IBridgehub.baseTokenAssetId.selector), + abi.encode(tokenAssetId) ); - - 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, - // solhint-disable-next-line func-named-parameters - abi.encodeWithSelector( - IBridgehub.proveL2MessageInclusion.selector, - eraChainId, - legacyBatchNumber, - l2MessageIndex, - l2ToL1Message, - merkleProof - ), - abi.encode(true) + // solhint-disable-next-line func-named-parameters + L2TransactionRequestTwoBridgesInner memory request = sharedBridge.bridgehubDeposit{value: amount}( + chainId, + alice, + 0, + abi.encode(ETH_TOKEN_ADDRESS, 0, bob) ); - // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - emit WithdrawalFinalizedSharedBridge(eraChainId, alice, ETH_TOKEN_ADDRESS, amount); - sharedBridge.finalizeWithdrawal({ - _chainId: eraChainId, - _l2BatchNumber: legacyBatchNumber, - _l2MessageIndex: l2MessageIndex, - _l2TxNumberInBatch: l2TxNumberInBatch, - _message: message, - _merkleProof: merkleProof + bytes32 expectedTxHash = DataEncoding.encodeTxDataHash({ + _nativeTokenVault: address(nativeTokenVault), + _encodingVersion: LEGACY_ENCODING_VERSION, + _originalCaller: alice, + _assetId: nativeTokenVault.BASE_TOKEN_ASSET_ID(), + _transferData: abi.encode(amount, bob, ETH_TOKEN_ADDRESS) }); + + assertEq(request.txDataHash, expectedTxHash); } } diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol similarity index 52% rename from l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol index 63eb02aca..5c892cccb 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol @@ -1,38 +1,154 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -import {L1SharedBridgeTest} from "./_L1SharedBridge_Shared.t.sol"; +import "forge-std/console.sol"; + +import {L1AssetRouterTest} from "./_L1SharedBridge_Shared.t.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import {L1SharedBridge} from "contracts/bridge/L1SharedBridge.sol"; +import {SET_ASSET_HANDLER_COUNTERPART_ENCODING_VERSION} from "contracts/bridge/asset-router/IAssetRouterBase.sol"; +import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol"; +import {L1NativeTokenVault} from "contracts/bridge/ntv/L1NativeTokenVault.sol"; import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; import {L2Message, TxStatus} from "contracts/common/Messaging.sol"; import {IMailbox} from "contracts/state-transition/chain-interfaces/IMailbox.sol"; import {IL1ERC20Bridge} from "contracts/bridge/interfaces/IL1ERC20Bridge.sol"; +import {IL1NativeTokenVault} from "contracts/bridge/ntv/IL1NativeTokenVault.sol"; +import {INativeTokenVault} from "contracts/bridge/ntv/INativeTokenVault.sol"; +import {L1NativeTokenVault} from "contracts/bridge/ntv/L1NativeTokenVault.sol"; import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR} from "contracts/common/L2ContractAddresses.sol"; import {IGetters} from "contracts/state-transition/chain-interfaces/IGetters.sol"; -import {L2BridgeNotSet, L2WithdrawalMessageWrongLength, InsufficientChainBalance, ZeroAddress, ValueMismatch, NonEmptyMsgValue, DepositExists, ValueMismatch, NonEmptyMsgValue, TokenNotSupported, EmptyDeposit, L2BridgeNotDeployed, DepositIncorrectAmount, InvalidProof, NoFundsTransferred, InsufficientFunds, DepositDoesNotExist, WithdrawalAlreadyFinalized, InsufficientFunds, MalformedMessage, InvalidSelector, TokensWithFeesNotSupported} from "contracts/common/L1ContractErrors.sol"; +import {BurningNativeWETHNotSupported, AddressAlreadyUsed, WithdrawFailed, Unauthorized, AssetIdNotSupported, SharedBridgeKey, SharedBridgeValueNotSet, L2WithdrawalMessageWrongLength, InsufficientChainBalance, ZeroAddress, ValueMismatch, NonEmptyMsgValue, DepositExists, ValueMismatch, NonEmptyMsgValue, TokenNotSupported, EmptyDeposit, InvalidProof, NoFundsTransferred, DepositDoesNotExist, WithdrawalAlreadyFinalized, InvalidSelector, TokensWithFeesNotSupported} from "contracts/common/L1ContractErrors.sol"; +import {StdStorage, stdStorage} from "forge-std/Test.sol"; +import {DepositNotSet} from "test/foundry/L1TestsErrors.sol"; +import {WrongCounterpart, EthTransferFailed, EmptyToken, NativeTokenVaultAlreadySet, ZeroAmountToTransfer, WrongAmountTransferred, ClaimFailedDepositFailed} from "contracts/bridge/L1BridgeContractErrors.sol"; /// We are testing all the specified revert and require cases. -contract L1SharedBridgeFailTest is L1SharedBridgeTest { +contract L1AssetRouterFailTest is L1AssetRouterTest { + using stdStorage for StdStorage; + function test_initialize_wrongOwner() public { vm.expectRevert(ZeroAddress.selector); new TransparentUpgradeableProxy( address(sharedBridgeImpl), proxyAdmin, // solhint-disable-next-line func-named-parameters - abi.encodeWithSelector(L1SharedBridge.initialize.selector, address(0), eraPostUpgradeFirstBatch) + abi.encodeWithSelector( + L1AssetRouter.initialize.selector, + address(0), + eraPostUpgradeFirstBatch, + eraPostUpgradeFirstBatch, + 1, + 0 + ) + ); + } + + function test_initialize_wrongOwnerNTV() public { + vm.expectRevert(abi.encodeWithSelector(ZeroAddress.selector)); + new TransparentUpgradeableProxy( + address(nativeTokenVaultImpl), + admin, + // solhint-disable-next-line func-named-parameters + abi.encodeWithSelector(L1NativeTokenVault.initialize.selector, address(0), address(0)) + ); + } + + function test_transferTokenToNTV_wrongCaller() public { + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, address(this))); + l1Nullifier.transferTokenToNTV(address(token)); + } + + function test_nullifyChainBalanceByNTV_wrongCaller() public { + vm.expectRevert(); + l1Nullifier.nullifyChainBalanceByNTV(chainId, address(token)); + } + + function test_registerToken_noCode() public { + vm.expectRevert(abi.encodeWithSelector(EmptyToken.selector)); + nativeTokenVault.registerToken(address(0)); + } + + function test_setL1Erc20Bridge_alreadySet() public { + address currentBridge = address(sharedBridge.legacyBridge()); + vm.prank(owner); + vm.expectRevert(abi.encodeWithSelector(AddressAlreadyUsed.selector, currentBridge)); + sharedBridge.setL1Erc20Bridge(IL1ERC20Bridge(address(0))); + } + + function test_setL1Erc20Bridge_emptyAddressProvided() public { + stdstore.target(address(sharedBridge)).sig(sharedBridge.legacyBridge.selector).checked_write(address(0)); + vm.prank(owner); + vm.expectRevert(abi.encodeWithSelector(ZeroAddress.selector)); + sharedBridge.setL1Erc20Bridge(IL1ERC20Bridge(address(0))); + } + + function test_setNativeTokenVault_alreadySet() public { + vm.prank(owner); + vm.expectRevert(NativeTokenVaultAlreadySet.selector); + sharedBridge.setNativeTokenVault(INativeTokenVault(address(0))); + } + + function test_setNativeTokenVault_emptyAddressProvided() public { + stdstore.target(address(sharedBridge)).sig(sharedBridge.nativeTokenVault.selector).checked_write(address(0)); + vm.prank(owner); + vm.expectRevert(ZeroAddress.selector); + sharedBridge.setNativeTokenVault(INativeTokenVault(address(0))); + } + + function test_setAssetHandlerAddressOnCounterpart_wrongCounterPartAddress() public { + bytes memory data = bytes.concat( + SET_ASSET_HANDLER_COUNTERPART_ENCODING_VERSION, + abi.encode(tokenAssetId, address(token)) ); + + vm.prank(bridgehubAddress); + vm.expectRevert(abi.encodeWithSelector(WrongCounterpart.selector)); + sharedBridge.bridgehubDeposit(eraChainId, owner, 0, data); + } + + function test_transferFundsToSharedBridge_Eth_CallFailed() public { + vm.mockCallRevert(address(nativeTokenVault), "", "eth transfer failed"); + vm.prank(address(nativeTokenVault)); + vm.expectRevert(abi.encodeWithSelector(EthTransferFailed.selector)); + l1Nullifier.transferTokenToNTV(ETH_TOKEN_ADDRESS); + } + + function test_transferFundsToSharedBridge_Eth_0_AmountTransferred() public { + vm.deal(address(l1Nullifier), 0); + vm.prank(address(nativeTokenVault)); + vm.expectRevert(abi.encodeWithSelector(NoFundsTransferred.selector)); + nativeTokenVault.transferFundsFromSharedBridge(ETH_TOKEN_ADDRESS); + } + + function test_transferFundsToSharedBridge_Erc_0_AmountTransferred() public { + vm.prank(address(l1Nullifier)); + token.transfer(address(1), amount); + vm.prank(address(nativeTokenVault)); + vm.expectRevert(ZeroAmountToTransfer.selector); + nativeTokenVault.transferFundsFromSharedBridge(address(token)); + } + + function test_transferFundsToSharedBridge_Erc_WrongAmountTransferred() public { + vm.mockCall(address(token), abi.encodeWithSelector(IERC20.balanceOf.selector), abi.encode(10)); + vm.prank(address(nativeTokenVault)); + vm.expectRevert(abi.encodeWithSelector(WrongAmountTransferred.selector, 0, 10)); + nativeTokenVault.transferFundsFromSharedBridge(address(token)); + } + + function test_bridgehubDepositBaseToken_Eth_Token_incorrectSender() public { + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, address(this))); + sharedBridge.bridgehubDepositBaseToken{value: amount}(chainId, ETH_TOKEN_ASSET_ID, alice, amount); } function test_bridgehubDepositBaseToken_EthwrongMsgValue() public { vm.deal(bridgehubAddress, amount); vm.prank(bridgehubAddress); - vm.expectRevert(abi.encodeWithSelector(ValueMismatch.selector, amount, uint256(0))); - sharedBridge.bridgehubDepositBaseToken(chainId, alice, ETH_TOKEN_ADDRESS, amount); + vm.expectRevert(abi.encodeWithSelector(ValueMismatch.selector, amount, uint256(1))); + sharedBridge.bridgehubDepositBaseToken{value: 1}(chainId, ETH_TOKEN_ASSET_ID, alice, amount); } function test_bridgehubDepositBaseToken_ErcWrongMsgValue() public { @@ -42,39 +158,20 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { token.approve(address(sharedBridge), amount); vm.prank(bridgehubAddress); vm.expectRevert(NonEmptyMsgValue.selector); - sharedBridge.bridgehubDepositBaseToken{value: amount}(chainId, alice, address(token), amount); + sharedBridge.bridgehubDepositBaseToken{value: amount}(chainId, tokenAssetId, alice, amount); } - function test_bridgehubDepositBaseToken_ErcWrongErcDepositAmount() public { - token.mint(alice, amount); - vm.prank(alice); - token.approve(address(sharedBridge), amount); - + function test_bridgehubDepositBaseToken_ercWrongErcDepositAmount() public { vm.mockCall(address(token), abi.encodeWithSelector(IERC20.balanceOf.selector), abi.encode(10)); - vm.expectRevert(TokensWithFeesNotSupported.selector); - vm.prank(bridgehubAddress); - sharedBridge.bridgehubDepositBaseToken(chainId, alice, address(token), amount); - } - - function test_bridgehubDeposit_Eth_l2BridgeNotDeployed() public { - vm.prank(owner); - sharedBridge.reinitializeChainGovernance(chainId, address(0)); - vm.deal(bridgehubAddress, amount); vm.prank(bridgehubAddress); - vm.mockCall( - bridgehubAddress, - abi.encodeWithSelector(IBridgehub.baseToken.selector), - abi.encode(address(token)) - ); - vm.expectRevert(abi.encodeWithSelector(L2BridgeNotSet.selector, chainId)); - // solhint-disable-next-line func-named-parameters - sharedBridge.bridgehubDeposit{value: amount}(chainId, alice, 0, abi.encode(ETH_TOKEN_ADDRESS, 0, bob)); + vm.expectRevert(TokensWithFeesNotSupported.selector); + sharedBridge.bridgehubDepositBaseToken(chainId, tokenAssetId, alice, amount); } function test_bridgehubDeposit_Erc_weth() public { vm.prank(bridgehubAddress); - vm.expectRevert(abi.encodeWithSelector(TokenNotSupported.selector, l1WethAddress)); + vm.expectRevert(BurningNativeWETHNotSupported.selector); // solhint-disable-next-line func-named-parameters sharedBridge.bridgehubDeposit(chainId, alice, 0, abi.encode(l1WethAddress, amount, bob)); } @@ -83,39 +180,33 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { vm.prank(bridgehubAddress); vm.mockCall( bridgehubAddress, - abi.encodeWithSelector(IBridgehub.baseToken.selector), - abi.encode(ETH_TOKEN_ADDRESS) + abi.encodeWithSelector(IBridgehub.baseTokenAssetId.selector), + abi.encode(ETH_TOKEN_ASSET_ID) ); - vm.expectRevert(abi.encodeWithSelector(TokenNotSupported.selector, ETH_TOKEN_ADDRESS)); + vm.expectRevert(abi.encodeWithSelector(AssetIdNotSupported.selector, ETH_TOKEN_ASSET_ID)); // solhint-disable-next-line func-named-parameters 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); + _setBaseTokenAssetId(tokenAssetId); vm.prank(bridgehubAddress); vm.mockCall( bridgehubAddress, - abi.encodeWithSelector(IBridgehub.baseToken.selector), - abi.encode(address(token)) + abi.encodeWithSelector(IBridgehub.baseTokenAssetId.selector), + abi.encode(tokenAssetId) ); - vm.expectRevert(abi.encodeWithSelector(DepositIncorrectAmount.selector, 0, amount)); + vm.expectRevert(abi.encodeWithSelector(ValueMismatch.selector, amount, 0)); // solhint-disable-next-line func-named-parameters 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) + abi.encodeWithSelector(IBridgehub.baseTokenAssetId.selector), + abi.encode(ETH_TOKEN_ASSET_ID) ); vm.expectRevert(NonEmptyMsgValue.selector); // solhint-disable-next-line func-named-parameters @@ -123,22 +214,15 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { } 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)); - vm.expectRevert(abi.encodeWithSelector(DepositIncorrectAmount.selector, 0, amount)); + vm.expectRevert(abi.encodeWithSelector(TokensWithFeesNotSupported.selector)); // solhint-disable-next-line func-named-parameters sharedBridge.bridgehubDeposit(chainId, alice, 0, abi.encode(address(token), amount, bob)); } function test_bridgehubDeposit_Eth() public { + _setBaseTokenAssetId(tokenAssetId); vm.prank(bridgehubAddress); vm.mockCall( bridgehubAddress, @@ -158,6 +242,162 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { sharedBridge.bridgehubConfirmL2Transaction(chainId, txDataHash, txHash); } + function test_finalizeWithdrawal_EthOnEth_withdrawalFailed() public { + vm.deal(address(nativeTokenVault), 0); + 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, + // solhint-disable-next-line func-named-parameters + abi.encodeWithSelector( + IBridgehub.proveL2MessageInclusion.selector, + chainId, + l2BatchNumber, + l2MessageIndex, + l2ToL1Message, + merkleProof + ), + abi.encode(true) + ); + + vm.expectRevert(abi.encodeWithSelector(WithdrawFailed.selector)); + sharedBridge.finalizeWithdrawal({ + _chainId: chainId, + _l2BatchNumber: l2BatchNumber, + _l2MessageIndex: l2MessageIndex, + _l2TxNumberInBatch: l2TxNumberInBatch, + _message: message, + _merkleProof: merkleProof + }); + } + + function test_bridgeRecoverFailedTransfer_Eth_claimFailedDepositFailed() public { + vm.deal(address(nativeTokenVault), 0); + bytes memory transferData = abi.encode(amount, alice, ETH_TOKEN_ADDRESS); + bytes32 txDataHash = keccak256(abi.encode(alice, ETH_TOKEN_ADDRESS, amount)); + _setSharedBridgeDepositHappened(chainId, txHash, txDataHash); + require(l1Nullifier.depositHappened(chainId, txHash) == txDataHash, "Deposit not set"); + + vm.mockCall( + bridgehubAddress, + // solhint-disable-next-line func-named-parameters + abi.encodeWithSelector( + IBridgehub.proveL1ToL2TransactionStatus.selector, + chainId, + txHash, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + merkleProof, + TxStatus.Failure + ), + abi.encode(true) + ); + + vm.expectRevert(ClaimFailedDepositFailed.selector); + l1Nullifier.bridgeRecoverFailedTransfer({ + _chainId: chainId, + _depositSender: alice, + _assetId: ETH_TOKEN_ASSET_ID, + _assetData: transferData, + _l2TxHash: txHash, + _l2BatchNumber: l2BatchNumber, + _l2MessageIndex: l2MessageIndex, + _l2TxNumberInBatch: l2TxNumberInBatch, + _merkleProof: merkleProof + }); + } + + function test_bridgeRecoverFailedTransfer_invalidChainID() public { + vm.store(address(l1Nullifier), bytes32(isWithdrawalFinalizedStorageLocation - 5), bytes32(uint256(0))); + + bytes memory transferData = abi.encode(amount, alice); + bytes32 txDataHash = keccak256(abi.encode(alice, ETH_TOKEN_ADDRESS, amount)); + _setSharedBridgeDepositHappened(chainId, txHash, txDataHash); + require(l1Nullifier.depositHappened(chainId, txHash) == txDataHash, "Deposit not set"); + + vm.mockCall( + bridgehubAddress, + // solhint-disable-next-line func-named-parameters + abi.encodeWithSelector( + IBridgehub.proveL1ToL2TransactionStatus.selector, + eraChainId, + txHash, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + merkleProof, + TxStatus.Failure + ), + abi.encode(true) + ); + + vm.expectRevert( + abi.encodeWithSelector(SharedBridgeValueNotSet.selector, SharedBridgeKey.LegacyBridgeLastDepositBatch) + ); + l1Nullifier.bridgeRecoverFailedTransfer({ + _chainId: eraChainId, + _depositSender: alice, + _assetId: ETH_TOKEN_ASSET_ID, + _assetData: transferData, + _l2TxHash: txHash, + _l2BatchNumber: l2BatchNumber, + _l2MessageIndex: l2MessageIndex, + _l2TxNumberInBatch: l2TxNumberInBatch, + _merkleProof: merkleProof + }); + } + + function test_bridgeRecoverFailedTransfer_eraLegacyDeposit() public { + vm.store(address(l1Nullifier), bytes32(isWithdrawalFinalizedStorageLocation - 5), bytes32(uint256(2))); + + uint256 l2BatchNumber = 0; + bytes memory transferData = abi.encode(amount, alice, ETH_TOKEN_ADDRESS); + bytes32 txDataHash = keccak256(abi.encode(alice, ETH_TOKEN_ADDRESS, amount)); + _setSharedBridgeDepositHappened(eraChainId, txHash, txDataHash); + require(l1Nullifier.depositHappened(eraChainId, txHash) == txDataHash, "Deposit not set"); + console.log("txDataHash", uint256(txDataHash)); + + vm.mockCall( + bridgehubAddress, + // solhint-disable-next-line func-named-parameters + abi.encodeWithSelector( + IBridgehub.proveL1ToL2TransactionStatus.selector, + eraChainId, + txHash, + l2BatchNumber, + l2MessageIndex, + l2TxNumberInBatch, + merkleProof, + TxStatus.Failure + ), + abi.encode(true) + ); + + vm.expectRevert(InsufficientChainBalance.selector); + vm.mockCall( + address(bridgehubAddress), + abi.encodeWithSelector(IBridgehub.proveL1ToL2TransactionStatus.selector), + abi.encode(true) + ); + l1Nullifier.bridgeRecoverFailedTransfer({ + _chainId: eraChainId, + _depositSender: alice, + _assetId: ETH_TOKEN_ASSET_ID, + _assetData: transferData, + _l2TxHash: txHash, + _l2BatchNumber: l2BatchNumber, + _l2MessageIndex: l2MessageIndex, + _l2TxNumberInBatch: l2TxNumberInBatch, + _merkleProof: merkleProof + }); + } + function test_claimFailedDeposit_proofInvalid() public { vm.mockCall( bridgehubAddress, @@ -165,8 +405,8 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { abi.encode(address(0)) ); vm.prank(bridgehubAddress); - vm.expectRevert(InvalidProof.selector); - sharedBridge.claimFailedDeposit({ + vm.expectRevert(abi.encodeWithSelector(InvalidProof.selector)); + l1Nullifier.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, _l1Token: ETH_TOKEN_ADDRESS, @@ -180,8 +420,6 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { } function test_claimFailedDeposit_amountZero() public { - vm.deal(address(sharedBridge), amount); - vm.mockCall( bridgehubAddress, // solhint-disable-next-line func-named-parameters @@ -198,8 +436,10 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { abi.encode(true) ); - vm.expectRevert(NoFundsTransferred.selector); - sharedBridge.claimFailedDeposit({ + bytes32 txDataHash = keccak256(abi.encode(alice, ETH_TOKEN_ADDRESS, 0)); + _setSharedBridgeDepositHappened(chainId, txHash, txDataHash); + vm.expectRevert(abi.encodeWithSelector((NoFundsTransferred.selector))); + l1Nullifier.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, _l1Token: ETH_TOKEN_ADDRESS, @@ -232,7 +472,7 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { ); vm.expectRevert(DepositDoesNotExist.selector); - sharedBridge.claimFailedDeposit({ + l1Nullifier.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, _l1Token: ETH_TOKEN_ADDRESS, @@ -246,11 +486,11 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { } function test_claimFailedDeposit_chainBalanceLow() public { - vm.deal(address(sharedBridge), amount); + _setNativeTokenVaultChainBalance(chainId, ETH_TOKEN_ADDRESS, 0); bytes32 txDataHash = keccak256(abi.encode(alice, ETH_TOKEN_ADDRESS, amount)); _setSharedBridgeDepositHappened(chainId, txHash, txDataHash); - require(sharedBridge.depositHappened(chainId, txHash) == txDataHash, "Deposit not set"); + require(l1Nullifier.depositHappened(chainId, txHash) == txDataHash, "Deposit not set"); vm.mockCall( bridgehubAddress, @@ -269,7 +509,7 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { ); vm.expectRevert(InsufficientChainBalance.selector); - sharedBridge.claimFailedDeposit({ + l1Nullifier.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, _l1Token: ETH_TOKEN_ADDRESS, @@ -282,36 +522,9 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { }); } - 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(WithdrawalAlreadyFinalized.selector); - sharedBridge.finalizeWithdrawal({ - _chainId: eraChainId, - _l2BatchNumber: legacyBatchNumber, - _l2MessageIndex: l2MessageIndex, - _l2TxNumberInBatch: l2TxNumberInBatch, - _message: message, - _merkleProof: merkleProof - }); - } - - function test_finalizeWithdrawal_EthOnEth_LegacyTxFinalizedInSharedBridge() public { + function test_finalizeWithdrawal_EthOnEth_legacyTxFinalizedInSharedBridge() public { vm.deal(address(sharedBridge), amount); + vm.deal(address(nativeTokenVault), amount); uint256 legacyBatchNumber = 0; vm.mockCall( @@ -321,7 +534,7 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { ); vm.store( - address(sharedBridge), + address(l1Nullifier), keccak256( abi.encode( l2MessageIndex, @@ -354,21 +567,32 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { }); } - function test_finalizeWithdrawal_EthOnEth_LegacyTxFinalizedInDiamondProxy() public { - vm.deal(address(sharedBridge), amount); - uint256 legacyBatchNumber = 0; + function test_finalizeWithdrawal_EthOnEth_diamondUpgradeFirstBatchNotSet() public { + vm.store(address(l1Nullifier), bytes32(isWithdrawalFinalizedStorageLocation - 7), bytes32(uint256(0))); + vm.deal(address(l1Nullifier), amount); + vm.deal(address(nativeTokenVault), amount); - vm.mockCall( - l1ERC20BridgeAddress, - abi.encodeWithSelector(IL1ERC20Bridge.isWithdrawalFinalized.selector), - abi.encode(false) + bytes memory message = abi.encodePacked( + IL1ERC20Bridge.finalizeWithdrawal.selector, + alice, + address(token), + amount ); + vm.expectRevert(); - vm.mockCall( - eraDiamondProxy, - abi.encodeWithSelector(IGetters.isEthWithdrawalFinalized.selector), - abi.encode(true) - ); + sharedBridge.finalizeWithdrawal({ + _chainId: eraChainId, + _l2BatchNumber: l2BatchNumber, + _l2MessageIndex: l2MessageIndex, + _l2TxNumberInBatch: l2TxNumberInBatch, + _message: message, + _merkleProof: merkleProof + }); + } + + function test_finalizeWithdrawal_TokenOnEth_legacyTokenWithdrawal() public { + vm.store(address(l1Nullifier), bytes32(isWithdrawalFinalizedStorageLocation - 6), bytes32(uint256(5))); + vm.deal(address(nativeTokenVault), amount); bytes memory message = abi.encodePacked( IL1ERC20Bridge.finalizeWithdrawal.selector, @@ -376,11 +600,11 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { address(token), amount ); + vm.expectRevert(); - vm.expectRevert(WithdrawalAlreadyFinalized.selector); sharedBridge.finalizeWithdrawal({ _chainId: eraChainId, - _l2BatchNumber: legacyBatchNumber, + _l2BatchNumber: l2BatchNumber, _l2MessageIndex: l2MessageIndex, _l2TxNumberInBatch: l2TxNumberInBatch, _message: message, @@ -388,15 +612,33 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { }); } - function test_finalizeWithdrawal_chainBalance() public { - vm.deal(address(sharedBridge), amount); + function test_finalizeWithdrawal_TokenOnEth_legacyUpgradeFirstBatchNotSet() public { + vm.store(address(l1Nullifier), bytes32(isWithdrawalFinalizedStorageLocation - 7), bytes32(uint256(0))); + vm.deal(address(nativeTokenVault), 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 ); + vm.mockCall(bridgehubAddress, abi.encode(IBridgehub.proveL2MessageInclusion.selector), abi.encode(true)); + + vm.expectRevert( + abi.encodeWithSelector(SharedBridgeValueNotSet.selector, SharedBridgeKey.PostUpgradeFirstBatch) + ); + sharedBridge.finalizeWithdrawal({ + _chainId: eraChainId, + _l2BatchNumber: l2BatchNumber, + _l2MessageIndex: l2MessageIndex, + _l2TxNumberInBatch: l2TxNumberInBatch, + _message: message, + _merkleProof: merkleProof + }); + } + + function test_finalizeWithdrawal_chainBalance() public { bytes memory message = abi.encodePacked(IMailbox.finalizeEthWithdrawal.selector, alice, amount); L2Message memory l2ToL1Message = L2Message({ txNumberInBatch: l2TxNumberInBatch, @@ -417,6 +659,7 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { ), abi.encode(true) ); + _setNativeTokenVaultChainBalance(chainId, ETH_TOKEN_ADDRESS, 1); vm.expectRevert(InsufficientChainBalance.selector); sharedBridge.finalizeWithdrawal({ @@ -430,14 +673,6 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { } 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, @@ -470,15 +705,7 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { }); } - function test_parseL2WithdrawalMessage_WrongMsgLength() public { - vm.deal(address(sharedBridge), amount); - - vm.mockCall( - bridgehubAddress, - abi.encodeWithSelector(IBridgehub.baseToken.selector), - abi.encode(ETH_TOKEN_ADDRESS) - ); - + function test_parseL2WithdrawalMessage_wrongMsgLength() public { bytes memory message = abi.encodePacked(IMailbox.finalizeEthWithdrawal.selector); vm.expectRevert(abi.encodeWithSelector(L2WithdrawalMessageWrongLength.selector, message.length)); @@ -506,7 +733,7 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { vm.expectRevert(abi.encodeWithSelector(L2WithdrawalMessageWrongLength.selector, message.length)); sharedBridge.finalizeWithdrawal({ - _chainId: eraChainId, + _chainId: chainId, _l2BatchNumber: l2BatchNumber, _l2MessageIndex: l2MessageIndex, _l2TxNumberInBatch: l2TxNumberInBatch, @@ -515,15 +742,7 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { }); } - function test_parseL2WithdrawalMessage_WrongSelector() public { - vm.deal(address(sharedBridge), amount); - - vm.mockCall( - bridgehubAddress, - abi.encodeWithSelector(IBridgehub.baseToken.selector), - abi.encode(ETH_TOKEN_ADDRESS) - ); - + function test_parseL2WithdrawalMessage_wrongSelector() public { // notice that the selector is wrong bytes memory message = abi.encodePacked(IMailbox.proveL2LogInclusion.selector, alice, amount); @@ -538,27 +757,6 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { }); } - function test_depositLegacyERC20Bridge_l2BridgeNotDeployed() public { - uint256 l2TxGasLimit = 100000; - uint256 l2TxGasPerPubdataByte = 100; - address refundRecipient = address(0); - - vm.prank(owner); - sharedBridge.reinitializeChainGovernance(eraChainId, address(0)); - - vm.expectRevert(abi.encodeWithSelector(L2BridgeNotSet.selector, eraChainId)); - vm.prank(l1ERC20BridgeAddress); - sharedBridge.depositLegacyErc20Bridge({ - _prevMsgSender: alice, - _l2Receiver: bob, - _l1Token: address(token), - _amount: amount, - _l2TxGasLimit: l2TxGasLimit, - _l2TxGasPerPubdataByte: l2TxGasPerPubdataByte, - _refundRecipient: refundRecipient - }); - } - function test_depositLegacyERC20Bridge_weth() public { uint256 l2TxGasLimit = 100000; uint256 l2TxGasPerPubdataByte = 100; @@ -567,7 +765,7 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { vm.expectRevert(abi.encodeWithSelector(TokenNotSupported.selector, l1WethAddress)); vm.prank(l1ERC20BridgeAddress); sharedBridge.depositLegacyErc20Bridge({ - _prevMsgSender: alice, + _originalCaller: alice, _l2Receiver: bob, _l1Token: l1WethAddress, _amount: amount, @@ -601,7 +799,7 @@ contract L1SharedBridgeFailTest is L1SharedBridgeTest { vm.prank(l1ERC20BridgeAddress); sharedBridge.depositLegacyErc20Bridge({ - _prevMsgSender: alice, + _originalCaller: alice, _l2Receiver: bob, _l1Token: address(token), _amount: amount, diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol similarity index 66% rename from l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol index b5e8e5467..232340082 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol @@ -1,27 +1,29 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -import {L1SharedBridgeTest} from "./_L1SharedBridge_Shared.t.sol"; +import {L1AssetRouterTest} from "./_L1SharedBridge_Shared.t.sol"; import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; import {L2Message, TxStatus} from "contracts/common/Messaging.sol"; import {IMailbox} from "contracts/state-transition/chain-interfaces/IMailbox.sol"; -import {IL1ERC20Bridge} from "contracts/bridge/interfaces/IL1ERC20Bridge.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IAssetRouterBase} from "contracts/bridge/asset-router/IAssetRouterBase.sol"; import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {DepositNotSet} from "test/foundry/L1TestsErrors.sol"; // note, this should be the same as where hyper is disabled -contract L1SharedBridgeHyperEnabledTest is L1SharedBridgeTest { +contract L1AssetRouterHyperEnabledTest is L1AssetRouterTest { function test_bridgehubDepositBaseToken_Eth() public { vm.deal(bridgehubAddress, amount); vm.prank(bridgehubAddress); // solhint-disable-next-line func-named-parameters vm.expectEmit(true, true, true, true, address(sharedBridge)); - emit BridgehubDepositBaseTokenInitiated(chainId, alice, ETH_TOKEN_ADDRESS, amount); + emit BridgehubDepositBaseTokenInitiated(chainId, alice, ETH_TOKEN_ASSET_ID, amount); sharedBridge.bridgehubDepositBaseToken{value: amount}({ _chainId: chainId, - _prevMsgSender: alice, - _l1Token: ETH_TOKEN_ADDRESS, + _assetId: ETH_TOKEN_ASSET_ID, + _originalCaller: alice, _amount: amount }); } @@ -33,69 +35,57 @@ contract L1SharedBridgeHyperEnabledTest is L1SharedBridgeTest { vm.prank(bridgehubAddress); // solhint-disable-next-line func-named-parameters vm.expectEmit(true, true, true, true, address(sharedBridge)); - emit BridgehubDepositBaseTokenInitiated(chainId, alice, address(token), amount); + emit BridgehubDepositBaseTokenInitiated(chainId, alice, tokenAssetId, amount); sharedBridge.bridgehubDepositBaseToken({ _chainId: chainId, - _prevMsgSender: alice, - _l1Token: address(token), + _assetId: tokenAssetId, + _originalCaller: alice, _amount: amount }); } function test_bridgehubDeposit_Eth() public { - vm.deal(bridgehubAddress, amount); - vm.prank(bridgehubAddress); + // vm.prank(bridgehubAddress); // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - vm.mockCall( - bridgehubAddress, - abi.encodeWithSelector(IBridgehub.baseToken.selector), - abi.encode(address(token)) - ); + vm.expectEmit(true, true, true, false, address(sharedBridge)); + _setBaseTokenAssetId(tokenAssetId); + vm.prank(bridgehubAddress); bytes32 txDataHash = keccak256(abi.encode(alice, ETH_TOKEN_ADDRESS, amount)); emit BridgehubDepositInitiated({ chainId: chainId, txDataHash: txDataHash, from: alice, - to: zkSync, - l1Token: ETH_TOKEN_ADDRESS, - amount: amount + assetId: ETH_TOKEN_ASSET_ID, + bridgeMintCalldata: abi.encode(0, bob) }); sharedBridge.bridgehubDeposit{value: amount}({ _chainId: chainId, - _prevMsgSender: alice, - _l2Value: 0, - _data: abi.encode(ETH_TOKEN_ADDRESS, 0, bob) + _originalCaller: alice, + _value: 0, + _data: abi.encode(ETH_TOKEN_ADDRESS, amount, bob) }); } function test_bridgehubDeposit_Erc() public { - token.mint(alice, amount); - vm.prank(alice); - token.approve(address(sharedBridge), amount); vm.prank(bridgehubAddress); // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - vm.mockCall( - bridgehubAddress, - abi.encodeWithSelector(IBridgehub.baseToken.selector), - abi.encode(ETH_TOKEN_ADDRESS) - ); + vm.expectEmit(true, true, true, false, address(sharedBridge)); + _setBaseTokenAssetId(ETH_TOKEN_ASSET_ID); + bytes32 txDataHash = keccak256(abi.encode(alice, address(token), amount)); emit BridgehubDepositInitiated({ chainId: chainId, txDataHash: txDataHash, from: alice, - to: zkSync, - l1Token: address(token), - amount: amount + assetId: tokenAssetId, + bridgeMintCalldata: abi.encode(amount, bob) }); sharedBridge.bridgehubDeposit(chainId, alice, 0, abi.encode(address(token), amount, bob)); } function test_bridgehubConfirmL2Transaction() public { // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); + vm.expectEmit(true, true, true, true, address(l1Nullifier)); bytes32 txDataHash = keccak256(abi.encode(alice, address(token), amount)); emit BridgehubDepositFinalized(chainId, txDataHash, txHash); vm.prank(bridgehubAddress); @@ -108,9 +98,9 @@ contract L1SharedBridgeHyperEnabledTest is L1SharedBridgeTest { // storing depositHappened[chainId][l2TxHash] = txDataHash. bytes32 txDataHash = keccak256(abi.encode(alice, address(token), amount)); _setSharedBridgeDepositHappened(chainId, txHash, txDataHash); - require(sharedBridge.depositHappened(chainId, txHash) == txDataHash, "Deposit not set"); + require(l1Nullifier.depositHappened(chainId, txHash) == txDataHash, "Deposit not set"); - _setSharedBridgeChainBalance(chainId, address(token), amount); + _setNativeTokenVaultChainBalance(chainId, address(token), amount); vm.mockCall( bridgehubAddress, @@ -129,10 +119,10 @@ contract L1SharedBridgeHyperEnabledTest is L1SharedBridgeTest { ); // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - emit ClaimedFailedDepositSharedBridge(chainId, alice, address(token), amount); + vm.expectEmit(true, true, true, false, address(sharedBridge)); + emit ClaimedFailedDepositAssetRouter(chainId, tokenAssetId, abi.encode(bytes32(0))); vm.prank(bridgehubAddress); - sharedBridge.claimFailedDeposit({ + l1Nullifier.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, _l1Token: address(token), @@ -146,19 +136,14 @@ contract L1SharedBridgeHyperEnabledTest is L1SharedBridgeTest { } function test_claimFailedDeposit_Eth() public { - vm.deal(address(sharedBridge), amount); - // storing depositHappened[chainId][l2TxHash] = txDataHash. bytes32 txDataHash = keccak256(abi.encode(alice, ETH_TOKEN_ADDRESS, amount)); _setSharedBridgeDepositHappened(chainId, txHash, txDataHash); - require(sharedBridge.depositHappened(chainId, txHash) == txDataHash, "Deposit not set"); - - /// storing chainBalance - _setSharedBridgeChainBalance(chainId, ETH_TOKEN_ADDRESS, amount); + require(l1Nullifier.depositHappened(chainId, txHash) == txDataHash, "Deposit not set"); // Bridgehub bridgehub = new Bridgehub(); // vm.store(address(bridgehub), bytes32(uint256(5 +2)), bytes32(uint256(31337))); - // require(address(bridgehub.deployer()) == address(31337), "Bridgehub: deployer wrong"); + // require(address(bridgehub.deployer()) == address(31337), "BH: deployer wrong"); vm.mockCall( bridgehubAddress, @@ -177,10 +162,10 @@ contract L1SharedBridgeHyperEnabledTest is L1SharedBridgeTest { ); // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - emit ClaimedFailedDepositSharedBridge(chainId, alice, ETH_TOKEN_ADDRESS, amount); + vm.expectEmit(true, true, true, false, address(sharedBridge)); + emit ClaimedFailedDepositAssetRouter(chainId, ETH_TOKEN_ASSET_ID, abi.encode(bytes32(0))); vm.prank(bridgehubAddress); - sharedBridge.claimFailedDeposit({ + l1Nullifier.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, _l1Token: ETH_TOKEN_ADDRESS, @@ -194,15 +179,7 @@ contract L1SharedBridgeHyperEnabledTest is L1SharedBridgeTest { } function test_finalizeWithdrawal_EthOnEth() public { - vm.deal(address(sharedBridge), amount); - - /// storing chainBalance - _setSharedBridgeChainBalance(chainId, ETH_TOKEN_ADDRESS, amount); - vm.mockCall( - bridgehubAddress, - abi.encodeWithSelector(IBridgehub.baseToken.selector), - abi.encode(ETH_TOKEN_ADDRESS) - ); + _setBaseTokenAssetId(ETH_TOKEN_ASSET_ID); bytes memory message = abi.encodePacked(IMailbox.finalizeEthWithdrawal.selector, alice, amount); L2Message memory l2ToL1Message = L2Message({ @@ -226,8 +203,8 @@ contract L1SharedBridgeHyperEnabledTest is L1SharedBridgeTest { ); // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - emit WithdrawalFinalizedSharedBridge(chainId, alice, ETH_TOKEN_ADDRESS, amount); + vm.expectEmit(true, true, true, false, address(sharedBridge)); + emit DepositFinalizedAssetRouter(chainId, ETH_TOKEN_ASSET_ID, message); sharedBridge.finalizeWithdrawal({ _chainId: chainId, _l2BatchNumber: l2BatchNumber, @@ -239,26 +216,17 @@ contract L1SharedBridgeHyperEnabledTest is L1SharedBridgeTest { } function test_finalizeWithdrawal_ErcOnEth() public { - token.mint(address(sharedBridge), amount); - - /// storing chainBalance - _setSharedBridgeChainBalance(chainId, address(token), amount); - - vm.mockCall( - bridgehubAddress, - abi.encodeWithSelector(IBridgehub.baseToken.selector), - abi.encode(ETH_TOKEN_ADDRESS) - ); + _setBaseTokenAssetId(ETH_TOKEN_ASSET_ID); bytes memory message = abi.encodePacked( - IL1ERC20Bridge.finalizeWithdrawal.selector, - alice, - address(token), - amount + IAssetRouterBase.finalizeDeposit.selector, + chainId, + tokenAssetId, + abi.encode(0, alice, 0, amount, new bytes(0)) ); L2Message memory l2ToL1Message = L2Message({ txNumberInBatch: l2TxNumberInBatch, - sender: l2SharedBridge, + sender: l2LegacySharedBridgeAddr, data: message }); @@ -277,8 +245,8 @@ contract L1SharedBridgeHyperEnabledTest is L1SharedBridgeTest { ); // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - emit WithdrawalFinalizedSharedBridge(chainId, alice, address(token), amount); + vm.expectEmit(true, true, true, false, address(sharedBridge)); + emit DepositFinalizedAssetRouter(chainId, tokenAssetId, message); sharedBridge.finalizeWithdrawal({ _chainId: chainId, _l2BatchNumber: l2BatchNumber, @@ -290,25 +258,17 @@ contract L1SharedBridgeHyperEnabledTest is L1SharedBridgeTest { } function test_finalizeWithdrawal_EthOnErc() public { - vm.deal(address(sharedBridge), amount); - - /// storing chainBalance - _setSharedBridgeChainBalance(chainId, ETH_TOKEN_ADDRESS, amount); - vm.mockCall( - bridgehubAddress, - abi.encodeWithSelector(IBridgehub.baseToken.selector), - abi.encode(address(token)) - ); + _setBaseTokenAssetId(tokenAssetId); bytes memory message = abi.encodePacked( - IL1ERC20Bridge.finalizeWithdrawal.selector, - alice, - ETH_TOKEN_ADDRESS, - amount + IAssetRouterBase.finalizeDeposit.selector, + chainId, + ETH_TOKEN_ASSET_ID, + abi.encode(0, alice, 0, amount, new bytes(0)) ); L2Message memory l2ToL1Message = L2Message({ txNumberInBatch: l2TxNumberInBatch, - sender: l2SharedBridge, + sender: l2LegacySharedBridgeAddr, data: message }); @@ -327,8 +287,8 @@ contract L1SharedBridgeHyperEnabledTest is L1SharedBridgeTest { ); // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - emit WithdrawalFinalizedSharedBridge(chainId, alice, ETH_TOKEN_ADDRESS, amount); + vm.expectEmit(true, true, true, false, address(sharedBridge)); + emit DepositFinalizedAssetRouter(chainId, ETH_TOKEN_ASSET_ID, message); sharedBridge.finalizeWithdrawal({ _chainId: chainId, _l2BatchNumber: l2BatchNumber, @@ -340,22 +300,13 @@ contract L1SharedBridgeHyperEnabledTest is L1SharedBridgeTest { } function test_finalizeWithdrawal_BaseErcOnErc() public { - token.mint(address(sharedBridge), amount); - - /// storing chainBalance - _setSharedBridgeChainBalance(chainId, address(token), amount); - - vm.mockCall( - bridgehubAddress, - abi.encodeWithSelector(IBridgehub.baseToken.selector), - abi.encode(address(token)) - ); + _setBaseTokenAssetId(tokenAssetId); bytes memory message = abi.encodePacked( - IL1ERC20Bridge.finalizeWithdrawal.selector, - alice, - address(token), - amount + IAssetRouterBase.finalizeDeposit.selector, + chainId, + tokenAssetId, + abi.encode(0, alice, 0, amount, new bytes(0)) ); L2Message memory l2ToL1Message = L2Message({ txNumberInBatch: l2TxNumberInBatch, @@ -368,18 +319,18 @@ contract L1SharedBridgeHyperEnabledTest is L1SharedBridgeTest { // solhint-disable-next-line func-named-parameters abi.encodeWithSelector( IBridgehub.proveL2MessageInclusion.selector, - chainId, - l2BatchNumber, - l2MessageIndex, - l2ToL1Message, - merkleProof + chainId + // l2BatchNumber, + // l2MessageIndex, + // l2ToL1Message, + // merkleProof ), abi.encode(true) ); // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - emit WithdrawalFinalizedSharedBridge(chainId, alice, address(token), amount); + vm.expectEmit(true, true, true, false, address(sharedBridge)); + emit DepositFinalizedAssetRouter(chainId, tokenAssetId, message); sharedBridge.finalizeWithdrawal({ _chainId: chainId, _l2BatchNumber: l2BatchNumber, @@ -390,22 +341,17 @@ contract L1SharedBridgeHyperEnabledTest is L1SharedBridgeTest { }); } - function test_finalizeWithdrawal_NonBaseErcOnErc() public { - token.mint(address(sharedBridge), amount); - - /// storing chainBalance - _setSharedBridgeChainBalance(chainId, address(token), amount); - + function test_finalizeWithdrawal_NonBaseErcOnErc2() public { bytes memory message = abi.encodePacked( - IL1ERC20Bridge.finalizeWithdrawal.selector, - alice, - address(token), - amount + IAssetRouterBase.finalizeDeposit.selector, + chainId, + tokenAssetId, + abi.encode(0, alice, 0, amount, new bytes(0)) ); - vm.mockCall(bridgehubAddress, abi.encodeWithSelector(IBridgehub.baseToken.selector), abi.encode(address(2))); //alt base token + _setBaseTokenAssetId(bytes32(uint256(2))); //alt base token L2Message memory l2ToL1Message = L2Message({ txNumberInBatch: l2TxNumberInBatch, - sender: l2SharedBridge, + sender: l2LegacySharedBridgeAddr, data: message }); @@ -424,8 +370,8 @@ contract L1SharedBridgeHyperEnabledTest is L1SharedBridgeTest { ); // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - emit WithdrawalFinalizedSharedBridge(chainId, alice, address(token), amount); + vm.expectEmit(true, true, true, false, address(sharedBridge)); + emit DepositFinalizedAssetRouter(chainId, tokenAssetId, message); sharedBridge.finalizeWithdrawal({ _chainId: chainId, _l2BatchNumber: l2BatchNumber, diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol similarity index 52% rename from l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol index 83e83df9c..788446502 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol @@ -1,16 +1,19 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -import {L1SharedBridgeTest} from "./_L1SharedBridge_Shared.t.sol"; +import "forge-std/console.sol"; + +import {L1AssetRouterTest} from "./_L1SharedBridge_Shared.t.sol"; import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; import {L2Message, TxStatus} from "contracts/common/Messaging.sol"; import {IMailbox} from "contracts/state-transition/chain-interfaces/IMailbox.sol"; import {IL1ERC20Bridge} from "contracts/bridge/interfaces/IL1ERC20Bridge.sol"; -import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, L2_ASSET_ROUTER_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {FinalizeL1DepositParams} from "contracts/bridge/interfaces/IL1Nullifier.sol"; -contract L1SharedBridgeLegacyTest is L1SharedBridgeTest { +contract L1AssetRouterLegacyTest is L1AssetRouterTest { function test_depositLegacyERC20Bridge() public { uint256 l2TxGasLimit = 100000; uint256 l2TxGasPerPubdataByte = 100; @@ -36,7 +39,7 @@ contract L1SharedBridgeLegacyTest is L1SharedBridgeTest { vm.prank(l1ERC20BridgeAddress); sharedBridge.depositLegacyErc20Bridge({ - _prevMsgSender: alice, + _originalCaller: alice, _l2Receiver: bob, _l1Token: address(token), _amount: amount, @@ -50,7 +53,7 @@ contract L1SharedBridgeLegacyTest is L1SharedBridgeTest { vm.deal(address(sharedBridge), amount); /// storing chainBalance - _setSharedBridgeChainBalance(eraChainId, ETH_TOKEN_ADDRESS, amount); + _setNativeTokenVaultChainBalance(eraChainId, ETH_TOKEN_ADDRESS, amount); vm.mockCall( bridgehubAddress, abi.encodeWithSelector(IBridgehub.baseToken.selector), @@ -79,28 +82,24 @@ contract L1SharedBridgeLegacyTest is L1SharedBridgeTest { ); // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - emit WithdrawalFinalizedSharedBridge(eraChainId, alice, ETH_TOKEN_ADDRESS, amount); + vm.expectEmit(true, true, true, false, address(sharedBridge)); + emit DepositFinalizedAssetRouter(eraChainId, ETH_TOKEN_ASSET_ID, message); vm.prank(l1ERC20BridgeAddress); - sharedBridge.finalizeWithdrawalLegacyErc20Bridge({ - _l2BatchNumber: l2BatchNumber, - _l2MessageIndex: l2MessageIndex, - _l2TxNumberInBatch: l2TxNumberInBatch, - _message: message, - _merkleProof: merkleProof + FinalizeL1DepositParams memory finalizeWithdrawalParams = FinalizeL1DepositParams({ + chainId: eraChainId, + l2BatchNumber: l2BatchNumber, + l2MessageIndex: l2MessageIndex, + l2Sender: L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, + l2TxNumberInBatch: l2TxNumberInBatch, + message: message, + merkleProof: merkleProof }); + l1Nullifier.finalizeDeposit(finalizeWithdrawalParams); } function test_finalizeWithdrawalLegacyErc20Bridge_ErcOnEth() public { - token.mint(address(sharedBridge), amount); - /// storing chainBalance - _setSharedBridgeChainBalance(eraChainId, address(token), amount); - vm.mockCall( - bridgehubAddress, - abi.encodeWithSelector(IBridgehub.baseToken.selector), - abi.encode(ETH_TOKEN_ADDRESS) - ); + _setNativeTokenVaultChainBalance(eraChainId, address(token), amount); // solhint-disable-next-line func-named-parameters bytes memory message = abi.encodePacked( @@ -111,7 +110,7 @@ contract L1SharedBridgeLegacyTest is L1SharedBridgeTest { ); L2Message memory l2ToL1Message = L2Message({ txNumberInBatch: l2TxNumberInBatch, - sender: l2SharedBridge, + sender: L2_ASSET_ROUTER_ADDR, data: message }); @@ -128,64 +127,25 @@ contract L1SharedBridgeLegacyTest is L1SharedBridgeTest { ), abi.encode(true) ); - - // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - emit WithdrawalFinalizedSharedBridge(eraChainId, alice, address(token), amount); - vm.prank(l1ERC20BridgeAddress); - sharedBridge.finalizeWithdrawalLegacyErc20Bridge({ - _l2BatchNumber: l2BatchNumber, - _l2MessageIndex: l2MessageIndex, - _l2TxNumberInBatch: l2TxNumberInBatch, - _message: message, - _merkleProof: merkleProof - }); - } - - function test_claimFailedDepositLegacyErc20Bridge_Erc() public { - token.mint(address(sharedBridge), amount); - - // storing depositHappened[chainId][l2TxHash] = txDataHash. - bytes32 txDataHash = keccak256(abi.encode(alice, address(token), amount)); - _setSharedBridgeDepositHappened(eraChainId, txHash, txDataHash); - require(sharedBridge.depositHappened(eraChainId, txHash) == txDataHash, "Deposit not set"); - - _setSharedBridgeChainBalance(eraChainId, address(token), 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, - // solhint-disable-next-line func-named-parameters - abi.encodeWithSelector( - IBridgehub.proveL1ToL2TransactionStatus.selector, - eraChainId, - txHash, - l2BatchNumber, - l2MessageIndex, - l2TxNumberInBatch, - merkleProof, - TxStatus.Failure - ), - abi.encode(true) + // console.log(sharedBridge.) + vm.store( + address(sharedBridge), + keccak256(abi.encode(tokenAssetId, isWithdrawalFinalizedStorageLocation + 2)), + bytes32(uint256(uint160(address(nativeTokenVault)))) ); - // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - emit ClaimedFailedDepositSharedBridge(eraChainId, alice, address(token), amount); + vm.expectEmit(true, true, false, false, address(sharedBridge)); + emit DepositFinalizedAssetRouter(eraChainId, tokenAssetId, new bytes(0)); vm.prank(l1ERC20BridgeAddress); - - sharedBridge.claimFailedDepositLegacyErc20Bridge({ - _depositSender: alice, - _l1Token: address(token), - _amount: amount, - _l2TxHash: txHash, - _l2BatchNumber: l2BatchNumber, - _l2MessageIndex: l2MessageIndex, - _l2TxNumberInBatch: l2TxNumberInBatch, - _merkleProof: merkleProof + FinalizeL1DepositParams memory finalizeWithdrawalParams = FinalizeL1DepositParams({ + chainId: eraChainId, + l2BatchNumber: l2BatchNumber, + l2MessageIndex: l2MessageIndex, + l2Sender: L2_ASSET_ROUTER_ADDR, + l2TxNumberInBatch: l2TxNumberInBatch, + message: message, + merkleProof: merkleProof }); + l1Nullifier.finalizeDeposit(finalizeWithdrawalParams); } } diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol new file mode 100644 index 000000000..0f21e5f03 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {StdStorage, stdStorage} from "forge-std/Test.sol"; +import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ERC20} from "@openzeppelin/contracts-v4/token/ERC20/ERC20.sol"; + +import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {TestnetERC20Token} from "contracts/dev-contracts/TestnetERC20Token.sol"; +import {L1NativeTokenVault} from "contracts/bridge/ntv/L1NativeTokenVault.sol"; +import {L1Nullifier} from "contracts/bridge/L1Nullifier.sol"; +import {L1NullifierDev} from "contracts/dev-contracts/L1NullifierDev.sol"; +import {IL1NativeTokenVault} from "contracts/bridge/ntv/IL1NativeTokenVault.sol"; +import {INativeTokenVault} from "contracts/bridge/ntv/INativeTokenVault.sol"; +import {IL1AssetHandler} from "contracts/bridge/interfaces/IL1AssetHandler.sol"; +import {IL1BaseTokenAssetHandler} from "contracts/bridge/interfaces/IL1BaseTokenAssetHandler.sol"; +import {IL1ERC20Bridge} from "contracts/bridge/interfaces/IL1ERC20Bridge.sol"; +import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; +import {L2_NATIVE_TOKEN_VAULT_ADDR, L2_ASSET_ROUTER_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; + +contract L1AssetRouterTest is Test { + using stdStorage for StdStorage; + + event BridgehubDepositBaseTokenInitiated( + uint256 indexed chainId, + address indexed from, + bytes32 assetId, + uint256 amount + ); + + event BridgehubDepositInitiated( + uint256 indexed chainId, + bytes32 indexed txDataHash, + address indexed from, + bytes32 assetId, + bytes bridgeMintCalldata + ); + + event BridgehubDepositFinalized( + uint256 indexed chainId, + bytes32 indexed txDataHash, + bytes32 indexed l2DepositTxHash + ); + + event DepositFinalizedAssetRouter(uint256 indexed chainId, bytes32 indexed assetId, bytes assetData); + + event ClaimedFailedDepositAssetRouter(uint256 indexed chainId, bytes32 indexed assetId, bytes assetData); + + event LegacyDepositInitiated( + uint256 indexed chainId, + bytes32 indexed l2DepositTxHash, + address indexed from, + address to, + address l1Token, + uint256 amount + ); + + L1AssetRouter sharedBridgeImpl; + L1AssetRouter sharedBridge; + L1NativeTokenVault nativeTokenVaultImpl; + L1NativeTokenVault nativeTokenVault; + L1Nullifier l1NullifierImpl; + L1Nullifier l1Nullifier; + address bridgehubAddress; + address l1ERC20BridgeAddress; + address l1WethAddress; + address l2SharedBridge; + address l1NullifierAddress; + TestnetERC20Token token; + bytes32 tokenAssetId; + uint256 eraPostUpgradeFirstBatch; + + address owner; + address admin; + address proxyAdmin; + address zkSync; + address alice; + address bob; + uint256 chainId; + uint256 amount = 100; + uint256 mintValue = 1; + bytes32 txHash; + uint256 gas = 1_000_000; + + uint256 eraChainId; + uint256 randomChainId; + address eraDiamondProxy; + address eraErc20BridgeAddress; + address l2LegacySharedBridgeAddr; + + uint256 l2BatchNumber; + uint256 l2MessageIndex; + uint16 l2TxNumberInBatch; + bytes32[] merkleProof; + uint256 legacyBatchNumber = 0; + + uint256 isWithdrawalFinalizedStorageLocation = uint256(8 - 1 + (1 + 49) + 0 + (1 + 49) + 50 + 1 + 50); + bytes32 ETH_TOKEN_ASSET_ID = keccak256(abi.encode(block.chainid, L2_NATIVE_TOKEN_VAULT_ADDR, ETH_TOKEN_ADDRESS)); + + function setUp() public { + owner = makeAddr("owner"); + admin = makeAddr("admin"); + proxyAdmin = makeAddr("proxyAdmin"); + // zkSync = makeAddr("zkSync"); + bridgehubAddress = makeAddr("bridgehub"); + alice = makeAddr("alice"); + // bob = makeAddr("bob"); + l1WethAddress = address(new ERC20("Wrapped ETH", "WETH")); + l1ERC20BridgeAddress = makeAddr("l1ERC20Bridge"); + l2SharedBridge = makeAddr("l2SharedBridge"); + + txHash = bytes32(uint256(uint160(makeAddr("txHash")))); + l2BatchNumber = 3; //uint256(uint160(makeAddr("l2BatchNumber"))); + l2MessageIndex = uint256(uint160(makeAddr("l2MessageIndex"))); + l2TxNumberInBatch = uint16(uint160(makeAddr("l2TxNumberInBatch"))); + l2LegacySharedBridgeAddr = makeAddr("l2LegacySharedBridge"); + merkleProof = new bytes32[](1); + eraPostUpgradeFirstBatch = 1; + + chainId = 1; + eraChainId = 9; + randomChainId = 999; + eraDiamondProxy = makeAddr("eraDiamondProxy"); + eraErc20BridgeAddress = makeAddr("eraErc20BridgeAddress"); + + token = new TestnetERC20Token("TestnetERC20Token", "TET", 18); + l1NullifierImpl = new L1NullifierDev({ + _bridgehub: IBridgehub(bridgehubAddress), + _eraChainId: eraChainId, + _eraDiamondProxy: eraDiamondProxy + }); + TransparentUpgradeableProxy l1NullifierProxy = new TransparentUpgradeableProxy( + address(l1NullifierImpl), + proxyAdmin, + abi.encodeWithSelector(L1Nullifier.initialize.selector, owner, 1, 1, 1, 0) + ); + L1NullifierDev(address(l1NullifierProxy)).setL2LegacySharedBridge(chainId, l2LegacySharedBridgeAddr); + L1NullifierDev(address(l1NullifierProxy)).setL2LegacySharedBridge(eraChainId, l2LegacySharedBridgeAddr); + + l1Nullifier = L1Nullifier(payable(l1NullifierProxy)); + sharedBridgeImpl = new L1AssetRouter({ + _l1WethAddress: l1WethAddress, + _bridgehub: bridgehubAddress, + _l1Nullifier: address(l1Nullifier), + _eraChainId: eraChainId, + _eraDiamondProxy: eraDiamondProxy + }); + TransparentUpgradeableProxy sharedBridgeProxy = new TransparentUpgradeableProxy( + address(sharedBridgeImpl), + proxyAdmin, + abi.encodeWithSelector(L1AssetRouter.initialize.selector, owner) + ); + sharedBridge = L1AssetRouter(payable(sharedBridgeProxy)); + nativeTokenVaultImpl = new L1NativeTokenVault({ + _l1WethAddress: l1WethAddress, + _l1AssetRouter: address(sharedBridge), + _l1Nullifier: l1Nullifier + }); + address tokenBeacon = makeAddr("tokenBeacon"); + TransparentUpgradeableProxy nativeTokenVaultProxy = new TransparentUpgradeableProxy( + address(nativeTokenVaultImpl), + proxyAdmin, + abi.encodeWithSelector(L1NativeTokenVault.initialize.selector, owner, tokenBeacon) + ); + nativeTokenVault = L1NativeTokenVault(payable(nativeTokenVaultProxy)); + + vm.prank(owner); + l1Nullifier.setL1AssetRouter(address(sharedBridge)); + vm.prank(owner); + l1Nullifier.setL1NativeTokenVault(nativeTokenVault); + vm.prank(owner); + l1Nullifier.setL1Erc20Bridge(IL1ERC20Bridge(l1ERC20BridgeAddress)); + vm.prank(owner); + sharedBridge.setL1Erc20Bridge(IL1ERC20Bridge(l1ERC20BridgeAddress)); + tokenAssetId = DataEncoding.encodeNTVAssetId(block.chainid, address(token)); + vm.prank(owner); + sharedBridge.setNativeTokenVault(INativeTokenVault(address(nativeTokenVault))); + vm.prank(address(nativeTokenVault)); + nativeTokenVault.registerToken(address(token)); + nativeTokenVault.registerEthToken(); + vm.prank(owner); + + vm.store( + address(sharedBridge), + bytes32(isWithdrawalFinalizedStorageLocation), + bytes32(eraPostUpgradeFirstBatch) + ); + vm.store( + address(sharedBridge), + bytes32(isWithdrawalFinalizedStorageLocation + 1), + bytes32(eraPostUpgradeFirstBatch) + ); + vm.store(address(sharedBridge), bytes32(isWithdrawalFinalizedStorageLocation + 2), bytes32(uint256(1))); + vm.store(address(sharedBridge), bytes32(isWithdrawalFinalizedStorageLocation + 3), bytes32(0)); + + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseTokenAssetId.selector), + abi.encode(ETH_TOKEN_ASSET_ID) + ); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseTokenAssetId.selector, chainId), + abi.encode(ETH_TOKEN_ASSET_ID) + ); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.requestL2TransactionDirect.selector), + abi.encode(txHash) + ); + + token.mint(address(nativeTokenVault), amount); + + /// storing chainBalance + _setNativeTokenVaultChainBalance(chainId, address(token), 1000 * amount); + _setNativeTokenVaultChainBalance(chainId, ETH_TOKEN_ADDRESS, amount); + // console.log("chainBalance %s, %s", address(token), nativeTokenVault.chainBalance(chainId, address(token))); + _setSharedBridgeChainBalance(chainId, address(token), amount); + _setSharedBridgeChainBalance(chainId, ETH_TOKEN_ADDRESS, amount); + + vm.deal(bridgehubAddress, amount); + vm.deal(address(sharedBridge), amount); + vm.deal(address(l1Nullifier), amount); + vm.deal(address(nativeTokenVault), amount); + token.mint(alice, amount); + token.mint(address(sharedBridge), amount); + token.mint(address(nativeTokenVault), amount); + token.mint(address(l1Nullifier), amount); + vm.prank(alice); + token.approve(address(sharedBridge), amount); + vm.prank(alice); + token.approve(address(nativeTokenVault), amount); + vm.prank(alice); + token.approve(address(l1Nullifier), amount); + + _setBaseTokenAssetId(ETH_TOKEN_ASSET_ID); + _setNativeTokenVaultChainBalance(chainId, address(token), amount); + + vm.mockCall( + address(nativeTokenVault), + abi.encodeWithSelector(IL1BaseTokenAssetHandler.tokenAddress.selector, tokenAssetId), + abi.encode(address(token)) + ); + vm.mockCall( + address(nativeTokenVault), + abi.encodeWithSelector(IL1BaseTokenAssetHandler.tokenAddress.selector, ETH_TOKEN_ASSET_ID), + abi.encode(address(ETH_TOKEN_ADDRESS)) + ); + vm.mockCall( + bridgehubAddress, + // solhint-disable-next-line func-named-parameters + abi.encodeWithSelector(IBridgehub.baseToken.selector, chainId), + abi.encode(ETH_TOKEN_ADDRESS) + ); + } + + function _setSharedBridgeDepositHappened(uint256 _chainId, bytes32 _txHash, bytes32 _txDataHash) internal { + stdstore + .target(address(l1Nullifier)) + .sig(l1Nullifier.depositHappened.selector) + .with_key(_chainId) + .with_key(_txHash) + .checked_write(_txDataHash); + } + + function _setNativeTokenVaultChainBalance(uint256 _chainId, address _token, uint256 _value) internal { + bytes32 assetId = DataEncoding.encodeNTVAssetId(block.chainid, _token); + stdstore + .target(address(nativeTokenVault)) + .sig(nativeTokenVault.chainBalance.selector) + .with_key(_chainId) + .with_key(assetId) + .checked_write(_value); + } + + function _setSharedBridgeChainBalance(uint256 _chainId, address _token, uint256 _value) internal { + stdstore + .target(address(l1Nullifier)) + .sig(l1Nullifier.chainBalance.selector) + .with_key(_chainId) + .with_key(_token) + .checked_write(_value); + } + + function _setBaseTokenAssetId(bytes32 _assetId) internal { + // vm.prank(bridgehubAddress); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseTokenAssetId.selector, chainId), + abi.encode(_assetId) + ); + } +} diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L2WrappedBaseTokenStore.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L2WrappedBaseTokenStore.t.sol new file mode 100644 index 000000000..7188f89a5 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L2WrappedBaseTokenStore.t.sol @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {Test} from "forge-std/Test.sol"; +import {L2WrappedBaseTokenStore} from "contracts/bridge/L2WrappedBaseTokenStore.sol"; +import {ZeroAddress, Unauthorized} from "contracts/common/L1ContractErrors.sol"; + +contract L2WrappedBaseTokenStoreTest is Test { + L2WrappedBaseTokenStore store; + + address owner = address(0x1); + address admin = address(0x2); + address other = address(0x3); + address newAdmin = address(0x4); + address newPendingAdmin = address(0x5); + uint256 chainId = 100; + address l2WBaseToken = address(0xABC); + address newL2WBaseToken = address(0xDEF); + + // Events + event NewAdmin(address indexed oldAdmin, address indexed newAdmin); + event NewPendingAdmin(address indexed oldPendingAdmin, address indexed newPendingAdmin); + event NewWBaseTokenAddress(uint256 indexed chainId, address indexed l2WBaseTokenAddress); + + function setUp() public { + // Deploy the contract with owner and admin + vm.startPrank(owner); + store = new L2WrappedBaseTokenStore(owner, admin); + vm.stopPrank(); + } + + // Deployment Tests + function testInitialOwner() public { + assertEq(store.owner(), owner, "Owner should be set correctly"); + } + + function testInitialAdmin() public { + assertEq(store.admin(), admin, "Admin should be set correctly"); + } + + function testConstructorRevertsZeroOwner() public { + vm.expectRevert(abi.encodeWithSelector(ZeroAddress.selector)); + new L2WrappedBaseTokenStore(address(0), admin); + } + + function testConstructorRevertsZeroAdmin() public { + vm.expectRevert(abi.encodeWithSelector(ZeroAddress.selector)); + new L2WrappedBaseTokenStore(owner, address(0)); + } + + // Access Control Tests + function testOnlyOwnerOrAdminCanInitializeChain() public { + // Attempt from owner + vm.startPrank(owner); + store.initializeChain(chainId, l2WBaseToken); + vm.stopPrank(); + + // Attempt from admin + vm.startPrank(admin); + store.initializeChain(chainId + 1, l2WBaseToken); + vm.stopPrank(); + + // Attempt from other + vm.startPrank(other); + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, other)); + store.initializeChain(chainId + 2, l2WBaseToken); + vm.stopPrank(); + } + + function testOnlyOwnerCanReinitializeChain() public { + // Initialize first + vm.startPrank(admin); + store.initializeChain(chainId, l2WBaseToken); + vm.stopPrank(); + + // Reinitialize from owner + vm.startPrank(owner); + store.reinitializeChain(chainId, newL2WBaseToken); + vm.stopPrank(); + + // Reinitialize from admin should fail + vm.startPrank(admin); + vm.expectRevert("Ownable: caller is not the owner"); + store.reinitializeChain(chainId, l2WBaseToken); + vm.stopPrank(); + + // Reinitialize from other should fail + vm.startPrank(other); + vm.expectRevert("Ownable: caller is not the owner"); + store.reinitializeChain(chainId, l2WBaseToken); + vm.stopPrank(); + } + + // initializeChain Tests + function testInitializeChain() public { + vm.startPrank(owner); + vm.expectEmit(true, true, false, true); + emit NewWBaseTokenAddress(chainId, l2WBaseToken); + store.initializeChain(chainId, l2WBaseToken); + vm.stopPrank(); + + address storedAddress = store.l2WBaseTokenAddress(chainId); + assertEq(storedAddress, l2WBaseToken, "L2 WBaseToken address should be set correctly"); + } + + function testInitializeChainRevertsZeroAddress() public { + vm.startPrank(owner); + vm.expectRevert(abi.encodeWithSelector(ZeroAddress.selector)); + store.initializeChain(chainId, address(0)); + vm.stopPrank(); + } + + // reinitializeChain Tests + function testReinitializeChain() public { + // Initialize first + vm.startPrank(admin); + store.initializeChain(chainId, l2WBaseToken); + vm.stopPrank(); + + // Reinitialize + vm.startPrank(owner); + vm.expectEmit(true, true, false, true); + emit NewWBaseTokenAddress(chainId, newL2WBaseToken); + store.reinitializeChain(chainId, newL2WBaseToken); + vm.stopPrank(); + + address storedAddress = store.l2WBaseTokenAddress(chainId); + assertEq(storedAddress, newL2WBaseToken, "L2 WBaseToken address should be updated correctly"); + } + + function testReinitializeChainRevertsZeroAddress() public { + vm.startPrank(owner); + vm.expectRevert(abi.encodeWithSelector(ZeroAddress.selector)); + store.reinitializeChain(chainId, address(0)); + vm.stopPrank(); + } + + // setPendingAdmin Tests + function testSetPendingAdmin() public { + vm.startPrank(owner); + vm.expectEmit(true, true, false, true); + emit NewPendingAdmin(address(0), newPendingAdmin); + store.setPendingAdmin(newPendingAdmin); + vm.stopPrank(); + + assertEq(store.pendingAdmin(), newPendingAdmin, "Pending admin should be set correctly"); + } + + function testSetPendingAdminByAdmin() public { + vm.startPrank(admin); + vm.expectEmit(true, true, false, true); + emit NewPendingAdmin(address(0), newPendingAdmin); + store.setPendingAdmin(newPendingAdmin); + vm.stopPrank(); + + assertEq(store.pendingAdmin(), newPendingAdmin, "Pending admin should be set correctly by admin"); + } + + function testSetPendingAdminRevertsZeroAddress() public { + vm.startPrank(owner); + vm.expectRevert(abi.encodeWithSelector(ZeroAddress.selector)); + store.setPendingAdmin(address(0)); + vm.stopPrank(); + } + + function testSetPendingAdminUnauthorized() public { + vm.startPrank(other); + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, other)); + store.setPendingAdmin(newPendingAdmin); + vm.stopPrank(); + } + + // acceptAdmin Tests + function testAcceptAdmin() public { + // Set pending admin + vm.startPrank(owner); + store.setPendingAdmin(newAdmin); + vm.stopPrank(); + + // Accept admin by newAdmin + vm.startPrank(newAdmin); + vm.expectEmit(true, true, false, true); + emit NewPendingAdmin(newAdmin, address(0)); + vm.expectEmit(true, true, false, true); + emit NewAdmin(admin, newAdmin); + store.acceptAdmin(); + vm.stopPrank(); + + assertEq(store.admin(), newAdmin, "Admin should be updated correctly"); + assertEq(store.pendingAdmin(), address(0), "Pending admin should be cleared"); + } + + function testAcceptAdminRevertsIfNotPendingAdmin() public { + // Set pending admin + vm.startPrank(owner); + store.setPendingAdmin(newAdmin); + vm.stopPrank(); + + // Attempt to accept by someone else + vm.startPrank(other); + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, other)); + store.acceptAdmin(); + vm.stopPrank(); + } + + function testSetAndAcceptAdminFlow() public { + // Set pending admin by admin + vm.startPrank(admin); + store.setPendingAdmin(newPendingAdmin); + vm.stopPrank(); + + // Accept admin by newPendingAdmin + vm.startPrank(newPendingAdmin); + store.acceptAdmin(); + vm.stopPrank(); + + assertEq(store.admin(), newPendingAdmin, "Admin should be updated to newPendingAdmin"); + assertEq(store.pendingAdmin(), address(0), "Pending admin should be cleared after acceptance"); + } + + // Event Emission Tests + function testInitializeChainEmitsEvent() public { + vm.startPrank(owner); + vm.expectEmit(true, true, false, true); + emit NewWBaseTokenAddress(chainId, l2WBaseToken); + store.initializeChain(chainId, l2WBaseToken); + vm.stopPrank(); + } + + function testReinitializeChainEmitsEvent() public { + vm.startPrank(owner); + store.initializeChain(chainId, l2WBaseToken); + vm.expectEmit(true, true, false, true); + emit NewWBaseTokenAddress(chainId, newL2WBaseToken); + store.reinitializeChain(chainId, newL2WBaseToken); + vm.stopPrank(); + } + + function testSetPendingAdminEmitsEvent() public { + vm.startPrank(owner); + vm.expectEmit(true, true, false, true); + emit NewPendingAdmin(address(0), newPendingAdmin); + store.setPendingAdmin(newPendingAdmin); + vm.stopPrank(); + } + + function testAcceptAdminEmitsEvents() public { + // Set pending admin + vm.startPrank(owner); + store.setPendingAdmin(newAdmin); + vm.stopPrank(); + + // Expect both events when accepting admin + vm.startPrank(newAdmin); + vm.expectEmit(true, true, false, true); + emit NewPendingAdmin(newAdmin, address(0)); + vm.expectEmit(true, true, false, true); + emit NewAdmin(admin, newAdmin); + store.acceptAdmin(); + vm.stopPrank(); + } + + // Edge Case Tests + function testReinitializeChainMultipleTimes() public { + vm.startPrank(owner); + store.initializeChain(chainId, l2WBaseToken); + store.reinitializeChain(chainId, newL2WBaseToken); + store.reinitializeChain(chainId, l2WBaseToken); + address stored = store.l2WBaseTokenAddress(chainId); + assertEq(stored, l2WBaseToken, "Mapping should reflect the last set address"); + vm.stopPrank(); + } + + function testSetPendingAdminTwice() public { + vm.startPrank(owner); + store.setPendingAdmin(newPendingAdmin); + store.setPendingAdmin(admin); + assertEq(store.pendingAdmin(), admin, "Pending admin should be updated to the latest set value"); + vm.stopPrank(); + } + + function testAcceptAdminWhenPendingAdminIsZero() public { + vm.startPrank(other); + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, other)); + store.acceptAdmin(); + vm.stopPrank(); + } +} diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Bridges/NativeTokenVault/L1NativeTokenVault.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/NativeTokenVault/L1NativeTokenVault.sol new file mode 100644 index 000000000..ff71bdfe1 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/NativeTokenVault/L1NativeTokenVault.sol @@ -0,0 +1,47 @@ +import {StdStorage, stdStorage} from "forge-std/Test.sol"; +import {Test} from "forge-std/Test.sol"; +import {Vm} from "forge-std/Vm.sol"; + +import {L1NativeTokenVault} from "contracts/bridge/ntv/L1NativeTokenVault.sol"; +import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol"; +import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {AssetIdAlreadyRegistered} from "contracts/common/L1ContractErrors.sol"; + +contract SomeToken { + constructor() {} + + function name() external { + // Just some function so that the bytecode is not empty, + // the actional functionality is not used. + } +} + +contract L1NativeTokenVaultTest is Test { + address assetRouter; + + L1NativeTokenVault ntv; + SomeToken token; + + function setUp() public { + assetRouter = makeAddr("assetRouter"); + + ntv = new L1NativeTokenVault(makeAddr("wethToken"), assetRouter, IL1Nullifier(address(0))); + + token = new SomeToken(); + } + + function test_revertWhenRegisteringSameAddressTwice() external { + vm.mockCall( + assetRouter, + abi.encodeCall( + L1AssetRouter.setAssetHandlerAddressThisChain, + (bytes32(uint256(uint160(address(token)))), address(ntv)) + ), + hex"" + ); + ntv.registerToken(address(token)); + + vm.expectRevert(AssetIdAlreadyRegistered.selector); + ntv.registerToken(address(token)); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/DiamondCut/FacetCut.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/DiamondCut/FacetCut.t.sol similarity index 96% rename from l1-contracts/test/foundry/unit/concrete/DiamondCut/FacetCut.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/DiamondCut/FacetCut.t.sol index 0aee58ce7..43cec79fd 100644 --- a/l1-contracts/test/foundry/unit/concrete/DiamondCut/FacetCut.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/DiamondCut/FacetCut.t.sol @@ -9,7 +9,7 @@ import {ExecutorFacet} from "contracts/state-transition/chain-deps/facets/Execut import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; import {MailboxFacet} from "contracts/state-transition/chain-deps/facets/Mailbox.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; -import {ReplaceFunctionFacetAddressZero, RemoveFunctionFacetAddressNotZero, FacetExists, SelectorsMustAllHaveSameFreezability, AddressHasNoCode, NonZeroAddress, ZeroAddress} from "contracts/common/L1ContractErrors.sol"; +import {ReplaceFunctionFacetAddressZero, RemoveFunctionFacetAddressNotZero, FacetExists, SelectorsMustAllHaveSameFreezability, AddressHasNoCode, ZeroAddress} from "contracts/common/L1ContractErrors.sol"; contract FacetCutTest is DiamondCutTest { MailboxFacet private mailboxFacet; @@ -20,20 +20,20 @@ contract FacetCutTest is DiamondCutTest { function getExecutorSelectors() private view returns (bytes4[] memory) { bytes4[] memory selectors = new bytes4[](4); - selectors[0] = executorFacet1.commitBatches.selector; - selectors[1] = executorFacet1.proveBatches.selector; - selectors[2] = executorFacet1.executeBatches.selector; - selectors[3] = executorFacet1.revertBatches.selector; + selectors[0] = executorFacet1.commitBatchesSharedBridge.selector; + selectors[1] = executorFacet1.proveBatchesSharedBridge.selector; + selectors[2] = executorFacet1.executeBatchesSharedBridge.selector; + selectors[3] = executorFacet1.revertBatchesSharedBridge.selector; return selectors; } function setUp() public { eraChainId = 9; diamondCutTestContract = new DiamondCutTestContract(); - mailboxFacet = new MailboxFacet(eraChainId); + mailboxFacet = new MailboxFacet(eraChainId, block.chainid); gettersFacet = new GettersFacet(); - executorFacet1 = new ExecutorFacet(); - executorFacet2 = new ExecutorFacet(); + executorFacet1 = new ExecutorFacet(block.chainid); + executorFacet2 = new ExecutorFacet(block.chainid); } function test_AddingFacetsToFreeSelectors() public { diff --git a/l1-contracts/test/foundry/unit/concrete/DiamondCut/Initialization.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/DiamondCut/Initialization.t.sol similarity index 96% rename from l1-contracts/test/foundry/unit/concrete/DiamondCut/Initialization.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/DiamondCut/Initialization.t.sol index 94996c5e1..238674049 100644 --- a/l1-contracts/test/foundry/unit/concrete/DiamondCut/Initialization.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/DiamondCut/Initialization.t.sol @@ -6,7 +6,7 @@ import {RevertFallback} from "contracts/dev-contracts/RevertFallback.sol"; import {ReturnSomething} from "contracts/dev-contracts/ReturnSomething.sol"; import {DiamondCutTestContract} from "contracts/dev-contracts/test/DiamondCutTestContract.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; -import {DelegateCallFailed, BadReturnData, MalformedCalldata, NonEmptyCalldata} from "contracts/common/L1ContractErrors.sol"; +import {DelegateCallFailed, NonEmptyCalldata} from "contracts/common/L1ContractErrors.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/l1/unit/concrete/DiamondCut/UpgradeLogic.t.sol similarity index 86% rename from l1-contracts/test/foundry/unit/concrete/DiamondCut/UpgradeLogic.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/DiamondCut/UpgradeLogic.t.sol index 7a4badf5f..ed92e27e3 100644 --- a/l1-contracts/test/foundry/unit/concrete/DiamondCut/UpgradeLogic.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/DiamondCut/UpgradeLogic.t.sol @@ -6,15 +6,18 @@ import {DiamondCutTest} from "./_DiamondCut_Shared.t.sol"; import {DiamondCutTestContract} from "contracts/dev-contracts/test/DiamondCutTestContract.sol"; import {DiamondInit} from "contracts/state-transition/chain-deps/DiamondInit.sol"; import {DiamondProxy} from "contracts/state-transition/chain-deps/DiamondProxy.sol"; -import {VerifierParams, FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol"; +import {VerifierParams, FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZKChainStorage.sol"; import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol"; import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; import {IVerifier} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {Utils} from "../Utils/Utils.sol"; import {InitializeData} from "contracts/state-transition/chain-deps/DiamondInit.sol"; -import {DummyStateTransitionManager} from "contracts/dev-contracts/test/DummyStateTransitionManager.sol"; -import {DiamondAlreadyFrozen, Unauthorized, DiamondFreezeIncorrectState, DiamondNotFrozen} from "contracts/common/L1ContractErrors.sol"; +import {DummyChainTypeManager} from "contracts/dev-contracts/test/DummyChainTypeManager.sol"; +import {DummyBridgehub} from "contracts/dev-contracts/test/DummyBridgehub.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; +import {DiamondAlreadyFrozen, Unauthorized, DiamondNotFrozen} from "contracts/common/L1ContractErrors.sol"; +import {RollupDAManager} from "contracts/state-transition/data-availability/RollupDAManager.sol"; contract UpgradeLogicTest is DiamondCutTest { DiamondProxy private diamondProxy; @@ -23,7 +26,7 @@ contract UpgradeLogicTest is DiamondCutTest { AdminFacet private proxyAsAdmin; GettersFacet private proxyAsGetters; address private admin; - address private stateTransitionManager; + address private chainTypeManager; address private randomSigner; function getAdminSelectors() private view returns (bytes4[] memory) { @@ -44,12 +47,13 @@ contract UpgradeLogicTest is DiamondCutTest { function setUp() public { admin = makeAddr("admin"); - stateTransitionManager = address(new DummyStateTransitionManager()); + chainTypeManager = address(new DummyChainTypeManager()); randomSigner = makeAddr("randomSigner"); + DummyBridgehub dummyBridgehub = new DummyBridgehub(); diamondCutTestContract = new DiamondCutTestContract(); diamondInit = new DiamondInit(); - adminFacet = new AdminFacet(); + adminFacet = new AdminFacet(block.chainid, RollupDAManager(address(0))); gettersFacet = new GettersFacet(); Diamond.FacetCut[] memory facetCuts = new Diamond.FacetCut[](2); @@ -75,13 +79,12 @@ contract UpgradeLogicTest is DiamondCutTest { InitializeData memory params = InitializeData({ // TODO REVIEW chainId: 1, - bridgehub: makeAddr("bridgehub"), - stateTransitionManager: stateTransitionManager, + bridgehub: address(dummyBridgehub), + chainTypeManager: chainTypeManager, protocolVersion: 0, admin: admin, validatorTimelock: makeAddr("validatorTimelock"), - baseToken: makeAddr("baseToken"), - baseTokenBridge: makeAddr("baseTokenBridge"), + baseTokenAssetId: DataEncoding.encodeNTVAssetId(1, (makeAddr("baseToken"))), storedBatchZero: bytes32(0), // genesisBatchHash: 0x02c775f0a90abf7a0e8043f2fdc38f0580ca9f9996a895d05a501bfeaa3b2e21, // genesisIndexRepeatedStorageChanges: 0, @@ -123,8 +126,8 @@ contract UpgradeLogicTest is DiamondCutTest { proxyAsAdmin.freezeDiamond(); } - function test_RevertWhen_DoubleFreezingBySTM() public { - vm.startPrank(stateTransitionManager); + function test_RevertWhen_DoubleFreezingByCTM() public { + vm.startPrank(chainTypeManager); proxyAsAdmin.freezeDiamond(); @@ -133,7 +136,7 @@ contract UpgradeLogicTest is DiamondCutTest { } function test_RevertWhen_UnfreezingWhenNotFrozen() public { - vm.startPrank(stateTransitionManager); + vm.startPrank(chainTypeManager); vm.expectRevert(DiamondNotFrozen.selector); proxyAsAdmin.unfreezeDiamond(); @@ -154,7 +157,7 @@ contract UpgradeLogicTest is DiamondCutTest { initCalldata: bytes("") }); - vm.startPrank(stateTransitionManager); + vm.startPrank(chainTypeManager); proxyAsAdmin.executeUpgrade(diamondCutData); @@ -185,7 +188,7 @@ contract UpgradeLogicTest is DiamondCutTest { initCalldata: bytes("") }); - vm.startPrank(stateTransitionManager); + vm.startPrank(chainTypeManager); 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/l1/unit/concrete/DiamondCut/_DiamondCut_Shared.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/DiamondCut/_DiamondCut_Shared.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/DiamondCut/_DiamondCut_Shared.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/Executor/Authorization.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Executor/Authorization.t.sol similarity index 73% rename from l1-contracts/test/foundry/unit/concrete/Executor/Authorization.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Executor/Authorization.t.sol index 8cc19a11d..59869620b 100644 --- a/l1-contracts/test/foundry/unit/concrete/Executor/Authorization.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Executor/Authorization.t.sol @@ -34,7 +34,7 @@ contract AuthorizationTest is ExecutorTest { bootloaderHeapInitialContentsHash: Utils.randomBytes32("bootloaderHeapInitialContentsHash"), eventsQueueStateHash: Utils.randomBytes32("eventsQueueStateHash"), systemLogs: bytes(""), - pubdataCommitments: bytes("") + operatorDAInput: bytes("") }); } @@ -45,7 +45,11 @@ contract AuthorizationTest is ExecutorTest { vm.prank(randomSigner); vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, randomSigner)); - executor.commitBatches(storedBatchInfo, commitBatchInfoArray); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + storedBatchInfo, + commitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); } function test_RevertWhen_ProvingByUnauthorisedAddress() public { @@ -55,7 +59,12 @@ contract AuthorizationTest is ExecutorTest { vm.prank(owner); vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, owner)); - executor.proveBatches(storedBatchInfo, storedBatchInfoArray, proofInput); + (uint256 proveBatchFrom, uint256 proveBatchTo, bytes memory proveData) = Utils.encodeProveBatchesData( + storedBatchInfo, + storedBatchInfoArray, + proofInput + ); + executor.proveBatchesSharedBridge(uint256(0), proveBatchFrom, proveBatchTo, proveData); } function test_RevertWhen_ExecutingByUnauthorizedAddress() public { @@ -65,6 +74,10 @@ contract AuthorizationTest is ExecutorTest { vm.prank(randomSigner); vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, randomSigner)); - executor.executeBatches(storedBatchInfoArray); + (uint256 executeBatchFrom, uint256 executeBatchTo, bytes memory executeData) = Utils.encodeExecuteBatchesData( + storedBatchInfoArray, + Utils.emptyData() + ); + executor.executeBatchesSharedBridge(uint256(0), executeBatchFrom, executeBatchTo, executeData); } } diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Executor/Committing.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Executor/Committing.t.sol new file mode 100644 index 000000000..416bcfd17 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/Executor/Committing.t.sol @@ -0,0 +1,962 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "forge-std/console.sol"; +import {Vm} from "forge-std/Test.sol"; +import {Utils, L2_BOOTLOADER_ADDRESS, L2_SYSTEM_CONTEXT_ADDRESS, L2_TO_L1_MESSENGER} from "../Utils/Utils.sol"; +import {ExecutorTest, EMPTY_PREPUBLISHED_COMMITMENT, POINT_EVALUATION_PRECOMPILE_RESULT} from "./_Executor_Shared.t.sol"; + +import {IExecutor, TOTAL_BLOBS_IN_COMMITMENT} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; +import {SystemLogKey} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; +import {POINT_EVALUATION_PRECOMPILE_ADDR} from "contracts/common/Config.sol"; +import {L2_PUBDATA_CHUNK_PUBLISHER_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {BLS_MODULUS} from "../../../da-contracts-imports/DAUtils.sol"; +import {BLOB_DATA_OFFSET} from "../../../da-contracts-imports/CalldataDA.sol"; +import {PubdataCommitmentsEmpty, BlobHashCommitmentError, OperatorDAInputTooSmall, EmptyBlobVersionHash, InvalidPubdataCommitmentsSize, NonEmptyBlobVersionHash} from "../../../da-contracts-imports/DAContractsErrors.sol"; +import {TimeNotReached, BatchNumberMismatch, L2TimestampTooBig, CanOnlyProcessOneBatch, TimestampError, LogAlreadyProcessed, InvalidLogSender, UnexpectedSystemLog, HashMismatch, BatchHashMismatch, ValueMismatch, MissingSystemLogs} from "contracts/common/L1ContractErrors.sol"; + +contract CommittingTest is ExecutorTest { + bytes32[] defaultBlobVersionedHashes; + bytes32 l2DAValidatorOutputHash; + bytes operatorDAInput; + bytes defaultBlobCommitment; + bytes16 defaultBlobOpeningPoint = 0x7142c5851421a2dc03dde0aabdb0ffdb; + bytes32 defaultBlobClaimedValue = 0x1e5eea3bbb85517461c1d1c7b84c7c2cec050662a5e81a71d5d7e2766eaff2f0; + + function setUp() public { + // the values below are taken from the actual blob used by Era + bytes1 source = bytes1(0x01); + defaultBlobCommitment = Utils.getDefaultBlobCommitment(); + + bytes32 uncompressedStateDiffHash = Utils.randomBytes32("uncompressedStateDiffHash"); + bytes32 totalL2PubdataHash = Utils.randomBytes32("totalL2PubdataHash"); + uint8 numberOfBlobs = 1; + bytes32[] memory blobsLinearHashes = new bytes32[](1); + blobsLinearHashes[0] = Utils.randomBytes32("blobsLinearHashes"); + + operatorDAInput = abi.encodePacked( + uncompressedStateDiffHash, + totalL2PubdataHash, + numberOfBlobs, + blobsLinearHashes, + source, + defaultBlobCommitment, + EMPTY_PREPUBLISHED_COMMITMENT + ); + + l2DAValidatorOutputHash = Utils.constructRollupL2DAValidatorOutputHash( + uncompressedStateDiffHash, + totalL2PubdataHash, + uint8(numberOfBlobs), + blobsLinearHashes + ); + + defaultBlobVersionedHashes = new bytes32[](1); + defaultBlobVersionedHashes[0] = 0x01c024b4740620a5849f95930cefe298933bdf588123ea897cdf0f2462f6d2d5; + + bytes memory precompileInput = Utils.defaultPointEvaluationPrecompileInput(defaultBlobVersionedHashes[0]); + vm.mockCall(POINT_EVALUATION_PRECOMPILE_ADDR, precompileInput, POINT_EVALUATION_PRECOMPILE_RESULT); + } + + function test_RevertWhen_CommittingWithWrongLastCommittedBatchData() public { + IExecutor.CommitBatchInfo[] memory newCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + newCommitBatchInfoArray[0] = newCommitBatchInfo; + + IExecutor.StoredBatchInfo memory wrongGenesisStoredBatchInfo = genesisStoredBatchInfo; + wrongGenesisStoredBatchInfo.timestamp = 1000; + + vm.prank(validator); + + vm.expectRevert( + abi.encodeWithSelector( + BatchHashMismatch.selector, + keccak256(abi.encode(genesisStoredBatchInfo)), + keccak256(abi.encode(wrongGenesisStoredBatchInfo)) + ) + ); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + wrongGenesisStoredBatchInfo, + newCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + } + + function test_RevertWhen_CommittingWithWrongOrderOfBatches() public { + IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; + wrongNewCommitBatchInfo.batchNumber = 2; // wrong batch number + + IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; + + vm.prank(validator); + + vm.expectRevert(abi.encodeWithSelector(BatchNumberMismatch.selector, uint256(1), uint256(2))); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + wrongNewCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + } + + function test_RevertWhen_CommittingWithWrongNewBatchTimestamp() public { + bytes32 wrongNewBatchTimestamp = Utils.randomBytes32("wrongNewBatchTimestamp"); + bytes[] memory wrongL2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); + + wrongL2Logs[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), + wrongNewBatchTimestamp + ); + + IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; + wrongNewCommitBatchInfo.systemLogs = Utils.encodePacked(wrongL2Logs); + wrongNewCommitBatchInfo.operatorDAInput = operatorDAInput; + + IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; + + vm.prank(validator); + vm.blobhashes(defaultBlobVersionedHashes); + + vm.expectRevert(TimestampError.selector); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + wrongNewCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + } + + function test_RevertWhen_CommittingWithTooSmallNewBatchTimestamp() public { + uint256 wrongNewBatchTimestamp = 1; + bytes[] memory wrongL2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); + wrongL2Logs[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(1, 1) + ); + + IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; + wrongNewCommitBatchInfo.systemLogs = Utils.encodePacked(wrongL2Logs); + wrongNewCommitBatchInfo.timestamp = uint64(wrongNewBatchTimestamp); + wrongNewCommitBatchInfo.operatorDAInput = operatorDAInput; + + IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; + + vm.prank(validator); + vm.blobhashes(defaultBlobVersionedHashes); + + vm.expectRevert(abi.encodeWithSelector(TimeNotReached.selector, 1, 2)); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + wrongNewCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + } + + function test_RevertWhen_CommittingTooBigLastL2BatchTimestamp() public { + uint64 wrongNewBatchTimestamp = 0xffffffff; + bytes[] memory wrongL2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); + wrongL2Logs[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(wrongNewBatchTimestamp, wrongNewBatchTimestamp) + ); + + IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; + wrongNewCommitBatchInfo.systemLogs = Utils.encodePacked(wrongL2Logs); + wrongNewCommitBatchInfo.timestamp = wrongNewBatchTimestamp; + wrongNewCommitBatchInfo.operatorDAInput = operatorDAInput; + + IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; + + vm.prank(validator); + vm.blobhashes(defaultBlobVersionedHashes); + + vm.expectRevert(abi.encodeWithSelector(L2TimestampTooBig.selector)); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + wrongNewCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + } + + function test_RevertWhen_CommittingWithWrongPreviousBatchHash() public { + bytes32 wrongPreviousBatchHash = Utils.randomBytes32("wrongPreviousBatchHash"); + bytes[] memory wrongL2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); + wrongL2Logs[uint256(uint256(SystemLogKey.PREV_BATCH_HASH_KEY))] = Utils.constructL2Log( + true, + L2_SYSTEM_CONTEXT_ADDRESS, + uint256(SystemLogKey.PREV_BATCH_HASH_KEY), + wrongPreviousBatchHash + ); + + IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; + wrongNewCommitBatchInfo.systemLogs = Utils.encodePacked(wrongL2Logs); + wrongNewCommitBatchInfo.operatorDAInput = operatorDAInput; + + IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; + + vm.prank(validator); + vm.blobhashes(defaultBlobVersionedHashes); + + vm.expectRevert(abi.encodeWithSelector(HashMismatch.selector, wrongPreviousBatchHash, bytes32(0))); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + wrongNewCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + } + + function test_RevertWhen_CommittingWithoutProcessingSystemContextLog() public { + bytes[] memory wrongL2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); + delete wrongL2Logs[uint256(uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY))]; + + IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; + wrongNewCommitBatchInfo.systemLogs = Utils.encodePacked(wrongL2Logs); + wrongNewCommitBatchInfo.operatorDAInput = operatorDAInput; + + IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; + + vm.prank(validator); + vm.blobhashes(defaultBlobVersionedHashes); + + vm.expectRevert(abi.encodeWithSelector(MissingSystemLogs.selector, 127, 125)); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + wrongNewCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + } + + function test_RevertWhen_CommittingWithProcessingSystemContextLogTwice() public { + bytes[] memory l2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); + + bytes memory wrongL2Logs = abi.encodePacked( + Utils.encodePacked(l2Logs), + // solhint-disable-next-line func-named-parameters + Utils.constructL2Log( + true, + L2_SYSTEM_CONTEXT_ADDRESS, + uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY), + bytes32("") + ) + ); + + IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; + wrongNewCommitBatchInfo.systemLogs = wrongL2Logs; + wrongNewCommitBatchInfo.operatorDAInput = operatorDAInput; + + IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; + + vm.prank(validator); + vm.blobhashes(defaultBlobVersionedHashes); + + vm.expectRevert(abi.encodeWithSelector(LogAlreadyProcessed.selector, 1)); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + wrongNewCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + } + + function test_RevertWhen_UnexpectedL2ToL1Log() public { + address unexpectedAddress = address(0); + bytes[] memory wrongL2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); + wrongL2Logs[uint256(uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY))] = Utils.constructL2Log( + true, + unexpectedAddress, + uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY), + bytes32("") + ); + + IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; + wrongNewCommitBatchInfo.systemLogs = Utils.encodePacked(wrongL2Logs); + + IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; + + vm.prank(validator); + + vm.expectRevert( + abi.encodeWithSelector( + InvalidLogSender.selector, + address(0), + uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY) + ) + ); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + wrongNewCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + } + + function test_RevertWhen_CommittingWithWrongCanonicalTxHash() public { + bytes32 wrongChainedPriorityHash = Utils.randomBytes32("canonicalTxHash"); + bytes[] memory wrongL2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); + wrongL2Logs[uint256(uint256(SystemLogKey.CHAINED_PRIORITY_TXN_HASH_KEY))] = Utils.constructL2Log( + true, + L2_BOOTLOADER_ADDRESS, + uint256(SystemLogKey.CHAINED_PRIORITY_TXN_HASH_KEY), + wrongChainedPriorityHash + ); + + IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; + wrongNewCommitBatchInfo.systemLogs = Utils.encodePacked(wrongL2Logs); + wrongNewCommitBatchInfo.operatorDAInput = operatorDAInput; + + IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; + + vm.blobhashes(defaultBlobVersionedHashes); + vm.prank(validator); + + vm.expectRevert(abi.encodeWithSelector(HashMismatch.selector, wrongChainedPriorityHash, keccak256(""))); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + wrongNewCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + } + + function test_RevertWhen_CommittingWithWrongNumberOfLayer1txs() public { + bytes[] memory wrongL2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); + wrongL2Logs[uint256(uint256(SystemLogKey.NUMBER_OF_LAYER_1_TXS_KEY))] = Utils.constructL2Log( + true, + L2_BOOTLOADER_ADDRESS, + uint256(SystemLogKey.NUMBER_OF_LAYER_1_TXS_KEY), + bytes32(bytes1(0x01)) + ); + + IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; + wrongNewCommitBatchInfo.systemLogs = Utils.encodePacked(wrongL2Logs); + wrongNewCommitBatchInfo.numberOfLayer1Txs = 2; + wrongNewCommitBatchInfo.operatorDAInput = operatorDAInput; + + IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; + + vm.blobhashes(defaultBlobVersionedHashes); + vm.prank(validator); + + vm.expectRevert(abi.encodeWithSelector(ValueMismatch.selector, uint256(bytes32(bytes1(0x01))), uint256(2))); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + wrongNewCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + } + + function test_RevertWhen_CommittingWithUnknownSystemLogKey() public { + bytes[] memory l2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); + bytes memory wrongL2Logs = abi.encodePacked( + Utils.encodePacked(l2Logs), + // solhint-disable-next-line func-named-parameters + abi.encodePacked(bytes2(0x0001), bytes2(0x0000), L2_SYSTEM_CONTEXT_ADDRESS, uint256(119), bytes32("")) + ); + + IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; + wrongNewCommitBatchInfo.systemLogs = abi.encodePacked(wrongL2Logs); + + IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; + + vm.prank(validator); + + vm.expectRevert(abi.encodeWithSelector(UnexpectedSystemLog.selector, uint256(119))); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + wrongNewCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + } + + function test_RevertWhen_SystemLogIsFromIncorrectAddress() public { + bytes32[7] memory values = [ + bytes32(""), + bytes32(""), + bytes32(""), + bytes32(""), + bytes32(""), + bytes32(""), + bytes32("") + ]; + + for (uint256 i = 0; i < values.length; i++) { + bytes[] memory wrongL2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); + address wrongAddress = makeAddr("randomAddress"); + wrongL2Logs[i] = Utils.constructL2Log(true, wrongAddress, i, values[i]); + + IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; + wrongNewCommitBatchInfo.systemLogs = Utils.encodePacked(wrongL2Logs); + + IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; + + vm.prank(validator); + + vm.expectRevert(abi.encodeWithSelector(InvalidLogSender.selector, wrongAddress, i)); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + wrongNewCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + } + } + + function test_RevertWhen_SystemLogIsMissing() public { + for (uint256 i = 0; i < 7; i++) { + bytes[] memory l2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); + delete l2Logs[i]; + + IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; + wrongNewCommitBatchInfo.systemLogs = Utils.encodePacked(l2Logs); + + IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; + + vm.prank(validator); + + uint256 allLogsProcessed = uint256(127); + vm.expectRevert(abi.encodeWithSelector(MissingSystemLogs.selector, 127, allLogsProcessed ^ (1 << i))); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + wrongNewCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + } + } + + function test_SuccessfullyCommitBatch() public { + bytes32 uncompressedStateDiffHash = Utils.randomBytes32("uncompressedStateDiffHash"); + bytes32 totalL2PubdataHash = Utils.randomBytes32("totalL2PubdataHash"); + uint8 numberOfBlobs = 1; + bytes32[] memory blobsLinearHashes = new bytes32[](1); + blobsLinearHashes[0] = Utils.randomBytes32("blobsLinearHashes"); + + operatorDAInput = abi.encodePacked( + uncompressedStateDiffHash, + totalL2PubdataHash, + numberOfBlobs, + blobsLinearHashes, + bytes1(0x01), + defaultBlobCommitment, + EMPTY_PREPUBLISHED_COMMITMENT + ); + + l2DAValidatorOutputHash = Utils.constructRollupL2DAValidatorOutputHash( + uncompressedStateDiffHash, + totalL2PubdataHash, + uint8(numberOfBlobs), + blobsLinearHashes + ); + + bytes[] memory correctL2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); + correctL2Logs[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) + ); + + IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; + correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); + correctNewCommitBatchInfo.operatorDAInput = operatorDAInput; + + bytes32[] memory blobHashes = new bytes32[](TOTAL_BLOBS_IN_COMMITMENT); + blobHashes[0] = blobsLinearHashes[0]; + + bytes32[] memory blobCommitments = new bytes32[](TOTAL_BLOBS_IN_COMMITMENT); + blobCommitments[0] = keccak256( + abi.encodePacked( + defaultBlobVersionedHashes[0], + abi.encodePacked(defaultBlobOpeningPoint, defaultBlobClaimedValue) + ) + ); + + bytes32 expectedBatchCommitment = Utils.createBatchCommitment( + correctNewCommitBatchInfo, + uncompressedStateDiffHash, + blobCommitments, + blobHashes + ); + + IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; + correctCommitBatchInfoArray[0].operatorDAInput = operatorDAInput; + + vm.prank(validator); + vm.blobhashes(defaultBlobVersionedHashes); + vm.recordLogs(); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + correctCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + + Vm.Log[] memory entries = vm.getRecordedLogs(); + + assertEq(entries.length, 1); + assertEq(entries[0].topics[0], keccak256("BlockCommit(uint256,bytes32,bytes32)")); + assertEq(entries[0].topics[1], bytes32(uint256(1))); // batchNumber + assertEq(entries[0].topics[2], correctNewCommitBatchInfo.newStateRoot); // batchHash + assertEq(entries[0].topics[3], expectedBatchCommitment); // commitment + + uint256 totalBatchesCommitted = getters.getTotalBatchesCommitted(); + assertEq(totalBatchesCommitted, 1); + } + + function test_SuccessfullyCommitBatchWithOneBlob() public { + bytes[] memory correctL2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); + correctL2Logs[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) + ); + + IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; + correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); + correctNewCommitBatchInfo.operatorDAInput = operatorDAInput; + + IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; + correctCommitBatchInfoArray[0].operatorDAInput = operatorDAInput; + + vm.prank(validator); + vm.blobhashes(defaultBlobVersionedHashes); + + vm.recordLogs(); + + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + correctCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + + Vm.Log[] memory entries = vm.getRecordedLogs(); + + assertEq(entries.length, 1); + assertEq(entries[0].topics[0], keccak256("BlockCommit(uint256,bytes32,bytes32)")); + assertEq(entries[0].topics[1], bytes32(uint256(1))); // batchNumber + + uint256 totalBatchesCommitted = getters.getTotalBatchesCommitted(); + assertEq(totalBatchesCommitted, 1); + + vm.clearMockedCalls(); + } + + function test_SuccessfullyCommitBatchWithTwoBlob() public { + bytes32 uncompressedStateDiffHash = Utils.randomBytes32("uncompressedStateDiffHash"); + bytes32 totalL2PubdataHash = Utils.randomBytes32("totalL2PubdataHash"); + uint8 numberOfBlobs = 2; + bytes32[] memory blobsLinearHashes = new bytes32[](2); + blobsLinearHashes[0] = Utils.randomBytes32("blobsLinearHashes1"); + blobsLinearHashes[1] = Utils.randomBytes32("blobsLinearHashes2"); + + bytes memory daInput = abi.encodePacked( + uncompressedStateDiffHash, + totalL2PubdataHash, + numberOfBlobs, + blobsLinearHashes, + bytes1(0x01), + defaultBlobCommitment, + EMPTY_PREPUBLISHED_COMMITMENT, + defaultBlobCommitment, + EMPTY_PREPUBLISHED_COMMITMENT + ); + + bytes32[] memory blobVersionedHashes = new bytes32[](2); + blobVersionedHashes[0] = defaultBlobVersionedHashes[0]; + blobVersionedHashes[1] = defaultBlobVersionedHashes[0]; + + bytes32 outputHash = Utils.constructRollupL2DAValidatorOutputHash( + uncompressedStateDiffHash, + totalL2PubdataHash, + uint8(numberOfBlobs), + blobsLinearHashes + ); + + bytes[] memory correctL2Logs = Utils.createSystemLogs(outputHash); + correctL2Logs[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) + ); + + IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; + correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); + correctNewCommitBatchInfo.operatorDAInput = daInput; + + IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; + + vm.prank(validator); + vm.blobhashes(blobVersionedHashes); + + vm.recordLogs(); + + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + correctCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + + Vm.Log[] memory entries = vm.getRecordedLogs(); + + assertEq(entries.length, 1); + assertEq(entries[0].topics[0], keccak256("BlockCommit(uint256,bytes32,bytes32)")); + assertEq(entries[0].topics[1], bytes32(uint256(1))); // batchNumber + + uint256 totalBatchesCommitted = getters.getTotalBatchesCommitted(); + assertEq(totalBatchesCommitted, 1); + + vm.clearMockedCalls(); + } + + function test_RevertWhen_CommittingBatchMoreThanOneBatch() public { + IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; + + IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](2); + correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; + correctCommitBatchInfoArray[1] = correctNewCommitBatchInfo; + + vm.prank(validator); + + vm.expectRevert(abi.encodeWithSelector(CanOnlyProcessOneBatch.selector)); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + correctCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + } + + function test_RevertWhen_EmptyPubdataCommitments() public { + bytes memory operatorDAInput = "\x01"; + + bytes[] memory correctL2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); + correctL2Logs[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) + ); + + IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; + correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); + + IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; + correctCommitBatchInfoArray[0].operatorDAInput = operatorDAInput; + + vm.prank(validator); + + vm.expectRevert( + abi.encodeWithSelector(OperatorDAInputTooSmall.selector, operatorDAInput.length, BLOB_DATA_OFFSET) + ); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + correctCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + } + + function test_RevertWhen_PartialPubdataCommitment() public { + bytes[] memory correctL2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); + correctL2Logs[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) + ); + + IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; + correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); + correctNewCommitBatchInfo.operatorDAInput = operatorDAInput; + + bytes32[] memory blobsLinearHashes = new bytes32[](1); + blobsLinearHashes[0] = Utils.randomBytes32("blobsLinearHashes"); + + bytes memory daInput = abi.encodePacked( + Utils.randomBytes32("uncompressedStateDiffHash"), + Utils.randomBytes32("totalL2PubdataHash"), + uint8(1), + blobsLinearHashes, + bytes1(0x01), + bytes("") + ); + + IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; + correctCommitBatchInfoArray[0].operatorDAInput = daInput; + + vm.prank(validator); + vm.blobhashes(defaultBlobVersionedHashes); + + vm.expectRevert(InvalidPubdataCommitmentsSize.selector); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + correctCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + } + + function test_RevertWhen_TooManyPubdataCommitments() public { + bytes32[] memory blobsLinearHashes = new bytes32[](1); + blobsLinearHashes[0] = Utils.randomBytes32("blobsLinearHashes"); + + bytes memory daInput = abi.encodePacked( + Utils.randomBytes32("uncompressedStateDiffHash"), + Utils.randomBytes32("totalL2PubdataHash"), + uint8(1), + blobsLinearHashes, + bytes1(0x01), + defaultBlobCommitment, + EMPTY_PREPUBLISHED_COMMITMENT, + defaultBlobCommitment, + EMPTY_PREPUBLISHED_COMMITMENT + ); + + bytes[] memory correctL2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); + correctL2Logs[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) + ); + + IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; + correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); + + IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; + correctCommitBatchInfoArray[0].operatorDAInput = daInput; + + vm.prank(validator); + + vm.expectRevert(InvalidPubdataCommitmentsSize.selector); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + correctCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + } + + function test_RevertWhen_NotEnoughPubdataCommitments() public { + bytes[] memory correctL2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); + correctL2Logs[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) + ); + + IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; + correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); + + IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; + correctCommitBatchInfoArray[0].operatorDAInput = operatorDAInput; + + bytes32[] memory versionedHashes = new bytes32[](2); + versionedHashes[0] = defaultBlobVersionedHashes[0]; + versionedHashes[1] = 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563; + + vm.prank(validator); + vm.blobhashes(versionedHashes); + + vm.expectRevert(abi.encodeWithSelector(NonEmptyBlobVersionHash.selector, uint256(1))); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + correctCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + + vm.clearMockedCalls(); + } + + function test_RevertWhen_BlobDoesNotExist() public { + vm.mockCall(blobVersionedHashRetriever, abi.encode(uint256(0)), abi.encode(bytes32(0))); + + bytes[] memory correctL2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); + correctL2Logs[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) + ); + + IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; + correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); + + IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; + correctCommitBatchInfoArray[0].operatorDAInput = operatorDAInput; + + vm.prank(validator); + + vm.expectRevert(abi.encodeWithSelector(EmptyBlobVersionHash.selector, 0)); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + correctCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + + vm.clearMockedCalls(); + } + + function test_RevertWhen_SecondBlobSentWithoutCommitmentData() public { + bytes[] memory correctL2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); + correctL2Logs[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) + ); + + IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; + correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); + + IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; + correctCommitBatchInfoArray[0].operatorDAInput = operatorDAInput; + + bytes32[] memory blobVersionedHashes = new bytes32[](2); + blobVersionedHashes[0] = defaultBlobVersionedHashes[0]; + blobVersionedHashes[1] = defaultBlobVersionedHashes[0]; + + vm.prank(validator); + vm.blobhashes(blobVersionedHashes); + + vm.expectRevert(abi.encodeWithSelector(NonEmptyBlobVersionHash.selector, uint256(1))); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + correctCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + + vm.clearMockedCalls(); + } + + function test_RevertWhen_SecondBlobLinearHashZeroWithCommitment() public { + bytes32 uncompressedStateDiffHash = Utils.randomBytes32("uncompressedStateDiffHash"); + bytes32 totalL2PubdataHash = Utils.randomBytes32("totalL2PubdataHash"); + uint8 numberOfBlobs = 2; + bytes32[] memory blobsLinearHashes = new bytes32[](2); + blobsLinearHashes[0] = Utils.randomBytes32("blobsLinearHashes1"); + blobsLinearHashes[1] = bytes32(0); + + bytes memory operatorDAInput = abi.encodePacked( + uncompressedStateDiffHash, + totalL2PubdataHash, + numberOfBlobs, + blobsLinearHashes, + bytes1(0x01), + defaultBlobCommitment, + EMPTY_PREPUBLISHED_COMMITMENT, + defaultBlobCommitment, + EMPTY_PREPUBLISHED_COMMITMENT + ); + + bytes32[] memory blobVersionedHashes = new bytes32[](2); + blobVersionedHashes[0] = defaultBlobVersionedHashes[0]; + blobVersionedHashes[1] = defaultBlobVersionedHashes[0]; + + bytes32 outputHash = Utils.constructRollupL2DAValidatorOutputHash( + uncompressedStateDiffHash, + totalL2PubdataHash, + uint8(numberOfBlobs), + blobsLinearHashes + ); + + bytes[] memory correctL2Logs = Utils.createSystemLogs(outputHash); + correctL2Logs[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) + ); + + IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; + correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); + + IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; + correctCommitBatchInfoArray[0].operatorDAInput = operatorDAInput; + + vm.blobhashes(blobVersionedHashes); + vm.prank(validator); + + vm.expectRevert(abi.encodeWithSelector(BlobHashCommitmentError.selector, uint256(1), true, false)); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + correctCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + } + + function test_RevertWhen_SecondBlobLinearHashNotZeroWithEmptyCommitment() public { + bytes32 uncompressedStateDiffHash = Utils.randomBytes32("uncompressedStateDiffHash"); + bytes32 totalL2PubdataHash = Utils.randomBytes32("totalL2PubdataHash"); + uint8 numberOfBlobs = 2; + bytes32[] memory blobsLinearHashes = new bytes32[](2); + blobsLinearHashes[0] = Utils.randomBytes32("blobsLinearHashes1"); + blobsLinearHashes[1] = Utils.randomBytes32("blobsLinearHashes2"); + + bytes memory operatorDAInput = abi.encodePacked( + uncompressedStateDiffHash, + totalL2PubdataHash, + numberOfBlobs, + blobsLinearHashes, + bytes1(0x01), + defaultBlobCommitment, + EMPTY_PREPUBLISHED_COMMITMENT + ); + + bytes32[] memory blobVersionedHashes = new bytes32[](2); + blobVersionedHashes[0] = defaultBlobVersionedHashes[0]; + blobVersionedHashes[1] = defaultBlobVersionedHashes[0]; + + bytes32 outputHash = Utils.constructRollupL2DAValidatorOutputHash( + uncompressedStateDiffHash, + totalL2PubdataHash, + uint8(numberOfBlobs), + blobsLinearHashes + ); + + bytes[] memory correctL2Logs = Utils.createSystemLogs(outputHash); + correctL2Logs[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) + ); + + IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; + correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); + + IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; + correctCommitBatchInfoArray[0].operatorDAInput = operatorDAInput; + + vm.blobhashes(blobVersionedHashes); + vm.prank(validator); + + // It will just panic with array out of bounds + vm.expectRevert(); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + correctCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Executor/Executing.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Executor/Executing.t.sol similarity index 55% rename from l1-contracts/test/foundry/unit/concrete/Executor/Executing.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Executor/Executing.t.sol index 288febf94..e9e0a1639 100644 --- a/l1-contracts/test/foundry/unit/concrete/Executor/Executing.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Executor/Executing.t.sol @@ -4,45 +4,129 @@ pragma solidity 0.8.24; import {Vm} from "forge-std/Test.sol"; import {Utils, L2_SYSTEM_CONTEXT_ADDRESS} from "../Utils/Utils.sol"; -import {ExecutorTest} from "./_Executor_Shared.t.sol"; +import {ExecutorTest, EMPTY_PREPUBLISHED_COMMITMENT, POINT_EVALUATION_PRECOMPILE_RESULT} from "./_Executor_Shared.t.sol"; +import {POINT_EVALUATION_PRECOMPILE_ADDR} from "contracts/common/Config.sol"; import {L2_BOOTLOADER_ADDRESS} from "contracts/common/L2ContractAddresses.sol"; import {COMMIT_TIMESTAMP_NOT_OLDER, REQUIRED_L2_GAS_PRICE_PER_PUBDATA} from "contracts/common/Config.sol"; import {IExecutor, SystemLogKey} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; import {PriorityOperationsRollingHashMismatch, BatchHashMismatch, NonSequentialBatch, CantExecuteUnprovenBatches, QueueIsEmpty, TxHashMismatch} from "contracts/common/L1ContractErrors.sol"; contract ExecutingTest is ExecutorTest { + bytes32 l2DAValidatorOutputHash; + bytes32[] blobVersionedHashes; + + bytes32[] priorityOpsHashes; + bytes32 correctRollingHash; + + function appendPriorityOps() internal { + for (uint256 i = 0; i < priorityOpsHashes.length; i++) { + executor.appendPriorityOp(priorityOpsHashes[i]); + } + } + + function generatePriorityOps() internal { + bytes32[] memory hashes = new bytes32[](2); + hashes[0] = keccak256("hash1"); + hashes[1] = keccak256("hash2"); + + bytes32 rollingHash = keccak256(""); + + for (uint256 i = 0; i < hashes.length; i++) { + rollingHash = keccak256(bytes.concat(rollingHash, hashes[i])); + } + + correctRollingHash = rollingHash; + priorityOpsHashes = hashes; + } + function setUp() public { + generatePriorityOps(); + + bytes1 source = bytes1(0x01); + bytes memory defaultBlobCommitment = Utils.getDefaultBlobCommitment(); + + bytes32 uncompressedStateDiffHash = Utils.randomBytes32("uncompressedStateDiffHash"); + bytes32 totalL2PubdataHash = Utils.randomBytes32("totalL2PubdataHash"); + uint8 numberOfBlobs = 1; + bytes32[] memory blobsLinearHashes = new bytes32[](1); + blobsLinearHashes[0] = Utils.randomBytes32("blobsLinearHashes"); + + bytes memory operatorDAInput = abi.encodePacked( + uncompressedStateDiffHash, + totalL2PubdataHash, + numberOfBlobs, + blobsLinearHashes, + source, + defaultBlobCommitment, + EMPTY_PREPUBLISHED_COMMITMENT + ); + + l2DAValidatorOutputHash = Utils.constructRollupL2DAValidatorOutputHash( + uncompressedStateDiffHash, + totalL2PubdataHash, + uint8(numberOfBlobs), + blobsLinearHashes + ); + + blobVersionedHashes = new bytes32[](1); + blobVersionedHashes[0] = 0x01c024b4740620a5849f95930cefe298933bdf588123ea897cdf0f2462f6d2d5; + + bytes memory precompileInput = Utils.defaultPointEvaluationPrecompileInput(blobVersionedHashes[0]); + vm.mockCall(POINT_EVALUATION_PRECOMPILE_ADDR, precompileInput, POINT_EVALUATION_PRECOMPILE_RESULT); + + // This currently only uses the legacy priority queue, not the priority tree. + executor.setPriorityTreeStartIndex(1); vm.warp(COMMIT_TIMESTAMP_NOT_OLDER + 1); currentTimestamp = block.timestamp; - bytes[] memory correctL2Logs = Utils.createSystemLogs(); + bytes[] memory correctL2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); 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) ); + correctL2Logs[uint256(uint256(SystemLogKey.CHAINED_PRIORITY_TXN_HASH_KEY))] = Utils.constructL2Log( + true, + L2_BOOTLOADER_ADDRESS, + uint256(SystemLogKey.CHAINED_PRIORITY_TXN_HASH_KEY), + correctRollingHash + ); + correctL2Logs[uint256(uint256(SystemLogKey.NUMBER_OF_LAYER_1_TXS_KEY))] = Utils.constructL2Log( + true, + L2_BOOTLOADER_ADDRESS, + uint256(SystemLogKey.NUMBER_OF_LAYER_1_TXS_KEY), + bytes32(priorityOpsHashes.length) + ); bytes memory l2Logs = Utils.encodePacked(correctL2Logs); newCommitBatchInfo.systemLogs = l2Logs; newCommitBatchInfo.timestamp = uint64(currentTimestamp); + newCommitBatchInfo.operatorDAInput = operatorDAInput; + newCommitBatchInfo.priorityOperationsHash = correctRollingHash; + newCommitBatchInfo.numberOfLayer1Txs = priorityOpsHashes.length; IExecutor.CommitBatchInfo[] memory commitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); commitBatchInfoArray[0] = newCommitBatchInfo; vm.prank(validator); + vm.blobhashes(blobVersionedHashes); vm.recordLogs(); - executor.commitBatches(genesisStoredBatchInfo, commitBatchInfoArray); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + commitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); Vm.Log[] memory entries = vm.getRecordedLogs(); newStoredBatchInfo = IExecutor.StoredBatchInfo({ batchNumber: 1, batchHash: entries[0].topics[2], indexRepeatedStorageChanges: 0, - numberOfLayer1Txs: 0, - priorityOperationsHash: keccak256(""), + numberOfLayer1Txs: priorityOpsHashes.length, + priorityOperationsHash: correctRollingHash, l2LogsTreeRoot: 0, timestamp: currentTimestamp, commitment: entries[0].topics[3] @@ -52,10 +136,17 @@ contract ExecutingTest is ExecutorTest { storedBatchInfoArray[0] = newStoredBatchInfo; vm.prank(validator); - executor.proveBatches(genesisStoredBatchInfo, storedBatchInfoArray, proofInput); + (uint256 proveBatchFrom, uint256 proveBatchTo, bytes memory proveData) = Utils.encodeProveBatchesData( + genesisStoredBatchInfo, + storedBatchInfoArray, + proofInput + ); + executor.proveBatchesSharedBridge(uint256(0), proveBatchFrom, proveBatchTo, proveData); } function test_RevertWhen_ExecutingBlockWithWrongBatchNumber() public { + appendPriorityOps(); + IExecutor.StoredBatchInfo memory wrongNewStoredBatchInfo = newStoredBatchInfo; wrongNewStoredBatchInfo.batchNumber = 10; // Correct is 1 @@ -64,10 +155,16 @@ contract ExecutingTest is ExecutorTest { vm.prank(validator); vm.expectRevert(NonSequentialBatch.selector); - executor.executeBatches(storedBatchInfoArray); + (uint256 executeBatchFrom, uint256 executeBatchTo, bytes memory executeData) = Utils.encodeExecuteBatchesData( + storedBatchInfoArray, + Utils.generatePriorityOps(storedBatchInfoArray.length) + ); + executor.executeBatchesSharedBridge(uint256(0), executeBatchFrom, executeBatchTo, executeData); } function test_RevertWhen_ExecutingBlockWithWrongData() public { + appendPriorityOps(); + IExecutor.StoredBatchInfo memory wrongNewStoredBatchInfo = newStoredBatchInfo; wrongNewStoredBatchInfo.timestamp = 0; // incorrect timestamp @@ -82,29 +179,39 @@ contract ExecutingTest is ExecutorTest { keccak256(abi.encode(wrongNewStoredBatchInfo)) ) ); - executor.executeBatches(storedBatchInfoArray); + (uint256 executeBatchFrom, uint256 executeBatchTo, bytes memory executeData) = Utils.encodeExecuteBatchesData( + storedBatchInfoArray, + Utils.generatePriorityOps(storedBatchInfoArray.length) + ); + executor.executeBatchesSharedBridge(uint256(0), executeBatchFrom, executeBatchTo, executeData); } function test_RevertWhen_ExecutingRevertedBlockWithoutCommittingAndProvingAgain() public { + appendPriorityOps(); + vm.prank(validator); - executor.revertBatches(0); + executor.revertBatchesSharedBridge(0, 0); IExecutor.StoredBatchInfo[] memory storedBatchInfoArray = new IExecutor.StoredBatchInfo[](1); storedBatchInfoArray[0] = newStoredBatchInfo; vm.prank(validator); vm.expectRevert(CantExecuteUnprovenBatches.selector); - executor.executeBatches(storedBatchInfoArray); + (uint256 executeBatchFrom, uint256 executeBatchTo, bytes memory executeData) = Utils.encodeExecuteBatchesData( + storedBatchInfoArray, + Utils.generatePriorityOps(storedBatchInfoArray.length) + ); + executor.executeBatchesSharedBridge(uint256(0), executeBatchFrom, executeBatchTo, executeData); } function test_RevertWhen_ExecutingUnavailablePriorityOperationHash() public { vm.prank(validator); - executor.revertBatches(0); + executor.revertBatchesSharedBridge(0, 0); bytes32 arbitraryCanonicalTxHash = Utils.randomBytes32("arbitraryCanonicalTxHash"); bytes32 chainedPriorityTxHash = keccak256(bytes.concat(keccak256(""), arbitraryCanonicalTxHash)); - bytes[] memory correctL2Logs = Utils.createSystemLogs(); + bytes[] memory correctL2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); correctL2Logs[uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY)] = Utils.constructL2Log( true, L2_SYSTEM_CONTEXT_ADDRESS, @@ -133,8 +240,13 @@ contract ExecutingTest is ExecutorTest { correctNewCommitBatchInfoArray[0] = correctNewCommitBatchInfo; vm.prank(validator); + vm.blobhashes(blobVersionedHashes); vm.recordLogs(); - executor.commitBatches(genesisStoredBatchInfo, correctNewCommitBatchInfoArray); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + correctNewCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); Vm.Log[] memory entries = vm.getRecordedLogs(); IExecutor.StoredBatchInfo memory correctNewStoredBatchInfo = newStoredBatchInfo; @@ -147,21 +259,39 @@ contract ExecutingTest is ExecutorTest { correctNewStoredBatchInfoArray[0] = correctNewStoredBatchInfo; vm.prank(validator); - executor.proveBatches(genesisStoredBatchInfo, correctNewStoredBatchInfoArray, proofInput); + uint256 processBatchFrom; + uint256 processBatchTo; + bytes memory processData; + { + (processBatchFrom, processBatchTo, processData) = Utils.encodeProveBatchesData( + genesisStoredBatchInfo, + correctNewStoredBatchInfoArray, + proofInput + ); + executor.proveBatchesSharedBridge(uint256(0), processBatchFrom, processBatchTo, processData); + } vm.prank(validator); vm.expectRevert(QueueIsEmpty.selector); - executor.executeBatches(correctNewStoredBatchInfoArray); + { + (processBatchFrom, processBatchTo, processData) = Utils.encodeExecuteBatchesData( + correctNewStoredBatchInfoArray, + Utils.generatePriorityOps(correctNewStoredBatchInfoArray.length) + ); + executor.executeBatchesSharedBridge(uint256(0), processBatchFrom, processBatchTo, processData); + } } function test_RevertWhen_ExecutingWithUnmatchedPriorityOperationHash() public { + appendPriorityOps(); + vm.prank(validator); - executor.revertBatches(0); + executor.revertBatchesSharedBridge(0, 0); bytes32 arbitraryCanonicalTxHash = Utils.randomBytes32("arbitraryCanonicalTxHash"); bytes32 chainedPriorityTxHash = keccak256(bytes.concat(keccak256(""), arbitraryCanonicalTxHash)); - bytes[] memory correctL2Logs = Utils.createSystemLogs(); + bytes[] memory correctL2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); correctL2Logs[uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY)] = Utils.constructL2Log( true, L2_SYSTEM_CONTEXT_ADDRESS, @@ -189,8 +319,13 @@ contract ExecutingTest is ExecutorTest { correctNewCommitBatchInfoArray[0] = correctNewCommitBatchInfo; vm.prank(validator); + vm.blobhashes(blobVersionedHashes); vm.recordLogs(); - executor.commitBatches(genesisStoredBatchInfo, correctNewCommitBatchInfoArray); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + correctNewCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); Vm.Log[] memory entries = vm.getRecordedLogs(); IExecutor.StoredBatchInfo memory correctNewStoredBatchInfo = newStoredBatchInfo; @@ -203,7 +338,17 @@ contract ExecutingTest is ExecutorTest { correctNewStoredBatchInfoArray[0] = correctNewStoredBatchInfo; vm.prank(validator); - executor.proveBatches(genesisStoredBatchInfo, correctNewStoredBatchInfoArray, proofInput); + uint256 processBatchFrom; + uint256 processBatchTo; + bytes memory processData; + { + (processBatchFrom, processBatchTo, processData) = Utils.encodeProveBatchesData( + genesisStoredBatchInfo, + correctNewStoredBatchInfoArray, + proofInput + ); + executor.proveBatchesSharedBridge(uint256(0), processBatchFrom, processBatchTo, processData); + } bytes32 randomFactoryDeps0 = Utils.randomBytes32("randomFactoryDeps0"); @@ -228,10 +373,19 @@ contract ExecutingTest is ExecutorTest { vm.prank(validator); vm.expectRevert(PriorityOperationsRollingHashMismatch.selector); - executor.executeBatches(correctNewStoredBatchInfoArray); + + { + (processBatchFrom, processBatchTo, processData) = Utils.encodeExecuteBatchesData( + correctNewStoredBatchInfoArray, + Utils.generatePriorityOps(correctNewStoredBatchInfoArray.length) + ); + executor.executeBatchesSharedBridge(uint256(0), processBatchFrom, processBatchTo, processData); + } } function test_RevertWhen_CommittingBlockWithWrongPreviousBatchHash() public { + appendPriorityOps(); + // solhint-disable-next-line func-named-parameters bytes memory correctL2Logs = abi.encodePacked( bytes4(0x00000001), @@ -258,17 +412,33 @@ contract ExecutingTest is ExecutorTest { vm.expectRevert( abi.encodeWithSelector(BatchHashMismatch.selector, storedBatchHash, keccak256(abi.encode(genesisBlock))) ); - executor.commitBatches(genesisBlock, correctNewCommitBatchInfoArray); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisBlock, + correctNewCommitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); } function test_ShouldExecuteBatchesuccessfully() public { + appendPriorityOps(); + IExecutor.StoredBatchInfo[] memory storedBatchInfoArray = new IExecutor.StoredBatchInfo[](1); storedBatchInfoArray[0] = newStoredBatchInfo; vm.prank(validator); - executor.executeBatches(storedBatchInfoArray); + (uint256 executeBatchFrom, uint256 executeBatchTo, bytes memory executeData) = Utils.encodeExecuteBatchesData( + storedBatchInfoArray, + Utils.generatePriorityOps(storedBatchInfoArray.length) + ); + executor.executeBatchesSharedBridge(uint256(0), executeBatchFrom, executeBatchTo, executeData); uint256 totalBlocksExecuted = getters.getTotalBlocksExecuted(); assertEq(totalBlocksExecuted, 1); + + bool isPriorityQueueActive = getters.isPriorityQueueActive(); + assertFalse(isPriorityQueueActive); + + uint256 processed = getters.getFirstUnprocessedPriorityTx(); + assertEq(processed, 2); } } diff --git a/l1-contracts/test/foundry/unit/concrete/Executor/ExecutorProof.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Executor/ExecutorProof.t.sol similarity index 80% rename from l1-contracts/test/foundry/unit/concrete/Executor/ExecutorProof.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Executor/ExecutorProof.t.sol index 6c6d8a935..b8aeaa2de 100644 --- a/l1-contracts/test/foundry/unit/concrete/Executor/ExecutorProof.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Executor/ExecutorProof.t.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.24; 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 {Utils} from "foundry-test/l1/unit/concrete/Utils/Utils.sol"; +import {UtilsFacet} from "foundry-test/l1/unit/concrete/Utils/UtilsFacet.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {ExecutorFacet} from "contracts/state-transition/chain-deps/facets/Executor.sol"; @@ -12,6 +12,8 @@ import {IExecutor, LogProcessingOutput} from "contracts/state-transition/chain-i import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; contract TestExecutorFacet is ExecutorFacet { + constructor() ExecutorFacet(block.chainid) {} + function createBatchCommitment( CommitBatchInfo calldata _newBatchData, bytes32 _stateDiffHash, @@ -31,7 +33,7 @@ contract TestExecutorFacet is ExecutorFacet { function processL2Logs( CommitBatchInfo calldata _newBatch, bytes32 _expectedSystemContractUpgradeTxHash - ) external pure returns (LogProcessingOutput memory logOutput) { + ) external view returns (LogProcessingOutput memory logOutput) { return _processL2Logs(_newBatch, _expectedSystemContractUpgradeTxHash); } @@ -71,8 +73,8 @@ contract ExecutorProofTest is Test { executor = TestExecutorFacet(diamondProxy); utilsFacet = UtilsFacet(diamondProxy); } - - /// This test is based on a block generated in a local system. + // todo + // This test is based on a block generated in a local system. function test_Hashes() public { utilsFacet.util_setL2DefaultAccountBytecodeHash( 0x0100065d134a862a777e50059f5e0fbe68b583f3617a67820f7edda0d7f253a0 @@ -80,6 +82,8 @@ contract ExecutorProofTest is Test { utilsFacet.util_setL2BootloaderBytecodeHash(0x010009416e909e0819593a9806bbc841d25c5cdfed3f4a1523497c6814e5194a); utilsFacet.util_setZkPorterAvailability(false); + bytes[] memory mockSystemLogs = Utils.createSystemLogsWithEmptyDAValidator(); + IExecutor.CommitBatchInfo memory nextBatch = IExecutor.CommitBatchInfo({ // ignored batchNumber: 1, @@ -93,10 +97,8 @@ contract ExecutorProofTest is Test { priorityOperationsHash: 0x167f4ca80269c9520ad951eeeda28dd3deb0715e9e2917461e81a60120a14183, bootloaderHeapInitialContentsHash: 0x540442e48142fa061a81822184f7790e7b69dea92153d38ef623802c6f0411c0, eventsQueueStateHash: 0xda42ab7994d4695a25f4ea8a9a485a592b7a31c20d5dae6363828de86d8826ea, - systemLogs: abi.encodePacked( - hex"00000000000000000000000000000000000000000000800b000000000000000000000000000000000000000000000000000000000000000416914ac26bb9cafa0f1dfaeaab10745a9094e1b60c7076fedf21651d6a25b5740000000a000000000000000000000000000000000000800b0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000651bcde0000000000000000000000000651bcde20001000a00000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000005167f4ca80269c9520ad951eeeda28dd3deb0715e9e2917461e81a60120a141830001000a00000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0001000a00000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000000ee6ee8f50659bd8be3d86c32efb02baa5571cf3b46dd7ea3db733ae181747b8b0001000a0000000000000000000000000000000000008008000000000000000000000000000000000000000000000000000000000000000160fc5fb513ca8e6f6232a7410797954dcb6edbf9081768da24b483aca91c54db0001000a000000000000000000000000000000000000800800000000000000000000000000000000000000000000000000000000000000029a67073c2df8f53087fcfc32d82c98bba591da35df6ce1fb55a23b677d37f9fc000000000000000000000000000000000000000000008011000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080110000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000801100000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008011000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008011000000000000000000000000000000000000000000000000000000000000000b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008011000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000" - ), - pubdataCommitments: abi.encodePacked( + systemLogs: Utils.encodePacked(mockSystemLogs), + operatorDAInput: abi.encodePacked( hex"000000000a000100000000000000000000000000000000000000008001760f6100ddbd86c4d5a58532923e7424d33ffb44145a26171d9b2595a349450b0000000000000000000000000000000000000000000000000000000000000001000100010000000000000000000000000000000000008001a789fe4e2a955eee45d44f408f86203c8f643910bf4888d1fd1465cdbc6376d800000000000000000000000000000000000000000000000000000000000000010001000200000000000000000000000000000000000080016ba43e7c7df11e5a655f22c9bce1b37434afd2bf8fcdb10100a460e6a2c0cc83000000000000000000000000000000000000000000000000000000000000000100010003000000000000000000000000000000000000800156e569838658c17c756aa9f6e40de8f1c41b1a67fea5214ec47869882ecda9bd0000000000000000000000000000000000000000000000000000000000000001000100040000000000000000000000000000000000008001ab5d064ba75c02635fd6e4de7fd8420eda54c4bda05bd61edabe201f2066d38f00000000000000000000000000000000000000000000000000000000000000010001000500000000000000000000000000000000000080015bcb6d7c735023e0884297db5016a6c704e3490ed0671417639313ecea86795b00000000000000000000000000000000000000000000000000000000000000010001000600000000000000000000000000000000000080015ee51b5b7d47fae5811a9f777174bb08d81d78098c8bd9430a7618756a0ceb8b00000000000000000000000000000000000000000000000000000000000000010001000700000000000000000000000000000000000080011ea63171021b9ab0846efbe0a06f7882d76e24a4900c74c14fa1e0bdf313ed560000000000000000000000000000000000000000000000000000000000000001000100080000000000000000000000000000000000008001574537f1665cd9c894d8d9834d32ed291f49ae1165a0e12a79a4937f2425bf70000000000000000000000000000000000000000000000000000000000000000100010009000000000000000000000000000000000000800190558033c8a3f7c20c81e613e00a9d0e678a7a14923e94e7cb99c8621c7918090000000000000000000000000000000000000000000000000000000000000001000000000000000001000c3104003d1291725c657fe486d0e626f562842175a705a9704c0980b40e3d716b95bbf9e8000100005dd96deb789fbc05264165795bf652190645bfae1ce253ce1db17087a898fb1e240ebf0d53563011198fddab33312923ba20f3c56cf1ba18ca5be9c053000100022bd65a924da61271d1dd5080fc640601185125830805e0ceb42f4185e5118fb454a12a3d9e0c1fbb89230f67044cc191e4f18459261233f659c9e2ba5e000100008b9feb52993729436da78b2863dd56d8d757e19c01a2cdcf1940e45ca9979941fa93f5f699afeab75e8b25cfea22004a8d2ea49f057741c2f2b910996d00010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4afee3cea48e96b9bddb544b4569e60736a1f1fe919e223fcc08f74acf3513be1200010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4a8755061217b6a78f5d5f8af6e326e482ebdc57f7144108662d122252ddcc27e7000100045dddc527887dc39b9cd189d6f183f16217393a5d3d3165fead2daeaf4f2d6916280c572561a809555de4a87d7a56d5bcca2c246a389dbb2a24c5639bdb0001000153c0f36532563ba2a10f52b865e558cd1a5eef9a9edd01c1cb23b74aa772beb4f3e3b784609f4e205a09863c0587e63b4b47664022cb34896a1711416b00010003e7842b0b4f4fd8e665883fe9c158ba8d38347840f1da0a75aca1fc284ce2428454b48df9f5551500fc50b63af4741b1cd21d4cfddc69aa46cb78eff45b00010000f183703a165afed04326ad5786316f6fc65b27f1cf17459a52bd1f57f27f896b7429e070ca76e3e33165ec75f6c9f439ee37f3b58822494b1251c8247500010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4a05ea3d0bb218598c42b2e25ae5f6cbc9369b273ee6610450cade89775646b2a08902000000000000000000000000000000008b71d4a184058d07fccac4348ae02a1f663403231b0a40fa2c8c0ff73bdca092890200000000000000000000000000000000ab63c4cebbd508a7d7184f0b9134453eea7a09ca749610d5576f8046241b9cde890200000000000000000000000000000000e58af14be53d8ac56f58ff3e5b07c239bfb549149f067597e9d028f35e3c2b77890200000000000000000000000000000000b78e94980fec3a5f68aa25d0d934084907688e537e82c2942af905aab21413ab890200000000000000000000000000000000c4db460819691e825328b532024bbecdc40394c74307a00bd245fc658b1bd34f0901908827f2052a14b24a10cae1f9e259ead06a89a1d74ff736a54f54ebcf05eeb30901d32d07305b87debd25698d4dfac4c2f986693a4e9d9baff7da37a7b5ca8d01cb0901e73042e5dacff2ce20a720c9c6d694576e4afa7bbbafdc4d409c63b7ca8027b70901760a7405795441aceea3be649a53d02785cb3487b7bd23e3b4888a935cee010d09011f3acf5d6d7bfeab8a7112771866e28c3714e0c315a81ec6a58ab4ad1c3d6eb10901c207b49d14deb3af9bc960d57074e27386285c73248abc5fa1d72aa6e8664fa40901644f0c4e15446d7e5ff363c944b55bd6801a1f38afd984c3427569530cb663210901743be0243628b8e7e8f04c00fc4f88efae001250a7482b31e6a0ec87ee3598e7090171e91721f9918576d760f02f03cac47c6f4003316031848e3c1d99e6e83a47434102d84e69f2f480002d5a6962cccee5d4adb48a36bbbf443a531721484381125937f3001ac5ff875b41022f496efbbdb2007b727eb806c926fb20c8ad087c57422977cebd06373e26d19b640e5fe32c85ff39a904faf736ce00a25420c1d9d705358c134cc601d9d184cb4dfdde7e1cac2bc3d4d38bf9ec44e6210ee6b280123bafc586f77764488cd24c6a77546e5a0fe8bdfb4fa203cfaffc36cce4dd5b8901000000000000000000000000651bcde08e7dd06ac5b73b473be6bc5a51030f4c7437657cb7b29bf376c564b8d1675a5e8903000000000000000000000000651bcde24ba84e1f37d041bc6e55ba396826cc494e84d4815b6db52690422eea7386314f00e8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244c3de2202ccb626ad387d70722e64fbe44562e2f231a290c08532b8d6aba402ff50025fe002039e87b424de2772b82d935f14e2b657429a1bcc04612391ea0330c90ebddefdda48eb2aa7f66ecf7940a280e9ef3fb2e95db0995538440a62af79861004434720529e816fd2e40f8031a8d7471ebcd00351db0787346bcfe8dfad8d2b479093588d0e847efa73a10ce20e4799fb1e46642d65617c7e5213fa04989d92d8903000000000000000000000000651bcde287ded247e1660f827071c7f1371934589751085384fc9f4462c1f1897c5c3eef890100000000000000000000000000000001911dd2ad743ff237d411648a0fe32c6d74eec060716a2a74352f6b1c435b5d670016914ac26bb9cafa0f1dfaeaab10745a9094e1b60c7076fedf21651d6a25b574686a068c708f1bdbefd9e6e454ac2b520fd41c8dcf23ecd4cee978c22f1c1f5f09ff974fe8b575175cefa919a5ba1c0ddf4409be4b16695dc7bd12f6701b99bd2e70a152312ad6f01657413b2eae9287f6b9adad93d5fed1a0dd5e13ec74ce1163146509bfe426f2315a69cb452bf388cccd321eca2746a1adf793b489e5c8f61c40688b7ef3e53defc56c78facf513e511f9f5ba0eb50dbcc745afea3b860da75b394d2d1627b6e2ef54fb7b187d0af61e4532c238f387ecf9f0b466f1d54414100018e519b65c8901b344a480638beadb923fbd3462e475d39acebe559d65ed5cb11a1b25279f1918477c35eec1332ff07001d3f85cf854b70d7552f93ba8e88d581064ca4c0df6ac456c00a0e83898ccd464c63e5008aa1a498cc0646b78eb216d9eeeec76ed0eb0ee6c352f35ca5f0b2edc2ca17d211cc5cb905ba10142f042a6ac836d9cef9a6916635c9a1c1d2dc62a9fe83e2230b506b98e0fded46249008fe28b813907a05ae0d773d8f31e330200e9336e0159034c137ed645fb67ccca8a152312ad6f01657413b2eae9287f6b9adad93d5fee5d8f810abde496ccbeb45a4f3c06af828975163a006257cbf18cefebbfb4cd409025f40404a3d37bba024799ce32d7c2a833aec8474288a26b246afa32b07b4a3ce00577261707065642045746865720000000000000000000000000000000000001a09cf14f266dfe87c4b33e6d934de01f8f7242199fa8783178117218fa033f7ab005745544800000000000000000000000000000000000000000000000000000008289026c5fa173652bd62774824698a6848c63031f853d0e275174552f35df33000577261707065642045746865720000000000000000000000000000000000001a1e59309944cbc900ae848855e10bc929f78e86c2179d6e96cf52bfd520f039200031000000000000000000000000000000000000000000000000000000000000021653a735395136e5494c5426ba972b45e34d36ebcb86ac104c724ab375fcce90a18580ba6aeebc6e6b89d226c79be8927257a436ad11d9c0305b18e9d78cab8f75a3aec2096302b67e3815939e29476fb36a0d8299a1b25279f1918477c35eec1332ff07001d3f85cf85688525f98e4859a9c6939f2d2f92e6b1950ed57e56137d717aca1ccf9754f719a1c7ebe9226d26524400a8959a08f411a727ae7bb68f8febecd89ffe9d84708d24544d452de3e22e62b3b2b872e430839a15115818a152312ad6f01657413b2eae9287f6b9adad93d5fe3fb60af355125687beeb90c066ace76c442b0f963a6afd0e3316fcdd673ad22c09ff30c8a03ec44e5337a1f9d66763cf1b319fdc6d8bc4981e1f47edbd86210614b909ff0cbdceb634b81192417b64d114d535ad3bdba97d6d7e90ee2a79bf1c132d3c2d09ff5cd85060f4ff26eb5b68a6687aee76c1b7a77575fdc86ba49b4faf5041377a79b14de8989f2385a6e23f6bd05a80e0d9231870c15a000142e50adc0d84bff439d0086d9fbab9984f8b27aa208935238a60cc62e7c9bb2ea1709e94c96366b3c40ea4854837c18733e5ac1193b8d8e4070d2eca4441b0378b572bd949ab764fd71c002b759613c3e29d425cf4000100012730c940a81021004e899c6ee4bec02f0667757b9d75a8f0714ce6c157f5940b7664e4f69f01fc530db36965e33599a1348629f07ae2d724007ac36a71a16baac84db583d88e0f3a8c082e3632fcc0e15757f0dcf5234b87af41fdee4c0999c4fe698a8d824415979ab839e6913a975a3055a152312ad6f01657413b2eae9287f6b9adad93d5fe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" ) }); @@ -104,26 +106,24 @@ contract ExecutorProofTest is Test { nextBatch, 0x0000000000000000000000000000000000000000000000000000000000000000 ); - assertEq( - logOutput.stateDiffHash, - 0x9a67073c2df8f53087fcfc32d82c98bba591da35df6ce1fb55a23b677d37f9fc, - "stateDiffHash computation failed" - ); bytes32 nextCommitment = executor.createBatchCommitment( nextBatch, logOutput.stateDiffHash, - new bytes32[](6), - new bytes32[](6) + new bytes32[](16), + new bytes32[](16) ); assertEq( nextCommitment, - 0xa1dcde434352cda8e331e721232ff2d457d4074efae1e3d06ef5b10ffada0c9a, + 0xc83d94049ae723b98705c52a4fdcd767b7818ccd229aa1738ac71dce0d4ff317, "nextCommitment computation failed" ); bytes32 prevCommitment = 0x6ebf945305689a8c3ac993df7f002d41d311a762cd6bf39bb054ead8d1f54404; uint256 result = executor.getBatchProofPublicInput(prevCommitment, nextCommitment); - assertEq(result, 0xAC7931F2C11013FC24963E41B86E5325A79F1150350CB41E4F0876A7, "getBatchProofPublicInput"); + assertEq(result, 0xbe1803fdbeb40f12f953296979313339ac1d8289731a1e0dd3bcd39b, "getBatchProofPublicInput"); } + + // add this to be excluded from coverage report + function test() internal {} } diff --git a/l1-contracts/test/foundry/unit/concrete/Executor/Proving.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Executor/Proving.t.sol similarity index 52% rename from l1-contracts/test/foundry/unit/concrete/Executor/Proving.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Executor/Proving.t.sol index 92f1878f8..73b104186 100644 --- a/l1-contracts/test/foundry/unit/concrete/Executor/Proving.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Executor/Proving.t.sol @@ -4,18 +4,24 @@ pragma solidity 0.8.24; import {Vm} from "forge-std/Test.sol"; import {Utils, L2_SYSTEM_CONTEXT_ADDRESS} from "../Utils/Utils.sol"; -import {ExecutorTest} from "./_Executor_Shared.t.sol"; +import {ExecutorTest, POINT_EVALUATION_PRECOMPILE_RESULT, EMPTY_PREPUBLISHED_COMMITMENT} from "./_Executor_Shared.t.sol"; -import {COMMIT_TIMESTAMP_NOT_OLDER} from "contracts/common/Config.sol"; +import {COMMIT_TIMESTAMP_NOT_OLDER, POINT_EVALUATION_PRECOMPILE_ADDR} from "contracts/common/Config.sol"; import {IExecutor, SystemLogKey} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; import {VerifiedBatchesExceedsCommittedBatches, BatchHashMismatch} from "contracts/common/L1ContractErrors.sol"; contract ProvingTest is ExecutorTest { + bytes32 l2DAValidatorOutputHash; + bytes32[] blobVersionedHashes; + bytes operatorDAInput; + function setUp() public { + setUpCommitBatch(); + vm.warp(COMMIT_TIMESTAMP_NOT_OLDER + 1); currentTimestamp = block.timestamp; - bytes[] memory correctL2Logs = Utils.createSystemLogs(); + bytes[] memory correctL2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); correctL2Logs[uint256(uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY))] = Utils.constructL2Log( true, L2_SYSTEM_CONTEXT_ADDRESS, @@ -27,13 +33,19 @@ contract ProvingTest is ExecutorTest { newCommitBatchInfo.timestamp = uint64(currentTimestamp); newCommitBatchInfo.systemLogs = l2Logs; + newCommitBatchInfo.operatorDAInput = operatorDAInput; IExecutor.CommitBatchInfo[] memory commitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); commitBatchInfoArray[0] = newCommitBatchInfo; vm.prank(validator); + vm.blobhashes(blobVersionedHashes); vm.recordLogs(); - executor.commitBatches(genesisStoredBatchInfo, commitBatchInfoArray); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + commitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); Vm.Log[] memory entries = vm.getRecordedLogs(); newStoredBatchInfo = IExecutor.StoredBatchInfo({ @@ -48,6 +60,40 @@ contract ProvingTest is ExecutorTest { }); } + function setUpCommitBatch() public { + bytes1 source = bytes1(0x01); + bytes memory defaultBlobCommitment = Utils.getDefaultBlobCommitment(); + + bytes32 uncompressedStateDiffHash = Utils.randomBytes32("uncompressedStateDiffHash"); + bytes32 totalL2PubdataHash = Utils.randomBytes32("totalL2PubdataHash"); + uint8 numberOfBlobs = 1; + bytes32[] memory blobsLinearHashes = new bytes32[](1); + blobsLinearHashes[0] = Utils.randomBytes32("blobsLinearHashes"); + + operatorDAInput = abi.encodePacked( + uncompressedStateDiffHash, + totalL2PubdataHash, + numberOfBlobs, + blobsLinearHashes, + source, + defaultBlobCommitment, + EMPTY_PREPUBLISHED_COMMITMENT + ); + + l2DAValidatorOutputHash = Utils.constructRollupL2DAValidatorOutputHash( + uncompressedStateDiffHash, + totalL2PubdataHash, + uint8(numberOfBlobs), + blobsLinearHashes + ); + + blobVersionedHashes = new bytes32[](1); + blobVersionedHashes[0] = 0x01c024b4740620a5849f95930cefe298933bdf588123ea897cdf0f2462f6d2d5; + + bytes memory precompileInput = Utils.defaultPointEvaluationPrecompileInput(blobVersionedHashes[0]); + vm.mockCall(POINT_EVALUATION_PRECOMPILE_ADDR, precompileInput, POINT_EVALUATION_PRECOMPILE_RESULT); + } + function test_RevertWhen_ProvingWithWrongPreviousBlockData() public { IExecutor.StoredBatchInfo memory wrongPreviousStoredBatchInfo = genesisStoredBatchInfo; wrongPreviousStoredBatchInfo.batchNumber = 10; // Correct is 0 @@ -64,7 +110,12 @@ contract ProvingTest is ExecutorTest { keccak256(abi.encode(wrongPreviousStoredBatchInfo)) ) ); - executor.proveBatches(wrongPreviousStoredBatchInfo, storedBatchInfoArray, proofInput); + (uint256 proveBatchFrom, uint256 proveBatchTo, bytes memory proveData) = Utils.encodeProveBatchesData( + wrongPreviousStoredBatchInfo, + storedBatchInfoArray, + proofInput + ); + executor.proveBatchesSharedBridge(uint256(0), proveBatchFrom, proveBatchTo, proveData); } function test_RevertWhen_ProvingWithWrongCommittedBlock() public { @@ -83,12 +134,17 @@ contract ProvingTest is ExecutorTest { keccak256(abi.encode(wrongNewStoredBatchInfo)) ) ); - executor.proveBatches(genesisStoredBatchInfo, storedBatchInfoArray, proofInput); + (uint256 proveBatchFrom, uint256 proveBatchTo, bytes memory proveData) = Utils.encodeProveBatchesData( + genesisStoredBatchInfo, + storedBatchInfoArray, + proofInput + ); + executor.proveBatchesSharedBridge(uint256(0), proveBatchFrom, proveBatchTo, proveData); } function test_RevertWhen_ProvingRevertedBlockWithoutCommittingAgain() public { vm.prank(validator); - executor.revertBatches(0); + executor.revertBatchesSharedBridge(0, 0); IExecutor.StoredBatchInfo[] memory storedBatchInfoArray = new IExecutor.StoredBatchInfo[](1); storedBatchInfoArray[0] = newStoredBatchInfo; @@ -96,7 +152,12 @@ contract ProvingTest is ExecutorTest { vm.prank(validator); vm.expectRevert(VerifiedBatchesExceedsCommittedBatches.selector); - executor.proveBatches(genesisStoredBatchInfo, storedBatchInfoArray, proofInput); + (uint256 proveBatchFrom, uint256 proveBatchTo, bytes memory proveData) = Utils.encodeProveBatchesData( + genesisStoredBatchInfo, + storedBatchInfoArray, + proofInput + ); + executor.proveBatchesSharedBridge(uint256(0), proveBatchFrom, proveBatchTo, proveData); } function test_SuccessfulProve() public { @@ -104,8 +165,12 @@ contract ProvingTest is ExecutorTest { storedBatchInfoArray[0] = newStoredBatchInfo; vm.prank(validator); - - executor.proveBatches(genesisStoredBatchInfo, storedBatchInfoArray, proofInput); + (uint256 proveBatchFrom, uint256 proveBatchTo, bytes memory proveData) = Utils.encodeProveBatchesData( + genesisStoredBatchInfo, + storedBatchInfoArray, + proofInput + ); + executor.proveBatchesSharedBridge(uint256(0), proveBatchFrom, proveBatchTo, proveData); uint256 totalBlocksVerified = getters.getTotalBlocksVerified(); assertEq(totalBlocksVerified, 1); diff --git a/l1-contracts/test/foundry/unit/concrete/Executor/Reverting.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Executor/Reverting.t.sol similarity index 51% rename from l1-contracts/test/foundry/unit/concrete/Executor/Reverting.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Executor/Reverting.t.sol index c6e3ea777..ba2fc4b60 100644 --- a/l1-contracts/test/foundry/unit/concrete/Executor/Reverting.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Executor/Reverting.t.sol @@ -4,18 +4,24 @@ pragma solidity 0.8.24; import {Vm} from "forge-std/Test.sol"; import {Utils, L2_SYSTEM_CONTEXT_ADDRESS} from "../Utils/Utils.sol"; -import {ExecutorTest} from "./_Executor_Shared.t.sol"; +import {ExecutorTest, POINT_EVALUATION_PRECOMPILE_RESULT, EMPTY_PREPUBLISHED_COMMITMENT} from "./_Executor_Shared.t.sol"; -import {COMMIT_TIMESTAMP_NOT_OLDER} from "contracts/common/Config.sol"; +import {COMMIT_TIMESTAMP_NOT_OLDER, POINT_EVALUATION_PRECOMPILE_ADDR} from "contracts/common/Config.sol"; import {IExecutor, SystemLogKey} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; import {RevertedBatchNotAfterNewLastBatch} from "contracts/common/L1ContractErrors.sol"; contract RevertingTest is ExecutorTest { + bytes32 l2DAValidatorOutputHash; + bytes32[] blobVersionedHashes; + bytes operatorDAInput; + function setUp() public { + setUpCommitBatch(); + vm.warp(COMMIT_TIMESTAMP_NOT_OLDER + 1); currentTimestamp = block.timestamp; - bytes[] memory correctL2Logs = Utils.createSystemLogs(); + bytes[] memory correctL2Logs = Utils.createSystemLogs(l2DAValidatorOutputHash); correctL2Logs[uint256(uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY))] = Utils.constructL2Log( true, L2_SYSTEM_CONTEXT_ADDRESS, @@ -26,13 +32,19 @@ contract RevertingTest is ExecutorTest { bytes memory l2Logs = Utils.encodePacked(correctL2Logs); newCommitBatchInfo.timestamp = uint64(currentTimestamp); newCommitBatchInfo.systemLogs = l2Logs; + newCommitBatchInfo.operatorDAInput = operatorDAInput; IExecutor.CommitBatchInfo[] memory commitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); commitBatchInfoArray[0] = newCommitBatchInfo; vm.prank(validator); + vm.blobhashes(blobVersionedHashes); vm.recordLogs(); - executor.commitBatches(genesisStoredBatchInfo, commitBatchInfoArray); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + commitBatchInfoArray + ); + executor.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); Vm.Log[] memory entries = vm.getRecordedLogs(); newStoredBatchInfo = IExecutor.StoredBatchInfo({ @@ -50,14 +62,52 @@ contract RevertingTest is ExecutorTest { storedBatchInfoArray[0] = newStoredBatchInfo; vm.prank(validator); + (uint256 proveBatchFrom, uint256 proveBatchTo, bytes memory proveData) = Utils.encodeProveBatchesData( + genesisStoredBatchInfo, + storedBatchInfoArray, + proofInput + ); + executor.proveBatchesSharedBridge(uint256(0), proveBatchFrom, proveBatchTo, proveData); + } + + function setUpCommitBatch() public { + bytes1 source = bytes1(0x01); + bytes memory defaultBlobCommitment = Utils.getDefaultBlobCommitment(); + + bytes32 uncompressedStateDiffHash = Utils.randomBytes32("uncompressedStateDiffHash"); + bytes32 totalL2PubdataHash = Utils.randomBytes32("totalL2PubdataHash"); + uint8 numberOfBlobs = 1; + bytes32[] memory blobsLinearHashes = new bytes32[](1); + blobsLinearHashes[0] = Utils.randomBytes32("blobsLinearHashes"); + + operatorDAInput = abi.encodePacked( + uncompressedStateDiffHash, + totalL2PubdataHash, + numberOfBlobs, + blobsLinearHashes, + source, + defaultBlobCommitment, + EMPTY_PREPUBLISHED_COMMITMENT + ); + + l2DAValidatorOutputHash = Utils.constructRollupL2DAValidatorOutputHash( + uncompressedStateDiffHash, + totalL2PubdataHash, + uint8(numberOfBlobs), + blobsLinearHashes + ); + + blobVersionedHashes = new bytes32[](1); + blobVersionedHashes[0] = 0x01c024b4740620a5849f95930cefe298933bdf588123ea897cdf0f2462f6d2d5; - executor.proveBatches(genesisStoredBatchInfo, storedBatchInfoArray, proofInput); + bytes memory precompileInput = Utils.defaultPointEvaluationPrecompileInput(blobVersionedHashes[0]); + vm.mockCall(POINT_EVALUATION_PRECOMPILE_ADDR, precompileInput, POINT_EVALUATION_PRECOMPILE_RESULT); } function test_RevertWhen_RevertingMoreBatchesThanAlreadyCommitted() public { vm.prank(validator); vm.expectRevert(RevertedBatchNotAfterNewLastBatch.selector); - executor.revertBatches(10); + executor.revertBatchesSharedBridge(0, 10); } function test_SuccessfulRevert() public { @@ -68,7 +118,7 @@ contract RevertingTest is ExecutorTest { assertEq(totalBlocksVerifiedBefore, 1, "totalBlocksVerifiedBefore"); vm.prank(validator); - executor.revertBatches(0); + executor.revertBatchesSharedBridge(0, 0); uint256 totalBlocksCommitted = getters.getTotalBlocksCommitted(); assertEq(totalBlocksCommitted, 0, "totalBlocksCommitted"); diff --git a/l1-contracts/test/foundry/unit/concrete/Executor/_Executor_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Executor/_Executor_Shared.t.sol similarity index 69% rename from l1-contracts/test/foundry/unit/concrete/Executor/_Executor_Shared.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Executor/_Executor_Shared.t.sol index d81e9cc30..a45d237f0 100644 --- a/l1-contracts/test/foundry/unit/concrete/Executor/_Executor_Shared.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Executor/_Executor_Shared.t.sol @@ -3,29 +3,43 @@ pragma solidity 0.8.24; import {Test} from "forge-std/Test.sol"; -import {Utils, DEFAULT_L2_LOGS_TREE_ROOT_HASH} from "../Utils/Utils.sol"; +import {Utils, DEFAULT_L2_LOGS_TREE_ROOT_HASH, L2_DA_VALIDATOR_ADDRESS} from "../Utils/Utils.sol"; import {COMMIT_TIMESTAMP_NOT_OLDER, ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; import {DummyEraBaseTokenBridge} from "contracts/dev-contracts/test/DummyEraBaseTokenBridge.sol"; -import {DummyStateTransitionManager} from "contracts/dev-contracts/test/DummyStateTransitionManager.sol"; -import {IStateTransitionManager} from "contracts/state-transition/IStateTransitionManager.sol"; +import {DummyChainTypeManager} from "contracts/dev-contracts/test/DummyChainTypeManager.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; import {DiamondInit} from "contracts/state-transition/chain-deps/DiamondInit.sol"; import {DiamondProxy} from "contracts/state-transition/chain-deps/DiamondProxy.sol"; -import {VerifierParams, FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol"; +import {VerifierParams, FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZKChainStorage.sol"; import {TestExecutor} from "contracts/dev-contracts/test/TestExecutor.sol"; +import {ExecutorFacet} from "contracts/state-transition/chain-deps/facets/Executor.sol"; import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol"; import {MailboxFacet} from "contracts/state-transition/chain-deps/facets/Mailbox.sol"; import {InitializeData} from "contracts/state-transition/chain-interfaces/IDiamondInit.sol"; -import {IExecutor} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; +import {IExecutor, TOTAL_BLOBS_IN_COMMITMENT} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; import {IVerifier} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; +import {IL1DAValidator} from "contracts/state-transition/chain-interfaces/IL1DAValidator.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; +import {DummyBridgehub} from "contracts/dev-contracts/test/DummyBridgehub.sol"; +import {MessageRoot} from "contracts/bridgehub/MessageRoot.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; + +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IAssetRouterBase} from "contracts/bridge/asset-router/IAssetRouterBase.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; +import {RollupDAManager} from "contracts/state-transition/data-availability/RollupDAManager.sol"; + +bytes32 constant EMPTY_PREPUBLISHED_COMMITMENT = 0x0000000000000000000000000000000000000000000000000000000000000000; +bytes constant POINT_EVALUATION_PRECOMPILE_RESULT = hex"000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"; contract ExecutorTest is Test { address internal owner; address internal validator; address internal randomSigner; address internal blobVersionedHashRetriever; + address internal l1DAValidator; AdminFacet internal admin; TestExecutor internal executor; GettersFacet internal getters; @@ -35,14 +49,17 @@ contract ExecutorTest is Test { uint256 internal currentTimestamp; IExecutor.CommitBatchInfo internal newCommitBatchInfo; IExecutor.StoredBatchInfo internal newStoredBatchInfo; + DummyEraBaseTokenBridge internal sharedBridge; + address internal rollupL1DAValidator; + MessageRoot internal messageRoot; uint256 eraChainId; IExecutor.StoredBatchInfo internal genesisStoredBatchInfo; - IExecutor.ProofInput internal proofInput; + uint256[] internal proofInput; function getAdminSelectors() private view returns (bytes4[] memory) { - bytes4[] memory selectors = new bytes4[](11); + bytes4[] memory selectors = new bytes4[](12); selectors[0] = admin.setPendingAdmin.selector; selectors[1] = admin.acceptAdmin.selector; selectors[2] = admin.setValidator.selector; @@ -54,15 +71,18 @@ contract ExecutorTest is Test { selectors[8] = admin.executeUpgrade.selector; selectors[9] = admin.freezeDiamond.selector; selectors[10] = admin.unfreezeDiamond.selector; + selectors[11] = admin.setDAValidatorPair.selector; return selectors; } function getExecutorSelectors() private view returns (bytes4[] memory) { - bytes4[] memory selectors = new bytes4[](4); - selectors[0] = executor.commitBatches.selector; - selectors[1] = executor.proveBatches.selector; - selectors[2] = executor.executeBatches.selector; - selectors[3] = executor.revertBatches.selector; + bytes4[] memory selectors = new bytes4[](6); + selectors[0] = executor.commitBatchesSharedBridge.selector; + selectors[1] = executor.proveBatchesSharedBridge.selector; + selectors[2] = executor.executeBatchesSharedBridge.selector; + selectors[3] = executor.revertBatchesSharedBridge.selector; + selectors[4] = executor.setPriorityTreeStartIndex.selector; + selectors[5] = executor.appendPriorityOp.selector; return selectors; } @@ -77,7 +97,7 @@ contract ExecutorTest is Test { selectors[6] = getters.getTotalPriorityTxs.selector; selectors[7] = getters.getFirstUnprocessedPriorityTx.selector; selectors[8] = getters.getPriorityQueueSize.selector; - selectors[9] = getters.priorityQueueFrontOperation.selector; + selectors[9] = getters.getTotalBatchesExecuted.selector; selectors[10] = getters.isValidator.selector; selectors[11] = getters.l2LogsRootHash.selector; selectors[12] = getters.storedBatchHash.selector; @@ -95,8 +115,8 @@ contract ExecutorTest is Test { selectors[24] = getters.isFacetFreezable.selector; selectors[25] = getters.getTotalBatchesCommitted.selector; selectors[26] = getters.getTotalBatchesVerified.selector; - selectors[27] = getters.getTotalBatchesExecuted.selector; - selectors[28] = getters.storedBlockHash.selector; + selectors[27] = getters.storedBlockHash.selector; + selectors[28] = getters.isPriorityQueueActive.selector; return selectors; } @@ -127,18 +147,32 @@ contract ExecutorTest is Test { validator = makeAddr("validator"); randomSigner = makeAddr("randomSigner"); blobVersionedHashRetriever = makeAddr("blobVersionedHashRetriever"); + DummyBridgehub dummyBridgehub = new DummyBridgehub(); + messageRoot = new MessageRoot(IBridgehub(address(dummyBridgehub))); + dummyBridgehub.setMessageRoot(address(messageRoot)); + sharedBridge = new DummyEraBaseTokenBridge(); + + dummyBridgehub.setSharedBridge(address(sharedBridge)); + + vm.mockCall( + address(messageRoot), + abi.encodeWithSelector(MessageRoot.addChainBatchRoot.selector, 9, 1, bytes32(0)), + abi.encode() + ); eraChainId = 9; - executor = new TestExecutor(); - admin = new AdminFacet(); + rollupL1DAValidator = Utils.deployL1RollupDAValidatorBytecode(); + + admin = new AdminFacet(block.chainid, RollupDAManager(address(0))); getters = new GettersFacet(); - mailbox = new MailboxFacet(eraChainId); + executor = new TestExecutor(); + mailbox = new MailboxFacet(eraChainId, block.chainid); - DummyStateTransitionManager stateTransitionManager = new DummyStateTransitionManager(); + DummyChainTypeManager chainTypeManager = new DummyChainTypeManager(); vm.mockCall( - address(stateTransitionManager), - abi.encodeWithSelector(IStateTransitionManager.protocolVersionIsActive.selector), + address(chainTypeManager), + abi.encodeWithSelector(IChainTypeManager.protocolVersionIsActive.selector), abi.encode(bool(true)) ); DiamondInit diamondInit = new DiamondInit(); @@ -161,13 +195,12 @@ contract ExecutorTest is Test { InitializeData memory params = InitializeData({ // TODO REVIEW chainId: eraChainId, - bridgehub: makeAddr("bridgehub"), - stateTransitionManager: address(stateTransitionManager), + bridgehub: address(dummyBridgehub), + chainTypeManager: address(chainTypeManager), protocolVersion: 0, admin: owner, validatorTimelock: validator, - baseToken: ETH_TOKEN_ADDRESS, - baseTokenBridge: address(new DummyEraBaseTokenBridge()), + baseTokenAssetId: DataEncoding.encodeNTVAssetId(block.chainid, ETH_TOKEN_ADDRESS), storedBatchZero: keccak256(abi.encode(genesisStoredBatchInfo)), verifier: IVerifier(testnetVerifier), // verifier verifierParams: VerifierParams({ @@ -225,19 +258,17 @@ contract ExecutorTest is Test { admin = AdminFacet(address(diamondProxy)); // Initiate the token multiplier to enable L1 -> L2 transactions. - vm.prank(address(stateTransitionManager)); + vm.prank(address(chainTypeManager)); admin.setTokenMultiplier(1, 1); - - uint256[] memory recursiveAggregationInput; - uint256[] memory serializedProof; - proofInput = IExecutor.ProofInput(recursiveAggregationInput, serializedProof); + vm.prank(address(owner)); + admin.setDAValidatorPair(address(rollupL1DAValidator), L2_DA_VALIDATOR_ADDRESS); // 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()); + bytes memory l2Logs = Utils.encodePacked(Utils.createSystemLogs(bytes32(0))); newCommitBatchInfo = IExecutor.CommitBatchInfo({ batchNumber: 1, timestamp: uint64(currentTimestamp), @@ -248,8 +279,14 @@ contract ExecutorTest is Test { 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" + operatorDAInput: "\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" }); + + vm.mockCall( + address(sharedBridge), + abi.encodeWithSelector(IL1AssetRouter.bridgehubDepositBaseToken.selector), + abi.encode(true) + ); } // add this to be excluded from coverage report diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Governance/AccessControlRestriction.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Governance/AccessControlRestriction.t.sol new file mode 100644 index 000000000..51699797e --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/Governance/AccessControlRestriction.t.sol @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; + +import "@openzeppelin/contracts-v4/utils/Strings.sol"; +import "forge-std/console.sol"; +import {IChainAdmin} from "contracts/governance/IChainAdmin.sol"; +import {ChainAdmin} from "contracts/governance/ChainAdmin.sol"; +import {AccessControlRestriction} from "contracts/governance/AccessControlRestriction.sol"; +import {IAccessControlRestriction} from "contracts/governance/IAccessControlRestriction.sol"; +import {Utils} from "test/foundry/l1/unit/concrete/Utils/Utils.sol"; +import {ZeroAddress, NoCallsProvided, AccessToFallbackDenied, AccessToFunctionDenied} from "contracts/common/L1ContractErrors.sol"; +import {Call} from "contracts/governance/Common.sol"; + +contract AccessRestrictionTest is Test { + AccessControlRestriction internal restriction; + ChainAdmin internal chainAdmin; + address owner; + address randomCaller; + bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; + + function getChainAdminSelectors() public pure returns (bytes4[] memory) { + bytes4[] memory selectors = new bytes4[](12); + selectors[0] = IChainAdmin.getRestrictions.selector; + selectors[1] = IChainAdmin.isRestrictionActive.selector; + selectors[2] = IChainAdmin.addRestriction.selector; + selectors[3] = IChainAdmin.removeRestriction.selector; + + return selectors; + } + + function setUp() public { + owner = makeAddr("random address"); + randomCaller = makeAddr("random caller"); + + restriction = new AccessControlRestriction(0, owner); + address[] memory restrictions = new address[](1); + restrictions[0] = address(restriction); + + chainAdmin = new ChainAdmin(restrictions); + } + + function test_adminAsAddressZero() public { + vm.expectRevert("AccessControl: 0 default admin"); + new AccessControlRestriction(0, address(0)); + } + + function test_setRequiredRoleForCallZeroTarget(bytes32 role) public { + vm.assume(role != DEFAULT_ADMIN_ROLE); + + vm.startPrank(owner); + vm.expectRevert(abi.encodeWithSelector(ZeroAddress.selector)); + restriction.setRequiredRoleForCall(address(0), bytes4(0), role); + vm.stopPrank(); + } + + function test_setRequiredRoleForCallByNotDefaultAdmin(bytes32 role) public { + vm.assume(role != DEFAULT_ADMIN_ROLE); + + bytes4[] memory chainAdminSelectors = getChainAdminSelectors(); + string memory revertMsg = string( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(uint160(randomCaller), 20), + " is missing role ", + Strings.toHexString(uint256(DEFAULT_ADMIN_ROLE), 32) + ) + ); + + vm.expectRevert(bytes(revertMsg)); + vm.prank(randomCaller); + restriction.setRequiredRoleForCall(address(chainAdmin), chainAdminSelectors[0], role); + } + + function test_setRequiredRoleForCallAccessToFunctionDenied(bytes32 role) public { + vm.assume(role != DEFAULT_ADMIN_ROLE); + + bytes4[] memory chainAdminSelectors = getChainAdminSelectors(); + + vm.startPrank(owner); + restriction.setRequiredRoleForCall(address(chainAdmin), chainAdminSelectors[0], role); + vm.stopPrank(); + + Call memory call = Call({ + target: address(chainAdmin), + value: 0, + data: abi.encodeCall(IChainAdmin.getRestrictions, ()) + }); + + vm.expectRevert( + abi.encodeWithSelector( + AccessToFunctionDenied.selector, + address(chainAdmin), + chainAdminSelectors[0], + randomCaller + ) + ); + restriction.validateCall(call, randomCaller); + } + + function test_setRequiredRoleForCall(bytes32 role) public { + vm.assume(role != DEFAULT_ADMIN_ROLE); + + bytes4[] memory chainAdminSelectors = getChainAdminSelectors(); + + vm.expectEmit(true, true, false, true); + emit IAccessControlRestriction.RoleSet(address(chainAdmin), chainAdminSelectors[0], role); + + vm.startPrank(owner); + restriction.setRequiredRoleForCall(address(chainAdmin), chainAdminSelectors[0], role); + restriction.grantRole(role, randomCaller); + vm.stopPrank(); + + Call memory call = Call({ + target: address(chainAdmin), + value: 0, + data: abi.encodeCall(IChainAdmin.getRestrictions, ()) + }); + restriction.validateCall(call, randomCaller); + } + + function test_setRequiredRoleForFallbackZeroTarget(bytes32 role) public { + vm.assume(role != DEFAULT_ADMIN_ROLE); + + vm.startPrank(owner); + vm.expectRevert(abi.encodeWithSelector(ZeroAddress.selector)); + restriction.setRequiredRoleForFallback(address(0), role); + vm.stopPrank(); + } + + function test_setRequiredRoleForFallbackByNotDefaultAdmin(bytes32 role) public { + vm.assume(role != DEFAULT_ADMIN_ROLE); + + string memory revertMsg = string( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(uint160(randomCaller), 20), + " is missing role ", + Strings.toHexString(uint256(DEFAULT_ADMIN_ROLE), 32) + ) + ); + + vm.expectRevert(bytes(revertMsg)); + vm.prank(randomCaller); + restriction.setRequiredRoleForFallback(address(chainAdmin), role); + } + + function test_setRequiredRoleForFallbackAccessToFallbackDenied(bytes32 role) public { + vm.assume(role != DEFAULT_ADMIN_ROLE); + + vm.startPrank(owner); + restriction.setRequiredRoleForFallback(address(chainAdmin), role); + vm.stopPrank(); + + Call memory call = Call({target: address(chainAdmin), value: 0, data: ""}); + + vm.expectRevert(abi.encodeWithSelector(AccessToFallbackDenied.selector, address(chainAdmin), randomCaller)); + restriction.validateCall(call, randomCaller); + } + + function test_setRequiredRoleForFallback(bytes32 role) public { + vm.assume(role != DEFAULT_ADMIN_ROLE); + + vm.expectEmit(true, false, false, true); + emit IAccessControlRestriction.FallbackRoleSet(address(chainAdmin), role); + + vm.startPrank(owner); + restriction.setRequiredRoleForFallback(address(chainAdmin), role); + restriction.grantRole(role, randomCaller); + vm.stopPrank(); + + Call memory call = Call({target: address(chainAdmin), value: 0, data: ""}); + restriction.validateCall(call, randomCaller); + } + + function test_validateCallFunction(bytes32 role) public { + vm.assume(role != DEFAULT_ADMIN_ROLE); + + bytes4[] memory chainAdminSelectors = getChainAdminSelectors(); + vm.startPrank(owner); + restriction.setRequiredRoleForCall(address(chainAdmin), chainAdminSelectors[0], role); + restriction.grantRole(role, randomCaller); + vm.stopPrank(); + + Call memory call = Call({ + target: address(chainAdmin), + value: 0, + data: abi.encodeCall(IChainAdmin.getRestrictions, ()) + }); + restriction.validateCall(call, randomCaller); + } + + function test_validateCallFallback(bytes32 role) public { + vm.assume(role != DEFAULT_ADMIN_ROLE); + vm.startPrank(owner); + restriction.setRequiredRoleForFallback(address(chainAdmin), role); + restriction.grantRole(role, randomCaller); + vm.stopPrank(); + + Call memory call = Call({target: address(chainAdmin), value: 0, data: ""}); + restriction.validateCall(call, randomCaller); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Governance/Authorization.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Governance/Authorization.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/Governance/Authorization.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Governance/Authorization.t.sol diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Governance/ChainAdmin.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Governance/ChainAdmin.t.sol new file mode 100644 index 000000000..47e9a7912 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/Governance/ChainAdmin.t.sol @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; + +import "@openzeppelin/contracts-v4/utils/Strings.sol"; +import {AccessControlRestriction} from "contracts/governance/AccessControlRestriction.sol"; +import {IChainAdmin} from "contracts/governance/IChainAdmin.sol"; +import {ChainAdmin} from "contracts/governance/ChainAdmin.sol"; +import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; +import {Call} from "contracts/governance/Common.sol"; +import {DummyRestriction} from "contracts/dev-contracts/DummyRestriction.sol"; +import {NotARestriction, NoCallsProvided, RestrictionWasAlreadyPresent, RestrictionWasNotPresent, AccessToFallbackDenied, AccessToFunctionDenied} from "contracts/common/L1ContractErrors.sol"; +import {Utils} from "test/foundry/l1/unit/concrete/Utils/Utils.sol"; + +contract ChainAdminTest is Test { + ChainAdmin internal chainAdmin; + AccessControlRestriction internal restriction; + GettersFacet internal gettersFacet; + DummyRestriction internal dummyRestriction; + + address internal owner; + uint32 internal major; + uint32 internal minor; + uint32 internal patch; + bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; + + function setUp() public { + owner = makeAddr("random address"); + + restriction = new AccessControlRestriction(0, owner); + address[] memory restrictions = new address[](1); + restrictions[0] = address(restriction); + + chainAdmin = new ChainAdmin(restrictions); + + gettersFacet = new GettersFacet(); + dummyRestriction = new DummyRestriction(true); + } + + function test_getRestrictions() public { + address[] memory restrictions = chainAdmin.getRestrictions(); + assertEq(restrictions[0], address(restriction)); + } + + function test_isRestrictionActive() public { + bool isActive = chainAdmin.isRestrictionActive(address(restriction)); + assertEq(isActive, true); + } + + function test_addRestriction() public { + address[] memory restrictions = chainAdmin.getRestrictions(); + + vm.expectEmit(true, false, false, true); + emit IChainAdmin.RestrictionAdded(address(dummyRestriction)); + + vm.prank(address(chainAdmin)); + chainAdmin.addRestriction(address(dummyRestriction)); + } + + function test_addRestrictionRevertInvalidRestriction() public { + vm.startPrank(address(chainAdmin)); + vm.expectRevert(); + chainAdmin.addRestriction(makeAddr("emptyRestriction")); + vm.stopPrank(); + } + + function test_addRestrictionRevertInvalidRestrictionMagic() public { + DummyRestriction badRestriction = new DummyRestriction(false); + + vm.startPrank(address(chainAdmin)); + vm.expectRevert(abi.encodeWithSelector(NotARestriction.selector, address(badRestriction))); + chainAdmin.addRestriction(address(badRestriction)); + vm.stopPrank(); + } + + function test_addRestrictionZeroAddress() public { + address[] memory restrictions = chainAdmin.getRestrictions(); + + vm.prank(address(chainAdmin)); + vm.expectRevert(); + chainAdmin.addRestriction(address(0)); + } + + function test_addRestrictionRevert() public { + vm.startPrank(address(chainAdmin)); + chainAdmin.addRestriction(address(dummyRestriction)); + + vm.expectRevert(abi.encodeWithSelector(RestrictionWasAlreadyPresent.selector, address(dummyRestriction))); + chainAdmin.addRestriction(address(dummyRestriction)); + vm.stopPrank(); + } + + function test_removeRestriction() public { + address[] memory restrictions = chainAdmin.getRestrictions(); + + vm.startPrank(address(chainAdmin)); + chainAdmin.addRestriction(address(dummyRestriction)); + + vm.expectEmit(true, false, false, true); + emit IChainAdmin.RestrictionRemoved(address(dummyRestriction)); + + chainAdmin.removeRestriction(address(dummyRestriction)); + vm.stopPrank(); + } + + function test_removeRestrictionRevert() public { + address[] memory restrictions = chainAdmin.getRestrictions(); + + vm.startPrank(address(chainAdmin)); + chainAdmin.addRestriction(address(dummyRestriction)); + chainAdmin.removeRestriction(address(dummyRestriction)); + + vm.expectRevert(abi.encodeWithSelector(RestrictionWasNotPresent.selector, dummyRestriction)); + chainAdmin.removeRestriction(address(dummyRestriction)); + vm.stopPrank(); + } + + function test_setUpgradeTimestamp(uint256 semverMinorVersionMultiplier, uint256 timestamp) public { + (major, minor, patch) = gettersFacet.getSemverProtocolVersion(); + uint256 protocolVersion = packSemver(major, minor, patch + 1, semverMinorVersionMultiplier); + + vm.expectEmit(true, false, false, true); + emit IChainAdmin.UpdateUpgradeTimestamp(protocolVersion, timestamp); + + vm.prank(address(chainAdmin)); + chainAdmin.setUpgradeTimestamp(protocolVersion, timestamp); + } + + function test_multicallRevertNoCalls() public { + Call[] memory calls = new Call[](0); + + vm.expectRevert(NoCallsProvided.selector); + chainAdmin.multicall(calls, false); + } + + function test_multicallRevertFailedCall() public { + Call[] memory calls = new Call[](1); + calls[0] = Call({target: address(chainAdmin), value: 0, data: abi.encodeCall(gettersFacet.getAdmin, ())}); + + vm.expectRevert(); + vm.prank(owner); + chainAdmin.multicall(calls, true); + } + + function test_validateCallAccessToFunctionDenied(bytes32 role) public { + vm.assume(role != DEFAULT_ADMIN_ROLE); + + Call[] memory calls = new Call[](2); + calls[0] = Call({target: address(gettersFacet), value: 0, data: abi.encodeCall(gettersFacet.getAdmin, ())}); + calls[1] = Call({target: address(gettersFacet), value: 0, data: abi.encodeCall(gettersFacet.getVerifier, ())}); + + vm.prank(owner); + restriction.setRequiredRoleForCall(address(gettersFacet), gettersFacet.getAdmin.selector, role); + + vm.expectRevert( + abi.encodeWithSelector( + AccessToFunctionDenied.selector, + address(gettersFacet), + gettersFacet.getAdmin.selector, + owner + ) + ); + vm.prank(owner); + chainAdmin.multicall(calls, true); + } + + function test_validateCallAccessToFallbackDenied(bytes32 role) public { + vm.assume(role != DEFAULT_ADMIN_ROLE); + + Call[] memory calls = new Call[](2); + calls[0] = Call({target: address(gettersFacet), value: 0, data: ""}); + calls[1] = Call({target: address(gettersFacet), value: 0, data: abi.encodeCall(gettersFacet.getVerifier, ())}); + + vm.prank(owner); + restriction.setRequiredRoleForFallback(address(gettersFacet), role); + + vm.expectRevert(abi.encodeWithSelector(AccessToFallbackDenied.selector, address(gettersFacet), owner)); + vm.prank(owner); + chainAdmin.multicall(calls, true); + } + + function test_multicall() public { + Call[] memory calls = new Call[](2); + calls[0] = Call({target: address(gettersFacet), value: 0, data: abi.encodeCall(gettersFacet.getAdmin, ())}); + calls[1] = Call({target: address(gettersFacet), value: 0, data: abi.encodeCall(gettersFacet.getVerifier, ())}); + + vm.prank(owner); + chainAdmin.multicall(calls, true); + } + + function packSemver( + uint32 major, + uint32 minor, + uint32 patch, + uint256 semverMinorVersionMultiplier + ) public returns (uint256) { + if (major != 0) { + revert("Major version must be 0"); + } + + return minor * semverMinorVersionMultiplier + patch; + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Governance/Executing.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Governance/Executing.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/Governance/Executing.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Governance/Executing.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/Governance/Fallback.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Governance/Fallback.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/Governance/Fallback.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Governance/Fallback.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/Governance/OperationStatus.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Governance/OperationStatus.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/Governance/OperationStatus.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Governance/OperationStatus.t.sol diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol new file mode 100644 index 000000000..d903a91d5 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol @@ -0,0 +1,409 @@ +pragma solidity 0.8.24; + +import "@openzeppelin/contracts-v4/utils/Strings.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; +import {L2TransactionRequestTwoBridgesOuter, BridgehubBurnCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; +import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; +import {ChainTypeManager} from "contracts/state-transition/ChainTypeManager.sol"; +import {DiamondInit} from "contracts/state-transition/chain-deps/DiamondInit.sol"; +import {PermanentRestriction} from "contracts/governance/PermanentRestriction.sol"; +import {IPermanentRestriction} from "contracts/governance/IPermanentRestriction.sol"; +import {NotAllowed, NotEnoughGas, UnsupportedEncodingVersion, InvalidSelector, ZeroAddress, UnallowedImplementation, RemovingPermanentRestriction, CallNotAllowed} from "contracts/common/L1ContractErrors.sol"; +import {Call} from "contracts/governance/Common.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {VerifierParams, FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZKChainStorage.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {AccessControlRestriction} from "contracts/governance/AccessControlRestriction.sol"; +import {ChainAdmin} from "contracts/governance/ChainAdmin.sol"; +import {IChainAdmin} from "contracts/governance/IChainAdmin.sol"; +import {ChainTypeManagerTest} from "test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/_ChainTypeManager_Shared.t.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; +import {ICTMDeploymentTracker} from "contracts/bridgehub/ICTMDeploymentTracker.sol"; +import {IMessageRoot} from "contracts/bridgehub/IMessageRoot.sol"; +import {MessageRoot} from "contracts/bridgehub/MessageRoot.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; +import {IAssetRouterBase} from "contracts/bridge/asset-router/IAssetRouterBase.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts-v4/token/ERC20/extensions/IERC20Metadata.sol"; + +contract TestPermanentRestriction is PermanentRestriction { + constructor(IBridgehub _bridgehub, address _l2AdminFactory) PermanentRestriction(_bridgehub, _l2AdminFactory) {} + + function isAdminOfAChain(address _chain) external view returns (bool) { + return _isAdminOfAChain(_chain); + } + + function getNewAdminFromMigration(Call calldata _call) external view returns (address, bool) { + return _getNewAdminFromMigration(_call); + } +} + +contract PermanentRestrictionTest is ChainTypeManagerTest { + ChainAdmin internal chainAdmin; + AccessControlRestriction internal restriction; + TestPermanentRestriction internal permRestriction; + + address constant L2_FACTORY_ADDR = address(0); + + address internal owner; + address internal hyperchain; + + function setUp() public { + deploy(); + + createNewChainBridgehub(); + + owner = makeAddr("owner"); + hyperchain = chainContractAddress.getHyperchain(chainId); + (permRestriction, ) = _deployPermRestriction(bridgehub, L2_FACTORY_ADDR, owner); + restriction = new AccessControlRestriction(0, owner); + address[] memory restrictions = new address[](1); + restrictions[0] = address(restriction); + chainAdmin = new ChainAdmin(restrictions); + } + + function _deployPermRestriction( + IBridgehub _bridgehub, + address _l2AdminFactory, + address _owner + ) internal returns (TestPermanentRestriction proxy, TestPermanentRestriction impl) { + impl = new TestPermanentRestriction(_bridgehub, _l2AdminFactory); + TransparentUpgradeableProxy tup = new TransparentUpgradeableProxy( + address(impl), + address(uint160(1)), + abi.encodeCall(PermanentRestriction.initialize, (_owner)) + ); + + proxy = TestPermanentRestriction(address(tup)); + } + + function test_ownerAsAddressZero() public { + TestPermanentRestriction impl = new TestPermanentRestriction(bridgehub, L2_FACTORY_ADDR); + vm.expectRevert(ZeroAddress.selector); + new TransparentUpgradeableProxy( + address(impl), + address(uint160(1)), + abi.encodeCall(PermanentRestriction.initialize, (address(0))) + ); + } + + function test_setAllowedAdminImplementation(bytes32 implementationHash) public { + vm.expectEmit(true, false, false, true); + emit IPermanentRestriction.AdminImplementationAllowed(implementationHash, true); + + vm.prank(owner); + permRestriction.setAllowedAdminImplementation(implementationHash, true); + } + + function test_setAllowedData(bytes memory data) public { + vm.expectEmit(false, false, false, true); + emit IPermanentRestriction.AllowedDataChanged(data, true); + + vm.prank(owner); + permRestriction.setAllowedData(data, true); + } + + function test_setSelectorShouldBeValidated(bytes4 selector) public { + vm.expectEmit(true, false, false, true); + emit IPermanentRestriction.SelectorValidationChanged(selector, true); + + vm.prank(owner); + permRestriction.setSelectorShouldBeValidated(selector, true); + } + + function isAddressAdmin(address chainAddr, address _potentialAdmin) internal returns (bool) { + // The permanent restriction compares it only against the msg.sender, + // so we have to use `prank` to test the function + vm.prank(_potentialAdmin); + return permRestriction.isAdminOfAChain(chainAddr); + } + + function test_isAdminOfAChainIsAddressZero() public { + assertFalse(permRestriction.isAdminOfAChain(address(0))); + } + + function test_isAdminOfAChainNotAHyperchain() public { + assertFalse(permRestriction.isAdminOfAChain(makeAddr("random"))); + } + + function test_isAdminOfAChainOfAChainNotAnAdmin() public { + assertFalse(permRestriction.isAdminOfAChain(hyperchain)); + } + + function test_tryCompareAdminOfAChain() public { + assertTrue(isAddressAdmin(hyperchain, newChainAdmin)); + } + + function test_validateCallTooShortData() public { + Call memory call = Call({target: hyperchain, value: 0, data: ""}); + + vm.startPrank(newChainAdmin); + permRestriction.validateCall(call, owner); + vm.stopPrank(); + } + + function test_validateCallSetPendingAdminUnallowedImplementation() public { + Call memory call = Call({ + target: hyperchain, + value: 0, + data: abi.encodeWithSelector(IAdmin.setPendingAdmin.selector, owner) + }); + + vm.expectRevert(abi.encodeWithSelector(UnallowedImplementation.selector, owner.codehash)); + + vm.startPrank(newChainAdmin); + permRestriction.validateCall(call, owner); + vm.stopPrank(); + } + + function test_validateCallSetPendingAdminRemovingPermanentRestriction() public { + vm.prank(owner); + permRestriction.setAllowedAdminImplementation(address(chainAdmin).codehash, true); + + Call memory call = Call({ + target: hyperchain, + value: 0, + data: abi.encodeWithSelector(IAdmin.setPendingAdmin.selector, address(chainAdmin)) + }); + + vm.expectRevert(RemovingPermanentRestriction.selector); + + vm.startPrank(newChainAdmin); + permRestriction.validateCall(call, owner); + vm.stopPrank(); + } + + function test_validateCallSetPendingAdmin() public { + vm.prank(owner); + permRestriction.setAllowedAdminImplementation(address(chainAdmin).codehash, true); + + vm.prank(address(chainAdmin)); + chainAdmin.addRestriction(address(permRestriction)); + + Call memory call = Call({ + target: hyperchain, + value: 0, + data: abi.encodeWithSelector(IAdmin.setPendingAdmin.selector, address(chainAdmin)) + }); + + vm.startPrank(newChainAdmin); + permRestriction.validateCall(call, owner); + vm.stopPrank(); + } + + function test_validateCallNotValidatedSelector() public { + Call memory call = Call({ + target: hyperchain, + value: 0, + data: abi.encodeWithSelector(IAdmin.acceptAdmin.selector) + }); + + vm.startPrank(newChainAdmin); + permRestriction.validateCall(call, owner); + vm.stopPrank(); + } + + function test_validateCallCallNotAllowed() public { + vm.prank(owner); + permRestriction.setSelectorShouldBeValidated(IAdmin.acceptAdmin.selector, true); + Call memory call = Call({ + target: hyperchain, + value: 0, + data: abi.encodeWithSelector(IAdmin.acceptAdmin.selector) + }); + + vm.expectRevert(abi.encodeWithSelector(CallNotAllowed.selector, call.data)); + + vm.startPrank(newChainAdmin); + permRestriction.validateCall(call, owner); + vm.stopPrank(); + } + + function test_validateCall() public { + vm.prank(owner); + permRestriction.setSelectorShouldBeValidated(IAdmin.acceptAdmin.selector, true); + Call memory call = Call({ + target: hyperchain, + value: 0, + data: abi.encodeWithSelector(IAdmin.acceptAdmin.selector) + }); + + vm.prank(owner); + permRestriction.setAllowedData(call.data, true); + + vm.startPrank(newChainAdmin); + permRestriction.validateCall(call, owner); + vm.stopPrank(); + } + + function _encodeMigraationCall( + bool correctTarget, + bool correctSelector, + bool correctSecondBridge, + bool correctEncodingVersion, + bool correctAssetId, + address l2Admin + ) internal returns (Call memory call) { + if (!correctTarget) { + call.target = address(0); + return call; + } + call.target = address(bridgehub); + + if (!correctSelector) { + call.data = hex"00000000"; + return call; + } + + L2TransactionRequestTwoBridgesOuter memory outer = L2TransactionRequestTwoBridgesOuter({ + chainId: chainId, + mintValue: 0, + l2Value: 0, + l2GasLimit: 0, + l2GasPerPubdataByteLimit: 0, + refundRecipient: address(0), + secondBridgeAddress: address(0), + secondBridgeValue: 0, + secondBridgeCalldata: hex"" + }); + if (!correctSecondBridge) { + call.data = abi.encodeCall(Bridgehub.requestL2TransactionTwoBridges, (outer)); + // 0 is not correct second bridge + return call; + } + outer.secondBridgeAddress = sharedBridge; + + uint8 encoding = correctEncodingVersion ? 1 : 12; + + bytes32 chainAssetId = correctAssetId ? bridgehub.ctmAssetIdFromChainId(chainId) : bytes32(0); + + bytes memory bridgehubData = abi.encode( + BridgehubBurnCTMAssetData({ + // Gateway chain id, we do not need it + chainId: 0, + ctmData: abi.encode(l2Admin, hex""), + chainData: abi.encode(IZKChain(IBridgehub(bridgehub).getZKChain(chainId)).getProtocolVersion()) + }) + ); + outer.secondBridgeCalldata = abi.encodePacked(bytes1(encoding), abi.encode(chainAssetId, bridgehubData)); + + call.data = abi.encodeCall(Bridgehub.requestL2TransactionTwoBridges, (outer)); + } + + function assertInvalidMigrationCall(Call memory call) public { + (address newAdmin, bool migration) = permRestriction.getNewAdminFromMigration(call); + assertFalse(migration); + assertEq(newAdmin, address(0)); + } + + function test_tryGetNewAdminFromMigrationRevertWhenInvalidSelector() public { + Call memory call = _encodeMigraationCall(false, true, true, true, true, address(0)); + + assertInvalidMigrationCall(call); + } + + function test_tryGetNewAdminFromMigrationRevertWhenNotBridgehub() public { + Call memory call = _encodeMigraationCall(true, false, true, true, true, address(0)); + + assertInvalidMigrationCall(call); + } + + function test_tryGetNewAdminFromMigrationRevertWhenNotSharedBridge() public { + Call memory call = _encodeMigraationCall(true, true, false, true, true, address(0)); + + assertInvalidMigrationCall(call); + } + + function test_tryGetNewAdminFromMigrationRevertWhenIncorrectEncoding() public { + Call memory call = _encodeMigraationCall(true, true, true, false, true, address(0)); + + assertInvalidMigrationCall(call); + } + + function test_tryGetNewAdminFromMigrationRevertWhenIncorrectAssetId() public { + Call memory call = _encodeMigraationCall(true, true, true, true, false, address(0)); + + assertInvalidMigrationCall(call); + } + + function test_tryGetNewAdminFromMigrationShouldWorkCorrectly() public { + address l2Addr = makeAddr("l2Addr"); + Call memory call = _encodeMigraationCall(true, true, true, true, true, l2Addr); + + (address newAdmin, bool migration) = permRestriction.getNewAdminFromMigration(call); + assertTrue(migration); + assertEq(newAdmin, l2Addr); + } + + function test_validateMigrationToL2RevertNotAllowed() public { + Call memory call = _encodeMigraationCall(true, true, true, true, true, address(0)); + + vm.expectRevert(abi.encodeWithSelector(NotAllowed.selector, address(0))); + permRestriction.validateCall(call, owner); + } + + function test_validateMigrationToL2() public { + address expectedAddress = L2ContractHelper.computeCreateAddress(L2_FACTORY_ADDR, uint256(0)); + + vm.expectEmit(true, false, false, true); + emit IPermanentRestriction.AllowL2Admin(expectedAddress); + permRestriction.allowL2Admin(uint256(0)); + + Call memory call = _encodeMigraationCall(true, true, true, true, true, expectedAddress); + + // Should not fail + permRestriction.validateCall(call, owner); + } + + function createNewChainBridgehub() internal { + bytes[] memory factoryDeps = new bytes[](0); + vm.stopPrank(); + vm.startPrank(governor); + bridgehub.addChainTypeManager(address(chainContractAddress)); + bridgehub.addTokenAssetId(DataEncoding.encodeNTVAssetId(block.chainid, baseToken)); + bridgehub.setAddresses(sharedBridge, ICTMDeploymentTracker(address(0)), new MessageRoot(bridgehub)); + vm.stopPrank(); + + // ctm deployer address is 0 in this test + vm.startPrank(address(0)); + bridgehub.setCTMAssetAddress( + bytes32(uint256(uint160(address(chainContractAddress)))), + address(chainContractAddress) + ); + vm.stopPrank(); + + address l1Nullifier = makeAddr("l1Nullifier"); + vm.mockCall( + address(sharedBridge), + abi.encodeWithSelector(IL1AssetRouter.L1_NULLIFIER.selector), + abi.encode(l1Nullifier) + ); + vm.mockCall( + address(sharedBridge), + abi.encodeWithSelector(IAssetRouterBase.assetHandlerAddress.selector), + abi.encode(bridgehub) + ); + vm.mockCall( + address(bridgehub), + abi.encodeWithSelector(Bridgehub.baseToken.selector, chainId), + abi.encode(baseToken) + ); + vm.mockCall(address(baseToken), abi.encodeWithSelector(IERC20Metadata.name.selector), abi.encode("TestToken")); + vm.mockCall(address(baseToken), abi.encodeWithSelector(IERC20Metadata.symbol.selector), abi.encode("TT")); + + vm.startPrank(governor); + bridgehub.createNewChain({ + _chainId: chainId, + _chainTypeManager: address(chainContractAddress), + _baseTokenAssetId: DataEncoding.encodeNTVAssetId(block.chainid, baseToken), + _salt: 0, + _admin: newChainAdmin, + _initData: getCTMInitData(), + _factoryDeps: factoryDeps + }); + vm.stopPrank(); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Governance/Reentrancy.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Governance/Reentrancy.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/Governance/Reentrancy.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Governance/Reentrancy.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/Governance/SelfUpgrades.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Governance/SelfUpgrades.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/Governance/SelfUpgrades.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Governance/SelfUpgrades.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/Governance/_Governance_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Governance/_Governance_Shared.t.sol similarity index 85% rename from l1-contracts/test/foundry/unit/concrete/Governance/_Governance_Shared.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Governance/_Governance_Shared.t.sol index e7f499254..1c081cd8d 100644 --- a/l1-contracts/test/foundry/unit/concrete/Governance/_Governance_Shared.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Governance/_Governance_Shared.t.sol @@ -6,9 +6,11 @@ import {Test} from "forge-std/Test.sol"; import {Governance} from "contracts/governance/Governance.sol"; import {IGovernance} from "contracts/governance/IGovernance.sol"; +import {Call} from "contracts/governance/Common.sol"; import {EventOnFallback} from "contracts/dev-contracts/EventOnFallback.sol"; import {Forwarder} from "contracts/dev-contracts/Forwarder.sol"; import {RevertFallback} from "contracts/dev-contracts/RevertFallback.sol"; +import {EventOnFallbackTargetExpected} from "../../../../L1TestsErrors.sol"; contract GovernanceTest is Test, EventOnFallback { address internal owner; @@ -46,7 +48,9 @@ contract GovernanceTest is Test, EventOnFallback { function _checkEventBeforeExecution(IGovernance.Operation memory op) private { for (uint256 i = 0; i < op.calls.length; i++) { - require(op.calls[i].target == address(eventOnFallback), "EventOnFallback target expected"); + if (op.calls[i].target != address(eventOnFallback)) { + revert EventOnFallbackTargetExpected(); + } // Check event vm.expectEmit(false, false, false, true); emit Called(address(governance), op.calls[i].value, op.calls[i].data); @@ -58,8 +62,8 @@ contract GovernanceTest is Test, EventOnFallback { uint256 _value, bytes memory _data ) internal pure returns (IGovernance.Operation memory) { - IGovernance.Call[] memory calls = new IGovernance.Call[](1); - calls[0] = IGovernance.Call({target: _target, value: _value, data: _data}); + Call[] memory calls = new Call[](1); + calls[0] = Call({target: _target, value: _value, data: _data}); return IGovernance.Operation({calls: calls, salt: bytes32(0), predecessor: bytes32(0)}); } diff --git a/l1-contracts/test/foundry/unit/concrete/Utils/Utils.sol b/l1-contracts/test/foundry/l1/unit/concrete/Utils/Utils.sol similarity index 61% rename from l1-contracts/test/foundry/unit/concrete/Utils/Utils.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Utils/Utils.sol index 1260334fd..3794ff85a 100644 --- a/l1-contracts/test/foundry/unit/concrete/Utils/Utils.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Utils/Utils.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.24; import {UtilsFacet} from "../Utils/UtilsFacet.sol"; +import "forge-std/console.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {DiamondInit} from "contracts/state-transition/chain-deps/DiamondInit.sol"; import {DiamondProxy} from "contracts/state-transition/chain-deps/DiamondProxy.sol"; @@ -11,18 +12,23 @@ import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol import {ExecutorFacet} from "contracts/state-transition/chain-deps/facets/Executor.sol"; import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; import {MailboxFacet} from "contracts/state-transition/chain-deps/facets/Mailbox.sol"; -import {IVerifier, VerifierParams} from "contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol"; -import {FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol"; +import {IVerifier, VerifierParams} from "contracts/state-transition/chain-deps/ZKChainStorage.sol"; +import {FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZKChainStorage.sol"; import {InitializeData, InitializeDataNewChain} from "contracts/state-transition/chain-interfaces/IDiamondInit.sol"; import {IExecutor, SystemLogKey} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; import {L2CanonicalTransaction} from "contracts/common/Messaging.sol"; +import {DummyBridgehub} from "contracts/dev-contracts/test/DummyBridgehub.sol"; +import {PriorityOpsBatchInfo} from "contracts/state-transition/libraries/PriorityTree.sol"; +import {InvalidBlobCommitmentsLength, InvalidBlobHashesLength} from "test/foundry/L1TestsErrors.sol"; +import {Utils as DeployUtils} from "deploy-scripts/Utils.sol"; bytes32 constant DEFAULT_L2_LOGS_TREE_ROOT_HASH = 0x0000000000000000000000000000000000000000000000000000000000000000; address constant L2_SYSTEM_CONTEXT_ADDRESS = 0x000000000000000000000000000000000000800B; address constant L2_BOOTLOADER_ADDRESS = 0x0000000000000000000000000000000000008001; address constant L2_KNOWN_CODE_STORAGE_ADDRESS = 0x0000000000000000000000000000000000008004; address constant L2_TO_L1_MESSENGER = 0x0000000000000000000000000000000000008008; -address constant PUBDATA_PUBLISHER_ADDRESS = 0x0000000000000000000000000000000000008011; +// constant in tests, but can be arbitrary address in real environments +address constant L2_DA_VALIDATOR_ADDRESS = 0x2f3Bc0cB46C9780990afbf86A60bdf6439DE991C; uint256 constant MAX_NUMBER_OF_BLOBS = 6; uint256 constant TOTAL_BLOBS_IN_COMMITMENT = 16; @@ -55,8 +61,8 @@ library Utils { return abi.encodePacked(servicePrefix, bytes2(0x0000), sender, key, value); } - function createSystemLogs() public pure returns (bytes[] memory) { - bytes[] memory logs = new bytes[](13); + function createSystemLogs(bytes32 _outputHash) public returns (bytes[] memory) { + bytes[] memory logs = new bytes[](7); logs[0] = constructL2Log( true, L2_TO_L1_MESSENGER, @@ -64,64 +70,61 @@ library Utils { bytes32("") ); logs[1] = constructL2Log( - true, - L2_TO_L1_MESSENGER, - uint256(SystemLogKey.TOTAL_L2_TO_L1_PUBDATA_KEY), - 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563 - ); - logs[2] = constructL2Log(true, L2_TO_L1_MESSENGER, uint256(SystemLogKey.STATE_DIFF_HASH_KEY), bytes32("")); - logs[3] = constructL2Log( true, L2_SYSTEM_CONTEXT_ADDRESS, uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY), bytes32("") ); - logs[4] = constructL2Log( - true, - L2_SYSTEM_CONTEXT_ADDRESS, - uint256(SystemLogKey.PREV_BATCH_HASH_KEY), - bytes32("") - ); - logs[5] = constructL2Log( + logs[2] = constructL2Log( true, L2_BOOTLOADER_ADDRESS, uint256(SystemLogKey.CHAINED_PRIORITY_TXN_HASH_KEY), keccak256("") ); - logs[6] = constructL2Log( + logs[3] = constructL2Log( true, L2_BOOTLOADER_ADDRESS, uint256(SystemLogKey.NUMBER_OF_LAYER_1_TXS_KEY), bytes32("") ); - logs[7] = constructL2Log(true, PUBDATA_PUBLISHER_ADDRESS, uint256(SystemLogKey.BLOB_ONE_HASH_KEY), bytes32(0)); - logs[8] = constructL2Log(true, PUBDATA_PUBLISHER_ADDRESS, uint256(SystemLogKey.BLOB_TWO_HASH_KEY), bytes32(0)); - logs[9] = constructL2Log( + logs[4] = constructL2Log( true, - PUBDATA_PUBLISHER_ADDRESS, - uint256(SystemLogKey.BLOB_THREE_HASH_KEY), - bytes32(0) + L2_SYSTEM_CONTEXT_ADDRESS, + uint256(SystemLogKey.PREV_BATCH_HASH_KEY), + bytes32("") ); - logs[10] = constructL2Log( + logs[5] = constructL2Log( true, - PUBDATA_PUBLISHER_ADDRESS, - uint256(SystemLogKey.BLOB_FOUR_HASH_KEY), - bytes32(0) + L2_TO_L1_MESSENGER, + uint256(SystemLogKey.L2_DA_VALIDATOR_OUTPUT_HASH_KEY), + _outputHash ); - logs[11] = constructL2Log( + logs[6] = constructL2Log( true, - PUBDATA_PUBLISHER_ADDRESS, - uint256(SystemLogKey.BLOB_FIVE_HASH_KEY), - bytes32(0) + L2_TO_L1_MESSENGER, + uint256(SystemLogKey.USED_L2_DA_VALIDATOR_ADDRESS_KEY), + bytes32(uint256(uint160(L2_DA_VALIDATOR_ADDRESS))) ); - logs[12] = constructL2Log(true, PUBDATA_PUBLISHER_ADDRESS, uint256(SystemLogKey.BLOB_SIX_HASH_KEY), bytes32(0)); + return logs; } + function createSystemLogsWithEmptyDAValidator() public returns (bytes[] memory) { + bytes[] memory systemLogs = createSystemLogs(bytes32(0)); + systemLogs[uint256(SystemLogKey.USED_L2_DA_VALIDATOR_ADDRESS_KEY)] = constructL2Log( + true, + L2_TO_L1_MESSENGER, + uint256(SystemLogKey.USED_L2_DA_VALIDATOR_ADDRESS_KEY), + bytes32(uint256(0)) + ); + + return systemLogs; + } + function createSystemLogsWithUpgradeTransaction( bytes32 _expectedSystemContractUpgradeTxHash - ) public pure returns (bytes[] memory) { - bytes[] memory logsWithoutUpgradeTx = createSystemLogs(); + ) public returns (bytes[] memory) { + bytes[] memory logsWithoutUpgradeTx = createSystemLogs(bytes32(0)); bytes[] memory logs = new bytes[](logsWithoutUpgradeTx.length + 1); for (uint256 i = 0; i < logsWithoutUpgradeTx.length; i++) { logs[i] = logsWithoutUpgradeTx[i]; @@ -135,6 +138,30 @@ library Utils { return logs; } + function createSystemLogsWithUpgradeTransactionForCTM( + bytes32 _expectedSystemContractUpgradeTxHash, + bytes32 _outputHash + ) public returns (bytes[] memory) { + bytes[] memory logsWithoutUpgradeTx = createSystemLogs(_outputHash); + bytes[] memory logs = new bytes[](logsWithoutUpgradeTx.length + 1); + for (uint256 i = 0; i < logsWithoutUpgradeTx.length; i++) { + logs[i] = logsWithoutUpgradeTx[i]; + } + logs[uint256(SystemLogKey.PREV_BATCH_HASH_KEY)] = constructL2Log( + true, + L2_SYSTEM_CONTEXT_ADDRESS, + uint256(SystemLogKey.PREV_BATCH_HASH_KEY), + bytes32(uint256(0x01)) + ); + 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({ @@ -161,18 +188,7 @@ library Utils { bootloaderHeapInitialContentsHash: randomBytes32("bootloaderHeapInitialContentsHash"), eventsQueueStateHash: randomBytes32("eventsQueueStateHash"), systemLogs: abi.encode(randomBytes32("systemLogs")), - pubdataCommitments: abi.encodePacked(uint256(0)) - }); - } - - function createProofInput() public pure returns (IExecutor.ProofInput memory) { - uint256[] memory recursiveAggregationInput; - uint256[] memory serializedProof; - - return - IExecutor.ProofInput({ - recursiveAggregationInput: recursiveAggregationInput, - serializedProof: serializedProof + operatorDAInput: abi.encodePacked(uint256(0)) }); } @@ -184,8 +200,42 @@ library Utils { return result; } + function encodeCommitBatchesData( + IExecutor.StoredBatchInfo memory _lastCommittedBatchData, + IExecutor.CommitBatchInfo[] memory _newBatchesData + ) internal pure returns (uint256, uint256, bytes memory) { + return ( + _newBatchesData[0].batchNumber, + _newBatchesData[_newBatchesData.length - 1].batchNumber, + bytes.concat(bytes1(0x00), abi.encode(_lastCommittedBatchData, _newBatchesData)) + ); + } + + function encodeProveBatchesData( + IExecutor.StoredBatchInfo memory _prevBatch, + IExecutor.StoredBatchInfo[] memory _committedBatches, + uint256[] memory _proof + ) internal pure returns (uint256, uint256, bytes memory) { + return ( + _committedBatches[0].batchNumber, + _committedBatches[_committedBatches.length - 1].batchNumber, + bytes.concat(bytes1(0x00), abi.encode(_prevBatch, _committedBatches, _proof)) + ); + } + + function encodeExecuteBatchesData( + IExecutor.StoredBatchInfo[] memory _batchesData, + PriorityOpsBatchInfo[] memory _priorityOpsData + ) internal pure returns (uint256, uint256, bytes memory) { + return ( + _batchesData[0].batchNumber, + _batchesData[_batchesData.length - 1].batchNumber, + bytes.concat(bytes1(0x00), abi.encode(_batchesData, _priorityOpsData)) + ); + } + function getAdminSelectors() public pure returns (bytes4[] memory) { - bytes4[] memory selectors = new bytes4[](11); + bytes4[] memory selectors = new bytes4[](13); selectors[0] = AdminFacet.setPendingAdmin.selector; selectors[1] = AdminFacet.acceptAdmin.selector; selectors[2] = AdminFacet.setValidator.selector; @@ -197,20 +247,22 @@ library Utils { selectors[8] = AdminFacet.executeUpgrade.selector; selectors[9] = AdminFacet.freezeDiamond.selector; selectors[10] = AdminFacet.unfreezeDiamond.selector; + selectors[11] = AdminFacet.genesisUpgrade.selector; + selectors[12] = AdminFacet.setDAValidatorPair.selector; return selectors; } function getExecutorSelectors() public pure 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; + selectors[0] = ExecutorFacet.commitBatchesSharedBridge.selector; + selectors[1] = ExecutorFacet.proveBatchesSharedBridge.selector; + selectors[2] = ExecutorFacet.executeBatchesSharedBridge.selector; + selectors[3] = ExecutorFacet.revertBatchesSharedBridge.selector; return selectors; } function getGettersSelectors() public pure returns (bytes4[] memory) { - bytes4[] memory selectors = new bytes4[](29); + bytes4[] memory selectors = new bytes4[](31); selectors[0] = GettersFacet.getVerifier.selector; selectors[1] = GettersFacet.getAdmin.selector; selectors[2] = GettersFacet.getPendingAdmin.selector; @@ -220,7 +272,7 @@ library Utils { selectors[6] = GettersFacet.getTotalPriorityTxs.selector; selectors[7] = GettersFacet.getFirstUnprocessedPriorityTx.selector; selectors[8] = GettersFacet.getPriorityQueueSize.selector; - selectors[9] = GettersFacet.priorityQueueFrontOperation.selector; + selectors[9] = GettersFacet.getL2SystemContractsUpgradeTxHash.selector; selectors[10] = GettersFacet.isValidator.selector; selectors[11] = GettersFacet.l2LogsRootHash.selector; selectors[12] = GettersFacet.storedBatchHash.selector; @@ -239,7 +291,9 @@ library Utils { selectors[25] = GettersFacet.getTotalBatchesCommitted.selector; selectors[26] = GettersFacet.getTotalBatchesVerified.selector; selectors[27] = GettersFacet.getTotalBatchesExecuted.selector; - selectors[28] = GettersFacet.getL2SystemContractsUpgradeTxHash.selector; + selectors[28] = GettersFacet.getProtocolVersion.selector; + selectors[29] = GettersFacet.getPriorityTreeRoot.selector; + selectors[30] = GettersFacet.getChainId.selector; return selectors; } @@ -252,53 +306,52 @@ library Utils { selectors[4] = MailboxFacet.requestL2Transaction.selector; selectors[5] = MailboxFacet.bridgehubRequestL2Transaction.selector; selectors[6] = MailboxFacet.l2TransactionBaseCost.selector; - selectors[7] = MailboxFacet.transferEthToSharedBridge.selector; + selectors[7] = MailboxFacet.proveL2LeafInclusion.selector; return selectors; } function getUtilsFacetSelectors() public pure returns (bytes4[] memory) { - bytes4[] memory selectors = new bytes4[](41); + bytes4[] memory selectors = new bytes4[](39); 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; - selectors[36] = UtilsFacet.util_setTransactionFilterer.selector; - selectors[37] = UtilsFacet.util_setBaseTokenGasPriceMultiplierDenominator.selector; - selectors[38] = UtilsFacet.util_setTotalBatchesExecuted.selector; - selectors[39] = UtilsFacet.util_setL2LogsRootHash.selector; - selectors[40] = UtilsFacet.util_setBaseTokenGasPriceMultiplierNominator.selector; + selectors[5] = UtilsFacet.util_getBaseTokenAssetId.selector; + selectors[6] = UtilsFacet.util_setVerifier.selector; + selectors[7] = UtilsFacet.util_getVerifier.selector; + selectors[8] = UtilsFacet.util_setStoredBatchHashes.selector; + selectors[9] = UtilsFacet.util_getStoredBatchHashes.selector; + selectors[10] = UtilsFacet.util_setVerifierParams.selector; + selectors[11] = UtilsFacet.util_getVerifierParams.selector; + selectors[12] = UtilsFacet.util_setL2BootloaderBytecodeHash.selector; + selectors[13] = UtilsFacet.util_getL2BootloaderBytecodeHash.selector; + selectors[14] = UtilsFacet.util_setL2DefaultAccountBytecodeHash.selector; + selectors[15] = UtilsFacet.util_getL2DefaultAccountBytecodeHash.selector; + selectors[16] = UtilsFacet.util_setPendingAdmin.selector; + selectors[17] = UtilsFacet.util_getPendingAdmin.selector; + selectors[18] = UtilsFacet.util_setAdmin.selector; + selectors[19] = UtilsFacet.util_getAdmin.selector; + selectors[20] = UtilsFacet.util_setValidator.selector; + selectors[21] = UtilsFacet.util_getValidator.selector; + selectors[22] = UtilsFacet.util_setZkPorterAvailability.selector; + selectors[23] = UtilsFacet.util_getZkPorterAvailability.selector; + selectors[24] = UtilsFacet.util_setChainTypeManager.selector; + selectors[25] = UtilsFacet.util_getChainTypeManager.selector; + selectors[26] = UtilsFacet.util_setPriorityTxMaxGasLimit.selector; + selectors[27] = UtilsFacet.util_getPriorityTxMaxGasLimit.selector; + selectors[28] = UtilsFacet.util_setFeeParams.selector; + selectors[29] = UtilsFacet.util_getFeeParams.selector; + selectors[30] = UtilsFacet.util_setProtocolVersion.selector; + selectors[31] = UtilsFacet.util_getProtocolVersion.selector; + selectors[32] = UtilsFacet.util_setIsFrozen.selector; + selectors[33] = UtilsFacet.util_getIsFrozen.selector; + selectors[34] = UtilsFacet.util_setTransactionFilterer.selector; + selectors[35] = UtilsFacet.util_setBaseTokenGasPriceMultiplierDenominator.selector; + selectors[36] = UtilsFacet.util_setTotalBatchesExecuted.selector; + selectors[37] = UtilsFacet.util_setL2LogsRootHash.selector; + selectors[38] = UtilsFacet.util_setBaseTokenGasPriceMultiplierNominator.selector; + return selectors; } @@ -323,17 +376,18 @@ library Utils { }); } - function makeInitializeData(address testnetVerifier) public pure returns (InitializeData memory) { + function makeInitializeData(address testnetVerifier) public returns (InitializeData memory) { + DummyBridgehub dummyBridgehub = new DummyBridgehub(); + return InitializeData({ chainId: 1, - bridgehub: address(0x876543567890), - stateTransitionManager: address(0x1234567890876543567890), + bridgehub: address(dummyBridgehub), + chainTypeManager: address(0x1234567890876543567890), protocolVersion: 0, admin: address(0x32149872498357874258787), validatorTimelock: address(0x85430237648403822345345), - baseToken: address(0x923645439232223445), - baseTokenBridge: address(0x23746765237749923040872834), + baseTokenAssetId: bytes32(uint256(0x923645439232223445)), storedBatchZero: bytes32(0), verifier: makeVerifier(testnetVerifier), verifierParams: makeVerifierParams(), @@ -463,8 +517,12 @@ library Utils { ) internal pure returns (bytes32[] memory blobAuxOutputWords) { // These invariants should be checked by the caller of this function, but we double check // just in case. - require(_blobCommitments.length == MAX_NUMBER_OF_BLOBS, "b10"); - require(_blobHashes.length == MAX_NUMBER_OF_BLOBS, "b11"); + if (_blobCommitments.length != TOTAL_BLOBS_IN_COMMITMENT) { + revert InvalidBlobCommitmentsLength(); + } + if (_blobHashes.length != TOTAL_BLOBS_IN_COMMITMENT) { + revert InvalidBlobHashesLength(); + } // for each blob we have: // linear hash (hash of preimage from system logs) and @@ -476,12 +534,88 @@ library Utils { blobAuxOutputWords = new bytes32[](2 * TOTAL_BLOBS_IN_COMMITMENT); - for (uint256 i = 0; i < MAX_NUMBER_OF_BLOBS; i++) { + for (uint256 i = 0; i < TOTAL_BLOBS_IN_COMMITMENT; i++) { blobAuxOutputWords[i * 2] = _blobHashes[i]; blobAuxOutputWords[i * 2 + 1] = _blobCommitments[i]; } } + function constructRollupL2DAValidatorOutputHash( + bytes32 _stateDiffHash, + bytes32 _totalPubdataHash, + uint8 _blobsAmount, + bytes32[] memory _blobHashes + ) public pure returns (bytes32) { + return keccak256(abi.encodePacked(_stateDiffHash, _totalPubdataHash, _blobsAmount, _blobHashes)); + } + + function getDefaultBlobCommitment() public pure returns (bytes memory) { + bytes16 blobOpeningPoint = 0x7142c5851421a2dc03dde0aabdb0ffdb; + bytes32 blobClaimedValue = 0x1e5eea3bbb85517461c1d1c7b84c7c2cec050662a5e81a71d5d7e2766eaff2f0; + bytes + memory commitment = hex"ad5a32c9486ad7ab553916b36b742ed89daffd4538d95f4fc8a6c5c07d11f4102e34b3c579d9b4eb6c295a78e484d3bf"; + bytes + memory blobProof = hex"b7565b1cf204d9f35cec98a582b8a15a1adff6d21f3a3a6eb6af5a91f0a385c069b34feb70bea141038dc7faca5ed364"; + + return abi.encodePacked(blobOpeningPoint, blobClaimedValue, commitment, blobProof); + } + + function defaultPointEvaluationPrecompileInput(bytes32 _versionedHash) public view returns (bytes memory) { + return + abi.encodePacked( + _versionedHash, + bytes32(uint256(uint128(0x7142c5851421a2dc03dde0aabdb0ffdb))), // opening point + abi.encodePacked( + bytes32(0x1e5eea3bbb85517461c1d1c7b84c7c2cec050662a5e81a71d5d7e2766eaff2f0), // claimed value + hex"ad5a32c9486ad7ab553916b36b742ed89daffd4538d95f4fc8a6c5c07d11f4102e34b3c579d9b4eb6c295a78e484d3bf", // commitment + hex"b7565b1cf204d9f35cec98a582b8a15a1adff6d21f3a3a6eb6af5a91f0a385c069b34feb70bea141038dc7faca5ed364" // proof + ) + ); + } + + function emptyData() internal pure returns (PriorityOpsBatchInfo[] calldata _empty) { + assembly { + _empty.offset := 0 + _empty.length := 0 + } + } + + function generatePriorityOps(uint256 len) internal pure returns (PriorityOpsBatchInfo[] memory _ops) { + _ops = new PriorityOpsBatchInfo[](len); + bytes32[] memory empty; + PriorityOpsBatchInfo memory info = PriorityOpsBatchInfo({leftPath: empty, rightPath: empty, itemHashes: empty}); + + for (uint256 i = 0; i < len; ++i) { + _ops[i] = info; + } + } + + function deployL1RollupDAValidatorBytecode() internal returns (address) { + bytes memory bytecode = DeployUtils.readRollupDAValidatorBytecode(); + + return deployViaCreate(bytecode); + } + + /** + * @dev Deploys contract using CREATE. + */ + function deployViaCreate(bytes memory _bytecode) internal returns (address addr) { + if (_bytecode.length == 0) { + revert("Bytecode is not set"); + } + + assembly { + // Allocate memory for the bytecode + let size := mload(_bytecode) // Load the size of the bytecode + let ptr := add(_bytecode, 0x20) // Skip the length prefix (32 bytes) + + // Create the contract + addr := create(0, ptr, size) + } + + require(addr != address(0), "Deployment failed"); + } + // 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/l1/unit/concrete/Utils/Utils.t.sol similarity index 66% rename from l1-contracts/test/foundry/unit/concrete/Utils/Utils.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Utils/Utils.t.sol index b7659295c..919c14a60 100644 --- a/l1-contracts/test/foundry/unit/concrete/Utils/Utils.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Utils/Utils.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.24; 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 {Utils, L2_TO_L1_MESSENGER, L2_SYSTEM_CONTEXT_ADDRESS, L2_BOOTLOADER_ADDRESS, L2_TO_L1_MESSENGER, L2_DA_VALIDATOR_ADDRESS} from "./Utils.sol"; import {SystemLogKey} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; // solhint-enable max-line-length @@ -43,9 +43,9 @@ contract UtilsTest is Test { } function test_CreateSystemLogs() public { - bytes[] memory logs = Utils.createSystemLogs(); + bytes[] memory logs = Utils.createSystemLogs(bytes32(0)); - assertEq(logs.length, 13, "logs length should be correct"); + assertEq(logs.length, 7, "logs length should be correct"); assertEq( logs[0], @@ -62,16 +62,21 @@ contract UtilsTest is Test { logs[1], Utils.constructL2Log( true, - L2_TO_L1_MESSENGER, - uint256(SystemLogKey.TOTAL_L2_TO_L1_PUBDATA_KEY), - 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563 + L2_SYSTEM_CONTEXT_ADDRESS, + uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY), + bytes32("") ), "log[1] should be correct" ); assertEq( logs[2], - Utils.constructL2Log(true, L2_TO_L1_MESSENGER, uint256(SystemLogKey.STATE_DIFF_HASH_KEY), bytes32("")), + Utils.constructL2Log( + true, + L2_BOOTLOADER_ADDRESS, + uint256(SystemLogKey.CHAINED_PRIORITY_TXN_HASH_KEY), + keccak256("") + ), "log[2] should be correct" ); @@ -79,8 +84,8 @@ contract UtilsTest is Test { logs[3], Utils.constructL2Log( true, - L2_SYSTEM_CONTEXT_ADDRESS, - uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY), + L2_BOOTLOADER_ADDRESS, + uint256(SystemLogKey.NUMBER_OF_LAYER_1_TXS_KEY), bytes32("") ), "log[3] should be correct" @@ -101,9 +106,9 @@ contract UtilsTest is Test { logs[5], Utils.constructL2Log( true, - L2_BOOTLOADER_ADDRESS, - uint256(SystemLogKey.CHAINED_PRIORITY_TXN_HASH_KEY), - keccak256("") + L2_TO_L1_MESSENGER, + uint256(SystemLogKey.L2_DA_VALIDATOR_OUTPUT_HASH_KEY), + bytes32(0) ), "log[5] should be correct" ); @@ -112,53 +117,12 @@ contract UtilsTest is Test { logs[6], Utils.constructL2Log( true, - L2_BOOTLOADER_ADDRESS, - uint256(SystemLogKey.NUMBER_OF_LAYER_1_TXS_KEY), - bytes32("") + L2_TO_L1_MESSENGER, + uint256(SystemLogKey.USED_L2_DA_VALIDATOR_ADDRESS_KEY), + bytes32(uint256(uint160(L2_DA_VALIDATOR_ADDRESS))) ), "log[6] should be correct" ); - - assertEq( - logs[7], - Utils.constructL2Log(true, PUBDATA_PUBLISHER_ADDRESS, uint256(SystemLogKey.BLOB_ONE_HASH_KEY), bytes32(0)), - "log[7] should be correct" - ); - - assertEq( - logs[8], - Utils.constructL2Log(true, PUBDATA_PUBLISHER_ADDRESS, uint256(SystemLogKey.BLOB_TWO_HASH_KEY), bytes32(0)), - "log[8] should be correct" - ); - - assertEq( - logs[9], - Utils.constructL2Log( - true, - PUBDATA_PUBLISHER_ADDRESS, - uint256(SystemLogKey.BLOB_THREE_HASH_KEY), - bytes32(0) - ), - "log[9] should be correct" - ); - - assertEq( - logs[10], - Utils.constructL2Log(true, PUBDATA_PUBLISHER_ADDRESS, uint256(SystemLogKey.BLOB_FOUR_HASH_KEY), bytes32(0)), - "log[8] should be correct" - ); - - assertEq( - logs[11], - Utils.constructL2Log(true, PUBDATA_PUBLISHER_ADDRESS, uint256(SystemLogKey.BLOB_FIVE_HASH_KEY), bytes32(0)), - "log[11] should be correct" - ); - - assertEq( - logs[12], - Utils.constructL2Log(true, PUBDATA_PUBLISHER_ADDRESS, uint256(SystemLogKey.BLOB_SIX_HASH_KEY), bytes32(0)), - "log[12] should be correct" - ); } // add this to be excluded from coverage report diff --git a/l1-contracts/test/foundry/unit/concrete/Utils/UtilsFacet.sol b/l1-contracts/test/foundry/l1/unit/concrete/Utils/UtilsFacet.sol similarity index 84% rename from l1-contracts/test/foundry/unit/concrete/Utils/UtilsFacet.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Utils/UtilsFacet.sol index ce9e659a0..08afae8c9 100644 --- a/l1-contracts/test/foundry/unit/concrete/Utils/UtilsFacet.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Utils/UtilsFacet.sol @@ -2,12 +2,12 @@ pragma solidity 0.8.24; -import {IVerifier, VerifierParams} from "contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol"; -import {FeeParams} from "contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol"; -import {ZkSyncHyperchainBase} from "contracts/state-transition/chain-deps/facets/ZkSyncHyperchainBase.sol"; +import {IVerifier, VerifierParams} from "contracts/state-transition/chain-deps/ZKChainStorage.sol"; +import {FeeParams} from "contracts/state-transition/chain-deps/ZKChainStorage.sol"; +import {ZKChainBase} from "contracts/state-transition/chain-deps/facets/ZKChainBase.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; -contract UtilsFacet is ZkSyncHyperchainBase { +contract UtilsFacet is ZKChainBase { function util_setChainId(uint256 _chainId) external { s.chainId = _chainId; } @@ -24,20 +24,12 @@ contract UtilsFacet is ZkSyncHyperchainBase { return s.bridgehub; } - function util_setBaseToken(address _baseToken) external { - s.baseToken = _baseToken; + function util_setBaseToken(bytes32 _baseTokenAssetId) external { + s.baseTokenAssetId = _baseTokenAssetId; } - 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_getBaseTokenAssetId() external view returns (bytes32) { + return s.baseTokenAssetId; } function util_setVerifier(IVerifier _verifier) external { @@ -120,12 +112,12 @@ contract UtilsFacet is ZkSyncHyperchainBase { return s.zkPorterIsAvailable; } - function util_setStateTransitionManager(address _stateTransitionManager) external { - s.stateTransitionManager = _stateTransitionManager; + function util_setChainTypeManager(address _chainTypeManager) external { + s.chainTypeManager = _chainTypeManager; } - function util_getStateTransitionManager() external view returns (address) { - return s.stateTransitionManager; + function util_getChainTypeManager() external view returns (address) { + return s.chainTypeManager; } function util_setPriorityTxMaxGasLimit(uint256 _priorityTxMaxGasLimit) external { diff --git a/l1-contracts/test/foundry/unit/concrete/ValidatorTimelock/ValidatorTimelock.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/ValidatorTimelock/ValidatorTimelock.t.sol similarity index 58% rename from l1-contracts/test/foundry/unit/concrete/ValidatorTimelock/ValidatorTimelock.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/ValidatorTimelock/ValidatorTimelock.t.sol index a2217cc13..67c769ca2 100644 --- a/l1-contracts/test/foundry/unit/concrete/ValidatorTimelock/ValidatorTimelock.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/ValidatorTimelock/ValidatorTimelock.t.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.24; import {Test} from "forge-std/Test.sol"; import {Utils} from "../Utils/Utils.sol"; import {ValidatorTimelock, IExecutor} from "contracts/state-transition/ValidatorTimelock.sol"; -import {DummyStateTransitionManagerForValidatorTimelock} from "contracts/dev-contracts/test/DummyStateTransitionManagerForValidatorTimelock.sol"; -import {IStateTransitionManager} from "contracts/state-transition/IStateTransitionManager.sol"; +import {DummyChainTypeManagerForValidatorTimelock} from "contracts/dev-contracts/test/DummyChainTypeManagerForValidatorTimelock.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; import {Unauthorized, TimeNotReached} from "contracts/common/L1ContractErrors.sol"; contract ValidatorTimelockTest is Test { @@ -22,7 +22,7 @@ contract ValidatorTimelockTest is Test { error ValidatorDoesNotExist(uint256 _chainId); ValidatorTimelock validator; - DummyStateTransitionManagerForValidatorTimelock stateTransitionManager; + DummyChainTypeManagerForValidatorTimelock chainTypeManager; address owner; address zkSync; @@ -45,10 +45,10 @@ contract ValidatorTimelockTest is Test { lastBatchNumber = 123; executionDelay = 10; - stateTransitionManager = new DummyStateTransitionManagerForValidatorTimelock(owner, zkSync); - validator = new ValidatorTimelock(owner, executionDelay, eraChainId); + chainTypeManager = new DummyChainTypeManagerForValidatorTimelock(owner, zkSync); + validator = new ValidatorTimelock(owner, executionDelay); vm.prank(owner); - validator.setStateTransitionManager(IStateTransitionManager(address(stateTransitionManager))); + validator.setChainTypeManager(IChainTypeManager(address(chainTypeManager))); vm.prank(owner); validator.addValidator(chainId, alice); vm.prank(owner); @@ -92,20 +92,24 @@ contract ValidatorTimelockTest is Test { batchesToCommit[0] = batchToCommit; vm.prank(alice); - validator.commitBatchesSharedBridge(chainId, storedBatch, batchesToCommit); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + storedBatch, + batchesToCommit + ); + validator.commitBatchesSharedBridge(chainId, commitBatchFrom, commitBatchTo, commitData); } - function test_setStateTransitionManager() public { - assert(validator.stateTransitionManager() == IStateTransitionManager(address(stateTransitionManager))); + function test_setChainTypeManager() public { + assert(validator.chainTypeManager() == IChainTypeManager(address(chainTypeManager))); - DummyStateTransitionManagerForValidatorTimelock newManager = new DummyStateTransitionManagerForValidatorTimelock( - bob, - zkSync - ); + DummyChainTypeManagerForValidatorTimelock newManager = new DummyChainTypeManagerForValidatorTimelock( + bob, + zkSync + ); vm.prank(owner); - validator.setStateTransitionManager(IStateTransitionManager(address(newManager))); + validator.setChainTypeManager(IChainTypeManager(address(newManager))); - assert(validator.stateTransitionManager() == IStateTransitionManager(address(newManager))); + assert(validator.chainTypeManager() == IChainTypeManager(address(newManager))); } function test_setExecutionDelay() public { @@ -126,7 +130,11 @@ contract ValidatorTimelockTest is Test { uint64 timestamp = 123456; vm.warp(timestamp); - vm.mockCall(zkSync, abi.encodeWithSelector(IExecutor.commitBatches.selector), abi.encode(eraChainId)); + vm.mockCall( + zkSync, + abi.encodeWithSelector(IExecutor.commitBatchesSharedBridge.selector), + abi.encode(eraChainId) + ); IExecutor.StoredBatchInfo memory storedBatch = Utils.createStoredBatchInfo(); IExecutor.CommitBatchInfo memory batchToCommit = Utils.createCommitBatchInfo(); @@ -135,14 +143,18 @@ contract ValidatorTimelockTest is Test { IExecutor.CommitBatchInfo[] memory batchesToCommit = new IExecutor.CommitBatchInfo[](1); batchesToCommit[0] = batchToCommit; - vm.prank(dan); - validator.commitBatches(storedBatch, batchesToCommit); + vm.prank(alice); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + storedBatch, + batchesToCommit + ); + validator.commitBatchesSharedBridge(chainId, commitBatchFrom, commitBatchTo, commitData); - assert(validator.getCommittedBatchTimestamp(eraChainId, batchNumber) == timestamp); + assert(validator.getCommittedBatchTimestamp(chainId, batchNumber) == timestamp); } function test_commitBatches() public { - vm.mockCall(zkSync, abi.encodeWithSelector(IExecutor.commitBatches.selector), abi.encode(chainId)); + vm.mockCall(zkSync, abi.encodeWithSelector(IExecutor.commitBatchesSharedBridge.selector), abi.encode(chainId)); IExecutor.StoredBatchInfo memory storedBatch = Utils.createStoredBatchInfo(); IExecutor.CommitBatchInfo memory batchToCommit = Utils.createCommitBatchInfo(); @@ -150,93 +162,48 @@ contract ValidatorTimelockTest is Test { 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); + vm.prank(alice); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + storedBatch, + batchesToCommit + ); + validator.commitBatchesSharedBridge(chainId, commitBatchFrom, commitBatchTo, commitData); } function test_revertBatchesSharedBridge() public { - vm.mockCall(zkSync, abi.encodeWithSelector(IExecutor.revertBatches.selector), abi.encode(chainId)); + vm.mockCall(zkSync, abi.encodeWithSelector(IExecutor.revertBatchesSharedBridge.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(); + uint256[] memory proof = new uint256[](0); IExecutor.StoredBatchInfo[] memory batchesToProve = new IExecutor.StoredBatchInfo[](1); batchesToProve[0] = batchToProve; vm.mockCall( zkSync, - abi.encodeWithSelector(IExecutor.proveBatches.selector), + abi.encodeWithSelector(IExecutor.proveBatchesSharedBridge.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); + (uint256 proveBatchFrom, uint256 proveBatchTo, bytes memory proveData) = Utils.encodeProveBatchesData( + prevBatch, + batchesToProve, + proof + ); + validator.proveBatchesSharedBridge(chainId, proveBatchFrom, proveBatchTo, proveData); } 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)); + vm.mockCall(zkSync, abi.encodeWithSelector(IExecutor.commitBatchesSharedBridge.selector), abi.encode(chainId)); IExecutor.StoredBatchInfo memory storedBatch1 = Utils.createStoredBatchInfo(); IExecutor.CommitBatchInfo memory batchToCommit = Utils.createCommitBatchInfo(); @@ -247,7 +214,11 @@ contract ValidatorTimelockTest is Test { vm.prank(alice); vm.warp(timestamp); - validator.commitBatchesSharedBridge(chainId, storedBatch1, batchesToCommit); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + storedBatch1, + batchesToCommit + ); + validator.commitBatchesSharedBridge(chainId, commitBatchFrom, commitBatchTo, commitData); // Execute batches IExecutor.StoredBatchInfo memory storedBatch2 = Utils.createStoredBatchInfo(); @@ -255,11 +226,19 @@ contract ValidatorTimelockTest is Test { IExecutor.StoredBatchInfo[] memory storedBatches = new IExecutor.StoredBatchInfo[](1); storedBatches[0] = storedBatch2; - vm.mockCall(zkSync, abi.encodeWithSelector(IExecutor.proveBatches.selector), abi.encode(storedBatches)); + vm.mockCall( + zkSync, + abi.encodeWithSelector(IExecutor.proveBatchesSharedBridge.selector), + abi.encode(storedBatches) + ); vm.prank(alice); vm.warp(timestamp + executionDelay + 1); - validator.executeBatchesSharedBridge(chainId, storedBatches); + (uint256 executeBatchFrom, uint256 executeBatchTo, bytes memory executeData) = Utils.encodeExecuteBatchesData( + storedBatches, + Utils.emptyData() + ); + validator.executeBatchesSharedBridge(chainId, executeBatchFrom, executeBatchTo, executeData); } function test_RevertWhen_setExecutionDelayNotOwner() public { @@ -311,17 +290,21 @@ contract ValidatorTimelockTest is Test { vm.prank(bob); vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, bob)); - validator.commitBatches(storedBatch, batchesToCommit); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + storedBatch, + batchesToCommit + ); + validator.commitBatchesSharedBridge(chainId, commitBatchFrom, commitBatchTo, commitData); } - function test_RevertWhen_setStateTransitionManagerNotOwner() public { + function test_RevertWhen_setChainTypeManagerNotOwner() public { vm.expectRevert("Ownable: caller is not the owner"); - validator.setStateTransitionManager(IStateTransitionManager(address(stateTransitionManager))); + validator.setChainTypeManager(IChainTypeManager(address(chainTypeManager))); } function test_RevertWhen_revertBatchesNotValidator() public { vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, address(this))); - validator.revertBatches(lastBatchNumber); + validator.revertBatchesSharedBridge(uint256(0), lastBatchNumber); } function test_RevertWhen_revertBatchesSharedBridgeNotValidator() public { @@ -329,40 +312,22 @@ contract ValidatorTimelockTest is Test { validator.revertBatchesSharedBridge(chainId, lastBatchNumber); } - function test_RevertWhen_proveBatchesNotValidator() 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.expectRevert(abi.encodeWithSelector(Unauthorized.selector, address(this))); - 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(); + uint256[] memory proof = new uint256[](0); IExecutor.StoredBatchInfo[] memory batchesToProve = new IExecutor.StoredBatchInfo[](1); batchesToProve[0] = batchToProve; vm.prank(bob); vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, bob)); - 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(abi.encodeWithSelector(Unauthorized.selector, bob)); - validator.executeBatches(storedBatches); + (uint256 proveBatchFrom, uint256 proveBatchTo, bytes memory proveData) = Utils.encodeProveBatchesData( + prevBatch, + batchesToProve, + proof + ); + validator.proveBatchesSharedBridge(chainId, proveBatchFrom, proveBatchTo, proveData); } function test_RevertWhen_executeBatchesSharedBridgeNotValidator() public { @@ -373,45 +338,18 @@ contract ValidatorTimelockTest is Test { vm.prank(bob); vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, bob)); - 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( - abi.encodeWithSelector(TimeNotReached.selector, timestamp + executionDelay, timestamp + executionDelay - 1) + (uint256 executeBatchFrom, uint256 executeBatchTo, bytes memory executeData) = Utils.encodeExecuteBatchesData( + storedBatches, + Utils.emptyData() ); - validator.executeBatches(storedBatches); + validator.executeBatchesSharedBridge(chainId, executeBatchFrom, executeBatchTo, executeData); } 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)); + vm.mockCall(zkSync, abi.encodeWithSelector(IExecutor.commitBatchesSharedBridge.selector), abi.encode(chainId)); IExecutor.StoredBatchInfo memory storedBatch1 = Utils.createStoredBatchInfo(); IExecutor.CommitBatchInfo memory batchToCommit = Utils.createCommitBatchInfo(); @@ -422,7 +360,11 @@ contract ValidatorTimelockTest is Test { vm.prank(alice); vm.warp(timestamp); - validator.commitBatchesSharedBridge(chainId, storedBatch1, batchesToCommit); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + storedBatch1, + batchesToCommit + ); + validator.commitBatchesSharedBridge(chainId, commitBatchFrom, commitBatchTo, commitData); // Execute batches IExecutor.StoredBatchInfo memory storedBatch2 = Utils.createStoredBatchInfo(); @@ -435,6 +377,10 @@ contract ValidatorTimelockTest is Test { vm.expectRevert( abi.encodeWithSelector(TimeNotReached.selector, timestamp + executionDelay, timestamp + executionDelay - 1) ); - validator.executeBatchesSharedBridge(chainId, storedBatches); + (uint256 executeBatchFrom, uint256 executeBatchTo, bytes memory executeData) = Utils.encodeExecuteBatchesData( + storedBatches, + Utils.emptyData() + ); + validator.executeBatchesSharedBridge(chainId, executeBatchFrom, executeBatchTo, executeData); } } diff --git a/l1-contracts/test/foundry/unit/concrete/Verifier/Verifier.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Verifier/Verifier.t.sol similarity index 90% rename from l1-contracts/test/foundry/unit/concrete/Verifier/Verifier.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/Verifier/Verifier.t.sol index 54ab49974..bd67cfa2b 100644 --- a/l1-contracts/test/foundry/unit/concrete/Verifier/Verifier.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Verifier/Verifier.t.sol @@ -12,7 +12,6 @@ contract VerifierTestTest is Test { uint256[] public publicInputs; uint256[] public serializedProof; - uint256[] public recursiveAggregationInput; Verifier public verifier; @@ -68,7 +67,7 @@ contract VerifierTestTest is Test { } function testShouldVerify() public view { - bool success = verifier.verify(publicInputs, serializedProof, recursiveAggregationInput); + bool success = verifier.verify(publicInputs, serializedProof); assert(success); } @@ -76,7 +75,7 @@ contract VerifierTestTest is Test { uint256[] memory newPublicInputs = publicInputs; newPublicInputs[0] += uint256(bytes32(0xe000000000000000000000000000000000000000000000000000000000000000)); - bool success = verifier.verify(newPublicInputs, serializedProof, recursiveAggregationInput); + bool success = verifier.verify(newPublicInputs, serializedProof); assert(success); } @@ -86,7 +85,7 @@ contract VerifierTestTest is Test { newSerializedProof[1] += Q_MOD; newSerializedProof[1] += Q_MOD; - bool success = verifier.verify(publicInputs, newSerializedProof, recursiveAggregationInput); + bool success = verifier.verify(publicInputs, newSerializedProof); assert(success); } @@ -94,7 +93,7 @@ contract VerifierTestTest is Test { uint256[] memory newSerializedProof = serializedProof; newSerializedProof[22] += R_MOD; - bool success = verifier.verify(publicInputs, newSerializedProof, recursiveAggregationInput); + bool success = verifier.verify(publicInputs, newSerializedProof); assert(success); } @@ -104,14 +103,14 @@ contract VerifierTestTest is Test { newPublicInputs[1] = publicInputs[0]; vm.expectRevert(bytes("loadProof: Proof is invalid")); - verifier.verify(newPublicInputs, serializedProof, recursiveAggregationInput); + verifier.verify(newPublicInputs, serializedProof); } function testEmptyPublicInput_shouldRevert() public { uint256[] memory newPublicInputs; vm.expectRevert(bytes("loadProof: Proof is invalid")); - verifier.verify(newPublicInputs, serializedProof, recursiveAggregationInput); + verifier.verify(newPublicInputs, serializedProof); } function testMoreThan44WordsProof_shouldRevert() public { @@ -123,21 +122,25 @@ contract VerifierTestTest is Test { newSerializedProof[newSerializedProof.length - 1] = serializedProof[serializedProof.length - 1]; vm.expectRevert(bytes("loadProof: Proof is invalid")); - verifier.verify(publicInputs, newSerializedProof, recursiveAggregationInput); + verifier.verify(publicInputs, newSerializedProof); } function testEmptyProof_shouldRevert() public { uint256[] memory newSerializedProof; vm.expectRevert(bytes("loadProof: Proof is invalid")); - verifier.verify(publicInputs, newSerializedProof, recursiveAggregationInput); + verifier.verify(publicInputs, newSerializedProof); } - function testNotEmptyRecursiveAggregationInput_shouldRevert() public { - uint256[] memory newRecursiveAggregationInput = publicInputs; + function testLongerProofInput_shouldRevert() public { + uint256[] memory newSerializedProof = new uint256[](serializedProof.length + 1); + for (uint256 i = 0; i < serializedProof.length; i++) { + newSerializedProof[i] = serializedProof[i]; + } + newSerializedProof[newSerializedProof.length - 1] = publicInputs[0]; vm.expectRevert(bytes("loadProof: Proof is invalid")); - verifier.verify(publicInputs, serializedProof, newRecursiveAggregationInput); + verifier.verify(publicInputs, newSerializedProof); } function testEllipticCurvePointAtInfinity_shouldRevert() public { @@ -146,7 +149,7 @@ contract VerifierTestTest is Test { newSerializedProof[1] = 0; vm.expectRevert(bytes("loadProof: Proof is invalid")); - verifier.verify(publicInputs, newSerializedProof, recursiveAggregationInput); + verifier.verify(publicInputs, newSerializedProof); } function testInvalidPublicInput_shouldRevert() public { @@ -154,7 +157,7 @@ contract VerifierTestTest is Test { newPublicInputs[0] = 0; vm.expectRevert(bytes("invalid quotient evaluation")); - verifier.verify(newPublicInputs, serializedProof, recursiveAggregationInput); + verifier.verify(newPublicInputs, serializedProof); } function testVerificationKeyHash() public virtual { diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Verifier/VerifierRecursive.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Verifier/VerifierRecursive.t.sol new file mode 100644 index 000000000..c23759f35 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/Verifier/VerifierRecursive.t.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {VerifierTestTest} from "./Verifier.t.sol"; +import {VerifierRecursiveTest} from "contracts/dev-contracts/test/VerifierRecursiveTest.sol"; + +contract VerifierRecursiveTestTest is VerifierTestTest { + function setUp() public override { + super.setUp(); + + serializedProof.push(2257920826825449939414463854743099397427742128922725774525544832270890253504); + serializedProof.push(9091218701914748532331969127001446391756173432977615061129552313204917562530); + serializedProof.push(16188304989094043810949359833767911976672882599560690320245309499206765021563); + serializedProof.push(3201093556796962656759050531176732990872300033146738631772984017549903765305); + + verifier = new VerifierRecursiveTest(); + } + + function testMoreThan4WordsRecursiveInput_shouldRevert() public { + uint256[] memory newSerializedProof = new uint256[](serializedProof.length + 1); + + for (uint256 i = 0; i < serializedProof.length; i++) { + newSerializedProof[i] = serializedProof[i]; + } + newSerializedProof[newSerializedProof.length - 1] = serializedProof[serializedProof.length - 1]; + + vm.expectRevert(bytes("loadProof: Proof is invalid")); + verifier.verify(publicInputs, newSerializedProof); + } + + function testEmptyRecursiveInput_shouldRevert() public { + uint256[] memory newSerializedProof = new uint256[](serializedProof.length - 4); + for (uint256 i = 0; i < newSerializedProof.length; i++) { + newSerializedProof[i] = serializedProof[i]; + } + + vm.expectRevert(bytes("loadProof: Proof is invalid")); + verifier.verify(publicInputs, newSerializedProof); + } + + function testInvalidRecursiveInput_shouldRevert() public { + uint256[] memory newSerializedProof = serializedProof; + newSerializedProof[newSerializedProof.length - 4] = 1; + newSerializedProof[newSerializedProof.length - 3] = 2; + newSerializedProof[newSerializedProof.length - 2] = 1; + newSerializedProof[newSerializedProof.length - 1] = 2; + + vm.expectRevert(bytes("finalPairing: pairing failure")); + verifier.verify(publicInputs, newSerializedProof); + } + + function testVerificationKeyHash() public override { + bytes32 verificationKeyHash = verifier.verificationKeyHash(); + assertEq(verificationKeyHash, 0x88b3ddc4ed85974c7e14297dcad4097169440305c05fdb6441ca8dfd77cd7fa7); + } +} diff --git a/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/FullMerkle/PushNewLeaf.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/FullMerkle/PushNewLeaf.t.sol new file mode 100644 index 000000000..dc08fde8a --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/FullMerkle/PushNewLeaf.t.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {FullMerkleTest} from "./_FullMerkle_Shared.t.sol"; + +contract PushNewLeafTest is FullMerkleTest { + function test_oneLeaf() public { + // Inserting one leaf + bytes32 leaf0 = keccak256("Leaf 0"); + merkleTest.pushNewLeaf(leaf0); + + // Checking the tree structure + assertEq(merkleTest.height(), 0, "Height should be 0 after one insert"); + assertEq(merkleTest.index(), 1, "Leaf number should be 1 after one insert"); + + // Checking leaf node + assertEq(merkleTest.node(0, 0), leaf0, "Node 0,0 should be correctly inserted"); + + // Chekcking zeros tree structure + assertEq(merkleTest.zeros(0), zeroHash, "Zero 0 should be correctly inserted"); + } + + function test_twoLeaves() public { + // Inserting two leaves + bytes32 leaf0 = keccak256("Leaf 0"); + bytes32 leaf1 = keccak256("Leaf 1"); + merkleTest.pushNewLeaf(leaf0); + merkleTest.pushNewLeaf(leaf1); + + // Checking the tree structure + assertEq(merkleTest.height(), 1, "Height should be 1 after two inserts"); + assertEq(merkleTest.index(), 2, "Leaf number should be 2 after two inserts"); + + // Checking leaf nodes + assertEq(merkleTest.node(0, 0), leaf0, "Node 0,0 should be correctly inserted"); + assertEq(merkleTest.node(0, 1), leaf1, "Node 0,1 should be correctly inserted"); + + // Checking parent node + bytes32 l01Hashed = keccak(leaf0, leaf1); + assertEq(merkleTest.node(1, 0), l01Hashed, "Node 1,0 should be correctly inserted"); + + // Checking zeros + bytes32 zeroHashed = keccak(zeroHash, zeroHash); + assertEq(merkleTest.zeros(1), zeroHashed, "Zero 1 should be correctly inserted"); + } + + function test_threeLeaves() public { + // Insert three leaves + bytes32 leaf0 = keccak256("Leaf 0"); + bytes32 leaf1 = keccak256("Leaf 1"); + bytes32 leaf2 = keccak256("Leaf 2"); + merkleTest.pushNewLeaf(leaf0); + merkleTest.pushNewLeaf(leaf1); + merkleTest.pushNewLeaf(leaf2); + + // Checking the tree structure + assertEq(merkleTest.height(), 2, "Height should be 2 after three inserts"); + assertEq(merkleTest.index(), 3, "Leaf number should be 3 after three inserts"); + + // Checking leaf nodes + assertEq(merkleTest.node(0, 0), leaf0, "Node 0,0 should be correctly inserted"); + assertEq(merkleTest.node(0, 1), leaf1, "Node 0,1 should be correctly inserted"); + assertEq(merkleTest.node(0, 2), leaf2, "Node 0,2 should be correctly inserted"); + + // Checking parent nodes + bytes32 l01Hashed = keccak(leaf0, leaf1); + assertEq(merkleTest.node(1, 0), l01Hashed, "Node 1,0 should be correctly inserted"); + // there is no leaf3 so we hash leaf2 with zero + bytes32 l23Hashed = keccak(leaf2, merkleTest.zeros(0)); + assertEq(merkleTest.node(1, 1), l23Hashed, "Node 1,1 should be correctly inserted"); + + // Checking root node + bytes32 l01l23Hashed = keccak(l01Hashed, l23Hashed); + assertEq(merkleTest.node(2, 0), l01l23Hashed, "Node 2,0 should be correctly inserted"); + + // Checking zero + bytes32 zeroHashed = keccak(zeroHash, zeroHash); + assertEq(merkleTest.zeros(1), zeroHashed, "Zero 1 should be correctly inserted"); + bytes32 zhHashed = keccak(zeroHashed, zeroHashed); + assertEq(merkleTest.zeros(2), zhHashed, "Zero 2 should be correctly inserted"); + } +} diff --git a/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/FullMerkle/Root.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/FullMerkle/Root.t.sol new file mode 100644 index 000000000..3ae519259 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/FullMerkle/Root.t.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {FullMerkleTest} from "./_FullMerkle_Shared.t.sol"; + +contract RootTest is FullMerkleTest { + function test_emptyTree() public view { + // Initially tree is empty, root is the zero hash + assertEq(merkleTest.root(), zeroHash, "Root should be zero hash initially"); + } + + function test_oneLeaf() public { + // Inserting one leaf + bytes32 leaf = keccak256("Leaf 0"); + merkleTest.pushNewLeaf(leaf); + + // With one leaf, root is the leaf itself + assertEq(merkleTest.root(), leaf, "Root should be the leaf hash"); + } + + function test_twoLeaves() public { + // Inserting two leaves + bytes32 leaf0 = keccak256("Leaf 0"); + bytes32 leaf1 = keccak256("Leaf 1"); + merkleTest.pushNewLeaf(leaf0); + merkleTest.pushNewLeaf(leaf1); + + // Calculate expected root + bytes32 expectedRoot = keccak(leaf0, leaf1); + assertEq(merkleTest.root(), expectedRoot, "Root should be the hash of the two leaves"); + } + + function test_nodeCountAndRoot() public { + // Initially tree is empty + assertEq(merkleTest.nodeCount(0), 1, "Initial node count at height 0 should be 1"); + + // Inserting three leaves and checking counts and root + bytes32 leaf0 = keccak256("Leaf 0"); + bytes32 leaf1 = keccak256("Leaf 1"); + bytes32 leaf2 = keccak256("Leaf 2"); + merkleTest.pushNewLeaf(leaf0); + merkleTest.pushNewLeaf(leaf1); + merkleTest.pushNewLeaf(leaf2); + + assertEq(merkleTest.nodeCount(0), 3, "Node count at height 0 should be 3 after three inserts"); + assertEq(merkleTest.nodeCount(1), 2, "Node count at height 1 should be 2"); + assertEq(merkleTest.nodeCount(2), 1, "Node count at height 2 should be 1"); + + // Calculate expected root to verify correctness + bytes32 leftChild = keccak(leaf0, leaf1); + bytes32 rightChild = keccak(leaf2, merkleTest.zeros(0)); + bytes32 expectedRoot = keccak(leftChild, rightChild); + + assertEq(merkleTest.root(), expectedRoot, "Root should match expected value after inserts"); + } +} diff --git a/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/FullMerkle/Setup.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/FullMerkle/Setup.t.sol new file mode 100644 index 000000000..6b6af0bc4 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/FullMerkle/Setup.t.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {FullMerkleTest} from "./_FullMerkle_Shared.t.sol"; + +contract SetupTest is FullMerkleTest { + function test_checkInit() public view { + assertEq(merkleTest.height(), 0, "Height should be 0"); + assertEq(merkleTest.index(), 0, "Leaf number should be 0"); + assertEq(merkleTest.zeros(0), zeroHash, "Zero hash should be correctly initialized"); + } +} diff --git a/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/FullMerkle/UpdateAllLeaves.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/FullMerkle/UpdateAllLeaves.t.sol new file mode 100644 index 000000000..5b030f634 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/FullMerkle/UpdateAllLeaves.t.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {FullMerkleTest} from "./_FullMerkle_Shared.t.sol"; +import {MerkleWrongLength} from "contracts/common/L1ContractErrors.sol"; + +contract UpdateAllLeavesTest is FullMerkleTest { + function test_revertWhen_wrongLength() public { + // Inserting two leaves + bytes32 leaf0 = keccak256("Leaf 0"); + bytes32 leaf1 = keccak256("Leaf 1"); + merkleTest.pushNewLeaf(leaf0); + merkleTest.pushNewLeaf(leaf1); + + // Preparing new leaves for full update + bytes32[] memory newLeaves = new bytes32[](3); + newLeaves[0] = keccak256("New Leaf 0"); + newLeaves[1] = keccak256("New Leaf 1"); + newLeaves[2] = keccak256("New Leaf 2"); + + // Updating all leaves with wrong length + vm.expectRevert(abi.encodeWithSelector(MerkleWrongLength.selector, newLeaves.length, 2)); + merkleTest.updateAllLeaves(newLeaves); + } + + function test_oneLeaf() public { + // Inserting one leaf + bytes32 leaf0 = keccak256("Leaf 0"); + merkleTest.pushNewLeaf(leaf0); + + // Preparing new leaves for full update + bytes32[] memory newLeaves = new bytes32[](1); + newLeaves[0] = keccak256("New Leaf 0"); + + // Updating all leaves + merkleTest.updateAllLeaves(newLeaves); + + // Checking leaf nodes + assertEq(merkleTest.node(0, 0), newLeaves[0], "Node 0,0 should be correctly updated"); + } + + function test_twoLeaves() public { + // Inserting two leaves + bytes32 leaf0 = keccak256("Leaf 0"); + bytes32 leaf1 = keccak256("Leaf 1"); + merkleTest.pushNewLeaf(leaf0); + merkleTest.pushNewLeaf(leaf1); + + // Preparing new leaves for full update + bytes32[] memory newLeaves = new bytes32[](2); + newLeaves[0] = keccak256("New Leaf 0"); + newLeaves[1] = keccak256("New Leaf 1"); + + // Updating all leaves + merkleTest.updateAllLeaves(newLeaves); + + // Checking leaf nodes + assertEq(merkleTest.node(0, 0), newLeaves[0], "Node 0,0 should be correctly updated"); + assertEq(merkleTest.node(0, 1), newLeaves[1], "Node 0,1 should be correctly updated"); + + // Checking parent node + bytes32 l01Hashed = keccak(newLeaves[0], newLeaves[1]); + assertEq(merkleTest.node(1, 0), l01Hashed, "Node 1,0 should be correctly updated"); + } + + function test_threeLeaves() public { + // Inserting two leaves + bytes32 leaf0 = keccak256("Leaf 0"); + bytes32 leaf1 = keccak256("Leaf 1"); + bytes32 leaf2 = keccak256("Leaf 2"); + merkleTest.pushNewLeaf(leaf0); + merkleTest.pushNewLeaf(leaf1); + merkleTest.pushNewLeaf(leaf2); + + // Preparing new leaves for full update + bytes32[] memory newLeaves = new bytes32[](3); + newLeaves[0] = keccak256("New Leaf 0"); + newLeaves[1] = keccak256("New Leaf 1"); + newLeaves[2] = keccak256("New Leaf 2"); + + // Updating all leaves + merkleTest.updateAllLeaves(newLeaves); + + // Checking leaf nodes + assertEq(merkleTest.node(0, 0), newLeaves[0], "Node 0,0 should be correctly updated"); + assertEq(merkleTest.node(0, 1), newLeaves[1], "Node 0,1 should be correctly updated"); + assertEq(merkleTest.node(0, 2), newLeaves[2], "Node 0,2 should be correctly updated"); + + // Checking parent nodes + bytes32 l01Hashed = keccak(newLeaves[0], newLeaves[1]); + assertEq(merkleTest.node(1, 0), l01Hashed, "Node 1,0 should be correctly updated"); + // There is no leaf3 so we hash leaf2 with zero + bytes32 l23Hashed = keccak(newLeaves[2], merkleTest.zeros(0)); + assertEq(merkleTest.node(1, 1), l23Hashed, "Node 1,1 should be correctly updated"); + + // Checking root node + bytes32 l01l23Hashed = keccak(l01Hashed, l23Hashed); + assertEq(merkleTest.node(2, 0), l01l23Hashed, "Node 2,0 should be correctly updated"); + } +} diff --git a/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/FullMerkle/UpdateAllNodesAtHeight.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/FullMerkle/UpdateAllNodesAtHeight.t.sol new file mode 100644 index 000000000..be93cd032 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/FullMerkle/UpdateAllNodesAtHeight.t.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {FullMerkleTest} from "./_FullMerkle_Shared.t.sol"; + +contract UpdateAllNodesAtHeightTest is FullMerkleTest { + function test_height0() public { + // Inserting two leaves + bytes32 leaf0 = keccak256("Leaf 0"); + bytes32 leaf1 = keccak256("Leaf 1"); + merkleTest.pushNewLeaf(leaf0); + merkleTest.pushNewLeaf(leaf1); + + // Preparing new leaves for full update + bytes32[] memory newLeaves = new bytes32[](2); + newLeaves[0] = keccak256("New Leaf 0"); + newLeaves[1] = keccak256("New Leaf 1"); + + // Updating all nodes at height 0 + merkleTest.updateAllNodesAtHeight(0, newLeaves); + + // Checking leaf nodes + assertEq(merkleTest.node(0, 0), newLeaves[0], "Node 0,0 should be correctly updated"); + assertEq(merkleTest.node(0, 1), newLeaves[1], "Node 0,1 should be correctly updated"); + + // Checking parent node + bytes32 l01Hashed = keccak(newLeaves[0], newLeaves[1]); + assertEq(merkleTest.node(1, 0), l01Hashed, "Node 1,0 should be correctly updated"); + } + + function test_height1() public { + // Inserting two leaves + bytes32 leaf0 = keccak256("Leaf 0"); + bytes32 leaf1 = keccak256("Leaf 1"); + bytes32 leaf2 = keccak256("Leaf 2"); + merkleTest.pushNewLeaf(leaf0); + merkleTest.pushNewLeaf(leaf1); + merkleTest.pushNewLeaf(leaf2); + + // Preparing new leaves for full update + bytes32[] memory newLeaves = new bytes32[](2); + newLeaves[0] = keccak256("New Leaf 0"); + newLeaves[1] = keccak256("New Leaf 1"); + + // Updating all nodes at height 1 + merkleTest.updateAllNodesAtHeight(1, newLeaves); + + // Checking leaf nodes + assertEq(merkleTest.node(0, 0), leaf0, "Node 0,0 should be correctly inserted"); + assertEq(merkleTest.node(0, 1), leaf1, "Node 0,1 should be correctly inserted"); + assertEq(merkleTest.node(0, 2), leaf2, "Node 0,2 should be correctly inserted"); + + // Checking parent nodes + assertEq(merkleTest.node(1, 0), newLeaves[0], "Node 1,0 should be correctly updated"); + assertEq(merkleTest.node(1, 1), newLeaves[1], "Node 1,1 should be correctly updated"); + } + + function test_height2() public { + // Inserting two leaves + bytes32 leaf0 = keccak256("Leaf 0"); + bytes32 leaf1 = keccak256("Leaf 1"); + bytes32 leaf2 = keccak256("Leaf 2"); + merkleTest.pushNewLeaf(leaf0); + merkleTest.pushNewLeaf(leaf1); + merkleTest.pushNewLeaf(leaf2); + + // Preparing new leaves for full update + bytes32[] memory newLeaves = new bytes32[](1); + newLeaves[0] = keccak256("New Leaf 0"); + + // Updating all nodes at height 2 + merkleTest.updateAllNodesAtHeight(2, newLeaves); + + // Checking leaf nodes + assertEq(merkleTest.node(0, 0), leaf0, "Node 0,0 should be correctly inserted"); + assertEq(merkleTest.node(0, 1), leaf1, "Node 0,1 should be correctly inserted"); + + // Checking parent node + assertEq(merkleTest.node(1, 0), keccak(leaf0, leaf1), "Node 1,0 should be correctly inserted"); + assertEq(merkleTest.node(2, 0), newLeaves[0], "Node 2,0 should be correctly updated"); + } +} diff --git a/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/FullMerkle/UpdateLeaf.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/FullMerkle/UpdateLeaf.t.sol new file mode 100644 index 000000000..6aee94198 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/FullMerkle/UpdateLeaf.t.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {FullMerkleTest} from "./_FullMerkle_Shared.t.sol"; +import {MerkleWrongIndex} from "contracts/common/L1ContractErrors.sol"; + +contract UpdateLeafTest is FullMerkleTest { + function test_revertWhen_wrongIndex() public { + // Inserting two leaves + bytes32 leaf0 = keccak256("Leaf 0"); + bytes32 leaf1 = keccak256("Leaf 1"); + merkleTest.pushNewLeaf(leaf0); + merkleTest.pushNewLeaf(leaf1); + + // Preparing new leaf 1 + bytes32 newLeaf1 = keccak256("New Leaf 1"); + + // Updating leaf 1 with wrong index + vm.expectRevert(abi.encodeWithSelector(MerkleWrongIndex.selector, 2, 1)); + merkleTest.updateLeaf(2, newLeaf1); + } + + function test_updateLeaf() public { + // Inserting two leaves + bytes32 leaf0 = keccak256("Leaf 0"); + bytes32 leaf1 = keccak256("Leaf 1"); + merkleTest.pushNewLeaf(leaf0); + merkleTest.pushNewLeaf(leaf1); + + // Updating leaf 1 + bytes32 newLeaf1 = keccak256("New Leaf 1"); + merkleTest.updateLeaf(1, newLeaf1); + + // Checking leaf nodes + assertEq(merkleTest.node(0, 0), leaf0, "Node 0,0 should be correctly inserted"); + assertEq(merkleTest.node(0, 1), newLeaf1, "Node 0,1 should be correctly inserted"); + + // Checking parent node + bytes32 l01Hashed = keccak(leaf0, newLeaf1); + assertEq(merkleTest.node(1, 0), l01Hashed, "Node 1,0 should be correctly inserted"); + } +} diff --git a/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/FullMerkle/_FullMerkle_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/FullMerkle/_FullMerkle_Shared.t.sol new file mode 100644 index 000000000..29c271edd --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/FullMerkle/_FullMerkle_Shared.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Test} from "forge-std/Test.sol"; + +import {FullMerkleTest as FullMerkleTestContract} from "contracts/dev-contracts/test/FullMerkleTest.sol"; + +contract FullMerkleTest is Test { + // add this to be excluded from coverage report + function test() internal {} + + FullMerkleTestContract internal merkleTest; + bytes32 constant zeroHash = keccak256(abi.encodePacked("ZERO")); + + function setUp() public { + merkleTest = new FullMerkleTestContract(zeroHash); + } + + // ### Helper functions ### + function keccak(bytes32 left, bytes32 right) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(left, right)); + } +} diff --git a/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/IncrementalMerkle/IncrementalMerkle.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/IncrementalMerkle/IncrementalMerkle.t.sol new file mode 100644 index 000000000..bb7fe7090 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/IncrementalMerkle/IncrementalMerkle.t.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {IncrementalMerkleTest} from "contracts/dev-contracts/test/IncrementalMerkleTest.sol"; + +contract IncrementalMerkleTestTest is Test { + IncrementalMerkleTest merkleTest; + bytes32[] elements; + bytes32 root; + bytes32 zero = "0x1234567"; + + function setUp() public { + merkleTest = new IncrementalMerkleTest(zero); + } + + function testCheckSetup() public { + assertEq(merkleTest.height(), 0); + assertEq(merkleTest.index(), 0); + } + + function testSingleElement() public { + addMoreElements(1); + + assertEq(merkleTest.root(), bytes32(abi.encodePacked(uint256(0)))); + assertEq(merkleTest.height(), 0); + assertEq(merkleTest.index(), 1); + } + + function testTwoElements() public { + addMoreElements(2); + + assertEq(merkleTest.root(), keccak256(abi.encodePacked(uint256(0), uint256(1)))); + assertEq(merkleTest.index(), 2); + assertEq(merkleTest.height(), 1); + } + + function testPrepare3Elements() public { + merkleTest.push(bytes32(uint256(2))); + merkleTest.push(bytes32(uint256(zero))); + assertEq(merkleTest.index(), 2); + assertEq(merkleTest.height(), 1); + assertEq(merkleTest.zeros(0), zero); + + assertEq(merkleTest.root(), keccak256(abi.encodePacked(uint256(2), uint256(zero)))); + } + + function testThreeElements() public { + addMoreElements(3); + + assertEq(merkleTest.index(), 3); + assertEq(merkleTest.height(), 2); + assertEq(merkleTest.zeros(0), zero); + assertEq(merkleTest.zeros(1), keccak256(abi.encodePacked(uint256(zero), uint256(zero)))); + assertEq(merkleTest.zeros(2), keccak256(abi.encodePacked(merkleTest.zeros(1), merkleTest.zeros(1)))); + assertEq(merkleTest.side(0), bytes32((uint256(2)))); + assertEq(merkleTest.side(1), keccak256(abi.encodePacked(uint256(0), uint256(1)))); + assertEq( + merkleTest.root(), + keccak256( + abi.encodePacked( + keccak256(abi.encodePacked(uint256(0), uint256(1))), + keccak256(abi.encodePacked(uint256(2), uint256(zero))) + ) + ) + ); + } + + function addMoreElements(uint256 n) public { + for (uint256 i = 0; i < n; i++) { + elements.push(bytes32(abi.encodePacked(i))); + merkleTest.push(elements[i]); + } + } +} diff --git a/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/Merkle/Merkle.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/Merkle/Merkle.t.sol new file mode 100644 index 000000000..c0b0ba653 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/Merkle/Merkle.t.sol @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {MerkleTest} from "contracts/dev-contracts/test/MerkleTest.sol"; +import {MerkleTreeNoSort} from "./MerkleTreeNoSort.sol"; +import {MerklePathEmpty, MerkleIndexOutOfBounds, MerklePathOutOfBounds, MerklePathLengthMismatch, MerkleIndexOrHeightMismatch, MerkleNothingToProve} from "contracts/common/L1ContractErrors.sol"; + +contract MerkleTestTest is Test { + MerkleTreeNoSort merkleTree; + MerkleTreeNoSort smallMerkleTree; + MerkleTest merkleTest; + bytes32[] elements; + bytes32 root; + + function setUp() public { + merkleTree = new MerkleTreeNoSort(); + smallMerkleTree = new MerkleTreeNoSort(); + merkleTest = new MerkleTest(); + + for (uint256 i = 0; i < 65; i++) { + elements.push(keccak256(abi.encodePacked(i))); + } + + root = merkleTree.getRoot(elements); + } + + function testElements(uint256 i) public { + vm.assume(i < elements.length); + bytes32 leaf = elements[i]; + bytes32[] memory proof = merkleTree.getProof(elements, i); + + bytes32 rootFromContract = merkleTest.calculateRoot(proof, i, leaf); + + assertEq(rootFromContract, root); + } + + function prepareRangeProof( + uint256 start, + uint256 end + ) public returns (bytes32[] memory, bytes32[] memory, bytes32[] memory) { + bytes32[] memory left = merkleTree.getProof(elements, start); + bytes32[] memory right = merkleTree.getProof(elements, end); + bytes32[] memory leaves = new bytes32[](end - start + 1); + for (uint256 i = start; i <= end; ++i) { + leaves[i - start] = elements[i]; + } + + return (left, right, leaves); + } + + function testFirstElement() public { + testElements(0); + } + + function testLastElement() public { + testElements(elements.length - 1); + } + + function testEmptyProof_shouldSucceed() public { + bytes32 leaf = elements[0]; + bytes32[] memory proof; + + bytes32 root = merkleTest.calculateRoot(proof, 0, leaf); + assertEq(root, leaf); + } + + function testLeafIndexTooBig_shouldRevert() public { + bytes32 leaf = elements[0]; + bytes32[] memory proof = merkleTree.getProof(elements, 0); + + vm.expectRevert(MerkleIndexOutOfBounds.selector); + merkleTest.calculateRoot(proof, 2 ** 255, leaf); + } + + function testProofLengthTooLarge_shouldRevert() public { + bytes32 leaf = elements[0]; + bytes32[] memory proof = new bytes32[](256); + + vm.expectRevert(MerklePathOutOfBounds.selector); + merkleTest.calculateRoot(proof, 0, leaf); + } + + function testRangeProof() public { + (bytes32[] memory left, bytes32[] memory right, bytes32[] memory leaves) = prepareRangeProof(10, 13); + bytes32 rootFromContract = merkleTest.calculateRoot(left, right, 10, leaves); + assertEq(rootFromContract, root); + } + + function testRangeProofIncorrect() public { + (bytes32[] memory left, bytes32[] memory right, bytes32[] memory leaves) = prepareRangeProof(10, 13); + bytes32 rootFromContract = merkleTest.calculateRoot(left, right, 9, leaves); + assertNotEq(rootFromContract, root); + } + + function testRangeProofLengthMismatch_shouldRevert() public { + (, bytes32[] memory right, bytes32[] memory leaves) = prepareRangeProof(10, 13); + bytes32[] memory leftShortened = new bytes32[](right.length - 1); + + vm.expectRevert(abi.encodeWithSelector(MerklePathLengthMismatch.selector, 6, 7)); + merkleTest.calculateRoot(leftShortened, right, 10, leaves); + } + + function testRangeProofEmptyPaths_shouldRevert() public { + (, , bytes32[] memory leaves) = prepareRangeProof(10, 13); + bytes32[] memory left; + bytes32[] memory right; + + vm.expectRevert(MerklePathEmpty.selector); + merkleTest.calculateRoot(left, right, 10, leaves); + } + + function testRangeProofWrongIndex_shouldRevert() public { + (bytes32[] memory left, bytes32[] memory right, bytes32[] memory leaves) = prepareRangeProof(10, 13); + vm.expectRevert(MerkleIndexOrHeightMismatch.selector); + merkleTest.calculateRoot(left, right, 128, leaves); + } + + function testRangeProofSingleLeaf() public { + (bytes32[] memory left, bytes32[] memory right, bytes32[] memory leaves) = prepareRangeProof(10, 10); + bytes32 rootFromContract = merkleTest.calculateRoot(left, right, 10, leaves); + assertEq(rootFromContract, root); + } + + function testRangeProofEmpty_shouldRevert() public { + bytes32[] memory left = merkleTree.getProof(elements, 10); + bytes32[] memory right = merkleTree.getProof(elements, 10); + bytes32[] memory leaves; + vm.expectRevert(MerkleNothingToProve.selector); + merkleTest.calculateRoot(left, right, 10, leaves); + } + + function testRangeProofSingleElementTree() public { + bytes32[] memory leaves = new bytes32[](1); + leaves[0] = elements[10]; + bytes32[] memory left = new bytes32[](0); + bytes32[] memory right = new bytes32[](0); + bytes32 rootFromContract = merkleTest.calculateRoot(left, right, 0, leaves); + assertEq(rootFromContract, leaves[0]); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/Merkle/MerkleTreeNoSort.sol b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/Merkle/MerkleTreeNoSort.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/libraries/Merkle/MerkleTreeNoSort.sol rename to l1-contracts/test/foundry/l1/unit/concrete/common/libraries/Merkle/MerkleTreeNoSort.sol diff --git a/l1-contracts/test/foundry/unit/concrete/common/libraries/UncheckedMath/UncheckedAdd.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/UncheckedMath/UncheckedAdd.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/common/libraries/UncheckedMath/UncheckedAdd.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/common/libraries/UncheckedMath/UncheckedAdd.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/common/libraries/UncheckedMath/UncheckedInc.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/UncheckedMath/UncheckedInc.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/common/libraries/UncheckedMath/UncheckedInc.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/common/libraries/UncheckedMath/UncheckedInc.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/common/libraries/UncheckedMath/_UncheckedMath_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/UncheckedMath/_UncheckedMath_Shared.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/common/libraries/UncheckedMath/_UncheckedMath_Shared.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/common/libraries/UncheckedMath/_UncheckedMath_Shared.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/common/libraries/UnsafeBytes/UnsafeBytes.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/common/libraries/UnsafeBytes/UnsafeBytes.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/common/libraries/UnsafeBytes/UnsafeBytes.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/common/libraries/UnsafeBytes/UnsafeBytes.t.sol diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/BytecodesSupplier.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/BytecodesSupplier.t.sol new file mode 100644 index 000000000..5c6268d83 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/BytecodesSupplier.t.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "forge-std/Test.sol"; +import "contracts/upgrades/BytecodesSupplier.sol"; +import "contracts/common/libraries/L2ContractHelper.sol"; +import "contracts/common/L1ContractErrors.sol"; + +contract BytecodesSupplierTest is Test { + BytecodesSupplier bytecodesSupplier; + bytes internal bytecode1 = hex"0000000000000000000000000000000000000000000000000000000000000000"; + bytes internal bytecode2 = hex"1111111111111111111111111111111111111111111111111111111111111111"; + + // Declare the event to use with vm.expectEmit + event BytecodePublished(bytes32 indexed bytecodeHash, bytes bytecode); + + function setUp() public { + bytecodesSupplier = new BytecodesSupplier(); + } + + function testPublishNewBytecode() public { + bytes memory bytecode = bytecode1; + + // Calculate the bytecode hash using the same function as the contract + bytes32 bytecodeHash = L2ContractHelper.hashL2Bytecode(bytecode); + + // Expect the event to be emitted + vm.expectEmit(true, false, false, true); + emit BytecodePublished(bytecodeHash, bytecode); + + // Publish the bytecode + bytecodesSupplier.publishBytecode(bytecode); + + // Check that the publishingBlock mapping is updated + uint256 publishedBlock = bytecodesSupplier.publishingBlock(bytecodeHash); + assertEq(publishedBlock, block.number); + } + + function testPublishBytecodeAlreadyPublished() public { + bytes memory bytecode = bytecode1; + + // Calculate the bytecode hash + bytes32 bytecodeHash = L2ContractHelper.hashL2Bytecode(bytecode); + + // Publish the bytecode + bytecodesSupplier.publishBytecode(bytecode); + + // Try to publish the same bytecode again, expect revert + vm.expectRevert(abi.encodeWithSelector(BytecodeAlreadyPublished.selector, bytecodeHash)); + bytecodesSupplier.publishBytecode(bytecode); + } + + function testPublishMultipleBytecodes() public { + bytes[] memory bytecodes = new bytes[](2); + bytecodes[0] = bytecode1; + bytecodes[1] = bytecode2; + + // Expect events for each bytecode published + for (uint256 i = 0; i < bytecodes.length; ++i) { + bytes32 bytecodeHash = L2ContractHelper.hashL2Bytecode(bytecodes[i]); + vm.expectEmit(true, false, false, true); + emit BytecodePublished(bytecodeHash, bytecodes[i]); + } + + // Publish multiple bytecodes + bytecodesSupplier.publishBytecodes(bytecodes); + + // Check that both bytecodes are published + for (uint256 i = 0; i < bytecodes.length; ++i) { + bytes32 bytecodeHash = L2ContractHelper.hashL2Bytecode(bytecodes[i]); + uint256 publishedBlock = bytecodesSupplier.publishingBlock(bytecodeHash); + assertEq(publishedBlock, block.number); + } + } + + function testPublishMultipleBytecodesWithDuplicate() public { + bytes[] memory bytecodes = new bytes[](2); + bytecodes[0] = bytecode1; + bytecodes[1] = bytecode2; + + // Publish the first bytecode + bytecodesSupplier.publishBytecode(bytecodes[0]); + + // Calculate the bytecode hash of the first bytecode + bytes32 bytecodeHash = L2ContractHelper.hashL2Bytecode(bytecodes[0]); + + // Now try to publish both bytecodes, one of which is already published + vm.expectRevert(abi.encodeWithSelector(BytecodeAlreadyPublished.selector, bytecodeHash)); + bytecodesSupplier.publishBytecodes(bytecodes); + } +} diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/Admin.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/Admin.t.sol new file mode 100644 index 000000000..5194b1da5 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/Admin.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {ChainTypeManagerTest} from "./_ChainTypeManager_Shared.t.sol"; + +contract AdminTest is ChainTypeManagerTest { + function setUp() public { + deploy(); + } + + function test_setPendingAdmin() public { + address newAdmin = makeAddr("newAdmin"); + + vm.expectEmit(true, true, true, false); + emit IChainTypeManager.NewPendingAdmin(address(0), newAdmin); + chainContractAddress.setPendingAdmin(newAdmin); + } + + function test_acceptPendingAdmin() public { + address newAdmin = makeAddr("newAdmin"); + + chainContractAddress.setPendingAdmin(newAdmin); + + // Need this because in shared setup we start a prank as the governor + vm.stopPrank(); + vm.prank(newAdmin); + vm.expectEmit(true, true, true, false); + emit IChainTypeManager.NewPendingAdmin(newAdmin, address(0)); + vm.expectEmit(true, true, true, false); + emit IChainTypeManager.NewAdmin(address(0), newAdmin); + chainContractAddress.acceptAdmin(); + + address currentAdmin = chainContractAddress.admin(); + + assertEq(currentAdmin, newAdmin); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/CreateNewChain.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/CreateNewChain.t.sol similarity index 65% rename from l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/CreateNewChain.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/CreateNewChain.t.sol index ba69c6bd7..c422dca99 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/CreateNewChain.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/CreateNewChain.t.sol @@ -1,11 +1,17 @@ // // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -import {StateTransitionManagerTest} from "./_StateTransitionManager_Shared.t.sol"; +import {ChainTypeManagerTest} from "./_ChainTypeManager_Shared.t.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; import {Unauthorized, HashMismatch} from "contracts/common/L1ContractErrors.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; + +contract createNewChainTest is ChainTypeManagerTest { + function setUp() public { + deploy(); + } -contract createNewChainTest is StateTransitionManagerTest { function test_RevertWhen_InitialDiamondCutHashMismatch() public { Diamond.DiamondCutData memory initialDiamondCutData = getDiamondCutData(sharedBridge); Diamond.DiamondCutData memory correctDiamondCutData = getDiamondCutData(address(diamondInit)); @@ -26,18 +32,17 @@ contract createNewChainTest is StateTransitionManagerTest { vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, governor)); chainContractAddress.createNewChain({ _chainId: chainId, - _baseToken: baseToken, - _sharedBridge: sharedBridge, + _baseTokenAssetId: DataEncoding.encodeNTVAssetId(block.chainid, baseToken), _admin: admin, - _diamondCut: abi.encode(initialDiamondCutData) + _initData: abi.encode(abi.encode(initialDiamondCutData), bytes("")), + _factoryDeps: new bytes[](0) }); } function test_SuccessfulCreationOfNewChain() public { - createNewChain(getDiamondCutData(diamondInit)); + address newChainAddress = createNewChain(getDiamondCutData(diamondInit)); - address admin = chainContractAddress.getChainAdmin(chainId); - address newChainAddress = chainContractAddress.getHyperchain(chainId); + address admin = IZKChain(newChainAddress).getAdmin(); 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/l1/unit/concrete/state-transition/ChainTypeManager/FreezeChain.t.sol similarity index 63% rename from l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/FreezeChain.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/FreezeChain.t.sol index 1bf8c8a40..838d7a77f 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/FreezeChain.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/FreezeChain.t.sol @@ -1,29 +1,32 @@ // // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -import {StateTransitionManagerTest} from "./_StateTransitionManager_Shared.t.sol"; +import {ChainTypeManagerTest} from "./_ChainTypeManager_Shared.t.sol"; import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; -import {FacetIsFrozen} from "contracts/common/L1ContractErrors.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; -contract freezeChainTest is StateTransitionManagerTest { - function test_FreezingChain() public { - createNewChain(getDiamondCutData(diamondInit)); +contract freezeChainTest is ChainTypeManagerTest { + function setUp() public { + deploy(); + } - address newChainAddress = chainContractAddress.getHyperchain(chainId); + function test_FreezingChain() public { + address newChainAddress = createNewChain(getDiamondCutData(diamondInit)); + vm.mockCall( + address(bridgehub), + abi.encodeWithSelector(IBridgehub.getZKChain.selector), + abi.encode(newChainAddress) + ); 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("q1")); // storage frozen chainContractAddress.freezeChain(block.chainid); - // Call fails as storage is frozen vm.expectRevert(bytes("q1")); isChainFrozen = gettersFacet.isDiamondStorageFrozen(); diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/RevertBatches.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/RevertBatches.t.sol new file mode 100644 index 000000000..610c6c1a3 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/RevertBatches.t.sol @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Vm} from "forge-std/Test.sol"; + +import {SafeCast} from "@openzeppelin/contracts-v4/utils/math/SafeCast.sol"; + +import {Utils, L2_SYSTEM_CONTEXT_ADDRESS, L2_DA_VALIDATOR_ADDRESS} from "../../Utils/Utils.sol"; +import {ChainTypeManagerTest} from "./_ChainTypeManager_Shared.t.sol"; + +import {COMMIT_TIMESTAMP_NOT_OLDER, DEFAULT_L2_LOGS_TREE_ROOT_HASH, EMPTY_STRING_KECCAK, POINT_EVALUATION_PRECOMPILE_ADDR, REQUIRED_L2_GAS_PRICE_PER_PUBDATA, SYSTEM_UPGRADE_L2_TX_TYPE, PRIORITY_TX_MAX_GAS_LIMIT} from "contracts/common/Config.sol"; +import {L2_FORCE_DEPLOYER_ADDR, L2_COMPLEX_UPGRADER_ADDR, L2_GENESIS_UPGRADE_ADDR} from "contracts/common/L2ContractAddresses.sol"; //, COMPLEX_UPGRADER_ADDR, GENESIS_UPGRADE_ADDR +import {SemVer} from "contracts/common/libraries/SemVer.sol"; +import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; +import {L2CanonicalTransaction} from "contracts/common/Messaging.sol"; +import {IExecutor, SystemLogKey, TOTAL_BLOBS_IN_COMMITMENT} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; +import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; +import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol"; +import {ExecutorFacet} from "contracts/state-transition/chain-deps/facets/Executor.sol"; +import {IExecutor} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; +import {IL2GenesisUpgrade} from "contracts/state-transition/l2-deps/IL2GenesisUpgrade.sol"; +import {IComplexUpgrader} from "contracts/state-transition/l2-deps/IComplexUpgrader.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; + +contract revertBatchesTest is ChainTypeManagerTest { + // Items for logs & commits + uint256 internal currentTimestamp; + IExecutor.CommitBatchInfo internal newCommitBatchInfo; + IExecutor.StoredBatchInfo internal newStoredBatchInfo; + IExecutor.StoredBatchInfo internal genesisStoredBatchInfo; + uint256[] internal proofInput; + bytes32 l2DAValidatorOutputHash; + bytes operatorDAInput; + bytes defaultBlobCommitment; + bytes32[] defaultBlobVersionedHashes; + bytes16 defaultBlobOpeningPoint = 0x7142c5851421a2dc03dde0aabdb0ffdb; + bytes32 defaultBlobClaimedValue = 0x1e5eea3bbb85517461c1d1c7b84c7c2cec050662a5e81a71d5d7e2766eaff2f0; + bytes l2Logs; + address newChainAddress; + + bytes32 constant EMPTY_PREPUBLISHED_COMMITMENT = 0x0000000000000000000000000000000000000000000000000000000000000000; + bytes constant POINT_EVALUATION_PRECOMPILE_RESULT = + hex"000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"; + + // Facets exposing the diamond + AdminFacet internal adminFacet; + ExecutorFacet internal executorFacet; + GettersFacet internal gettersFacet; + + function setUp() public { + deploy(); + + defaultBlobCommitment = Utils.getDefaultBlobCommitment(); + defaultBlobVersionedHashes = new bytes32[](1); + defaultBlobVersionedHashes[0] = 0x01c024b4740620a5849f95930cefe298933bdf588123ea897cdf0f2462f6d2d5; + + bytes memory precompileInput = Utils.defaultPointEvaluationPrecompileInput(defaultBlobVersionedHashes[0]); + vm.mockCall(POINT_EVALUATION_PRECOMPILE_ADDR, precompileInput, POINT_EVALUATION_PRECOMPILE_RESULT); + + l2Logs = Utils.encodePacked(Utils.createSystemLogs(bytes32(0))); + genesisStoredBatchInfo = IExecutor.StoredBatchInfo({ + batchNumber: 0, + batchHash: bytes32(uint256(0x01)), + indexRepeatedStorageChanges: 0x01, + numberOfLayer1Txs: 0, + priorityOperationsHash: keccak256(""), + l2LogsTreeRoot: DEFAULT_L2_LOGS_TREE_ROOT_HASH, + timestamp: 0, + commitment: bytes32(uint256(0x01)) + }); + vm.warp(COMMIT_TIMESTAMP_NOT_OLDER + 1 + 1); + currentTimestamp = block.timestamp; + 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, + operatorDAInput: "\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" + }); + + { + bytes memory complexUpgraderCalldata; + address l1CtmDeployer = address(bridgehub.l1CtmDeployer()); + { + bytes memory l2GenesisUpgradeCalldata = abi.encodeCall( + IL2GenesisUpgrade.genesisUpgrade, + (chainId, l1CtmDeployer, forceDeploymentsData, "0x") + ); + complexUpgraderCalldata = abi.encodeCall( + IComplexUpgrader.upgrade, + (L2_GENESIS_UPGRADE_ADDR, l2GenesisUpgradeCalldata) + ); + } + + // slither-disable-next-line unused-return + (, uint32 minorVersion, ) = SemVer.unpackSemVer(SafeCast.toUint96(0)); + } + + newChainAddress = createNewChain(getDiamondCutData(diamondInit)); + vm.mockCall( + address(bridgehub), + abi.encodeWithSelector(IBridgehub.getZKChain.selector), + abi.encode(newChainAddress) + ); + + executorFacet = ExecutorFacet(address(newChainAddress)); + gettersFacet = GettersFacet(address(newChainAddress)); + adminFacet = AdminFacet(address(newChainAddress)); + + vm.stopPrank(); + vm.prank(newChainAdmin); + adminFacet.setDAValidatorPair(address(rollupL1DAValidator), L2_DA_VALIDATOR_ADDRESS); + } + + function test_SuccessfulBatchReverting() public { + vm.startPrank(governor); + + bytes32 uncompressedStateDiffHash = Utils.randomBytes32("uncompressedStateDiffHash"); + bytes32 totalL2PubdataHash = Utils.randomBytes32("totalL2PubdataHash"); + uint8 numberOfBlobs = 1; + bytes32[] memory blobsLinearHashes = new bytes32[](1); + blobsLinearHashes[0] = Utils.randomBytes32("blobsLinearHashes"); + + operatorDAInput = abi.encodePacked( + uncompressedStateDiffHash, + totalL2PubdataHash, + numberOfBlobs, + blobsLinearHashes, + bytes1(0x01), + defaultBlobCommitment, + EMPTY_PREPUBLISHED_COMMITMENT + ); + + l2DAValidatorOutputHash = Utils.constructRollupL2DAValidatorOutputHash( + uncompressedStateDiffHash, + totalL2PubdataHash, + uint8(numberOfBlobs), + blobsLinearHashes + ); + + vm.warp(COMMIT_TIMESTAMP_NOT_OLDER + 1); + currentTimestamp = block.timestamp; + bytes32 expectedSystemContractUpgradeTxHash = gettersFacet.getL2SystemContractsUpgradeTxHash(); + bytes[] memory correctL2Logs = Utils.createSystemLogsWithUpgradeTransactionForCTM( + expectedSystemContractUpgradeTxHash, + l2DAValidatorOutputHash + ); + correctL2Logs[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) + ); + + IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; + correctNewCommitBatchInfo.timestamp = uint64(currentTimestamp); + correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); + correctNewCommitBatchInfo.operatorDAInput = operatorDAInput; + + bytes32[] memory blobHashes = new bytes32[](TOTAL_BLOBS_IN_COMMITMENT); + blobHashes[0] = blobsLinearHashes[0]; + + bytes32[] memory blobCommitments = new bytes32[](TOTAL_BLOBS_IN_COMMITMENT); + blobCommitments[0] = keccak256( + abi.encodePacked( + defaultBlobVersionedHashes[0], + abi.encodePacked(defaultBlobOpeningPoint, defaultBlobClaimedValue) + ) + ); + + bytes32 expectedBatchCommitment = Utils.createBatchCommitment( + correctNewCommitBatchInfo, + uncompressedStateDiffHash, + blobCommitments, + blobHashes + ); + + IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; + correctCommitBatchInfoArray[0].operatorDAInput = operatorDAInput; + + vm.stopPrank(); + vm.startPrank(validator); + vm.blobhashes(defaultBlobVersionedHashes); + vm.recordLogs(); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + correctCommitBatchInfoArray + ); + executorFacet.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + + Vm.Log[] memory entries = vm.getRecordedLogs(); + + assertEq(entries.length, 1); + assertEq(entries[0].topics[0], keccak256("BlockCommit(uint256,bytes32,bytes32)")); + assertEq(entries[0].topics[1], bytes32(uint256(1))); // batchNumber + assertEq(entries[0].topics[2], correctNewCommitBatchInfo.newStateRoot); // batchHash + + uint256 totalBatchesCommitted = gettersFacet.getTotalBatchesCommitted(); + assertEq(totalBatchesCommitted, 1); + + newStoredBatchInfo = IExecutor.StoredBatchInfo({ + batchNumber: 1, + batchHash: entries[0].topics[2], + indexRepeatedStorageChanges: 0, + numberOfLayer1Txs: 0, + priorityOperationsHash: keccak256(""), + l2LogsTreeRoot: DEFAULT_L2_LOGS_TREE_ROOT_HASH, + timestamp: currentTimestamp, + commitment: entries[0].topics[3] + }); + + IExecutor.StoredBatchInfo[] memory storedBatchInfoArray = new IExecutor.StoredBatchInfo[](1); + storedBatchInfoArray[0] = newStoredBatchInfo; + + (uint256 proveBatchFrom, uint256 proveBatchTo, bytes memory proveData) = Utils.encodeProveBatchesData( + genesisStoredBatchInfo, + storedBatchInfoArray, + proofInput + ); + + executorFacet.proveBatchesSharedBridge(uint256(0), proveBatchFrom, proveBatchTo, proveData); + + // Test batch revert triggered from CTM + vm.stopPrank(); + vm.prank(address(chainContractAddress)); + adminFacet.setValidator(address(chainContractAddress), true); + 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/SetChainCreationParams.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/SetChainCreationParams.t.sol similarity index 83% rename from l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetChainCreationParams.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/SetChainCreationParams.t.sol index 85fa1a316..e55334737 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetChainCreationParams.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/SetChainCreationParams.t.sol @@ -1,13 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -import {StateTransitionManagerTest} from "./_StateTransitionManager_Shared.t.sol"; +import {ChainTypeManagerTest} from "./_ChainTypeManager_Shared.t.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; -import {ChainCreationParams} from "contracts/state-transition/IStateTransitionManager.sol"; +import {ChainCreationParams} from "contracts/state-transition/IChainTypeManager.sol"; import {IExecutor} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; import {EMPTY_STRING_KECCAK, DEFAULT_L2_LOGS_TREE_ROOT_HASH} from "contracts/common/Config.sol"; -contract SetChainCreationParamsTest is StateTransitionManagerTest { +contract SetChainCreationParamsTest is ChainTypeManagerTest { + function setUp() public { + deploy(); + } + function test_SettingInitialCutHash() public { bytes32 initialCutHash = keccak256(abi.encode(getDiamondCutData(address(diamondInit)))); address randomDiamondInit = address(0x303030303030303030303); @@ -27,13 +31,14 @@ contract SetChainCreationParamsTest is StateTransitionManagerTest { genesisBatchHash: genesisBatchHash, genesisIndexRepeatedStorageChanges: genesisIndexRepeatedStorageChanges, genesisBatchCommitment: genesisBatchCommitment, - diamondCut: newDiamondCutData + diamondCut: newDiamondCutData, + forceDeploymentsData: bytes("") }); chainContractAddress.setChainCreationParams(newChainCreationParams); assertEq(chainContractAddress.initialCutHash(), newCutHash, "Initial cut hash update was not successful"); - assertEq(chainContractAddress.genesisUpgrade(), newGenesisUpgrade, "Genesis upgrade was not set correctly"); + assertEq(chainContractAddress.l1GenesisUpgrade(), newGenesisUpgrade, "Genesis upgrade was not set correctly"); // We need to initialize the state hash because it is used in the commitment of the next batch IExecutor.StoredBatchInfo memory newBatchZero = IExecutor.StoredBatchInfo({ diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetNewVersionUpgrade.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/SetNewVersionUpgrade.t.sol similarity index 63% rename from l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetNewVersionUpgrade.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/SetNewVersionUpgrade.t.sol index ced7e3f7d..c62773bab 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetNewVersionUpgrade.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/SetNewVersionUpgrade.t.sol @@ -1,12 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -import {StateTransitionManagerTest} from "./_StateTransitionManager_Shared.t.sol"; +import {ChainTypeManagerTest} from "./_ChainTypeManager_Shared.t.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; -contract setNewVersionUpgradeTest is StateTransitionManagerTest { +contract setNewVersionUpgradeTest is ChainTypeManagerTest { + function setUp() public { + deploy(); + } + function test_SettingNewVersionUpgrade() public { assertEq(chainContractAddress.protocolVersion(), 0, "Initial protocol version is not correct"); + vm.mockCall(address(bridgehub), abi.encodeWithSelector(bridgehub.migrationPaused.selector), abi.encode(true)); address randomDiamondInit = address(0x303030303030303030303); Diamond.DiamondCutData memory newDiamondCutData = getDiamondCutData(address(randomDiamondInit)); @@ -16,5 +21,11 @@ contract setNewVersionUpgradeTest is StateTransitionManagerTest { assertEq(chainContractAddress.upgradeCutHash(0), newCutHash, "Diamond cut upgrade was not successful"); assertEq(chainContractAddress.protocolVersion(), 1, "New protocol version is not correct"); + + (uint32 major, uint32 minor, uint32 patch) = chainContractAddress.getSemverProtocolVersion(); + + assertEq(major, 0); + assertEq(minor, 0); + assertEq(patch, 1); } } diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetUpgradeDiamondCut.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/SetUpgradeDiamondCut.t.sol similarity index 81% rename from l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetUpgradeDiamondCut.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/SetUpgradeDiamondCut.t.sol index a71f35d2e..d5ca40d50 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetUpgradeDiamondCut.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/SetUpgradeDiamondCut.t.sol @@ -1,10 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -import {StateTransitionManagerTest} from "./_StateTransitionManager_Shared.t.sol"; +import {ChainTypeManagerTest} from "./_ChainTypeManager_Shared.t.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; -contract setUpgradeDiamondCutTest is StateTransitionManagerTest { +contract setUpgradeDiamondCutTest is ChainTypeManagerTest { + function setUp() public { + deploy(); + } + function test_SettingUpgradeDiamondCut() public { assertEq(chainContractAddress.protocolVersion(), 0, "Initial protocol version is not correct"); diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/SetValidatorTimelock.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/SetValidatorTimelock.t.sol new file mode 100644 index 000000000..ddf137384 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/SetValidatorTimelock.t.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {ChainTypeManagerTest} from "./_ChainTypeManager_Shared.t.sol"; + +import {Unauthorized} from "contracts/common/L1ContractErrors.sol"; + +contract setValidatorTimelockTest is ChainTypeManagerTest { + function setUp() public { + deploy(); + } + + 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" + ); + } + + function test_RevertWhen_NotOwner() public { + // Need this because in shared setup we start a prank as the governor + vm.stopPrank(); + + address notOwner = makeAddr("notOwner"); + assertEq( + chainContractAddress.validatorTimelock(), + validator, + "Initial validator timelock address is not correct" + ); + + vm.prank(notOwner); + vm.expectRevert("Ownable: caller is not the owner"); + address newValidatorTimelock = address(0x0000000000000000000000000000000000004235); + chainContractAddress.setValidatorTimelock(newValidatorTimelock); + + assertEq(chainContractAddress.validatorTimelock(), validator, "Validator should not have been updated"); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/StateTransitionOwnerZero.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/StateTransitionOwnerZero.t.sol similarity index 52% rename from l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/StateTransitionOwnerZero.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/StateTransitionOwnerZero.t.sol index d8fb6e187..88c786c7b 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/StateTransitionOwnerZero.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/StateTransitionOwnerZero.t.sol @@ -2,22 +2,27 @@ pragma solidity 0.8.24; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; -import {StateTransitionManagerTest} from "./_StateTransitionManager_Shared.t.sol"; -import {StateTransitionManager} from "contracts/state-transition/StateTransitionManager.sol"; -import {StateTransitionManagerInitializeData, ChainCreationParams} from "contracts/state-transition/IStateTransitionManager.sol"; +import {ChainTypeManagerTest} from "./_ChainTypeManager_Shared.t.sol"; +import {ChainTypeManager} from "contracts/state-transition/ChainTypeManager.sol"; +import {ChainTypeManagerInitializeData, ChainCreationParams} from "contracts/state-transition/IChainTypeManager.sol"; import {ZeroAddress} from "contracts/common/L1ContractErrors.sol"; -contract initializingSTMOwnerZeroTest is StateTransitionManagerTest { - function test_InitializingSTMWithGovernorZeroShouldRevert() public { +contract initializingCTMOwnerZeroTest is ChainTypeManagerTest { + function setUp() public { + deploy(); + } + + function test_InitializingCTMWithGovernorZeroShouldRevert() public { ChainCreationParams memory chainCreationParams = ChainCreationParams({ genesisUpgrade: address(genesisUpgradeContract), genesisBatchHash: bytes32(uint256(0x01)), genesisIndexRepeatedStorageChanges: 1, genesisBatchCommitment: bytes32(uint256(0x01)), - diamondCut: getDiamondCutData(address(diamondInit)) + diamondCut: getDiamondCutData(address(diamondInit)), + forceDeploymentsData: bytes("") }); - StateTransitionManagerInitializeData memory stmInitializeDataNoOwner = StateTransitionManagerInitializeData({ + ChainTypeManagerInitializeData memory ctmInitializeDataNoOwner = ChainTypeManagerInitializeData({ owner: address(0), validatorTimelock: validator, chainCreationParams: chainCreationParams, @@ -26,9 +31,9 @@ contract initializingSTMOwnerZeroTest is StateTransitionManagerTest { vm.expectRevert(ZeroAddress.selector); new TransparentUpgradeableProxy( - address(stateTransitionManager), + address(chainTypeManager), admin, - abi.encodeCall(StateTransitionManager.initialize, stmInitializeDataNoOwner) + abi.encodeCall(ChainTypeManager.initialize, ctmInitializeDataNoOwner) ); } } diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/_ChainTypeManager_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/_ChainTypeManager_Shared.t.sol new file mode 100644 index 000000000..a99080672 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/_ChainTypeManager_Shared.t.sol @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.21; + +import {Test} from "forge-std/Test.sol"; +import {console2 as console} from "forge-std/Script.sol"; + +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; + +import {Utils} from "foundry-test/l1/unit/concrete/Utils/Utils.sol"; +import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {UtilsFacet} from "foundry-test/l1/unit/concrete/Utils/UtilsFacet.sol"; +import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol"; +import {ExecutorFacet} from "contracts/state-transition/chain-deps/facets/Executor.sol"; +import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; +import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; +import {DiamondInit} from "contracts/state-transition/chain-deps/DiamondInit.sol"; +import {L1GenesisUpgrade} from "contracts/upgrades/L1GenesisUpgrade.sol"; +import {InitializeDataNewChain} from "contracts/state-transition/chain-interfaces/IDiamondInit.sol"; +import {ChainTypeManager} from "contracts/state-transition/ChainTypeManager.sol"; +import {ChainTypeManagerInitializeData, ChainCreationParams} from "contracts/state-transition/IChainTypeManager.sol"; +import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; +import {DummyBridgehub} from "contracts/dev-contracts/test/DummyBridgehub.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; +import {ZeroAddress} from "contracts/common/L1ContractErrors.sol"; +import {ICTMDeploymentTracker} from "contracts/bridgehub/ICTMDeploymentTracker.sol"; +import {IMessageRoot} from "contracts/bridgehub/IMessageRoot.sol"; +import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol"; +import {RollupDAManager} from "contracts/state-transition/data-availability/RollupDAManager.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts-v4/token/ERC20/extensions/IERC20Metadata.sol"; + +contract ChainTypeManagerTest is Test { + ChainTypeManager internal chainTypeManager; + ChainTypeManager internal chainContractAddress; + L1GenesisUpgrade internal genesisUpgradeContract; + Bridgehub internal bridgehub; + address internal rollupL1DAValidator; + 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 constant l1Nullifier = address(0x6060606); + address internal newChainAdmin; + uint256 chainId = 112; + address internal testnetVerifier = address(new TestnetVerifier()); + bytes internal forceDeploymentsData = hex""; + uint256 eraChainId = 9; + + Diamond.FacetCut[] internal facetCuts; + + function deploy() public { + bridgehub = new Bridgehub(block.chainid, governor, type(uint256).max); + vm.prank(governor); + bridgehub.setAddresses(sharedBridge, ICTMDeploymentTracker(address(0)), IMessageRoot(address(0))); + + vm.mockCall( + address(sharedBridge), + abi.encodeCall(L1AssetRouter.l2BridgeAddress, (chainId)), + abi.encode(makeAddr("l2BridgeAddress")) + ); + + newChainAdmin = makeAddr("chainadmin"); + + vm.startPrank(address(bridgehub)); + chainTypeManager = new ChainTypeManager(address(IBridgehub(address(bridgehub)))); + diamondInit = address(new DiamondInit()); + genesisUpgradeContract = new L1GenesisUpgrade(); + + 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(block.chainid, RollupDAManager(address(0)))), + action: Diamond.Action.Add, + isFreezable: true, + selectors: Utils.getAdminSelectors() + }) + ); + facetCuts.push( + Diamond.FacetCut({ + facet: address(new ExecutorFacet(block.chainid)), + 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() + }) + ); + + ChainCreationParams memory chainCreationParams = ChainCreationParams({ + genesisUpgrade: address(genesisUpgradeContract), + genesisBatchHash: bytes32(uint256(0x01)), + genesisIndexRepeatedStorageChanges: 0x01, + genesisBatchCommitment: bytes32(uint256(0x01)), + diamondCut: getDiamondCutData(address(diamondInit)), + forceDeploymentsData: forceDeploymentsData + }); + + ChainTypeManagerInitializeData memory ctmInitializeDataNoGovernor = ChainTypeManagerInitializeData({ + owner: address(0), + validatorTimelock: validator, + chainCreationParams: chainCreationParams, + protocolVersion: 0 + }); + + vm.expectRevert(ZeroAddress.selector); + new TransparentUpgradeableProxy( + address(chainTypeManager), + admin, + abi.encodeCall(ChainTypeManager.initialize, ctmInitializeDataNoGovernor) + ); + + ChainTypeManagerInitializeData memory ctmInitializeData = ChainTypeManagerInitializeData({ + owner: governor, + validatorTimelock: validator, + chainCreationParams: chainCreationParams, + protocolVersion: 0 + }); + + TransparentUpgradeableProxy transparentUpgradeableProxy = new TransparentUpgradeableProxy( + address(chainTypeManager), + admin, + abi.encodeCall(ChainTypeManager.initialize, ctmInitializeData) + ); + chainContractAddress = ChainTypeManager(address(transparentUpgradeableProxy)); + + rollupL1DAValidator = Utils.deployL1RollupDAValidatorBytecode(); + + vm.stopPrank(); + vm.startPrank(governor); + } + + function getDiamondCutData(address _diamondInit) internal view returns (Diamond.DiamondCutData memory) { + InitializeDataNewChain memory initializeData = Utils.makeInitializeDataForNewChain(testnetVerifier); + + bytes memory initCalldata = abi.encode(initializeData); + + return Diamond.DiamondCutData({facetCuts: facetCuts, initAddress: _diamondInit, initCalldata: initCalldata}); + } + + function getCTMInitData() internal view returns (bytes memory) { + return abi.encode(abi.encode(getDiamondCutData(diamondInit)), forceDeploymentsData); + } + + function createNewChain(Diamond.DiamondCutData memory _diamondCut) internal returns (address) { + vm.stopPrank(); + vm.startPrank(address(bridgehub)); + + vm.mockCall( + address(sharedBridge), + abi.encodeWithSelector(IL1AssetRouter.L1_NULLIFIER.selector), + abi.encode(l1Nullifier) + ); + + vm.mockCall( + address(l1Nullifier), + abi.encodeWithSelector(IL1Nullifier.l2BridgeAddress.selector), + abi.encode(l1Nullifier) + ); + + vm.mockCall( + address(bridgehub), + abi.encodeWithSelector(Bridgehub.baseToken.selector, chainId), + abi.encode(baseToken) + ); + vm.mockCall(address(baseToken), abi.encodeWithSelector(IERC20Metadata.name.selector), abi.encode("TestToken")); + vm.mockCall(address(baseToken), abi.encodeWithSelector(IERC20Metadata.symbol.selector), abi.encode("TT")); + + return + chainContractAddress.createNewChain({ + _chainId: chainId, + _baseTokenAssetId: DataEncoding.encodeNTVAssetId(block.chainid, baseToken), + _admin: newChainAdmin, + _initData: abi.encode(abi.encode(_diamondCut), bytes("")), + _factoryDeps: new bytes[](0) + }); + } + + // 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/l1/unit/concrete/state-transition/chain-deps/DiamondInit/Initialize.t.sol similarity index 90% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondInit/Initialize.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/DiamondInit/Initialize.t.sol index 205752a9f..e31d841b9 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondInit/Initialize.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/DiamondInit/Initialize.t.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.24; 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 {Utils} from "foundry-test/l1/unit/concrete/Utils/Utils.sol"; +import {UtilsFacet} from "foundry-test/l1/unit/concrete/Utils/UtilsFacet.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {DiamondInit} from "contracts/state-transition/chain-deps/DiamondInit.sol"; @@ -11,7 +11,7 @@ import {DiamondProxy} from "contracts/state-transition/chain-deps/DiamondProxy.s import {InitializeData} from "contracts/state-transition/chain-interfaces/IDiamondInit.sol"; import {IVerifier} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; import {MAX_GAS_PER_TRANSACTION} from "contracts/common/Config.sol"; -import {MalformedCalldata, ZeroAddress, TooMuchGas} from "contracts/common/L1ContractErrors.sol"; +import {ZeroAddress, TooMuchGas} from "contracts/common/L1ContractErrors.sol"; contract InitializeTest is DiamondInitTest { function test_revertWhen_verifierIsZeroAddress() public { @@ -84,9 +84,8 @@ contract InitializeTest is DiamondInitTest { 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_getChainTypeManager(), initializeData.chainTypeManager); + assertEq(utilsFacet.util_getBaseTokenAssetId(), initializeData.baseTokenAssetId); assertEq(utilsFacet.util_getProtocolVersion(), initializeData.protocolVersion); assertEq(address(utilsFacet.util_getVerifier()), address(initializeData.verifier)); diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondInit/_DiamondInit_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/DiamondInit/_DiamondInit_Shared.t.sol similarity index 84% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondInit/_DiamondInit_Shared.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/DiamondInit/_DiamondInit_Shared.t.sol index 8a50fd5d5..79d0145dd 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondInit/_DiamondInit_Shared.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/DiamondInit/_DiamondInit_Shared.t.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.24; 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 {Utils} from "foundry-test/l1/unit/concrete/Utils/Utils.sol"; +import {UtilsFacet} from "foundry-test/l1/unit/concrete/Utils/UtilsFacet.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondProxy/DiamondProxy.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/DiamondProxy/DiamondProxy.t.sol similarity index 94% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondProxy/DiamondProxy.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/DiamondProxy/DiamondProxy.t.sol index 4637faabd..7c994ec1a 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondProxy/DiamondProxy.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/DiamondProxy/DiamondProxy.t.sol @@ -3,18 +3,18 @@ pragma solidity 0.8.24; 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 {Utils} from "foundry-test/l1/unit/concrete/Utils/Utils.sol"; +import {UtilsFacet} from "foundry-test/l1/unit/concrete/Utils/UtilsFacet.sol"; import {InitializeData} from "contracts/state-transition/chain-interfaces/IDiamondInit.sol"; import {DiamondInit} from "contracts/state-transition/chain-deps/DiamondInit.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {DiamondProxy} from "contracts/state-transition/chain-deps/DiamondProxy.sol"; -import {ZkSyncHyperchainBase} from "contracts/state-transition/chain-deps/facets/ZkSyncHyperchainBase.sol"; +import {ZKChainBase} from "contracts/state-transition/chain-deps/facets/ZKChainBase.sol"; import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; -import {FacetIsFrozen, ValueMismatch, InvalidSelector} from "contracts/common/L1ContractErrors.sol"; +import {ValueMismatch, InvalidSelector} from "contracts/common/L1ContractErrors.sol"; -contract TestFacet is ZkSyncHyperchainBase { +contract TestFacet is ZKChainBase { function func() public pure returns (bool) { return true; } diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/AcceptAdmin.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/AcceptAdmin.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/AcceptAdmin.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/AcceptAdmin.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/ChangeFeeParams.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/ChangeFeeParams.t.sol similarity index 80% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/ChangeFeeParams.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/ChangeFeeParams.t.sol index 70324aabf..e12e857c1 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/ChangeFeeParams.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/ChangeFeeParams.t.sol @@ -4,8 +4,9 @@ pragma solidity 0.8.24; import {AdminTest} from "./_Admin_Shared.t.sol"; -import {FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol"; +import {FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZKChainStorage.sol"; import {Unauthorized, PriorityTxPubdataExceedsMaxPubDataPerBatch} from "contracts/common/L1ContractErrors.sol"; +import {FeeParamsWereNotChangedCorrectly} from "../../../../../../../L1TestsErrors.sol"; contract ChangeFeeParamsTest is AdminTest { event NewFeeParams(FeeParams oldFeeParams, FeeParams newFeeParams); @@ -25,8 +26,8 @@ contract ChangeFeeParamsTest is AdminTest { ); } - function test_revertWhen_calledByNonStateTransitionManager() public { - address nonStateTransitionManager = makeAddr("nonStateTransitionManager"); + function test_revertWhen_calledByNonChainTypeManager() public { + address nonChainTypeManager = makeAddr("nonChainTypeManager"); FeeParams memory newFeeParams = FeeParams({ pubdataPricingMode: PubdataPricingMode.Rollup, batchOverheadL1Gas: 1_000_000, @@ -36,14 +37,14 @@ contract ChangeFeeParamsTest is AdminTest { minimalL2GasPrice: 250_000_000 }); - vm.startPrank(nonStateTransitionManager); - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, nonStateTransitionManager)); + vm.startPrank(nonChainTypeManager); + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, nonChainTypeManager)); adminFacet.changeFeeParams(newFeeParams); } function test_revertWhen_newMaxPubdataPerBatchIsLessThanMaxPubdataPerTransaction() public { - address stateTransitionManager = utilsFacet.util_getStateTransitionManager(); + address chainTypeManager = utilsFacet.util_getChainTypeManager(); uint32 priorityTxMaxPubdata = 88_000; uint32 maxPubdataPerBatch = priorityTxMaxPubdata - 1; FeeParams memory newFeeParams = FeeParams({ @@ -57,12 +58,12 @@ contract ChangeFeeParamsTest is AdminTest { vm.expectRevert(PriorityTxPubdataExceedsMaxPubDataPerBatch.selector); - vm.startPrank(stateTransitionManager); + vm.startPrank(chainTypeManager); adminFacet.changeFeeParams(newFeeParams); } function test_successfulChange() public { - address stateTransitionManager = utilsFacet.util_getStateTransitionManager(); + address chainTypeManager = utilsFacet.util_getChainTypeManager(); FeeParams memory oldFeeParams = utilsFacet.util_getFeeParams(); FeeParams memory newFeeParams = FeeParams({ pubdataPricingMode: PubdataPricingMode.Rollup, @@ -77,11 +78,13 @@ contract ChangeFeeParamsTest is AdminTest { vm.expectEmit(true, true, true, true, address(adminFacet)); emit NewFeeParams(oldFeeParams, newFeeParams); - vm.startPrank(stateTransitionManager); + vm.startPrank(chainTypeManager); 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"); + if (currentFeeParamsHash != newFeeParamsHash) { + revert FeeParamsWereNotChangedCorrectly(); + } } } diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/ExecuteUpgrade.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/ExecuteUpgrade.t.sol similarity index 86% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/ExecuteUpgrade.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/ExecuteUpgrade.t.sol index d09b6f204..19724c16b 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/ExecuteUpgrade.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/ExecuteUpgrade.t.sol @@ -15,17 +15,17 @@ import {ProposedUpgrade} from "contracts/upgrades/BaseZkSyncUpgrade.sol"; contract ExecuteUpgradeTest is AdminTest { event ExecuteUpgrade(Diamond.DiamondCutData diamondCut); - function test_revertWhen_calledByNonGovernorOrStateTransitionManager() public { - address nonStateTransitionManager = makeAddr("nonStateTransitionManager"); + function test_revertWhen_calledByNonGovernorOrChainTypeManager() public { + address nonChainTypeManager = makeAddr("nonChainTypeManager"); Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ facetCuts: new Diamond.FacetCut[](0), initAddress: address(0), initCalldata: new bytes(0) }); - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, nonStateTransitionManager)); + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, nonChainTypeManager)); - vm.startPrank(nonStateTransitionManager); + vm.startPrank(nonChainTypeManager); adminFacet.executeUpgrade(diamondCutData); } @@ -42,7 +42,6 @@ contract ExecuteUpgradeTest is AdminTest { ProposedUpgrade memory proposedUpgrade = ProposedUpgrade({ l2ProtocolUpgradeTx: Utils.makeEmptyL2CanonicalTransaction(), - factoryDeps: new bytes[](0), bootloaderHash: bytes32(0), defaultAccountHash: bytes32(0), verifier: address(0), @@ -61,8 +60,8 @@ contract ExecuteUpgradeTest is AdminTest { initCalldata: abi.encodeCall(upgrade.upgrade, (proposedUpgrade)) }); - address stm = utilsFacet.util_getStateTransitionManager(); - vm.startPrank(stm); + address ctm = utilsFacet.util_getChainTypeManager(); + vm.startPrank(ctm); adminFacet.executeUpgrade(diamondCutData); } diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/FreezeDiamond.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/FreezeDiamond.t.sol similarity index 59% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/FreezeDiamond.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/FreezeDiamond.t.sol index 77baed0ef..457611105 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/FreezeDiamond.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/FreezeDiamond.t.sol @@ -8,12 +8,12 @@ import {Unauthorized} from "contracts/common/L1ContractErrors.sol"; contract FreezeDiamondTest is AdminTest { event Freeze(); - function test_revertWhen_calledByNonStateTransitionManager() public { - address nonStateTransitionManager = makeAddr("nonStateTransitionManager"); + function test_revertWhen_calledByNonChainTypeManager() public { + address nonChainTypeManager = makeAddr("nonChainTypeManager"); - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, nonStateTransitionManager)); + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, nonChainTypeManager)); - vm.startPrank(nonStateTransitionManager); + vm.startPrank(nonChainTypeManager); adminFacet.freezeDiamond(); } } diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetPendingGovernor.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/SetPendingGovernor.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetPendingGovernor.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/SetPendingGovernor.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetPorterAvailability.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/SetPorterAvailability.t.sol similarity index 74% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetPorterAvailability.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/SetPorterAvailability.t.sol index ad0708f11..ca594b93a 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetPorterAvailability.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/SetPorterAvailability.t.sol @@ -8,17 +8,17 @@ import {Unauthorized} from "contracts/common/L1ContractErrors.sol"; contract SetPorterAvailabilityTest is AdminTest { event IsPorterAvailableStatusUpdate(bool isPorterAvailable); - function test_revertWhen_calledByNonStateTransitionManager() public { - address nonStateTransitionManager = makeAddr("nonStateTransitionManager"); + function test_revertWhen_calledByNonChainTypeManager() public { + address nonChainTypeManager = makeAddr("nonChainTypeManager"); bool isPorterAvailable = true; - vm.startPrank(nonStateTransitionManager); - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, nonStateTransitionManager)); + vm.startPrank(nonChainTypeManager); + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, nonChainTypeManager)); adminFacet.setPorterAvailability(isPorterAvailable); } function test_setPorterAvailabilityToFalse() public { - address stateTransitionManager = utilsFacet.util_getStateTransitionManager(); + address chainTypeManager = utilsFacet.util_getChainTypeManager(); bool isPorterAvailable = false; utilsFacet.util_setZkPorterAvailability(true); @@ -27,14 +27,14 @@ contract SetPorterAvailabilityTest is AdminTest { vm.expectEmit(true, true, true, true, address(adminFacet)); emit IsPorterAvailableStatusUpdate(isPorterAvailable); - vm.startPrank(stateTransitionManager); + vm.startPrank(chainTypeManager); adminFacet.setPorterAvailability(isPorterAvailable); assertEq(utilsFacet.util_getZkPorterAvailability(), isPorterAvailable); } function test_setPorterAvailabilityToTrue() public { - address stateTransitionManager = utilsFacet.util_getStateTransitionManager(); + address chainTypeManager = utilsFacet.util_getChainTypeManager(); bool isPorterAvailable = true; utilsFacet.util_setZkPorterAvailability(false); @@ -43,7 +43,7 @@ contract SetPorterAvailabilityTest is AdminTest { vm.expectEmit(true, true, true, true, address(adminFacet)); emit IsPorterAvailableStatusUpdate(isPorterAvailable); - vm.startPrank(stateTransitionManager); + vm.startPrank(chainTypeManager); 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/l1/unit/concrete/state-transition/chain-deps/facets/Admin/SetPriorityTxMaxGasLimit.t.sol similarity index 74% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetPriorityTxMaxGasLimit.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/SetPriorityTxMaxGasLimit.t.sol index 5581420fe..e5841bc87 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetPriorityTxMaxGasLimit.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/SetPriorityTxMaxGasLimit.t.sol @@ -10,26 +10,26 @@ import {Unauthorized, TooMuchGas} from "contracts/common/L1ContractErrors.sol"; contract SetPriorityTxMaxGasLimitTest is AdminTest { event NewPriorityTxMaxGasLimit(uint256 oldPriorityTxMaxGasLimit, uint256 newPriorityTxMaxGasLimit); - function test_revertWhen_calledByNonStateTransitionManager() public { - address nonStateTransitionManager = makeAddr("nonStateTransitionManager"); + function test_revertWhen_calledByNonChainTypeManager() public { + address nonChainTypeManager = makeAddr("nonChainTypeManager"); uint256 newPriorityTxMaxGasLimit = 100; - vm.startPrank(nonStateTransitionManager); - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, nonStateTransitionManager)); + vm.startPrank(nonChainTypeManager); + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, nonChainTypeManager)); adminFacet.setPriorityTxMaxGasLimit(newPriorityTxMaxGasLimit); } function test_revertWhen_newPriorityTxMaxGasLimitIsGreaterThanMaxGasPerTransaction() public { - address stateTransitionManager = utilsFacet.util_getStateTransitionManager(); + address chainTypeManager = utilsFacet.util_getChainTypeManager(); uint256 newPriorityTxMaxGasLimit = MAX_GAS_PER_TRANSACTION + 1; - vm.startPrank(stateTransitionManager); + vm.startPrank(chainTypeManager); vm.expectRevert(TooMuchGas.selector); adminFacet.setPriorityTxMaxGasLimit(newPriorityTxMaxGasLimit); } function test_successfulSet() public { - address stateTransitionManager = utilsFacet.util_getStateTransitionManager(); + address chainTypeManager = utilsFacet.util_getChainTypeManager(); uint256 oldPriorityTxMaxGasLimit = utilsFacet.util_getPriorityTxMaxGasLimit(); uint256 newPriorityTxMaxGasLimit = 100; @@ -37,7 +37,7 @@ contract SetPriorityTxMaxGasLimitTest is AdminTest { vm.expectEmit(true, true, true, true, address(adminFacet)); emit NewPriorityTxMaxGasLimit(oldPriorityTxMaxGasLimit, newPriorityTxMaxGasLimit); - vm.startPrank(stateTransitionManager); + vm.startPrank(chainTypeManager); adminFacet.setPriorityTxMaxGasLimit(newPriorityTxMaxGasLimit); assertEq(utilsFacet.util_getPriorityTxMaxGasLimit(), newPriorityTxMaxGasLimit); diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetTransactionFilterer.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/SetTransactionFilterer.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetTransactionFilterer.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/SetTransactionFilterer.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetValidator.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/SetValidator.t.sol similarity index 74% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetValidator.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/SetValidator.t.sol index 77990a285..5b75a0ac7 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/SetValidator.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/SetValidator.t.sol @@ -8,18 +8,18 @@ import {Unauthorized} from "contracts/common/L1ContractErrors.sol"; contract SetValidatorTest is AdminTest { event ValidatorStatusUpdate(address indexed validatorAddress, bool isActive); - function test_revertWhen_calledByNonStateTransitionManager() public { - address nonStateTransitionManager = makeAddr("nonStateTransitionManager"); + function test_revertWhen_calledByNonChainTypeManager() public { + address nonChainTypeManager = makeAddr("nonChainTypeManager"); address validator = makeAddr("validator"); bool isActive = true; - vm.startPrank(nonStateTransitionManager); - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, nonStateTransitionManager)); + vm.startPrank(nonChainTypeManager); + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, nonChainTypeManager)); adminFacet.setValidator(validator, isActive); } function test_deactivateValidator() public { - address stateTransitionManager = utilsFacet.util_getStateTransitionManager(); + address chainTypeManager = utilsFacet.util_getChainTypeManager(); address validator = makeAddr("validator"); bool isActive = false; @@ -29,14 +29,14 @@ contract SetValidatorTest is AdminTest { vm.expectEmit(true, true, true, true, address(adminFacet)); emit ValidatorStatusUpdate(validator, isActive); - vm.startPrank(stateTransitionManager); + vm.startPrank(chainTypeManager); adminFacet.setValidator(validator, isActive); assertEq(utilsFacet.util_getValidator(validator), isActive); } function test_reactivateValidator() public { - address stateTransitionManager = utilsFacet.util_getStateTransitionManager(); + address chainTypeManager = utilsFacet.util_getChainTypeManager(); address validator = makeAddr("validator"); bool isActive = true; @@ -46,7 +46,7 @@ contract SetValidatorTest is AdminTest { vm.expectEmit(true, true, true, true, address(adminFacet)); emit ValidatorStatusUpdate(validator, isActive); - vm.startPrank(stateTransitionManager); + vm.startPrank(chainTypeManager); 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/l1/unit/concrete/state-transition/chain-deps/facets/Admin/UnfreezeDiamond.t.sol similarity index 54% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/UnfreezeDiamond.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/UnfreezeDiamond.t.sol index e0da9d6dc..a2c1debf4 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/UnfreezeDiamond.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/UnfreezeDiamond.t.sol @@ -3,21 +3,21 @@ pragma solidity 0.8.24; import {AdminTest} from "./_Admin_Shared.t.sol"; -import {Unauthorized, DiamondFreezeIncorrectState, DiamondNotFrozen} from "contracts/common/L1ContractErrors.sol"; +import {Unauthorized, DiamondNotFrozen} from "contracts/common/L1ContractErrors.sol"; contract UnfreezeDiamondTest is AdminTest { event Unfreeze(); - function test_revertWhen_calledByNonStateTransitionManager() public { - address nonStateTransitionManager = makeAddr("nonStateTransitionManager"); + function test_revertWhen_calledByNonChainTypeManager() public { + address nonChainTypeManager = makeAddr("nonChainTypeManager"); - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, nonStateTransitionManager)); - vm.startPrank(nonStateTransitionManager); + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, nonChainTypeManager)); + vm.startPrank(nonChainTypeManager); adminFacet.unfreezeDiamond(); } function test_revertWhen_diamondIsNotFrozen() public { - address admin = utilsFacet.util_getStateTransitionManager(); + address admin = utilsFacet.util_getChainTypeManager(); utilsFacet.util_setIsFrozen(false); diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/UpgradeChainFromVersion.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/UpgradeChainFromVersion.t.sol similarity index 73% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/UpgradeChainFromVersion.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/UpgradeChainFromVersion.t.sol index 9e6efc1e5..50de804d5 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/UpgradeChainFromVersion.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/UpgradeChainFromVersion.t.sol @@ -5,14 +5,14 @@ pragma solidity 0.8.24; import {AdminTest} from "./_Admin_Shared.t.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; -import {IStateTransitionManager} from "contracts/state-transition/IStateTransitionManager.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; import {ProtocolIdMismatch, ProtocolIdNotGreater, InvalidProtocolVersion, ValueMismatch, Unauthorized, HashMismatch} from "contracts/common/L1ContractErrors.sol"; contract UpgradeChainFromVersionTest is AdminTest { event ExecuteUpgrade(Diamond.DiamondCutData diamondCut); - function test_revertWhen_calledByNonAdminOrStateTransitionManager() public { - address nonAdminOrStateTransitionManager = makeAddr("nonAdminOrStateTransitionManager"); + function test_revertWhen_calledByNonAdminOrChainTypeManager() public { + address nonAdminOrChainTypeManager = makeAddr("nonAdminOrChainTypeManager"); uint256 oldProtocolVersion = 1; Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ facetCuts: new Diamond.FacetCut[](0), @@ -20,14 +20,14 @@ contract UpgradeChainFromVersionTest is AdminTest { initCalldata: new bytes(0) }); - vm.startPrank(nonAdminOrStateTransitionManager); - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, nonAdminOrStateTransitionManager)); + vm.startPrank(nonAdminOrChainTypeManager); + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, nonAdminOrChainTypeManager)); adminFacet.upgradeChainFromVersion(oldProtocolVersion, diamondCutData); } function test_revertWhen_cutHashMismatch() public { address admin = utilsFacet.util_getAdmin(); - address stateTransitionManager = makeAddr("stateTransitionManager"); + address chainTypeManager = makeAddr("chainTypeManager"); uint256 oldProtocolVersion = 1; Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ @@ -36,12 +36,12 @@ contract UpgradeChainFromVersionTest is AdminTest { initCalldata: new bytes(0) }); - utilsFacet.util_setStateTransitionManager(stateTransitionManager); + utilsFacet.util_setChainTypeManager(chainTypeManager); bytes32 cutHashInput = keccak256("random"); vm.mockCall( - stateTransitionManager, - abi.encodeWithSelector(IStateTransitionManager.upgradeCutHash.selector), + chainTypeManager, + abi.encodeWithSelector(IChainTypeManager.upgradeCutHash.selector), abi.encode(cutHashInput) ); @@ -54,7 +54,7 @@ contract UpgradeChainFromVersionTest is AdminTest { function test_revertWhen_ProtocolVersionMismatchWhenUpgrading() public { address admin = utilsFacet.util_getAdmin(); - address stateTransitionManager = makeAddr("stateTransitionManager"); + address chainTypeManager = makeAddr("chainTypeManager"); uint256 oldProtocolVersion = 1; Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ @@ -64,12 +64,12 @@ contract UpgradeChainFromVersionTest is AdminTest { }); utilsFacet.util_setProtocolVersion(oldProtocolVersion + 1); - utilsFacet.util_setStateTransitionManager(stateTransitionManager); + utilsFacet.util_setChainTypeManager(chainTypeManager); bytes32 cutHashInput = keccak256(abi.encode(diamondCutData)); vm.mockCall( - stateTransitionManager, - abi.encodeWithSelector(IStateTransitionManager.upgradeCutHash.selector), + chainTypeManager, + abi.encodeWithSelector(IChainTypeManager.upgradeCutHash.selector), abi.encode(cutHashInput) ); @@ -80,7 +80,7 @@ contract UpgradeChainFromVersionTest is AdminTest { function test_revertWhen_ProtocolVersionMismatchAfterUpgrading() public { address admin = utilsFacet.util_getAdmin(); - address stateTransitionManager = makeAddr("stateTransitionManager"); + address chainTypeManager = makeAddr("chainTypeManager"); uint256 oldProtocolVersion = 1; Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ @@ -90,12 +90,12 @@ contract UpgradeChainFromVersionTest is AdminTest { }); utilsFacet.util_setProtocolVersion(oldProtocolVersion); - utilsFacet.util_setStateTransitionManager(stateTransitionManager); + utilsFacet.util_setChainTypeManager(chainTypeManager); bytes32 cutHashInput = keccak256(abi.encode(diamondCutData)); vm.mockCall( - stateTransitionManager, - abi.encodeWithSelector(IStateTransitionManager.upgradeCutHash.selector), + chainTypeManager, + abi.encodeWithSelector(IChainTypeManager.upgradeCutHash.selector), abi.encode(cutHashInput) ); diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/_Admin_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/_Admin_Shared.t.sol similarity index 86% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/_Admin_Shared.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/_Admin_Shared.t.sol index a4419a342..2903ba0f1 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/_Admin_Shared.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Admin/_Admin_Shared.t.sol @@ -3,13 +3,14 @@ pragma solidity 0.8.24; 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 {Utils} from "foundry-test/l1/unit/concrete/Utils/Utils.sol"; +import {UtilsFacet} from "foundry-test/l1/unit/concrete/Utils/UtilsFacet.sol"; import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; +import {RollupDAManager} from "contracts/state-transition/data-availability/RollupDAManager.sol"; contract AdminTest is Test { IAdmin internal adminFacet; @@ -36,7 +37,7 @@ contract AdminTest is Test { function setUp() public virtual { Diamond.FacetCut[] memory facetCuts = new Diamond.FacetCut[](2); facetCuts[0] = Diamond.FacetCut({ - facet: address(new AdminFacet()), + facet: address(new AdminFacet(block.chainid, RollupDAManager(address(0)))), action: Diamond.Action.Add, isFreezable: true, selectors: getAdminSelectors() diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyBridgehub.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Base/OnlyBridgehub.t.sol similarity index 84% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyBridgehub.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Base/OnlyBridgehub.t.sol index af92cde5f..459e71b47 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyBridgehub.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Base/OnlyBridgehub.t.sol @@ -2,10 +2,10 @@ pragma solidity 0.8.24; -import {ZkSyncHyperchainBaseTest} from "./_Base_Shared.t.sol"; +import {ZKChainBaseTest} from "./_Base_Shared.t.sol"; import {Unauthorized} from "contracts/common/L1ContractErrors.sol"; -contract OnlyBridgehubTest is ZkSyncHyperchainBaseTest { +contract OnlyBridgehubTest is ZKChainBaseTest { function test_revertWhen_calledByNonBridgehub() public { address nonBridgehub = makeAddr("nonBridgehub"); diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyGovernor.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Base/OnlyGovernor.t.sol similarity index 83% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyGovernor.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Base/OnlyGovernor.t.sol index 44c397c85..478372df9 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyGovernor.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Base/OnlyGovernor.t.sol @@ -2,10 +2,10 @@ pragma solidity 0.8.24; -import {ZkSyncHyperchainBaseTest} from "./_Base_Shared.t.sol"; +import {ZKChainBaseTest} from "./_Base_Shared.t.sol"; import {Unauthorized} from "contracts/common/L1ContractErrors.sol"; -contract OnlyAdminTest is ZkSyncHyperchainBaseTest { +contract OnlyAdminTest is ZKChainBaseTest { function test_revertWhen_calledByNonAdmin() public { address nonAdmin = makeAddr("nonAdmin"); diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Base/OnlyGovernorOrStateTransitionManager.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Base/OnlyGovernorOrStateTransitionManager.t.sol new file mode 100644 index 000000000..67cfe3d32 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Base/OnlyGovernorOrStateTransitionManager.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {ZKChainBaseTest} from "./_Base_Shared.t.sol"; +import {Unauthorized} from "contracts/common/L1ContractErrors.sol"; + +contract OnlyAdminOrChainTypeManagerTest is ZKChainBaseTest { + function test_revertWhen_calledByNonAdmin() public { + address nonAdmin = makeAddr("nonAdmin"); + + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, nonAdmin)); + vm.startPrank(nonAdmin); + testBaseFacet.functionWithOnlyAdminOrChainTypeManagerModifier(); + } + + function test_revertWhen_calledByNonChainTypeManager() public { + address nonChainTypeManager = makeAddr("nonChainTypeManager"); + + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, nonChainTypeManager)); + vm.startPrank(nonChainTypeManager); + testBaseFacet.functionWithOnlyAdminOrChainTypeManagerModifier(); + } + + function test_successfulCallWhenCalledByAdmin() public { + address admin = utilsFacet.util_getAdmin(); + + vm.startPrank(admin); + testBaseFacet.functionWithOnlyAdminOrChainTypeManagerModifier(); + } + + function test_successfulCallWhenCalledByChainTypeManager() public { + address chainTypeManager = utilsFacet.util_getChainTypeManager(); + + vm.startPrank(chainTypeManager); + testBaseFacet.functionWithOnlyAdminOrChainTypeManagerModifier(); + } +} diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Base/OnlyStateTransitionManager.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Base/OnlyStateTransitionManager.t.sol new file mode 100644 index 000000000..b7f7ec5a3 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Base/OnlyStateTransitionManager.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {ZKChainBaseTest} from "./_Base_Shared.t.sol"; +import {Unauthorized} from "contracts/common/L1ContractErrors.sol"; + +contract OnlyChainTypeManagerTest is ZKChainBaseTest { + function test_revertWhen_calledByNonChainTypeManager() public { + address nonChainTypeManager = makeAddr("nonChainTypeManager"); + + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, nonChainTypeManager)); + vm.startPrank(nonChainTypeManager); + testBaseFacet.functionWithOnlyChainTypeManagerModifier(); + } + + function test_successfulCall() public { + address chainTypeManager = utilsFacet.util_getChainTypeManager(); + + vm.startPrank(chainTypeManager); + testBaseFacet.functionWithOnlyChainTypeManagerModifier(); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyValidator.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Base/OnlyValidator.t.sol similarity index 86% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyValidator.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Base/OnlyValidator.t.sol index c002fec59..5997976ac 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyValidator.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Base/OnlyValidator.t.sol @@ -2,10 +2,10 @@ pragma solidity 0.8.24; -import {ZkSyncHyperchainBaseTest} from "./_Base_Shared.t.sol"; +import {ZKChainBaseTest} from "./_Base_Shared.t.sol"; import {Unauthorized} from "contracts/common/L1ContractErrors.sol"; -contract OnlyValidatorTest is ZkSyncHyperchainBaseTest { +contract OnlyValidatorTest is ZKChainBaseTest { function test_revertWhen_calledByNonValidator() public { address nonValidator = makeAddr("nonValidator"); diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/_Base_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Base/_Base_Shared.t.sol similarity index 57% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/_Base_Shared.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Base/_Base_Shared.t.sol index 15fa32883..6894fb1ba 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/_Base_Shared.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Base/_Base_Shared.t.sol @@ -3,41 +3,36 @@ pragma solidity 0.8.24; 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 {Utils} from "foundry-test/l1/unit/concrete/Utils/Utils.sol"; +import {UtilsFacet} from "foundry-test/l1/unit/concrete/Utils/UtilsFacet.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; -import {ZkSyncHyperchainBase} from "contracts/state-transition/chain-deps/facets/Admin.sol"; +import {ZKChainBase} from "contracts/state-transition/chain-deps/facets/Admin.sol"; import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; -contract TestBaseFacet is ZkSyncHyperchainBase { +contract TestBaseFacet is ZKChainBase { function functionWithOnlyAdminModifier() external onlyAdmin {} function functionWithOnlyValidatorModifier() external onlyValidator {} - function functionWithOnlyStateTransitionManagerModifier() external onlyStateTransitionManager {} + function functionWithOnlyChainTypeManagerModifier() external onlyChainTypeManager {} function functionWithOnlyBridgehubModifier() external onlyBridgehub {} - function functionWithOnlyAdminOrStateTransitionManagerModifier() external onlyAdminOrStateTransitionManager {} - - function functionWithonlyValidatorOrStateTransitionManagerModifier() - external - onlyValidatorOrStateTransitionManager - {} + function functionWithOnlyAdminOrChainTypeManagerModifier() external onlyAdminOrChainTypeManager {} // add this to be excluded from coverage report function test() internal virtual {} } -bytes constant ERROR_ONLY_ADMIN = "Hyperchain: not admin"; -bytes constant ERROR_ONLY_VALIDATOR = "Hyperchain: not validator"; -bytes constant ERROR_ONLY_STATE_TRANSITION_MANAGER = "Hyperchain: not state transition manager"; -bytes constant ERROR_ONLY_BRIDGEHUB = "Hyperchain: not bridgehub"; -bytes constant ERROR_ONLY_ADMIN_OR_STATE_TRANSITION_MANAGER = "Hyperchain: Only by admin or state transition manager"; -bytes constant ERROR_ONLY_VALIDATOR_OR_STATE_TRANSITION_MANAGER = "Hyperchain: Only by validator or state transition manager"; +bytes constant ERROR_ONLY_ADMIN = "ZKChain: not admin"; +bytes constant ERROR_ONLY_VALIDATOR = "ZKChain: not validator"; +bytes constant ERROR_ONLY_STATE_TRANSITION_MANAGER = "ZKChain: not state transition manager"; +bytes constant ERROR_ONLY_BRIDGEHUB = "ZKChain: not bridgehub"; +bytes constant ERROR_ONLY_ADMIN_OR_STATE_TRANSITION_MANAGER = "ZKChain: Only by admin or state transition manager"; +bytes constant ERROR_ONLY_VALIDATOR_OR_STATE_TRANSITION_MANAGER = "ZKChain: Only by validator or state transition manager"; -contract ZkSyncHyperchainBaseTest is Test { +contract ZKChainBaseTest is Test { TestBaseFacet internal testBaseFacet; UtilsFacet internal utilsFacet; address internal testnetVerifier = address(new TestnetVerifier()); @@ -46,10 +41,9 @@ contract ZkSyncHyperchainBaseTest is Test { selectors = new bytes4[](6); selectors[0] = TestBaseFacet.functionWithOnlyAdminModifier.selector; selectors[1] = TestBaseFacet.functionWithOnlyValidatorModifier.selector; - selectors[2] = TestBaseFacet.functionWithOnlyStateTransitionManagerModifier.selector; + selectors[2] = TestBaseFacet.functionWithOnlyChainTypeManagerModifier.selector; selectors[3] = TestBaseFacet.functionWithOnlyBridgehubModifier.selector; - selectors[4] = TestBaseFacet.functionWithOnlyAdminOrStateTransitionManagerModifier.selector; - selectors[5] = TestBaseFacet.functionWithonlyValidatorOrStateTransitionManagerModifier.selector; + selectors[4] = TestBaseFacet.functionWithOnlyAdminOrChainTypeManagerModifier.selector; } function setUp() public virtual { diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/FacetAddress.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/FacetAddress.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/FacetAddress.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/FacetAddress.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/FacetAddresses.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/FacetAddresses.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/FacetAddresses.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/FacetAddresses.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/FacetFunctionSelectors.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/FacetFunctionSelectors.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/FacetFunctionSelectors.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/FacetFunctionSelectors.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/Facets.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/Facets.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/Facets.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/Facets.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetAdmin.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetAdmin.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetAdmin.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetAdmin.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetBaseToken.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetBaseToken.t.sol similarity index 70% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetBaseToken.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetBaseToken.t.sol index 7feed3cd1..ce0611c96 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetBaseToken.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetBaseToken.t.sol @@ -6,10 +6,10 @@ import {GettersFacetTest} from "./_Getters_Shared.t.sol"; contract GetBaseTokenTest is GettersFacetTest { function test() public { - address expected = makeAddr("baseToken"); + bytes32 expected = bytes32(uint256(uint160(makeAddr("baseToken")))); gettersFacetWrapper.util_setBaseToken(expected); - address received = gettersFacet.getBaseToken(); + bytes32 received = gettersFacet.getBaseTokenAssetId(); assertEq(expected, received, "BaseToken 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/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetBridgehub.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetBridgehub.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetBridgehub.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetFirstUnprocessedPriorityTx.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetFirstUnprocessedPriorityTx.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetFirstUnprocessedPriorityTx.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetFirstUnprocessedPriorityTx.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2BootloaderBytecodeHash.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2BootloaderBytecodeHash.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2BootloaderBytecodeHash.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2BootloaderBytecodeHash.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2DefaultAccountBytecodeHash.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2DefaultAccountBytecodeHash.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2DefaultAccountBytecodeHash.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2DefaultAccountBytecodeHash.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeBatchNumber.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeBatchNumber.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeBatchNumber.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeBatchNumber.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeBlockNumber.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeBlockNumber.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeBlockNumber.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeBlockNumber.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeTxHash.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeTxHash.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeTxHash.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetL2SystemContractsUpgradeTxHash.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetPendingAdmin.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetPendingAdmin.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetPendingAdmin.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetPendingAdmin.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetPriorityQueueSize.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetPriorityQueueSize.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetPriorityQueueSize.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetPriorityQueueSize.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetPriorityTxMaxGasLimit.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetPriorityTxMaxGasLimit.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetPriorityTxMaxGasLimit.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetPriorityTxMaxGasLimit.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetProtocolVersion.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetProtocolVersion.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetProtocolVersion.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetProtocolVersion.t.sol diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetStateTransitionManager.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetStateTransitionManager.t.sol new file mode 100644 index 000000000..cf8b23ef0 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetStateTransitionManager.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {GettersFacetTest} from "./_Getters_Shared.t.sol"; + +contract GetChainTypeManagerTest is GettersFacetTest { + function test() public { + address expected = makeAddr("chainTypeManager"); + gettersFacetWrapper.util_setChainTypeManager(expected); + + address received = gettersFacet.getChainTypeManager(); + + assertEq(expected, received, "ChainTypeManager 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/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesCommitted.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesCommitted.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesCommitted.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesExecuted.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesExecuted.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesExecuted.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesExecuted.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesVerified.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesVerified.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesVerified.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBatchesVerified.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksCommitted.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksCommitted.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksCommitted.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksCommitted.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksExecuted.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksExecuted.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksExecuted.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksExecuted.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksVerified.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksVerified.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksVerified.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalBlocksVerified.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalPriorityTxs.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalPriorityTxs.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalPriorityTxs.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetTotalPriorityTxs.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetVerifier.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetVerifier.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetVerifier.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetVerifier.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetVerifierParams.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetVerifierParams.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetVerifierParams.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetVerifierParams.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsDiamondStorageFrozen.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/IsDiamondStorageFrozen.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsDiamondStorageFrozen.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/IsDiamondStorageFrozen.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsEthWithdrawalFinalized.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/IsEthWithdrawalFinalized.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsEthWithdrawalFinalized.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/IsEthWithdrawalFinalized.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsFacetFreezable.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/IsFacetFreezable.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsFacetFreezable.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/IsFacetFreezable.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsFunctionFreezable.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/IsFunctionFreezable.t.sol similarity index 81% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsFunctionFreezable.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/IsFunctionFreezable.t.sol index 4af9875e2..b5be77370 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsFunctionFreezable.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/IsFunctionFreezable.t.sol @@ -6,14 +6,13 @@ import {GettersFacetTest} from "./_Getters_Shared.t.sol"; import {InvalidSelector} from "contracts/common/L1ContractErrors.sol"; contract IsFunctionFreezableTest is GettersFacetTest { - function test_revertWhen_facetAddressIzZero() public { + function test_when_facetAddressIzZero() public { bytes4 selector = bytes4(keccak256("asdfghfjtyhrewd")); gettersFacetWrapper.util_setIsFunctionFreezable(selector, true); gettersFacetWrapper.util_setFacetAddress(selector, address(0)); - vm.expectRevert(abi.encodeWithSelector(InvalidSelector.selector, selector)); - gettersFacet.isFunctionFreezable(selector); + assertFalse(gettersFacet.isFunctionFreezable(selector)); } function test() public { diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsValidator.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/IsValidator.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/IsValidator.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/IsValidator.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/L2LogsRootHash.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/L2LogsRootHash.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/L2LogsRootHash.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/L2LogsRootHash.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/StoredBatchHash.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/StoredBatchHash.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/StoredBatchHash.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/StoredBatchHash.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/StoredBlockHash.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/StoredBlockHash.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/StoredBlockHash.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/StoredBlockHash.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/_Getters_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/_Getters_Shared.t.sol similarity index 88% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/_Getters_Shared.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/_Getters_Shared.t.sol index 1d64711fe..9f66926e7 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/_Getters_Shared.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/_Getters_Shared.t.sol @@ -28,16 +28,12 @@ contract GettersFacetWrapper is GettersFacet { s.bridgehub = _bridgehub; } - function util_setStateTransitionManager(address _stateTransitionManager) external { - s.stateTransitionManager = _stateTransitionManager; + function util_setChainTypeManager(address _chainTypeManager) external { + s.chainTypeManager = _chainTypeManager; } - function util_setBaseToken(address _baseToken) external { - s.baseToken = _baseToken; - } - - function util_setBaseTokenBridge(address _baseTokenBridge) external { - s.baseTokenBridge = _baseTokenBridge; + function util_setBaseToken(bytes32 _baseTokenAssetId) external { + s.baseTokenAssetId = _baseTokenAssetId; } function util_setTotalBatchesCommitted(uint256 _totalBatchesCommitted) external { @@ -53,21 +49,18 @@ contract GettersFacetWrapper is GettersFacet { } function util_setTotalPriorityTxs(uint256 _totalPriorityTxs) external { - s.priorityQueue.tail = _totalPriorityTxs; + s.priorityTree.startIndex = 0; + s.priorityTree.tree._nextLeafIndex = _totalPriorityTxs; } function util_setFirstUnprocessedPriorityTx(uint256 _firstUnprocessedPriorityTx) external { - s.priorityQueue.head = _firstUnprocessedPriorityTx; + s.priorityTree.startIndex = 0; + s.priorityTree.unprocessedIndex = _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; + s.priorityTree.unprocessedIndex = 1; + s.priorityTree.tree._nextLeafIndex = _priorityQueueSize + 1; } function util_setValidator(address _validator, bool _status) external { diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/BaseMailboxTests.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/BaseMailboxTests.t.sol similarity index 93% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/BaseMailboxTests.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/BaseMailboxTests.t.sol index 54ab2a135..230828ae7 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/BaseMailboxTests.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/BaseMailboxTests.t.sol @@ -3,9 +3,9 @@ pragma solidity 0.8.24; import {MailboxTest} from "./_Mailbox_Shared.t.sol"; -import {FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol"; +import {FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZKChainStorage.sol"; import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA} from "contracts/common/Config.sol"; -import {DummyHyperchain} from "contracts/dev-contracts/test/DummyHyperchain.sol"; +import {DummyZKChain} from "contracts/dev-contracts/test/DummyZKChain.sol"; import {BaseTokenGasPriceDenominatorNotSet} from "contracts/common/L1ContractErrors.sol"; contract MailboxBaseTests is MailboxTest { @@ -16,7 +16,7 @@ contract MailboxBaseTests is MailboxTest { } function test_mailboxConstructor() public { - DummyHyperchain h = new DummyHyperchain(address(0), eraChainId); + DummyZKChain h = new DummyZKChain(address(0), eraChainId, block.chainid); assertEq(h.getEraChainId(), eraChainId); } diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/BridgehubRequestL2Transaction.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/BridgehubRequestL2Transaction.t.sol similarity index 81% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/BridgehubRequestL2Transaction.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/BridgehubRequestL2Transaction.t.sol index 580da9a58..f435ecfbd 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/BridgehubRequestL2Transaction.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/BridgehubRequestL2Transaction.t.sol @@ -89,4 +89,25 @@ contract MailboxBridgehubRequestL2TransactionTest is MailboxTest { refundRecipient: sender }); } + + function test_priorityTreeRootChange() public { + bytes32 oldRootHash = gettersFacet.getPriorityTreeRoot(); + assertEq(oldRootHash, bytes32(0), "root hash should be 0"); + + address bridgehub = makeAddr("bridgehub"); + + utilsFacet.util_setBridgehub(bridgehub); + utilsFacet.util_setBaseTokenGasPriceMultiplierDenominator(1); + utilsFacet.util_setPriorityTxMaxGasLimit(100000000); + + BridgehubL2TransactionRequest memory req = getBridgehubRequestL2TransactionRequest(); + + vm.deal(bridgehub, 100 ether); + vm.prank(address(bridgehub)); + bytes32 canonicalTxHash = mailboxFacet.bridgehubRequestL2Transaction(req); + assertTrue(canonicalTxHash != bytes32(0), "canonicalTxHash should not be 0"); + + bytes32 newRootHash = gettersFacet.getPriorityTreeRoot(); + assertEq(canonicalTxHash, newRootHash, "root hash should have changed"); + } } diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/FinalizeWithdrawal.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/FinalizeWithdrawal.t.sol similarity index 77% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/FinalizeWithdrawal.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/FinalizeWithdrawal.t.sol index e561f4e3b..5e7fa27f6 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/FinalizeWithdrawal.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/FinalizeWithdrawal.t.sol @@ -4,23 +4,26 @@ pragma solidity 0.8.24; import {MailboxTest} from "./_Mailbox_Shared.t.sol"; import {DummyBridgehub} from "contracts/dev-contracts/test/DummyBridgehub.sol"; -import {L1SharedBridge} from "contracts/bridge/L1SharedBridge.sol"; +import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol"; import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; -import {IL1SharedBridge} from "contracts/bridge/interfaces/IL1SharedBridge.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; import {DummySharedBridge} from "contracts/dev-contracts/test/DummySharedBridge.sol"; import {OnlyEraSupported} from "contracts/common/L1ContractErrors.sol"; +import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; contract MailboxFinalizeWithdrawal is MailboxTest { bytes32[] proof; bytes message; - DummySharedBridge l1SharedBridge; + DummySharedBridge L1AssetRouter; address baseTokenBridgeAddress; function setUp() public virtual { setupDiamondProxy(); - l1SharedBridge = new DummySharedBridge(keccak256("dummyDepositHash")); - baseTokenBridgeAddress = address(l1SharedBridge); + L1AssetRouter = new DummySharedBridge(keccak256("dummyDepositHash")); + baseTokenBridgeAddress = address(L1AssetRouter); + + vm.mockCall(bridgehub, abi.encodeCall(Bridgehub.sharedBridge, ()), abi.encode(baseTokenBridgeAddress)); proof = new bytes32[](0); message = "message"; @@ -40,9 +43,7 @@ contract MailboxFinalizeWithdrawal is MailboxTest { } function test_success_withdrawal(uint256 amount) public { - address baseTokenBridge = makeAddr("baseTokenBridge"); utilsFacet.util_setChainId(eraChainId); - utilsFacet.util_setBaseTokenBridge(baseTokenBridgeAddress); address l1Receiver = makeAddr("receiver"); address l1Token = address(1); diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/ProvingL2LogsInclusion.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/ProvingL2LogsInclusion.t.sol new file mode 100644 index 000000000..9af6c7c2f --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/ProvingL2LogsInclusion.t.sol @@ -0,0 +1,558 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {MailboxTest} from "./_Mailbox_Shared.t.sol"; +import {L2Message, L2Log} from "contracts/common/Messaging.sol"; +import "forge-std/Test.sol"; +import {L2_L1_LOGS_TREE_DEFAULT_LEAF_HASH, L1_GAS_PER_PUBDATA_BYTE, L2_TO_L1_LOG_SERIALIZE_SIZE} from "contracts/common/Config.sol"; +import {L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, L2_BOOTLOADER_ADDRESS} from "contracts/common/L2ContractAddresses.sol"; +import {Merkle} from "contracts/common/libraries/Merkle.sol"; +import {BatchNotExecuted, HashedLogIsDefault} from "contracts/common/L1ContractErrors.sol"; +import {MurkyBase} from "murky/common/MurkyBase.sol"; +import {MerkleTest} from "contracts/dev-contracts/test/MerkleTest.sol"; +import {TxStatus} from "contracts/state-transition/chain-deps/facets/Mailbox.sol"; +import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {MerkleTreeNoSort} from "test/foundry/l1/unit/concrete/common/libraries/Merkle/MerkleTreeNoSort.sol"; +import {MessageHashing} from "contracts/common/libraries/MessageHashing.sol"; +import {IMailbox} from "contracts/state-transition/chain-interfaces/IMailbox.sol"; +import {IGetters} from "contracts/state-transition/chain-interfaces/IGetters.sol"; +import {UtilsFacet} from "foundry-test/l1/unit/concrete/Utils/UtilsFacet.sol"; + +contract MailboxL2LogsProve is MailboxTest { + bytes32[] elements; + MerkleTest merkle; + MerkleTreeNoSort merkleTree; + bytes data; + uint256 batchNumber; + bool isService; + uint8 shardId; + + function setUp() public virtual { + setupDiamondProxy(); + + data = abi.encodePacked("test data"); + merkleTree = new MerkleTreeNoSort(); + merkle = new MerkleTest(); + batchNumber = gettersFacet.getTotalBatchesExecuted(); + isService = true; + shardId = 0; + } + + function _addHashedLogToMerkleTree( + uint8 _shardId, + bool _isService, + uint16 _txNumberInBatch, + address _sender, + bytes32 _key, + bytes32 _value + ) internal returns (uint256 index) { + elements.push(keccak256(abi.encodePacked(_shardId, _isService, _txNumberInBatch, _sender, _key, _value))); + + index = elements.length - 1; + } + + function test_RevertWhen_batchNumberGreaterThanBatchesExecuted() public { + L2Message memory message = L2Message({txNumberInBatch: 0, sender: sender, data: data}); + bytes32[] memory proof = _appendProofMetadata(new bytes32[](1)); + + _proveL2MessageInclusion({ + _batchNumber: batchNumber + 1, + _index: 0, + _message: message, + _proof: proof, + _expectedError: abi.encodeWithSelector(BatchNotExecuted.selector, batchNumber + 1) + }); + } + + function test_success_proveL2MessageInclusion() public { + uint256 firstLogIndex = _addHashedLogToMerkleTree({ + _shardId: 0, + _isService: true, + _txNumberInBatch: 0, + _sender: L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, + _key: bytes32(uint256(uint160(sender))), + _value: keccak256(data) + }); + + uint256 secondLogIndex = _addHashedLogToMerkleTree({ + _shardId: 0, + _isService: true, + _txNumberInBatch: 1, + _sender: L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, + _key: bytes32(uint256(uint160(sender))), + _value: keccak256(data) + }); + + // Calculate the Merkle root + bytes32 root = merkleTree.getRoot(elements); + utilsFacet.util_setL2LogsRootHash(batchNumber, root); + + // Create L2 message + L2Message memory message = L2Message({txNumberInBatch: 0, sender: sender, data: data}); + + // Get Merkle proof for the first element + bytes32[] memory firstLogProof = merkleTree.getProof(elements, firstLogIndex); + + { + // Calculate the root using the Merkle proof + bytes32 leaf = elements[firstLogIndex]; + bytes32 calculatedRoot = merkle.calculateRoot(firstLogProof, firstLogIndex, leaf); + + // Assert that the calculated root matches the expected root + assertEq(calculatedRoot, root); + } + + // Prove L2 message inclusion + bool ret = _proveL2MessageInclusion(batchNumber, firstLogIndex, message, firstLogProof, bytes("")); + + // Assert that the proof was successful + assertEq(ret, true); + + // Prove L2 message inclusion for wrong leaf + ret = _proveL2MessageInclusion(batchNumber, secondLogIndex, message, firstLogProof, bytes("")); + + // Assert that the proof has failed + assertEq(ret, false); + } + + function test_success_proveL2LogInclusion() public { + uint256 firstLogIndex = _addHashedLogToMerkleTree({ + _shardId: shardId, + _isService: isService, + _txNumberInBatch: 0, + _sender: L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, + _key: bytes32(uint256(uint160(sender))), + _value: keccak256(data) + }); + + uint256 secondLogIndex = _addHashedLogToMerkleTree({ + _shardId: shardId, + _isService: isService, + _txNumberInBatch: 1, + _sender: L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, + _key: bytes32(uint256(uint160(sender))), + _value: keccak256(data) + }); + + L2Log memory log = L2Log({ + l2ShardId: shardId, + isService: isService, + txNumberInBatch: 1, + sender: L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, + key: bytes32(uint256(uint160(sender))), + value: keccak256(data) + }); + + // Calculate the Merkle root + bytes32 root = merkleTree.getRoot(elements); + // Set root hash for current batch + utilsFacet.util_setL2LogsRootHash(batchNumber, root); + + // Get Merkle proof for the first element + bytes32[] memory secondLogProof = merkleTree.getProof(elements, secondLogIndex); + + { + // Calculate the root using the Merkle proof + bytes32 leaf = elements[secondLogIndex]; + + bytes32 calculatedRoot = merkle.calculateRoot(secondLogProof, secondLogIndex, leaf); + // Assert that the calculated root matches the expected root + assertEq(calculatedRoot, root); + } + + // Prove l2 log inclusion with correct proof + bool ret = _proveL2LogInclusion({ + _batchNumber: batchNumber, + _index: secondLogIndex, + _proof: secondLogProof, + _log: log, + _expectedError: bytes("") + }); + + // Assert that the proof was successful + assertEq(ret, true); + + // Prove l2 log inclusion with wrong proof + ret = _proveL2LogInclusion({ + _batchNumber: batchNumber, + _index: firstLogIndex, + _proof: secondLogProof, + _log: log, + _expectedError: bytes("") + }); + + // Assert that the proof was successful + assertEq(ret, false); + } + + // this is not possible in case of message, because some default values + // are set during translation from message to log + function test_RevertWhen_proveL2LogInclusionDefaultLog() public { + L2Log memory log = L2Log({ + l2ShardId: 0, + isService: false, + txNumberInBatch: 0, + sender: address(0), + key: bytes32(0), + value: bytes32(0) + }); + + uint256 firstLogIndex = _addHashedLogToMerkleTree({ + _shardId: 0, + _isService: true, + _txNumberInBatch: 1, + _sender: L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, + _key: bytes32(uint256(uint160(sender))), + _value: keccak256(data) + }); + + // Add first element to the Merkle tree + elements.push(keccak256(new bytes(L2_TO_L1_LOG_SERIALIZE_SIZE))); + uint256 secondLogIndex = 1; + + // Calculate the Merkle root + bytes32 root = merkleTree.getRoot(elements); + // Set root hash for current batch + utilsFacet.util_setL2LogsRootHash(batchNumber, root); + + // Get Merkle proof for the first element + bytes32[] memory secondLogProof = merkleTree.getProof(elements, secondLogIndex); + + { + // Calculate the root using the Merkle proof + bytes32 leaf = elements[secondLogIndex]; + bytes32 calculatedRoot = merkle.calculateRoot(secondLogProof, secondLogIndex, leaf); + // Assert that the calculated root matches the expected root + assertEq(calculatedRoot, root); + } + + // Prove log inclusion reverts + _proveL2LogInclusion( + batchNumber, + secondLogIndex, + log, + secondLogProof, + bytes.concat(HashedLogIsDefault.selector) + ); + } + + function test_success_proveL1ToL2TransactionStatus() public { + bytes32 firstL2TxHash = keccak256("firstL2Transaction"); + bytes32 secondL2TxHash = keccak256("SecondL2Transaction"); + TxStatus txStatus = TxStatus.Success; + + uint256 firstLogIndex = _addHashedLogToMerkleTree({ + _shardId: shardId, + _isService: isService, + _txNumberInBatch: 0, + _sender: L2_BOOTLOADER_ADDRESS, + _key: firstL2TxHash, + _value: bytes32(uint256(txStatus)) + }); + + uint256 secondLogIndex = _addHashedLogToMerkleTree({ + _shardId: shardId, + _isService: isService, + _txNumberInBatch: 1, + _sender: L2_BOOTLOADER_ADDRESS, + _key: secondL2TxHash, + _value: bytes32(uint256(txStatus)) + }); + + // Calculate the Merkle root + bytes32 root = merkleTree.getRoot(elements); + // Set root hash for current batch + utilsFacet.util_setL2LogsRootHash(batchNumber, root); + + // Get Merkle proof for the first element + bytes32[] memory secondLogProof = merkleTree.getProof(elements, secondLogIndex); + + { + // Calculate the root using the Merkle proof + bytes32 leaf = elements[secondLogIndex]; + bytes32 calculatedRoot = merkle.calculateRoot(secondLogProof, secondLogIndex, leaf); + // Assert that the calculated root matches the expected root + assertEq(calculatedRoot, root); + } + + // Prove L1 to L2 transaction status + bool ret = _proveL1ToL2TransactionStatus({ + _l2TxHash: secondL2TxHash, + _l2BatchNumber: batchNumber, + _l2MessageIndex: secondLogIndex, + _l2TxNumberInBatch: 1, + _merkleProof: secondLogProof, + _status: txStatus + }); + // Assert that the proof was successful + assertEq(ret, true); + } + + function checkRecursiveLeafProof(RecursiveProofInfo memory proofInfo) internal returns (bool) { + address secondDiamondProxy = deployDiamondProxy(); + + IMailbox secondMailbox = IMailbox(secondDiamondProxy); + UtilsFacet secondUtils = UtilsFacet(secondDiamondProxy); + IGetters secondGetters = IGetters(secondDiamondProxy); + + uint256 secondBatchNumber = secondGetters.getTotalBatchesExecuted(); + + (bytes32[] memory proof, bytes32 requiredRoot) = _composeRecursiveProof( + RecursiveProofInfo({ + leaf: proofInfo.leaf, + logProof: proofInfo.logProof, + leafProofMask: proofInfo.leafProofMask, + // We override it since it is only known here + batchNumber: batchNumber, + batchProof: proofInfo.batchProof, + batchLeafProofMask: proofInfo.batchLeafProofMask, + // We override it since it is only known here + settlementLayerBatchNumber: secondBatchNumber, + settlementLayerBatchRootMask: proofInfo.settlementLayerBatchRootMask, + settlementLayerChainId: proofInfo.settlementLayerChainId, + chainIdProof: proofInfo.chainIdProof + }) + ); + utilsFacet.util_setL2LogsRootHash(secondBatchNumber, requiredRoot); + + vm.mockCall( + address(bridgehub), + abi.encodeCall(IBridgehub.whitelistedSettlementLayers, (proofInfo.settlementLayerChainId)), + abi.encode(true) + ); + vm.mockCall( + address(bridgehub), + abi.encodeCall(IBridgehub.getZKChain, (proofInfo.settlementLayerChainId)), + abi.encode(secondDiamondProxy) + ); + + return mailboxFacet.proveL2LeafInclusion(batchNumber, proofInfo.leafProofMask, proofInfo.leaf, proof); + } + + function test_successRecursiveProof() external { + assertTrue( + checkRecursiveLeafProof( + RecursiveProofInfo({ + leaf: bytes32(0), + logProof: bytes32Arr(2, bytes32(0), bytes32(uint256(1))), + leafProofMask: 2, + // We override it since it is only known here + batchNumber: 0, + batchProof: bytes32Arr(2, bytes32(uint256(1)), bytes32(uint256(1))), + batchLeafProofMask: 1, + // We override it since it is only known here + settlementLayerBatchNumber: 0, + settlementLayerBatchRootMask: 3, + settlementLayerChainId: 255, + chainIdProof: bytes32Arr(2, bytes32(uint256(1)), bytes32(uint256(0))) + }) + ) + ); + } + + function test_successRecursiveProofZeroLength() external { + assertTrue( + checkRecursiveLeafProof( + RecursiveProofInfo({ + leaf: bytes32(0), + logProof: bytes32Arr(2, bytes32(0), bytes32(uint256(1))), + leafProofMask: 2, + // We override it since it is only known here + batchNumber: 0, + batchProof: bytes32Arr(0, bytes32(0), bytes32(0)), + batchLeafProofMask: 0, + // We override it since it is only known here + settlementLayerBatchNumber: 0, + settlementLayerBatchRootMask: 3, + settlementLayerChainId: 255, + chainIdProof: bytes32Arr(2, bytes32(uint256(1)), bytes32(uint256(0))) + }) + ) + ); + } + + /// @notice Proves L1 to L2 transaction status and cross-checks new and old encoding + function _proveL1ToL2TransactionStatus( + bytes32 _l2TxHash, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes32[] memory _merkleProof, + TxStatus _status + ) internal returns (bool) { + bool retOldEncoding = mailboxFacet.proveL1ToL2TransactionStatus({ + _l2TxHash: _l2TxHash, + _l2BatchNumber: _l2BatchNumber, + _l2MessageIndex: _l2MessageIndex, + _l2TxNumberInBatch: _l2TxNumberInBatch, + _merkleProof: _merkleProof, + _status: _status + }); + bool retNewEncoding = mailboxFacet.proveL1ToL2TransactionStatus({ + _l2TxHash: _l2TxHash, + _l2BatchNumber: _l2BatchNumber, + _l2MessageIndex: _l2MessageIndex, + _l2TxNumberInBatch: _l2TxNumberInBatch, + _merkleProof: _appendProofMetadata(_merkleProof), + _status: _status + }); + + assertEq(retOldEncoding, retNewEncoding); + + return retOldEncoding; + } + + /// @notice Proves L2 log inclusion and cross-checks new and old encoding + function _proveL2LogInclusion( + uint256 _batchNumber, + uint256 _index, + L2Log memory _log, + bytes32[] memory _proof, + bytes memory _expectedError + ) internal returns (bool) { + if (_expectedError.length > 0) { + vm.expectRevert(_expectedError); + } + bool retOldEncoding = mailboxFacet.proveL2LogInclusion({ + _batchNumber: _batchNumber, + _index: _index, + _proof: _proof, + _log: _log + }); + + if (_expectedError.length > 0) { + vm.expectRevert(_expectedError); + } + bool retNewEncoding = mailboxFacet.proveL2LogInclusion({ + _batchNumber: _batchNumber, + _index: _index, + _proof: _appendProofMetadata(_proof), + _log: _log + }); + + assertEq(retOldEncoding, retNewEncoding); + return retOldEncoding; + } + + function _proveL2MessageInclusion( + uint256 _batchNumber, + uint256 _index, + L2Message memory _message, + bytes32[] memory _proof, + bytes memory _expectedError + ) internal returns (bool) { + if (_expectedError.length > 0) { + vm.expectRevert(_expectedError); + } + bool retOldEncoding = mailboxFacet.proveL2MessageInclusion({ + _batchNumber: _batchNumber, + _index: _index, + _message: _message, + _proof: _proof + }); + + if (_expectedError.length > 0) { + vm.expectRevert(_expectedError); + } + bool retNewEncoding = mailboxFacet.proveL2MessageInclusion({ + _batchNumber: _batchNumber, + _index: _index, + _message: _message, + _proof: _appendProofMetadata(_proof) + }); + + assertEq(retOldEncoding, retNewEncoding); + return retOldEncoding; + } + + function _composeMetadata(uint256 proofLen, uint256 batchProofLen, bool finalNode) internal pure returns (bytes32) { + return + bytes32( + bytes.concat( + bytes1(0x01), + bytes1(uint8(proofLen)), + bytes1(uint8(batchProofLen)), + bytes1(uint8(finalNode ? 1 : 0)), + bytes28(0) + ) + ); + } + + /// @notice Appends the proof metadata to the log proof as if the proof is for a batch that settled on L1. + function _appendProofMetadata(bytes32[] memory logProof) internal returns (bytes32[] memory result) { + result = new bytes32[](logProof.length + 1); + + result[0] = _composeMetadata(logProof.length, 0, true); + for (uint256 i = 0; i < logProof.length; i++) { + result[i + 1] = logProof[i]; + } + } + + // Just quicker to type than creating new bytes32[] each time, + function bytes32Arr(uint256 length, bytes32 elem1, bytes32 elem2) internal pure returns (bytes32[] memory result) { + result = new bytes32[](length); + if (length > 0) { + result[0] = elem1; + } + if (length > 1) { + result[1] = elem2; + } + } + + struct RecursiveProofInfo { + bytes32 leaf; + bytes32[] logProof; + uint256 leafProofMask; + uint256 batchNumber; + bytes32[] batchProof; + uint256 batchLeafProofMask; + uint256 settlementLayerBatchNumber; + uint256 settlementLayerBatchRootMask; + uint256 settlementLayerChainId; + bytes32[] chainIdProof; + } + + function _composeRecursiveProof( + RecursiveProofInfo memory info + ) internal returns (bytes32[] memory proof, bytes32 chainBRoot) { + uint256 ptr; + proof = new bytes32[](1 + info.logProof.length + 1 + info.batchProof.length + 2 + 1 + info.chainIdProof.length); + proof[ptr++] = _composeMetadata(info.logProof.length, info.batchProof.length, false); + copyBytes32(proof, info.logProof, ptr); + ptr += info.logProof.length; + + bytes32 batchSettlementRoot = Merkle.calculateRootMemory(info.logProof, info.leafProofMask, info.leaf); + + bytes32 batchLeafHash = MessageHashing.batchLeafHash(batchSettlementRoot, info.batchNumber); + + proof[ptr++] = bytes32(uint256(info.batchLeafProofMask)); + copyBytes32(proof, info.batchProof, ptr); + ptr += info.batchProof.length; + + bytes32 chainIdRoot = Merkle.calculateRootMemory(info.batchProof, info.batchLeafProofMask, batchLeafHash); + + bytes32 chainIdLeaf = MessageHashing.chainIdLeafHash(chainIdRoot, gettersFacet.getChainId()); + + uint256 settlementLayerPackedBatchInfo = (info.settlementLayerBatchNumber << 128) + + (info.settlementLayerBatchRootMask); + proof[ptr++] = bytes32(settlementLayerPackedBatchInfo); + proof[ptr++] = bytes32(info.settlementLayerChainId); + + proof[ptr++] = _composeMetadata(info.chainIdProof.length, 0, true); + copyBytes32(proof, info.chainIdProof, ptr); + ptr += info.chainIdProof.length; + + // Just in case + require(proof.length == ptr, "Incorrect ptr"); + + chainBRoot = Merkle.calculateRootMemory(info.chainIdProof, info.settlementLayerBatchRootMask, chainIdLeaf); + } + + function copyBytes32(bytes32[] memory to, bytes32[] memory from, uint256 pos) internal pure { + for (uint256 i = 0; i < from.length; i++) { + to[pos + i] = from[i]; + } + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/RequestL2Transaction.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/RequestL2Transaction.t.sol similarity index 95% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/RequestL2Transaction.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/RequestL2Transaction.t.sol index 76c501013..0ea16b46c 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/RequestL2Transaction.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/RequestL2Transaction.t.sol @@ -7,10 +7,11 @@ import {BridgehubL2TransactionRequest} from "contracts/common/Messaging.sol"; import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, MAX_NEW_FACTORY_DEPS, ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; import {TransactionFiltererTrue} from "contracts/dev-contracts/test/DummyTransactionFiltererTrue.sol"; import {TransactionFiltererFalse} from "contracts/dev-contracts/test/DummyTransactionFiltererFalse.sol"; -import {FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol"; -import {IL1SharedBridge} from "contracts/bridge/interfaces/IL1SharedBridge.sol"; +import {FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZKChainStorage.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; import {DummySharedBridge} from "contracts/dev-contracts/test/DummySharedBridge.sol"; import {OnlyEraSupported, TooManyFactoryDeps, MsgValueTooLow, GasPerPubdataMismatch} from "contracts/common/L1ContractErrors.sol"; +import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; contract MailboxRequestL2TransactionTest is MailboxTest { address tempAddress; @@ -24,6 +25,7 @@ contract MailboxRequestL2TransactionTest is MailboxTest { l1SharedBridge = new DummySharedBridge(keccak256("dummyDepositHash")); baseTokenBridgeAddress = address(l1SharedBridge); + vm.mockCall(bridgehub, abi.encodeCall(Bridgehub.sharedBridge, ()), abi.encode(baseTokenBridgeAddress)); tempAddress = makeAddr("temp"); tempBytesArr = new bytes[](0); @@ -122,7 +124,6 @@ contract MailboxRequestL2TransactionTest is MailboxTest { function test_RevertWhen_bridgePaused(uint256 randomValue) public { utilsFacet.util_setBaseTokenGasPriceMultiplierDenominator(1); utilsFacet.util_setPriorityTxMaxGasLimit(100000000); - utilsFacet.util_setBaseTokenBridge(baseTokenBridgeAddress); uint256 l2GasLimit = 1000000; uint256 baseCost = mailboxFacet.l2TransactionBaseCost(10000000, l2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA); @@ -137,7 +138,6 @@ contract MailboxRequestL2TransactionTest is MailboxTest { function test_success_requestL2Transaction(uint256 randomValue) public { utilsFacet.util_setBaseTokenGasPriceMultiplierDenominator(1); utilsFacet.util_setPriorityTxMaxGasLimit(100000000); - utilsFacet.util_setBaseTokenBridge(baseTokenBridgeAddress); uint256 l2GasLimit = 1000000; uint256 baseCost = mailboxFacet.l2TransactionBaseCost(10000000, l2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA); diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/_Mailbox_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/_Mailbox_Shared.t.sol similarity index 74% rename from l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/_Mailbox_Shared.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/_Mailbox_Shared.t.sol index 32a1e9c55..95ddccde9 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/_Mailbox_Shared.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/_Mailbox_Shared.t.sol @@ -3,10 +3,11 @@ pragma solidity 0.8.24; 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 {Utils} from "foundry-test/l1/unit/concrete/Utils/Utils.sol"; +import {UtilsFacet} from "foundry-test/l1/unit/concrete/Utils/UtilsFacet.sol"; import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; import {MailboxFacet} from "contracts/state-transition/chain-deps/facets/Mailbox.sol"; +import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {IMailbox} from "contracts/state-transition/chain-interfaces/IMailbox.sol"; import {IGetters} from "contracts/state-transition/chain-interfaces/IGetters.sol"; @@ -20,14 +21,16 @@ contract MailboxTest is Test { uint256 constant eraChainId = 9; address internal testnetVerifier = address(new TestnetVerifier()); address diamondProxy; + address bridgehub; - function setupDiamondProxy() public { + function deployDiamondProxy() internal returns (address proxy) { sender = makeAddr("sender"); + bridgehub = makeAddr("bridgehub"); vm.deal(sender, 100 ether); Diamond.FacetCut[] memory facetCuts = new Diamond.FacetCut[](3); facetCuts[0] = Diamond.FacetCut({ - facet: address(new MailboxFacet(eraChainId)), + facet: address(new MailboxFacet(eraChainId, block.chainid)), action: Diamond.Action.Add, isFreezable: true, selectors: Utils.getMailboxSelectors() @@ -45,7 +48,14 @@ contract MailboxTest is Test { selectors: Utils.getGettersSelectors() }); - diamondProxy = Utils.makeDiamondProxy(facetCuts, testnetVerifier); + proxy = Utils.makeDiamondProxy(facetCuts, testnetVerifier); + utilsFacet = UtilsFacet(proxy); + utilsFacet.util_setBridgehub(bridgehub); + } + + function setupDiamondProxy() public { + address diamondProxy = deployDiamondProxy(); + mailboxFacet = IMailbox(diamondProxy); utilsFacet = UtilsFacet(diamondProxy); gettersFacet = IGetters(diamondProxy); diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/data-availability/CalldataDA.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/data-availability/CalldataDA.t.sol new file mode 100644 index 000000000..767c0b84f --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/data-availability/CalldataDA.t.sol @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {Utils} from "../../Utils/Utils.sol"; +import {TestCalldataDA} from "contracts/dev-contracts/test/TestCalldataDA.sol"; +import {BLOB_SIZE_BYTES, BLOB_DATA_OFFSET, BLOB_COMMITMENT_SIZE} from "contracts/state-transition/data-availability/CalldataDA.sol"; +import {OperatorDAInputTooSmall, InvalidNumberOfBlobs, InvalidL2DAOutputHash, OnlyOneBlobWithCalldataAllowed, PubdataInputTooSmall, PubdataLengthTooBig, InvalidPubdataHash} from "contracts/state-transition/L1StateTransitionErrors.sol"; + +contract CalldataDATest is Test { + TestCalldataDA calldataDA; + + function setUp() public { + calldataDA = new TestCalldataDA(); + } + + /*////////////////////////////////////////////////////////////////////////// + CalldataDA::_processL2RollupDAValidatorOutputHash + //////////////////////////////////////////////////////////////////////////*/ + + function test_RevertWhen_OperatorInputTooSmall() public { + bytes32 l2DAValidatorOutputHash = Utils.randomBytes32("l2DAValidatorOutputHash"); + uint256 maxBlobsSupported = 1; + bytes memory operatorDAInput = hex""; + + vm.expectRevert( + abi.encodeWithSelector(OperatorDAInputTooSmall.selector, operatorDAInput.length, BLOB_DATA_OFFSET) + ); + calldataDA.processL2RollupDAValidatorOutputHash(l2DAValidatorOutputHash, maxBlobsSupported, operatorDAInput); + } + + function test_RevertWhen_InvalidNumberOfBlobs() public { + bytes32 l2DAValidatorOutputHash = Utils.randomBytes32("l2DAValidatorOutputHash"); + uint256 maxBlobsSupported = 1; + + bytes32 stateDiffHash = Utils.randomBytes32("stateDiffHash"); + bytes32 fullPubdataHash = Utils.randomBytes32("fullPubdataHash"); + uint8 blobsProvided = 8; + + bytes memory operatorDAInput = abi.encodePacked(stateDiffHash, fullPubdataHash, blobsProvided); + + vm.expectRevert( + abi.encodeWithSelector( + InvalidNumberOfBlobs.selector, + uint256(uint8(operatorDAInput[64])), + maxBlobsSupported + ) + ); + calldataDA.processL2RollupDAValidatorOutputHash(l2DAValidatorOutputHash, maxBlobsSupported, operatorDAInput); + } + + function test_RevertWhen_InvalidBlobHashes() public { + bytes32 l2DAValidatorOutputHash = Utils.randomBytes32("l2DAValidatorOutputHash"); + uint256 maxBlobsSupported = 1; + + bytes32 stateDiffHash = Utils.randomBytes32("stateDiffHash"); + bytes32 fullPubdataHash = Utils.randomBytes32("fullPubdataHash"); + uint8 blobsProvided = 1; + + bytes memory operatorDAInput = abi.encodePacked(stateDiffHash, fullPubdataHash, blobsProvided); + + vm.expectRevert( + abi.encodeWithSelector( + OperatorDAInputTooSmall.selector, + operatorDAInput.length, + BLOB_DATA_OFFSET + 32 * uint256(uint8(operatorDAInput[64])) + ) + ); + calldataDA.processL2RollupDAValidatorOutputHash(l2DAValidatorOutputHash, maxBlobsSupported, operatorDAInput); + } + + function test_RevertWhen_InvaliL2DAOutputHash() public { + bytes32 l2DAValidatorOutputHash = Utils.randomBytes32("l2DAValidatorOutputHash"); + uint256 maxBlobsSupported = 1; + + bytes32 stateDiffHash = Utils.randomBytes32("stateDiffHash"); + bytes32 fullPubdataHash = Utils.randomBytes32("fullPubdataHash"); + uint8 blobsProvided = 1; + bytes32 blobLinearHash = Utils.randomBytes32("blobLinearHash"); + + bytes memory operatorDAInput = abi.encodePacked(stateDiffHash, fullPubdataHash, blobsProvided, blobLinearHash); + + vm.expectRevert(abi.encodeWithSelector(InvalidL2DAOutputHash.selector, l2DAValidatorOutputHash)); + calldataDA.processL2RollupDAValidatorOutputHash(l2DAValidatorOutputHash, maxBlobsSupported, operatorDAInput); + } + + function test_ProcessL2RollupDAValidatorOutputHash() public { + bytes32 stateDiffHash = Utils.randomBytes32("stateDiffHash"); + bytes32 fullPubdataHash = Utils.randomBytes32("fullPubdataHash"); + uint8 blobsProvided = 1; + bytes32 blobLinearHash = Utils.randomBytes32("blobLinearHash"); + + bytes memory daInput = abi.encodePacked(stateDiffHash, fullPubdataHash, blobsProvided, blobLinearHash); + bytes memory l1DaInput = "verifydonttrust"; + + bytes32 l2DAValidatorOutputHash = keccak256(daInput); + + bytes memory operatorDAInput = abi.encodePacked(daInput, l1DaInput); + + ( + bytes32 outputStateDiffHash, + bytes32 outputFullPubdataHash, + bytes32[] memory blobsLinearHashes, + uint256 outputBlobsProvided, + bytes memory outputL1DaInput + ) = calldataDA.processL2RollupDAValidatorOutputHash(l2DAValidatorOutputHash, blobsProvided, operatorDAInput); + + assertEq(outputStateDiffHash, stateDiffHash, "stateDiffHash"); + assertEq(outputFullPubdataHash, fullPubdataHash, "fullPubdataHash"); + assertEq(blobsLinearHashes.length, 1, "blobsLinearHashesLength"); + assertEq(blobsLinearHashes[0], blobLinearHash, "blobsLinearHashes"); + assertEq(outputL1DaInput, l1DaInput, "l1DaInput"); + } + + /*////////////////////////////////////////////////////////////////////////// + CalldataDA::_processCalldataDA + //////////////////////////////////////////////////////////////////////////*/ + + function test_RevertWhen_OnlyOneBlobWithCalldataAllowed(uint256 blobsProvided) public { + vm.assume(blobsProvided != 1); + bytes32 fullPubdataHash = Utils.randomBytes32("fullPubdataHash"); + uint256 maxBlobsSupported = 6; + bytes memory pubdataInput = ""; + + vm.expectRevert(OnlyOneBlobWithCalldataAllowed.selector); + calldataDA.processCalldataDA(blobsProvided, fullPubdataHash, maxBlobsSupported, pubdataInput); + } + + function test_RevertWhen_PubdataTooBig() public { + uint256 blobsProvided = 1; + uint256 maxBlobsSupported = 6; + bytes calldata pubdataInput = makeBytesArrayOfLength(BLOB_SIZE_BYTES + 33); + bytes32 fullPubdataHash = keccak256(pubdataInput); + + vm.expectRevert(abi.encodeWithSelector(PubdataLengthTooBig.selector, 126977, blobsProvided * BLOB_SIZE_BYTES)); + calldataDA.processCalldataDA(blobsProvided, fullPubdataHash, maxBlobsSupported, pubdataInput); + } + + function test_RevertWhen_PubdataInputTooSmall() public { + uint256 blobsProvided = 1; + uint256 maxBlobsSupported = 6; + bytes calldata pubdataInput = makeBytesArrayOfLength(31); + bytes32 fullPubdataHash = keccak256(pubdataInput); + + vm.expectRevert( + abi.encodeWithSelector(PubdataInputTooSmall.selector, pubdataInput.length, BLOB_COMMITMENT_SIZE) + ); + calldataDA.processCalldataDA(blobsProvided, fullPubdataHash, maxBlobsSupported, pubdataInput); + } + + function test_RevertWhen_PubdataDoesntMatchPubdataHash() public { + uint256 blobsProvided = 1; + uint256 maxBlobsSupported = 6; + bytes memory pubdataInputWithoutBlobCommitment = "verifydonttrustzkistheendgamemagicmoonmath"; + bytes32 blobCommitment = Utils.randomBytes32("blobCommitment"); + bytes memory pubdataInput = abi.encodePacked(pubdataInputWithoutBlobCommitment, blobCommitment); + bytes32 fullPubdataHash = keccak256(pubdataInput); + + vm.expectRevert( + abi.encodeWithSelector( + InvalidPubdataHash.selector, + fullPubdataHash, + keccak256(pubdataInputWithoutBlobCommitment) + ) + ); + calldataDA.processCalldataDA(blobsProvided, fullPubdataHash, maxBlobsSupported, pubdataInput); + } + + function test_ProcessCalldataDA() public { + uint256 blobsProvided = 1; + uint256 maxBlobsSupported = 6; + bytes memory pubdataInputWithoutBlobCommitment = "verifydonttrustzkistheendgamemagicmoonmath"; + bytes32 blobCommitment = Utils.randomBytes32("blobCommitment"); + bytes memory pubdataInput = abi.encodePacked(pubdataInputWithoutBlobCommitment, blobCommitment); + bytes32 fullPubdataHash = keccak256(pubdataInputWithoutBlobCommitment); + + (bytes32[] memory blobCommitments, bytes memory pubdata) = calldataDA.processCalldataDA( + blobsProvided, + fullPubdataHash, + maxBlobsSupported, + pubdataInput + ); + + assertEq(blobCommitments.length, 6, "blobCommitmentsLength"); + assertEq(blobCommitments[0], blobCommitment, "blobCommitment1"); + assertEq(blobCommitments[1], bytes32(0), "blobCommitment2"); + assertEq(blobCommitments[2], bytes32(0), "blobCommitment3"); + assertEq(blobCommitments[3], bytes32(0), "blobCommitment4"); + assertEq(blobCommitments[4], bytes32(0), "blobCommitment5"); + assertEq(blobCommitments[5], bytes32(0), "blobCommitment6"); + assertEq(pubdata, pubdataInputWithoutBlobCommitment, "pubdata"); + } + + /*////////////////////////////////////////////////////////////////////////// + Util Functions + //////////////////////////////////////////////////////////////////////////*/ + + function makeBytesArrayOfLength(uint256 len) internal returns (bytes calldata arr) { + assembly { + arr.length := len + } + } +} diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/data-availability/RelayedSLDAValidator.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/data-availability/RelayedSLDAValidator.t.sol new file mode 100644 index 000000000..49c621fcf --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/data-availability/RelayedSLDAValidator.t.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {Test, console} from "forge-std/Test.sol"; +import {Utils} from "../../Utils/Utils.sol"; +import {RelayedSLDAValidator} from "contracts/state-transition/data-availability/RelayedSLDAValidator.sol"; +import {L1DAValidatorOutput, PubdataSource} from "contracts/state-transition/chain-interfaces/IL1DAValidator.sol"; +import {L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {IL1Messenger} from "contracts/common/interfaces/IL1Messenger.sol"; +import {L2_BRIDGEHUB_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {PubdataInputTooSmall, L1DAValidatorInvalidSender} from "contracts/state-transition/L1StateTransitionErrors.sol"; +import {InvalidPubdataSource} from "contracts/state-transition/L1StateTransitionErrors.sol"; + +contract RelayedSLDAValidatorTest is Test { + uint256 constant CHAIN_ID = 193; + address constant CHAIN_ADDRESS = address(0x1234); + RelayedSLDAValidator daValidator; + + function setUp() public { + daValidator = new RelayedSLDAValidator(); + vm.etch(L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, abi.encode(address(daValidator))); + vm.mockCall( + L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, + abi.encodeWithSelector(IL1Messenger.sendToL1.selector), + abi.encode(bytes32(0)) + ); + vm.mockCall( + L2_BRIDGEHUB_ADDR, + abi.encodeWithSelector(IBridgehub.getZKChain.selector, (CHAIN_ID)), + abi.encode(CHAIN_ADDRESS) + ); + } + + /*////////////////////////////////////////////////////////////////////////// + RelayedSLDAValidator::checkDA + //////////////////////////////////////////////////////////////////////////*/ + + function test_RevertWhen_InvalidPubdataSource() public { + bytes32 stateDiffHash = Utils.randomBytes32("stateDiffHash"); + bytes32 fullPubdataHash = Utils.randomBytes32("fullPubdataHash"); + uint8 blobsProvided = 1; + uint256 maxBlobsSupported = 6; + bytes32 blobLinearHash = Utils.randomBytes32("blobLinearHash"); + + bytes memory daInput = abi.encodePacked(stateDiffHash, fullPubdataHash, blobsProvided, blobLinearHash); + bytes memory l1DaInput = "verifydonttrust"; + + bytes32 l2DAValidatorOutputHash = keccak256(daInput); + + bytes memory operatorDAInput = abi.encodePacked(daInput, l1DaInput); + + vm.prank(CHAIN_ADDRESS); + // 118 is ascii encoding for `v` + vm.expectRevert(abi.encodeWithSelector(InvalidPubdataSource.selector, 118)); + daValidator.checkDA(CHAIN_ID, 0, l2DAValidatorOutputHash, operatorDAInput, maxBlobsSupported); + } + + function test_revertWhen_PubdataInputTooSmall() public { + bytes memory pubdata = "verifydont"; + console.logBytes(pubdata); + + bytes32 stateDiffHash = Utils.randomBytes32("stateDiffHash"); + uint8 blobsProvided = 1; + uint256 maxBlobsSupported = 6; + bytes32 blobLinearHash = Utils.randomBytes32("blobLinearHash"); + uint8 pubdataSource = uint8(PubdataSource.Calldata); + bytes memory l1DaInput = "verifydonttrust"; + bytes32 fullPubdataHash = keccak256(pubdata); + + bytes memory daInput = abi.encodePacked(stateDiffHash, fullPubdataHash, blobsProvided, blobLinearHash); + + bytes32 l2DAValidatorOutputHash = keccak256(daInput); + + bytes memory operatorDAInput = abi.encodePacked(daInput, pubdataSource, l1DaInput); + + vm.prank(CHAIN_ADDRESS); + vm.expectRevert(abi.encodeWithSelector(PubdataInputTooSmall.selector, 15, 32)); + daValidator.checkDA(CHAIN_ID, 0, l2DAValidatorOutputHash, operatorDAInput, maxBlobsSupported); + } + + function test_revertWhenInvalidSender() public { + bytes memory pubdata = "verifydont"; + console.logBytes(pubdata); + + bytes32 stateDiffHash = Utils.randomBytes32("stateDiffHash"); + uint8 blobsProvided = 1; + uint256 maxBlobsSupported = 6; + bytes32 blobLinearHash = Utils.randomBytes32("blobLinearHash"); + uint8 pubdataSource = uint8(PubdataSource.Calldata); + bytes memory l1DaInput = "verifydonttrust"; + bytes32 fullPubdataHash = keccak256(pubdata); + + bytes memory daInput = abi.encodePacked(stateDiffHash, fullPubdataHash, blobsProvided, blobLinearHash); + + bytes32 l2DAValidatorOutputHash = keccak256(daInput); + + bytes memory operatorDAInput = abi.encodePacked(daInput, pubdataSource, l1DaInput); + + vm.expectRevert(abi.encodeWithSelector(L1DAValidatorInvalidSender.selector, address(this))); + daValidator.checkDA(CHAIN_ID, 0, l2DAValidatorOutputHash, operatorDAInput, maxBlobsSupported); + } + + function test_checkDA() public { + bytes memory pubdata = "verifydont"; + console.logBytes(pubdata); + + bytes32 stateDiffHash = Utils.randomBytes32("stateDiffHash"); + uint8 blobsProvided = 1; + uint256 maxBlobsSupported = 6; + bytes32 blobLinearHash = Utils.randomBytes32("blobLinearHash"); + uint8 pubdataSource = uint8(PubdataSource.Calldata); + bytes memory l1DaInput = "verifydonttrustzkistheendgamemagicmoonmath"; + bytes32 fullPubdataHash = keccak256(pubdata); + + bytes memory daInput = abi.encodePacked(stateDiffHash, fullPubdataHash, blobsProvided, blobLinearHash); + + bytes32 l2DAValidatorOutputHash = keccak256(daInput); + + bytes memory operatorDAInput = abi.encodePacked(daInput, pubdataSource, l1DaInput); + + vm.prank(CHAIN_ADDRESS); + L1DAValidatorOutput memory output = daValidator.checkDA( + CHAIN_ID, + 0, + l2DAValidatorOutputHash, + operatorDAInput, + maxBlobsSupported + ); + assertEq(output.stateDiffHash, stateDiffHash, "stateDiffHash"); + assertEq(output.blobsLinearHashes.length, maxBlobsSupported, "blobsLinearHashesLength"); + assertEq(output.blobsOpeningCommitments.length, maxBlobsSupported, "blobsOpeningCommitmentsLength"); + } +} diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/data-availability/RollupDAManager.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/data-availability/RollupDAManager.t.sol new file mode 100644 index 000000000..d16c33a91 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/data-availability/RollupDAManager.t.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +// Import Foundry's Test framework +import {Test} from "forge-std/Test.sol"; + +// Import the RollupDAManager contract +import {RollupDAManager} from "contracts/state-transition/data-availability/RollupDAManager.sol"; + +import {ZeroAddress} from "contracts/common/L1ContractErrors.sol"; + +contract RollupDAManagerTest is Test { + // Instance of the contract under test + RollupDAManager rollupDAManager; + + // Addresses used in tests + address owner = address(0x1); + address newOwner = address(0x2); + address nonOwner = address(0x3); + address l1DAValidator1 = address(0x4); + address l2DAValidator1 = address(0x5); + address l1DAValidator2 = address(0x6); + address l2DAValidator2 = address(0x7); + address zeroAddress = address(0); + + // Events from the RollupDAManager contract + event DAPairUpdated(address indexed l1DAValidator, address indexed l2DAValidator, bool status); + + // setUp is run before each test + function setUp() public { + // Deploy the contract as the owner + vm.startPrank(owner); + rollupDAManager = new RollupDAManager(); + vm.stopPrank(); + } + + /* ========== Deployment Tests ========== */ + + function testOwnerIsSetCorrectly() public { + assertEq(rollupDAManager.owner(), owner, "Owner should be set correctly"); + } + + /* ========== Access Control Tests ========== */ + + function testOnlyOwnerCanUpdateDAPair() public { + // Attempt to update DA pair as owner + vm.startPrank(owner); + vm.expectEmit(true, true, false, true); + emit DAPairUpdated(l1DAValidator1, l2DAValidator1, true); + rollupDAManager.updateDAPair(l1DAValidator1, l2DAValidator1, true); + vm.stopPrank(); + + // Attempt to update DA pair as non-owner + vm.startPrank(nonOwner); + vm.expectRevert("Ownable: caller is not the owner"); + rollupDAManager.updateDAPair(l1DAValidator2, l2DAValidator2, true); + vm.stopPrank(); + } + + function testUpdateDAPairRevertsOnZeroAddresses() public { + vm.startPrank(owner); + + // Both addresses zero + vm.expectRevert(ZeroAddress.selector); + rollupDAManager.updateDAPair(zeroAddress, zeroAddress, true); + + // L1DAValidator zero + vm.expectRevert(ZeroAddress.selector); + rollupDAManager.updateDAPair(zeroAddress, l2DAValidator1, true); + + // L2DAValidator zero + vm.expectRevert(ZeroAddress.selector); + rollupDAManager.updateDAPair(l1DAValidator1, zeroAddress, true); + + vm.stopPrank(); + } + + /* ========== Functionality Tests ========== */ + + function testUpdateDAPairSetsAllowedDAPairsMapping() public { + vm.startPrank(owner); + + // Initially, the pair should not be allowed + bool allowed = rollupDAManager.isPairAllowed(l1DAValidator1, l2DAValidator1); + assertFalse(allowed, "DA pair should initially be disallowed"); + + // Update the DA pair to allowed + vm.expectEmit(true, true, false, true); + emit DAPairUpdated(l1DAValidator1, l2DAValidator1, true); + rollupDAManager.updateDAPair(l1DAValidator1, l2DAValidator1, true); + allowed = rollupDAManager.isPairAllowed(l1DAValidator1, l2DAValidator1); + assertTrue(allowed, "DA pair should be allowed after update"); + + // Update the DA pair to disallowed + vm.expectEmit(true, true, false, true); + emit DAPairUpdated(l1DAValidator1, l2DAValidator1, false); + rollupDAManager.updateDAPair(l1DAValidator1, l2DAValidator1, false); + allowed = rollupDAManager.isPairAllowed(l1DAValidator1, l2DAValidator1); + assertFalse(allowed, "DA pair should be disallowed after update"); + + vm.stopPrank(); + } + + function testUpdateMultipleDAPairs() public { + vm.startPrank(owner); + + // Update multiple DA pairs + rollupDAManager.updateDAPair(l1DAValidator1, l2DAValidator1, true); + rollupDAManager.updateDAPair(l1DAValidator2, l2DAValidator2, true); + + // Check both pairs + bool allowed1 = rollupDAManager.isPairAllowed(l1DAValidator1, l2DAValidator1); + bool allowed2 = rollupDAManager.isPairAllowed(l1DAValidator2, l2DAValidator2); + + assertTrue(allowed1, "First DA pair should be allowed"); + assertTrue(allowed2, "Second DA pair should be allowed"); + + vm.stopPrank(); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/PriorityQueue/OnEmptyQueue.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/libraries/PriorityQueue/OnEmptyQueue.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/libraries/PriorityQueue/OnEmptyQueue.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/libraries/PriorityQueue/OnEmptyQueue.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/PriorityQueue/PopOperations.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/libraries/PriorityQueue/PopOperations.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/libraries/PriorityQueue/PopOperations.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/libraries/PriorityQueue/PopOperations.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/PriorityQueue/PushOperations.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/libraries/PriorityQueue/PushOperations.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/libraries/PriorityQueue/PushOperations.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/libraries/PriorityQueue/PushOperations.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/PriorityQueue/_PriorityQueue_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/libraries/PriorityQueue/_PriorityQueue_Shared.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/libraries/PriorityQueue/_PriorityQueue_Shared.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/libraries/PriorityQueue/_PriorityQueue_Shared.t.sol diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/libraries/PriorityTree/PriorityTree.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/libraries/PriorityTree/PriorityTree.t.sol new file mode 100644 index 000000000..6acd8062e --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/libraries/PriorityTree/PriorityTree.t.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {PriorityTreeSharedTest, PriorityOpsBatchInfo} from "./_PriorityTree_Shared.t.sol"; +import {PriorityTreeCommitment} from "contracts/common/Config.sol"; +import {NotHistoricalRoot} from "contracts/state-transition/L1StateTransitionErrors.sol"; + +bytes32 constant ZERO_LEAF_HASH = keccak256(""); + +contract PriorityTreeTest is PriorityTreeSharedTest { + function test_gets() public { + assertEq(0, priorityTree.getSize()); + assertEq(0, priorityTree.getFirstUnprocessedPriorityTx()); + assertEq(0, priorityTree.getTotalPriorityTxs()); + assertEq(bytes32(0), priorityTree.getRoot()); + } + + function test_push() public { + bytes32 leaf1 = keccak256(abi.encode(1)); + bytes32 leaf2 = keccak256(abi.encode(2)); + + priorityTree.push(leaf1); + + assertEq(1, priorityTree.getSize()); + assertEq(0, priorityTree.getFirstUnprocessedPriorityTx()); + assertEq(1, priorityTree.getTotalPriorityTxs()); + assertEq(leaf1, priorityTree.getRoot()); + + priorityTree.push(leaf2); + + assertEq(2, priorityTree.getSize()); + assertEq(0, priorityTree.getFirstUnprocessedPriorityTx()); + assertEq(2, priorityTree.getTotalPriorityTxs()); + + bytes32 expectedRoot = keccak256(abi.encode(leaf1, leaf2)); + assertEq(expectedRoot, priorityTree.getRoot()); + } + + function test_processEmptyBatch() public { + pushMockEntries(3); + + assertEq(0, priorityTree.getFirstUnprocessedPriorityTx()); + priorityTree.processBatch( + PriorityOpsBatchInfo({ + leftPath: new bytes32[](0), + rightPath: new bytes32[](0), + itemHashes: new bytes32[](0) + }) + ); + + assertEq(0, priorityTree.getFirstUnprocessedPriorityTx()); + } + + function test_processBatch() public { + bytes32[] memory leaves = pushMockEntries(3); + assertEq(0, priorityTree.getFirstUnprocessedPriorityTx()); + + // 2 batches with: 1 tx, 2 txs. + + bytes32[] memory leftPath = new bytes32[](2); + bytes32[] memory rightPath = new bytes32[](2); + rightPath[0] = leaves[1]; + rightPath[1] = keccak256(abi.encode(leaves[2], ZERO_LEAF_HASH)); + bytes32[] memory batch1 = new bytes32[](1); + batch1[0] = leaves[0]; + + priorityTree.processBatch(PriorityOpsBatchInfo({leftPath: leftPath, rightPath: rightPath, itemHashes: batch1})); + + assertEq(1, priorityTree.getFirstUnprocessedPriorityTx()); + + leftPath[0] = leaves[0]; + rightPath[0] = ZERO_LEAF_HASH; + rightPath[1] = bytes32(0); + bytes32[] memory batch2 = new bytes32[](2); + batch2[0] = leaves[1]; + batch2[1] = leaves[2]; + + priorityTree.processBatch(PriorityOpsBatchInfo({leftPath: leftPath, rightPath: rightPath, itemHashes: batch2})); + + assertEq(3, priorityTree.getFirstUnprocessedPriorityTx()); + } + + function test_processBatch_shouldRevert() public { + bytes32[] memory itemHashes = pushMockEntries(3); + + vm.expectRevert(NotHistoricalRoot.selector); + priorityTree.processBatch( + PriorityOpsBatchInfo({leftPath: new bytes32[](2), rightPath: new bytes32[](2), itemHashes: itemHashes}) + ); + } + + function test_commitDecommit() public { + pushMockEntries(3); + bytes32 root = priorityTree.getRoot(); + + PriorityTreeCommitment memory commitment = priorityTree.getCommitment(); + priorityTree.initFromCommitment(commitment); + + assertEq(0, priorityTree.getFirstUnprocessedPriorityTx()); + assertEq(3, priorityTree.getTotalPriorityTxs()); + assertEq(root, priorityTree.getRoot()); + assertEq(ZERO_LEAF_HASH, priorityTree.getZero()); + } +} diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/libraries/PriorityTree/_PriorityTree_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/libraries/PriorityTree/_PriorityTree_Shared.t.sol new file mode 100644 index 000000000..0a39e74d1 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/libraries/PriorityTree/_PriorityTree_Shared.t.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {PriorityTreeTest, PriorityOpsBatchInfo} from "contracts/dev-contracts/test/PriorityTreeTest.sol"; + +contract PriorityTreeSharedTest is Test { + PriorityTreeTest internal priorityTree; + + constructor() { + priorityTree = new PriorityTreeTest(); + } + + // Pushes 'count' entries into the priority tree. + function pushMockEntries(uint256 count) public returns (bytes32[] memory) { + bytes32[] memory hashes = new bytes32[](count); + for (uint256 i = 0; i < count; ++i) { + bytes32 hash = keccak256(abi.encode(i)); + hashes[i] = hash; + priorityTree.push(hash); + } + return hashes; + } + + // add this to be excluded from coverage report + function test() internal virtual {} +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/TransactionValidator/ValidateL1L2Tx.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/libraries/TransactionValidator/ValidateL1L2Tx.t.sol similarity index 86% rename from l1-contracts/test/foundry/unit/concrete/state-transition/libraries/TransactionValidator/ValidateL1L2Tx.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/libraries/TransactionValidator/ValidateL1L2Tx.t.sol index dbba8ca2e..e01b6b4e4 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/TransactionValidator/ValidateL1L2Tx.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/libraries/TransactionValidator/ValidateL1L2Tx.t.sol @@ -4,7 +4,8 @@ pragma solidity 0.8.24; import {TransactionValidatorSharedTest} from "./_TransactionValidator_Shared.t.sol"; import {L2CanonicalTransaction} from "contracts/common/Messaging.sol"; -import {PubdataGreaterThanLimit, TxnBodyGasLimitNotEnoughGas, ValidateTxnNotEnoughGas, NotEnoughGas, TooMuchGas, InvalidPubdataLength} from "contracts/common/L1ContractErrors.sol"; +import {PubdataGreaterThanLimit, TxnBodyGasLimitNotEnoughGas, ValidateTxnNotEnoughGas, NotEnoughGas, TooMuchGas} from "contracts/common/L1ContractErrors.sol"; +import {OverheadForTransactionMustBeEqualToTxSlotOverhead} from "test/foundry/L1TestsErrors.sol"; contract ValidateL1L2TxTest is TransactionValidatorSharedTest { function test_BasicRequestL1L2() public pure { @@ -13,7 +14,7 @@ contract ValidateL1L2TxTest is TransactionValidatorSharedTest { validateL1ToL2Transaction(testTx, 500000, 100000); } - function test_RevertWhen_GasLimitDoesntCoverOverhead() public { + function test_RevertWhen_GasLimitdoesntCoverOverhead() public { L2CanonicalTransaction memory testTx = createTestTransaction(); // The limit is so low, that it doesn't even cover the overhead testTx.gasLimit = 0; @@ -79,16 +80,14 @@ contract ValidateL1L2TxTest is TransactionValidatorSharedTest { } function test_ShouldReturnCorrectOverhead_ShortTx() public pure { - require( - getOverheadForTransaction(32) == 10_000, - "The overhead for short transaction must be equal to the tx slot overhead" - ); + if (getOverheadForTransaction(32) != 10_000) { + revert OverheadForTransactionMustBeEqualToTxSlotOverhead(getOverheadForTransaction(32)); + } } function test_ShouldReturnCorrectOverhead_LongTx() public pure { - require( - getOverheadForTransaction(1000000) == 1000000 * 10, - "The overhead for long transaction must be equal to the tx slot overhead" - ); + if (getOverheadForTransaction(1000000) != 1000000 * 10) { + revert OverheadForTransactionMustBeEqualToTxSlotOverhead(getOverheadForTransaction(1000000)); + } } } diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/TransactionValidator/ValidateUpgradeTransaction.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/libraries/TransactionValidator/ValidateUpgradeTransaction.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/libraries/TransactionValidator/ValidateUpgradeTransaction.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/libraries/TransactionValidator/ValidateUpgradeTransaction.t.sol diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/TransactionValidator/_TransactionValidator_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/libraries/TransactionValidator/_TransactionValidator_Shared.t.sol similarity index 100% rename from l1-contracts/test/foundry/unit/concrete/state-transition/libraries/TransactionValidator/_TransactionValidator_Shared.t.sol rename to l1-contracts/test/foundry/l1/unit/concrete/state-transition/libraries/TransactionValidator/_TransactionValidator_Shared.t.sol diff --git a/l1-contracts/test/foundry/l1/unit/concrete/upgrades/GovernanceUpgradeTimer.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/upgrades/GovernanceUpgradeTimer.t.sol new file mode 100644 index 000000000..5feb00e0e --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/upgrades/GovernanceUpgradeTimer.t.sol @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +// Import Foundry's Test framework +import {Test} from "forge-std/Test.sol"; + +import {GovernanceUpgradeTimer} from "contracts/upgrades/GovernanceUpgradeTimer.sol"; + +import {ZeroAddress, TimerAlreadyStarted, CallerNotTimerAdmin, DeadlineNotYetPassed, NewDeadlineNotGreaterThanCurrent, NewDeadlineExceedsMaxDeadline} from "contracts/common/L1ContractErrors.sol"; + +contract GovernanceUpgradeTimerTest is Test { + // Instance of the contract under test + GovernanceUpgradeTimer timer; + + // Addresses used in tests + address owner = address(0x1); + address newOwner = address(0x2); + address timerGovernance = address(0x3); + address nonAdmin = address(0x4); + address anotherAddress = address(0x5); + + // Immutable parameters for the contract + uint256 initialDelay = 1000; // seconds + uint256 maxAdditionalDelay = 2000; // seconds + + // Events from the GovernanceUpgradeTimer contract + event TimerStarted(uint256 deadline, uint256 maxDeadline); + event DeadlineChanged(uint256 newDeadline); + + // setUp is run before each test + function setUp() public { + // Deploy the contract as the owner + vm.startPrank(owner); + timer = new GovernanceUpgradeTimer(initialDelay, maxAdditionalDelay, timerGovernance, owner); + vm.stopPrank(); + } + + /* ========== Deployment Tests ========== */ + + function testDeploymentSetsImmutableVariablesCorrectly() public { + assertEq(timer.INITIAL_DELAY(), initialDelay, "INITIAL_DELAY should be set correctly"); + assertEq(timer.MAX_ADDITIONAL_DELAY(), maxAdditionalDelay, "MAX_ADDITIONAL_DELAY should be set correctly"); + assertEq(timer.TIMER_GOVERNANCE(), timerGovernance, "TIMER_GOVERNANCE should be set correctly"); + } + + function testDeploymentSetsOwnerCorrectly() public { + assertEq(timer.owner(), owner, "Owner should be set correctly"); + } + + function testDeploymentRevertsIfTimerGovernanceIsZero() public { + vm.startPrank(owner); + vm.expectRevert(ZeroAddress.selector); + new GovernanceUpgradeTimer(initialDelay, maxAdditionalDelay, address(0), owner); + vm.stopPrank(); + } + + /* ========== Access Control Tests ========== */ + + function testOnlyTimerGovernanceCanStartTimer() public { + // Attempt to start timer as TIMER_GOVERNANCE + vm.startPrank(timerGovernance); + vm.expectEmit(true, true, false, true); + emit TimerStarted(block.timestamp + initialDelay, block.timestamp + initialDelay + maxAdditionalDelay); + timer.startTimer(); + vm.stopPrank(); + + // Attempt to start timer as non-TIMER_GOVERNANCE + vm.startPrank(nonAdmin); + vm.expectRevert(CallerNotTimerAdmin.selector); + timer.startTimer(); + vm.stopPrank(); + } + + function testOnlyOwnerCanChangeDeadline() public { + // Start the timer first + vm.startPrank(timerGovernance); + timer.startTimer(); + vm.stopPrank(); + + uint256 newDeadline = timer.deadline() + 500; + + // Attempt to change deadline as owner + vm.startPrank(owner); + vm.expectEmit(true, false, false, true); + emit DeadlineChanged(newDeadline); + timer.changeDeadline(newDeadline); + vm.stopPrank(); + + // Attempt to change deadline as non-owner + vm.startPrank(nonAdmin); + vm.expectRevert("Ownable: caller is not the owner"); + timer.changeDeadline(newDeadline + 100); + vm.stopPrank(); + } + + /* ========== Functionality Tests ========== */ + + function testStartTimerSetsDeadlineAndMaxDeadlineCorrectly() public { + uint256 currentBlockTimestamp = block.timestamp; + + vm.startPrank(timerGovernance); + vm.expectEmit(true, true, false, true); + emit TimerStarted( + currentBlockTimestamp + initialDelay, + currentBlockTimestamp + initialDelay + maxAdditionalDelay + ); + timer.startTimer(); + vm.stopPrank(); + + assertEq(timer.deadline(), currentBlockTimestamp + initialDelay, "Deadline should be set correctly"); + assertEq( + timer.maxDeadline(), + currentBlockTimestamp + initialDelay + maxAdditionalDelay, + "MaxDeadline should be set correctly" + ); + } + + function testStartTimerCanNotBeCalledMultipleTimesByTimerGovernance() public { + uint256 firstBlockTimestamp = block.timestamp; + + // First timer start + vm.startPrank(timerGovernance); + vm.expectEmit(true, true, false, true); + emit TimerStarted(firstBlockTimestamp + initialDelay, firstBlockTimestamp + initialDelay + maxAdditionalDelay); + timer.startTimer(); + vm.stopPrank(); + + // Second timer start + vm.startPrank(timerGovernance); + vm.expectRevert(TimerAlreadyStarted.selector); + timer.startTimer(); + vm.stopPrank(); + } + + function testCheckDeadlineRevertsIfDeadlineNotPassed() public { + // Start the timer + vm.startPrank(timerGovernance); + timer.startTimer(); + vm.stopPrank(); + + // Attempt to check deadline before it has passed + vm.expectRevert(DeadlineNotYetPassed.selector); + timer.checkDeadline(); + } + + function testCheckDeadlineDoesNotRevertIfDeadlinePassed() public { + // Start the timer + vm.startPrank(timerGovernance); + timer.startTimer(); + vm.stopPrank(); + + // Advance time past the deadline + vm.warp(timer.deadline() + 1); + + // Check deadline should not revert + timer.checkDeadline(); + } + + function testChangeDeadlineRevertsIfNewDeadlineNotGreater() public { + // Start the timer + vm.startPrank(timerGovernance); + timer.startTimer(); + vm.stopPrank(); + + uint256 originalDeadline = timer.deadline(); + uint256 invalidNewDeadline = originalDeadline - 100; + + // Attempt to change deadline to a value not greater than current + vm.startPrank(owner); + vm.expectRevert(NewDeadlineNotGreaterThanCurrent.selector); + timer.changeDeadline(invalidNewDeadline); + vm.stopPrank(); + } + + function testChangeDeadlineRevertsIfNewDeadlineExceedsMaxDeadline() public { + // Start the timer + vm.startPrank(timerGovernance); + timer.startTimer(); + vm.stopPrank(); + + uint256 maxDeadline = timer.maxDeadline(); + uint256 invalidNewDeadline = maxDeadline + 1; + + // Attempt to change deadline to exceed maxDeadline + vm.startPrank(owner); + vm.expectRevert(NewDeadlineExceedsMaxDeadline.selector); + timer.changeDeadline(invalidNewDeadline); + vm.stopPrank(); + } + + /* ========== Edge Case Tests ========== */ + + function testChangeDeadlineToMaxDeadline() public { + // Start the timer + vm.startPrank(timerGovernance); + timer.startTimer(); + vm.stopPrank(); + + uint256 maxDeadline = timer.maxDeadline(); + + // Change deadline to maxDeadline + vm.startPrank(owner); + vm.expectEmit(true, false, false, true); + emit DeadlineChanged(maxDeadline); + timer.changeDeadline(maxDeadline); + vm.stopPrank(); + + assertEq(timer.deadline(), maxDeadline, "Deadline should be set to maxDeadline"); + } + + function testChangeDeadlineRevertsIfNewDeadlineEqualsCurrent() public { + // Start the timer + vm.startPrank(timerGovernance); + timer.startTimer(); + vm.stopPrank(); + + uint256 currentDeadline = timer.deadline(); + + // Attempt to change deadline to the current deadline + vm.startPrank(owner); + vm.expectRevert(NewDeadlineNotGreaterThanCurrent.selector); + timer.changeDeadline(currentDeadline); + vm.stopPrank(); + } + + /* ========== Unauthorized Function Calls ========== */ + + function testNonTimerGovernanceCannotStartTimer() public { + vm.startPrank(owner); + vm.expectRevert(CallerNotTimerAdmin.selector); + timer.startTimer(); + vm.stopPrank(); + } + + function testNonOwnerCannotChangeDeadline() public { + // Start the timer first + vm.startPrank(timerGovernance); + timer.startTimer(); + vm.stopPrank(); + + uint256 newDeadline = timer.deadline() + 500; + + vm.startPrank(nonAdmin); + vm.expectRevert("Ownable: caller is not the owner"); + timer.changeDeadline(newDeadline); + vm.stopPrank(); + } +} diff --git a/l1-contracts/test/foundry/l1/unit/concrete/upgrades/L1GatewayBase.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/upgrades/L1GatewayBase.t.sol new file mode 100644 index 000000000..88b9e50d0 --- /dev/null +++ b/l1-contracts/test/foundry/l1/unit/concrete/upgrades/L1GatewayBase.t.sol @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "forge-std/Test.sol"; +import {L1GatewayBase} from "contracts/upgrades/L1GatewayBase.sol"; // Adjust the import path accordingly +import {ZKChainStorage} from "contracts/state-transition/chain-deps/ZKChainStorage.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {IL1SharedBridgeLegacy} from "contracts/bridge/interfaces/IL1SharedBridgeLegacy.sol"; +import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; +import {ZKChainSpecificForceDeploymentsData} from "contracts/state-transition/l2-deps/IL2GenesisUpgrade.sol"; + +// Concrete implementation of L1GatewayBase for testing +contract TestL1GatewayBase is L1GatewayBase { + ZKChainStorage s; + + // For testing, we need to be able to call the internal function from outside + function requestGetZKChainSpecificForceDeploymentsData( + address _wrappedBaseTokenStore, + address _baseTokenAddress + ) external view returns (bytes memory) { + return getZKChainSpecificForceDeploymentsData(s, _wrappedBaseTokenStore, _baseTokenAddress); + } + + function setChainId(uint256 _chainId) external { + s.chainId = _chainId; + } + + function setBaseTokenAssetId(bytes32 _assetId) external { + s.baseTokenAssetId = _assetId; + } + + function setBrideghub(address _bridgehub) external { + s.bridgehub = _bridgehub; + } +} + +contract MockERC20TokenWithMetadata { + string private _name; + string private _symbol; + + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + function name() external view returns (string memory) { + return _name; + } + + function symbol() external view returns (string memory) { + return _symbol; + } +} + +contract MockERC20TokenWithoutMetadata {} + +contract L1GatewayBaseTest is Test { + TestL1GatewayBase testGateway; + // Mocks for dependencies + address bridgehubMock; + address sharedBridgeMock; + // MockL2WrappedBaseTokenStore wrappedBaseTokenStoreMock; + + // Addresses + address sharedBridgeAddress; + address legacySharedBridgeAddress; + + // Chain ID for testing + uint256 chainId = 123; + bytes32 baseTokenAssetId; + + function setUp() public { + baseTokenAssetId = bytes32("baseTokenAssetId"); + bridgehubMock = makeAddr("bridgehubMock"); + sharedBridgeMock = makeAddr("sharedBridgeMock"); + legacySharedBridgeAddress = makeAddr("legacySharedBridgeAddress"); + + testGateway = new TestL1GatewayBase(); + + // Initialize ZKChainStorage + testGateway.setChainId(chainId); + testGateway.setBrideghub(bridgehubMock); + // Set base token asset ID + testGateway.setBaseTokenAssetId(baseTokenAssetId); + + vm.mockCall(bridgehubMock, abi.encodeCall(IBridgehub.sharedBridge, ()), abi.encode(sharedBridgeMock)); + vm.mockCall( + sharedBridgeMock, + abi.encodeCall(IL1SharedBridgeLegacy.l2BridgeAddress, (chainId)), + abi.encode(address(legacySharedBridgeAddress)) + ); + } + + // Test with ETH as the base token + function testWithETH() public { + // No wrapped base token store + address _wrappedBaseTokenStore = address(0); + + // Call the function + bytes memory data = testGateway.requestGetZKChainSpecificForceDeploymentsData( + _wrappedBaseTokenStore, + ETH_TOKEN_ADDRESS + ); + + // Decode the returned data + ZKChainSpecificForceDeploymentsData memory chainData = abi.decode(data, (ZKChainSpecificForceDeploymentsData)); + + // Check the values + assertEq(chainData.baseTokenAssetId, baseTokenAssetId); + assertEq(chainData.l2LegacySharedBridge, legacySharedBridgeAddress); + assertEq(chainData.predeployedL2WethAddress, address(0)); + assertEq(chainData.baseTokenL1Address, ETH_TOKEN_ADDRESS); + assertEq(chainData.baseTokenName, "Ether"); + assertEq(chainData.baseTokenSymbol, "ETH"); + } + + // Test with ERC20 that correctly implements metadata + function testWithERC20TokenWithMetadata() public { + // Deploy a mock ERC20 token that implements name() and symbol() + MockERC20TokenWithMetadata token = new MockERC20TokenWithMetadata("Test Token", "TTK"); + + // No wrapped base token store + address _wrappedBaseTokenStore = address(0); + + // Call the function + bytes memory data = testGateway.requestGetZKChainSpecificForceDeploymentsData( + _wrappedBaseTokenStore, + address(token) + ); + + // Decode the returned data + ZKChainSpecificForceDeploymentsData memory chainData = abi.decode(data, (ZKChainSpecificForceDeploymentsData)); + + // Check the values + assertEq(chainData.baseTokenAssetId, baseTokenAssetId); + assertEq(chainData.l2LegacySharedBridge, legacySharedBridgeAddress); + assertEq(chainData.predeployedL2WethAddress, address(0)); + assertEq(chainData.baseTokenL1Address, address(token)); + assertEq(chainData.baseTokenName, "Test Token"); + assertEq(chainData.baseTokenSymbol, "TTK"); + } + + // Test with ERC20 that does not implement metadata + function testWithERC20TokenWithoutMetadata() public { + // Deploy a mock ERC20 token that does not implement name() and symbol() + MockERC20TokenWithoutMetadata token = new MockERC20TokenWithoutMetadata(); + + // No wrapped base token store + address _wrappedBaseTokenStore = address(0); + + // Call the function + bytes memory data = testGateway.requestGetZKChainSpecificForceDeploymentsData( + _wrappedBaseTokenStore, + address(token) + ); + + // Decode the returned data + ZKChainSpecificForceDeploymentsData memory chainData = abi.decode(data, (ZKChainSpecificForceDeploymentsData)); + + // Check the values + assertEq(chainData.baseTokenAssetId, baseTokenAssetId); + assertEq(chainData.l2LegacySharedBridge, legacySharedBridgeAddress); + assertEq(chainData.predeployedL2WethAddress, address(0)); + assertEq(chainData.baseTokenL1Address, address(token)); + assertEq(chainData.baseTokenName, "Base Token"); + assertEq(chainData.baseTokenSymbol, "BT"); + } +} diff --git a/l1-contracts/test/foundry/l2/integration/L2ERC20BridgeTest.t.sol b/l1-contracts/test/foundry/l2/integration/L2ERC20BridgeTest.t.sol new file mode 100644 index 000000000..8fe0ffcc8 --- /dev/null +++ b/l1-contracts/test/foundry/l2/integration/L2ERC20BridgeTest.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +// solhint-disable gas-custom-errors + +import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; +import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {IL2NativeTokenVault} from "contracts/bridge/ntv/IL2NativeTokenVault.sol"; + +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; +import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; + +import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR} from "contracts/common/L2ContractAddresses.sol"; + +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; +import {SharedL2ContractL1DeployerUtils} from "../../l1/integration/l2-tests-in-l1-context/_SharedL2ContractL1DeployerUtils.sol"; +import {L2Utils, SystemContractsArgs} from "./L2Utils.sol"; +import {SharedL2ContractL2DeployerUtils} from "./_SharedL2ContractL2DeployerUtils.sol"; +import {L2Erc20TestAbstract} from "../../l1/integration/l2-tests-in-l1-context/L2Erc20TestAbstract.t.sol"; +import {SharedL2ContractDeployer} from "../../l1/integration/l2-tests-in-l1-context/_SharedL2ContractDeployer.sol"; + +contract L2Erc20Test is Test, L2Erc20TestAbstract, SharedL2ContractL2DeployerUtils { + function test() internal virtual override(DeployUtils, SharedL2ContractL2DeployerUtils) {} + + function initSystemContracts( + SystemContractsArgs memory _args + ) internal override(SharedL2ContractDeployer, SharedL2ContractL2DeployerUtils) { + super.initSystemContracts(_args); + } + + function deployViaCreate2( + bytes memory creationCode, + bytes memory constructorArgs + ) internal override(DeployUtils, SharedL2ContractL2DeployerUtils) returns (address) { + return super.deployViaCreate2(creationCode, constructorArgs); + } + + function deployL2Contracts( + uint256 _l1ChainId + ) public override(SharedL2ContractL1DeployerUtils, SharedL2ContractDeployer) { + super.deployL2Contracts(_l1ChainId); + } +} diff --git a/l1-contracts/test/foundry/l2/integration/L2GatewayTests.t.sol b/l1-contracts/test/foundry/l2/integration/L2GatewayTests.t.sol new file mode 100644 index 000000000..b1ae7bb5f --- /dev/null +++ b/l1-contracts/test/foundry/l2/integration/L2GatewayTests.t.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +// solhint-disable gas-custom-errors + +import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {IL2NativeTokenVault} from "contracts/bridge/ntv/IL2NativeTokenVault.sol"; + +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; + +import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR, L2_BRIDGEHUB_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {ETH_TOKEN_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "contracts/common/Config.sol"; + +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {BridgehubMintCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {IL2AssetRouter} from "contracts/bridge/asset-router/IL2AssetRouter.sol"; +import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; + +import {L2Utils, SystemContractsArgs} from "./L2Utils.sol"; + +import {SharedL2ContractL2DeployerUtils} from "./_SharedL2ContractL2DeployerUtils.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; +import {SharedL2ContractL1DeployerUtils} from "../../l1/integration/l2-tests-in-l1-context/_SharedL2ContractL1DeployerUtils.sol"; +import {L2GatewayTestAbstract} from "../../l1/integration/l2-tests-in-l1-context/L2GatewayTestAbstract.t.sol"; +import {SharedL2ContractDeployer} from "../../l1/integration/l2-tests-in-l1-context/_SharedL2ContractDeployer.sol"; + +contract L2GatewayTests is Test, L2GatewayTestAbstract, SharedL2ContractL2DeployerUtils { + // We need to emulate a L1->L2 transaction from the L1 bridge to L2 counterpart. + // It is a bit easier to use EOA and it is sufficient for the tests. + function test() internal virtual override(DeployUtils, SharedL2ContractL2DeployerUtils) {} + + function initSystemContracts( + SystemContractsArgs memory _args + ) internal override(SharedL2ContractDeployer, SharedL2ContractL2DeployerUtils) { + super.initSystemContracts(_args); + } + + function deployViaCreate2( + bytes memory creationCode, + bytes memory constructorArgs + ) internal override(DeployUtils, SharedL2ContractL2DeployerUtils) returns (address) { + return super.deployViaCreate2(creationCode, constructorArgs); + } + + function deployL2Contracts( + uint256 _l1ChainId + ) public override(SharedL2ContractL1DeployerUtils, SharedL2ContractDeployer) { + super.deployL2Contracts(_l1ChainId); + } +} diff --git a/l1-contracts/test/foundry/l2/integration/L2NativeTokenVault.t.sol b/l1-contracts/test/foundry/l2/integration/L2NativeTokenVault.t.sol new file mode 100644 index 000000000..bf292c930 --- /dev/null +++ b/l1-contracts/test/foundry/l2/integration/L2NativeTokenVault.t.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +// solhint-disable gas-custom-errors + +import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; +import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {IL2NativeTokenVault} from "contracts/bridge/ntv/IL2NativeTokenVault.sol"; + +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; +import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; + +import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR, L2_BRIDGEHUB_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {ETH_TOKEN_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "contracts/common/Config.sol"; + +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {BridgehubMintCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {IL2AssetRouter} from "contracts/bridge/asset-router/IL2AssetRouter.sol"; +import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; + +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {L2Utils, SystemContractsArgs} from "./L2Utils.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; +import {L2NativeTokenVaultTestAbstract} from "../../l1/integration/l2-tests-in-l1-context/L2NativeTokenVaultTestAbstract.t.sol"; +import {SharedL2ContractL2DeployerUtils} from "./_SharedL2ContractL2DeployerUtils.sol"; +import {SharedL2ContractDeployer} from "../../l1/integration/l2-tests-in-l1-context/_SharedL2ContractDeployer.sol"; +import {SharedL2ContractL1DeployerUtils} from "../../l1/integration/l2-tests-in-l1-context/_SharedL2ContractL1DeployerUtils.sol"; + +contract L2GatewayL1Test is + Test, + SharedL2ContractL2DeployerUtils, + SharedL2ContractDeployer, + L2NativeTokenVaultTestAbstract +{ + // We need to emulate a L1->L2 transaction from the L1 bridge to L2 counterpart. + // It is a bit easier to use EOA and it is sufficient for the tests. + function test() internal virtual override(DeployUtils, SharedL2ContractL2DeployerUtils) {} + + function initSystemContracts( + SystemContractsArgs memory _args + ) internal override(SharedL2ContractDeployer, SharedL2ContractL2DeployerUtils) { + super.initSystemContracts(_args); + } + + function deployViaCreate2( + bytes memory creationCode, + bytes memory constructorArgs + ) internal override(DeployUtils, SharedL2ContractL2DeployerUtils) returns (address) { + return super.deployViaCreate2(creationCode, constructorArgs); + } + + function deployL2Contracts( + uint256 _l1ChainId + ) public override(SharedL2ContractL1DeployerUtils, SharedL2ContractDeployer) { + super.deployL2Contracts(_l1ChainId); + } +} diff --git a/l1-contracts/test/foundry/l2/integration/L2Utils.sol b/l1-contracts/test/foundry/l2/integration/L2Utils.sol new file mode 100644 index 000000000..b1eafb86b --- /dev/null +++ b/l1-contracts/test/foundry/l2/integration/L2Utils.sol @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Vm} from "forge-std/Vm.sol"; +import "forge-std/console.sol"; + +import {L2_DEPLOYER_SYSTEM_CONTRACT_ADDR, L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR, L2_BRIDGEHUB_ADDR, L2_MESSAGE_ROOT_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {IContractDeployer, L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {L2NativeTokenVault} from "contracts/bridge/ntv/L2NativeTokenVault.sol"; +import {L2SharedBridgeLegacy} from "contracts/bridge/L2SharedBridgeLegacy.sol"; +import {IMessageRoot} from "contracts/bridgehub/IMessageRoot.sol"; +import {ICTMDeploymentTracker} from "contracts/bridgehub/ICTMDeploymentTracker.sol"; +import {Bridgehub, IBridgehub} from "contracts/bridgehub/Bridgehub.sol"; +import {MessageRoot} from "contracts/bridgehub/MessageRoot.sol"; + +import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; + +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; + +import {SystemContractsCaller} from "contracts/common/libraries/SystemContractsCaller.sol"; +import {DeployFailed} from "contracts/common/L1ContractErrors.sol"; +import {SystemContractsArgs} from "../../l1/integration/l2-tests-in-l1-context/_SharedL2ContractDeployer.sol"; + +library L2Utils { + address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); + Vm internal constant vm = Vm(VM_ADDRESS); + + address internal constant L2_FORCE_DEPLOYER_ADDR = address(0x8007); + + string internal constant L2_ASSET_ROUTER_PATH = "./zkout/L2AssetRouter.sol/L2AssetRouter.json"; + string internal constant L2_NATIVE_TOKEN_VAULT_PATH = "./zkout/L2NativeTokenVault.sol/L2NativeTokenVault.json"; + string internal constant BRIDGEHUB_PATH = "./zkout/Bridgehub.sol/Bridgehub.json"; + + function readFoundryBytecode(string memory artifactPath) internal view returns (bytes memory) { + string memory root = vm.projectRoot(); + string memory path = string.concat(root, artifactPath); + string memory json = vm.readFile(path); + bytes memory bytecode = vm.parseJsonBytes(json, ".bytecode.object"); + return bytecode; + } + + function readZKFoundryBytecodeL1( + string memory fileName, + string memory contractName + ) internal view returns (bytes memory) { + string memory path = string.concat("/../l1-contracts/zkout/", fileName, "/", contractName, ".json"); + bytes memory bytecode = readFoundryBytecode(path); + return bytecode; + } + + function readZKFoundryBytecodeSystemContracts( + string memory fileName, + string memory contractName + ) internal view returns (bytes memory) { + string memory path = string.concat("/../system-contracts/zkout/", fileName, "/", contractName, ".json"); + bytes memory bytecode = readFoundryBytecode(path); + return bytecode; + } + + /// @notice Returns the bytecode of a given system contract. + function readSystemContractsBytecode(string memory _filename) internal view returns (bytes memory) { + return readZKFoundryBytecodeSystemContracts(string.concat(_filename, ".sol"), _filename); + } + + /** + * @dev Initializes the system contracts. + * @dev It is a hack needed to make the tests be able to call system contracts directly. + */ + function initSystemContracts(SystemContractsArgs memory _args) internal { + bytes memory contractDeployerBytecode = readSystemContractsBytecode("ContractDeployer"); + vm.etch(L2_DEPLOYER_SYSTEM_CONTRACT_ADDR, contractDeployerBytecode); + forceDeploySystemContracts(_args); + } + + function forceDeploySystemContracts(SystemContractsArgs memory _args) internal { + forceDeployMessageRoot(); + forceDeployBridgehub( + _args.l1ChainId, + _args.eraChainId, + _args.aliasedOwner, + _args.l1AssetRouter, + _args.legacySharedBridge, + _args.l1CtmDeployer + ); + forceDeployAssetRouter( + _args.l1ChainId, + _args.eraChainId, + _args.aliasedOwner, + _args.l1AssetRouter, + _args.legacySharedBridge + ); + forceDeployNativeTokenVault( + _args.l1ChainId, + _args.aliasedOwner, + _args.l2TokenProxyBytecodeHash, + _args.legacySharedBridge, + _args.l2TokenBeacon, + _args.contractsDeployedAlready + ); + } + + function forceDeployMessageRoot() internal { + new MessageRoot(IBridgehub(L2_BRIDGEHUB_ADDR)); + forceDeployWithConstructor("MessageRoot", L2_MESSAGE_ROOT_ADDR, abi.encode(L2_BRIDGEHUB_ADDR)); + } + + function forceDeployBridgehub( + uint256 _l1ChainId, + uint256 _eraChainId, + address _aliasedOwner, + address _l1AssetRouter, + address _legacySharedBridge, + address _l1CtmDeployer + ) internal { + new Bridgehub(_l1ChainId, _aliasedOwner, 100); + forceDeployWithConstructor("Bridgehub", L2_BRIDGEHUB_ADDR, abi.encode(_l1ChainId, _aliasedOwner, 100)); + Bridgehub bridgehub = Bridgehub(L2_BRIDGEHUB_ADDR); + vm.prank(_aliasedOwner); + bridgehub.setAddresses( + L2_ASSET_ROUTER_ADDR, + ICTMDeploymentTracker(_l1CtmDeployer), + IMessageRoot(L2_MESSAGE_ROOT_ADDR) + ); + } + + /// @notice Deploys the L2AssetRouter contract. + /// @param _l1ChainId The chain ID of the L1 chain. + /// @param _eraChainId The chain ID of the era chain. + /// @param _l1AssetRouter The address of the L1 asset router. + /// @param _legacySharedBridge The address of the legacy shared bridge. + function forceDeployAssetRouter( + uint256 _l1ChainId, + uint256 _eraChainId, + address _aliasedOwner, + address _l1AssetRouter, + address _legacySharedBridge + ) internal { + // to ensure that the bytecode is known + bytes32 ethAssetId = DataEncoding.encodeNTVAssetId(_l1ChainId, ETH_TOKEN_ADDRESS); + { + new L2AssetRouter(_l1ChainId, _eraChainId, _l1AssetRouter, _legacySharedBridge, ethAssetId, _aliasedOwner); + } + forceDeployWithConstructor( + "L2AssetRouter", + L2_ASSET_ROUTER_ADDR, + abi.encode(_l1ChainId, _eraChainId, _l1AssetRouter, _legacySharedBridge, ethAssetId, _aliasedOwner) + ); + } + + /// @notice Deploys the L2NativeTokenVault contract. + /// @param _l1ChainId The chain ID of the L1 chain. + /// @param _aliasedOwner The address of the aliased owner. + /// @param _l2TokenProxyBytecodeHash The hash of the L2 token proxy bytecode. + /// @param _legacySharedBridge The address of the legacy shared bridge. + /// @param _l2TokenBeacon The address of the L2 token beacon. + /// @param _contractsDeployedAlready Whether the contracts are deployed already. + function forceDeployNativeTokenVault( + uint256 _l1ChainId, + address _aliasedOwner, + bytes32 _l2TokenProxyBytecodeHash, + address _legacySharedBridge, + address _l2TokenBeacon, + bool _contractsDeployedAlready + ) internal { + // to ensure that the bytecode is known + bytes32 ethAssetId = DataEncoding.encodeNTVAssetId(_l1ChainId, ETH_TOKEN_ADDRESS); + { + new L2NativeTokenVault({ + _l1ChainId: _l1ChainId, + _aliasedOwner: _aliasedOwner, + _l2TokenProxyBytecodeHash: _l2TokenProxyBytecodeHash, + _legacySharedBridge: _legacySharedBridge, + _bridgedTokenBeacon: _l2TokenBeacon, + _contractsDeployedAlready: _contractsDeployedAlready, + _wethToken: address(0), + _baseTokenAssetId: ethAssetId + }); + } + forceDeployWithConstructor( + "L2NativeTokenVault", + L2_NATIVE_TOKEN_VAULT_ADDR, + abi.encode( + _l1ChainId, + _aliasedOwner, + _l2TokenProxyBytecodeHash, + _legacySharedBridge, + _l2TokenBeacon, + _contractsDeployedAlready, + address(0), + ethAssetId + ) + ); + } + + function forceDeployWithConstructor( + string memory _contractName, + address _address, + bytes memory _constructorArgs + ) public { + bytes memory bytecode = readZKFoundryBytecodeL1(string.concat(_contractName, ".sol"), _contractName); + + bytes32 bytecodehash = L2ContractHelper.hashL2Bytecode(bytecode); + + IContractDeployer.ForceDeployment[] memory deployments = new IContractDeployer.ForceDeployment[](1); + deployments[0] = IContractDeployer.ForceDeployment({ + bytecodeHash: bytecodehash, + newAddress: _address, + callConstructor: true, + value: 0, + input: _constructorArgs + }); + + vm.prank(L2_FORCE_DEPLOYER_ADDR); + IContractDeployer(L2_DEPLOYER_SYSTEM_CONTRACT_ADDR).forceDeployOnAddresses(deployments); + } + + function deployViaCreat2L2( + bytes memory creationCode, + bytes memory constructorargs, + bytes32 create2salt + ) internal returns (address) { + bytes memory bytecode = abi.encodePacked(creationCode, constructorargs); + address contractAddress; + assembly { + contractAddress := create2(0, add(bytecode, 0x20), mload(bytecode), create2salt) + } + uint32 size; + assembly { + size := extcodesize(contractAddress) + } + if (size == 0) { + revert DeployFailed(); + } + return contractAddress; + } +} diff --git a/l1-contracts/test/foundry/l2/integration/WETH.t.sol b/l1-contracts/test/foundry/l2/integration/WETH.t.sol new file mode 100644 index 000000000..f7932b4eb --- /dev/null +++ b/l1-contracts/test/foundry/l2/integration/WETH.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; + +import {SharedL2ContractDeployer} from "../../l1/integration/l2-tests-in-l1-context/_SharedL2ContractDeployer.sol"; +import {SharedL2ContractL1DeployerUtils} from "../../l1/integration/l2-tests-in-l1-context/_SharedL2ContractL1DeployerUtils.sol"; +import {L2WethTestAbstract} from "../../l1/integration/l2-tests-in-l1-context/L2WethTestAbstract.t.sol"; + +import {SharedL2ContractL2DeployerUtils, SystemContractsArgs} from "./_SharedL2ContractL2DeployerUtils.sol"; + +contract WethTest is Test, L2WethTestAbstract, SharedL2ContractL2DeployerUtils { + function test() internal virtual override(DeployUtils, SharedL2ContractL2DeployerUtils) {} + + function initSystemContracts( + SystemContractsArgs memory _args + ) internal override(SharedL2ContractDeployer, SharedL2ContractL2DeployerUtils) { + super.initSystemContracts(_args); + } + + function deployViaCreate2( + bytes memory creationCode, + bytes memory constructorArgs + ) internal override(DeployUtils, SharedL2ContractL2DeployerUtils) returns (address) { + return super.deployViaCreate2(creationCode, constructorArgs); + } + + function deployL2Contracts( + uint256 _l1ChainId + ) public override(SharedL2ContractL1DeployerUtils, SharedL2ContractDeployer) { + super.deployL2Contracts(_l1ChainId); + } +} diff --git a/l1-contracts/test/foundry/l2/integration/_SharedL2ContractL2DeployerUtils.sol b/l1-contracts/test/foundry/l2/integration/_SharedL2ContractL2DeployerUtils.sol new file mode 100644 index 000000000..0b42255b5 --- /dev/null +++ b/l1-contracts/test/foundry/l2/integration/_SharedL2ContractL2DeployerUtils.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {StdStorage, stdStorage, stdToml} from "forge-std/Test.sol"; +import {Script, console2 as console} from "forge-std/Script.sol"; + +import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; +import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol"; +import {L1Nullifier} from "contracts/bridge/L1Nullifier.sol"; +import {L1NativeTokenVault} from "contracts/bridge/ntv/L1NativeTokenVault.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; +import {CTMDeploymentTracker} from "contracts/bridgehub/CTMDeploymentTracker.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {DeployedAddresses, Config} from "deploy-scripts/DeployUtils.s.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; + +import {L2_BRIDGEHUB_ADDR, L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR} from "contracts/common/L2ContractAddresses.sol"; + +import {L2Utils} from "./L2Utils.sol"; +import {SharedL2ContractL1DeployerUtils, SystemContractsArgs} from "../../l1/integration/l2-tests-in-l1-context/_SharedL2ContractL1DeployerUtils.sol"; + +contract SharedL2ContractL2DeployerUtils is DeployUtils, SharedL2ContractL1DeployerUtils { + using stdToml for string; + + function initSystemContracts(SystemContractsArgs memory _args) internal virtual override { + L2Utils.initSystemContracts(_args); + } + + function deployViaCreate2( + bytes memory creationCode, + bytes memory constructorArgs + ) internal virtual override returns (address) { + console.log("Deploying via create2 L2"); + return L2Utils.deployViaCreat2L2(creationCode, constructorArgs, config.contracts.create2FactorySalt); + } + + // add this to be excluded from coverage report + function test() internal virtual override(DeployUtils, SharedL2ContractL1DeployerUtils) {} +} diff --git a/l1-contracts/test/foundry/l2/unit/GatewayCTMDeployer/GatewayCTMDeployer.t.sol b/l1-contracts/test/foundry/l2/unit/GatewayCTMDeployer/GatewayCTMDeployer.t.sol new file mode 100644 index 000000000..f016b6559 --- /dev/null +++ b/l1-contracts/test/foundry/l2/unit/GatewayCTMDeployer/GatewayCTMDeployer.t.sol @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {GatewayCTMDeployer, GatewayCTMDeployerConfig, DeployedContracts, StateTransitionContracts, DAContracts} from "contracts/state-transition/chain-deps/GatewayCTMDeployer.sol"; +import {VerifierParams, IVerifier} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; +import {FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZKChainStorage.sol"; + +import {MailboxFacet} from "contracts/state-transition/chain-deps/facets/Mailbox.sol"; +import {ExecutorFacet} from "contracts/state-transition/chain-deps/facets/Executor.sol"; +import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; +import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol"; + +import {RollupDAManager} from "contracts/state-transition/data-availability/RollupDAManager.sol"; +import {RelayedSLDAValidator} from "contracts/state-transition/data-availability/RelayedSLDAValidator.sol"; +import {ValidiumL1DAValidator} from "contracts/state-transition/data-availability/ValidiumL1DAValidator.sol"; + +import {Verifier} from "contracts/state-transition/Verifier.sol"; +import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; +import {ValidatorTimelock} from "contracts/state-transition/ValidatorTimelock.sol"; + +import {DiamondInit} from "contracts/state-transition/chain-deps/DiamondInit.sol"; +import {L1GenesisUpgrade} from "contracts/upgrades/L1GenesisUpgrade.sol"; +import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; + +import {ChainTypeManager} from "contracts/state-transition/ChainTypeManager.sol"; + +import {L2_BRIDGEHUB_ADDR} from "contracts/common/L2ContractAddresses.sol"; + +import {ProxyAdmin} from "@openzeppelin/contracts-v4/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {RollupDAManager} from "contracts/state-transition/data-availability/RollupDAManager.sol"; + +import {GatewayCTMDeployerHelper} from "deploy-scripts/GatewayCTMDeployerHelper.sol"; + +import {L2_CREATE2_FACTORY_ADDRESS} from "deploy-scripts/Utils.sol"; + +// We need to use contract the zkfoundry consistently uses +// zk environment only within a deployed contract +contract GatewayCTMDeployerTester { + function deployCTMDeployer( + bytes memory data + ) external returns (DeployedContracts memory deployedContracts, address addr) { + (bool success, bytes memory result) = L2_CREATE2_FACTORY_ADDRESS.call(data); + require(success, "failed to deploy"); + + addr = abi.decode(result, (address)); + + deployedContracts = GatewayCTMDeployer(addr).getDeployedContracts(); + } +} + +contract GatewayCTMDeployerTest is Test { + GatewayCTMDeployerConfig deployerConfig; + + // This is done merely to publish the respective bytecodes. + function _predeployContracts() internal { + new MailboxFacet(1, 1); + new ExecutorFacet(1); + new GettersFacet(); + new AdminFacet(1, RollupDAManager(address(0))); + + new DiamondInit(); + new L1GenesisUpgrade(); + new RollupDAManager(); + new ValidiumL1DAValidator(); + new RelayedSLDAValidator(); + new ChainTypeManager(address(0)); + new ProxyAdmin(); + + new TestnetVerifier(); + new Verifier(); + + new ValidatorTimelock(address(0), 0); + + // This call will likely fail due to various checks, but we just need to get the bytecode published + try new TransparentUpgradeableProxy(address(0), address(0), hex"") {} catch {} + } + + function setUp() external { + // Initialize the configuration with sample data + GatewayCTMDeployerConfig memory config = GatewayCTMDeployerConfig({ + aliasedGovernanceAddress: address(0x123), + salt: keccak256("test-salt"), + eraChainId: 1001, + l1ChainId: 1, + rollupL2DAValidatorAddress: address(0x456), + testnetVerifier: true, + adminSelectors: new bytes4[](2), + executorSelectors: new bytes4[](2), + mailboxSelectors: new bytes4[](2), + gettersSelectors: new bytes4[](2), + verifierParams: VerifierParams({ + recursionNodeLevelVkHash: bytes32(0), + recursionLeafLevelVkHash: bytes32(0), + recursionCircuitsSetVksHash: bytes32(0) + }), + feeParams: FeeParams({ + // Just random values + pubdataPricingMode: PubdataPricingMode.Rollup, + batchOverheadL1Gas: uint32(1_000_000), + maxPubdataPerBatch: uint32(500_000), + maxL2GasPerBatch: uint32(2_000_000_000), + priorityTxMaxPubdata: uint32(99_000), + minimalL2GasPrice: uint64(20000000) + }), + bootloaderHash: bytes32(uint256(0xabc)), + defaultAccountHash: bytes32(uint256(0xdef)), + priorityTxMaxGasLimit: 100000, + genesisRoot: bytes32(uint256(0x123)), + genesisRollupLeafIndex: 10, + genesisBatchCommitment: bytes32(uint256(0x456)), + forceDeploymentsData: hex"deadbeef", + protocolVersion: 1 + }); + + // Initialize selectors with sample function selectors + config.adminSelectors[0] = bytes4(keccak256("adminFunction1()")); + config.adminSelectors[1] = bytes4(keccak256("adminFunction2()")); + config.executorSelectors[0] = bytes4(keccak256("executorFunction1()")); + config.executorSelectors[1] = bytes4(keccak256("executorFunction2()")); + config.mailboxSelectors[0] = bytes4(keccak256("mailboxFunction1()")); + config.mailboxSelectors[1] = bytes4(keccak256("mailboxFunction2()")); + config.gettersSelectors[0] = bytes4(keccak256("gettersFunction1()")); + config.gettersSelectors[1] = bytes4(keccak256("gettersFunction2()")); + + deployerConfig = config; + + _predeployContracts(); + } + + // It is more a smoke test that indeed the deployment works + function testGatewayCTMDeployer() external { + // Just to publish bytecode + new GatewayCTMDeployer(deployerConfig); + + ( + DeployedContracts memory calculatedDeployedContracts, + bytes memory create2Calldata, + address ctmDeployerAddress + ) = GatewayCTMDeployerHelper.calculateAddresses(bytes32(0), deployerConfig); + + GatewayCTMDeployerTester tester = new GatewayCTMDeployerTester(); + (DeployedContracts memory deployedContracts, address correctCTMDeployerAddress) = tester.deployCTMDeployer( + create2Calldata + ); + + require(ctmDeployerAddress == correctCTMDeployerAddress, "Incorrect address"); + + DeployedContractsComparator.compareDeployedContracts(calculatedDeployedContracts, deployedContracts); + + // require(keccak256(abi.encode(calculatedDeployedContracts)) == keccak256(abi.encode(deployedContracts)), "Incorrect calculated addresses"); + + // GatewayCTMDeployer deployer = new GatewayCTMDeployer( + // deployerConfig + // ); + + // DeployedContracts memory deployedContracts = deployer.getDeployedContracts(); + } +} + +library DeployedContractsComparator { + function compareDeployedContracts(DeployedContracts memory a, DeployedContracts memory b) internal pure { + compareStateTransitionContracts(a.stateTransition, b.stateTransition); + compareDAContracts(a.daContracts, b.daContracts); + compareBytes(a.diamondCutData, b.diamondCutData, "diamondCutData"); + } + + function compareStateTransitionContracts( + StateTransitionContracts memory a, + StateTransitionContracts memory b + ) internal pure { + require(a.chainTypeManagerProxy == b.chainTypeManagerProxy, "chainTypeManagerProxy differs"); + require( + a.chainTypeManagerImplementation == b.chainTypeManagerImplementation, + "chainTypeManagerImplementation differs" + ); + require(a.verifier == b.verifier, "verifier differs"); + require(a.adminFacet == b.adminFacet, "adminFacet differs"); + require(a.mailboxFacet == b.mailboxFacet, "mailboxFacet differs"); + require(a.executorFacet == b.executorFacet, "executorFacet differs"); + require(a.gettersFacet == b.gettersFacet, "gettersFacet differs"); + require(a.diamondInit == b.diamondInit, "diamondInit differs"); + require(a.genesisUpgrade == b.genesisUpgrade, "genesisUpgrade differs"); + require(a.validatorTimelock == b.validatorTimelock, "validatorTimelock differs"); + require(a.chainTypeManagerProxyAdmin == b.chainTypeManagerProxyAdmin, "chainTypeManagerProxyAdmin differs"); + } + + function compareDAContracts(DAContracts memory a, DAContracts memory b) internal pure { + require(a.rollupDAManager == b.rollupDAManager, "rollupDAManager differs"); + require(a.relayedSLDAValidator == b.relayedSLDAValidator, "relayedSLDAValidator differs"); + require(a.validiumDAValidator == b.validiumDAValidator, "validiumDAValidator differs"); + } + + function compareBytes(bytes memory a, bytes memory b, string memory fieldName) internal pure { + require(keccak256(a) == keccak256(b), string(abi.encodePacked(fieldName, " differs"))); + } +} diff --git a/l1-contracts/test/foundry/l2/unit/L2AdminFactory/L2AdminFactory.t.sol b/l1-contracts/test/foundry/l2/unit/L2AdminFactory/L2AdminFactory.t.sol new file mode 100644 index 000000000..7da200111 --- /dev/null +++ b/l1-contracts/test/foundry/l2/unit/L2AdminFactory/L2AdminFactory.t.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {L2AdminFactory} from "contracts/governance/L2AdminFactory.sol"; +import {PermanentRestriction} from "contracts/governance/PermanentRestriction.sol"; +import {IPermanentRestriction} from "contracts/governance/IPermanentRestriction.sol"; +import {DummyRestriction} from "contracts/dev-contracts/DummyRestriction.sol"; +import {NotARestriction} from "contracts/common/L1ContractErrors.sol"; + +contract L2AdminFactoryTest is Test { + address validRestriction1; + address validRestriction2; + + address invalidRestriction; + + function setUp() public { + validRestriction1 = address(new DummyRestriction(true)); + validRestriction2 = address(new DummyRestriction(true)); + + invalidRestriction = address(new DummyRestriction(false)); + } + + function test_invalidInitialRestriction() public { + address[] memory requiredRestrictions = new address[](1); + requiredRestrictions[0] = invalidRestriction; + + vm.expectRevert(abi.encodeWithSelector(NotARestriction.selector, address(invalidRestriction))); + L2AdminFactory factory = new L2AdminFactory(requiredRestrictions); + } + + function test_invalidAdditionalRestriction() public { + address[] memory requiredRestrictions = new address[](1); + requiredRestrictions[0] = validRestriction1; + + L2AdminFactory factory = new L2AdminFactory(requiredRestrictions); + + address[] memory additionalRestrictions = new address[](1); + additionalRestrictions[0] = invalidRestriction; + + vm.expectRevert(abi.encodeWithSelector(NotARestriction.selector, address(invalidRestriction))); + factory.deployAdmin(additionalRestrictions); + } + + function testL2AdminFactory() public { + address[] memory requiredRestrictions = new address[](1); + requiredRestrictions[0] = validRestriction1; + + L2AdminFactory factory = new L2AdminFactory(requiredRestrictions); + + address[] memory additionalRestrictions = new address[](1); + additionalRestrictions[0] = validRestriction2; + + address[] memory allRestrictions = new address[](2); + allRestrictions[0] = requiredRestrictions[0]; + allRestrictions[1] = additionalRestrictions[0]; + + address admin = factory.deployAdmin(additionalRestrictions); + + // Now, we need to check whether it would be able to accept such an admin + PermanentRestriction restriction = new PermanentRestriction(IBridgehub(address(0)), address(factory)); + + bytes32 codeHash; + assembly { + codeHash := extcodehash(admin) + } + + vm.expectEmit(true, false, false, true); + emit IPermanentRestriction.AllowL2Admin(admin); + restriction.allowL2Admin(uint256(0)); + } +} 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 deleted file mode 100644 index e434dd8f4..000000000 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/ClaimFailedDeposit.t.sol +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -import {L1Erc20BridgeTest} from "./_L1Erc20Bridge_Shared.t.sol"; -import {StdStorage, stdStorage} from "forge-std/Test.sol"; -import {IL1SharedBridge} from "contracts/bridge/interfaces/IL1SharedBridge.sol"; -import {EmptyDeposit} from "contracts/common/L1ContractErrors.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(EmptyDeposit.selector); - bytes32[] memory merkleProof; - - bridge.claimFailedDeposit({ - _depositSender: randomSigner, - _l1Token: address(token), - _l2TxHash: bytes32(""), - _l2BatchNumber: 0, - _l2MessageIndex: 0, - _l2TxNumberInBatch: 0, - _merkleProof: merkleProof - }); - } - - function test_claimFailedDepositSuccessfully() public { - uint256 amount = 16; - bytes32 l2DepositTxHash = keccak256("l2tx"); - bytes32[] memory merkleProof; - - uint256 depositedAmountBefore = bridge.depositAmount(alice, address(token), l2DepositTxHash); - assertEq(depositedAmountBefore, 0); - - stdstore - .target(address(bridge)) - .sig("depositAmount(address,address,bytes32)") - .with_key(alice) - .with_key(address(token)) - .with_key(l2DepositTxHash) - .checked_write(amount); - - uint256 depositedAmountAfterDeposit = bridge.depositAmount(alice, address(token), l2DepositTxHash); - assertEq(depositedAmountAfterDeposit, amount); - - vm.mockCall( - sharedBridgeAddress, - abi.encodeWithSelector( - IL1SharedBridge.claimFailedDepositLegacyErc20Bridge.selector, - alice, - address(token), - amount, - l2DepositTxHash, - 0, - 0, - 0, - merkleProof - ), - abi.encode("") - ); - - vm.prank(alice); - // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(bridge)); - emit ClaimedFailedDeposit(alice, address(token), amount); - - bridge.claimFailedDeposit({ - _depositSender: alice, - _l1Token: address(token), - _l2TxHash: l2DepositTxHash, - _l2BatchNumber: 0, - _l2MessageIndex: 0, - _l2TxNumberInBatch: 0, - _merkleProof: merkleProof - }); - - uint256 depositedAmountAfterWithdrawal = bridge.depositAmount(alice, address(token), l2DepositTxHash); - assertEq(depositedAmountAfterWithdrawal, 0); - } -} 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 deleted file mode 100644 index 25b617d3e..000000000 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/_L1Erc20Bridge_Shared.t.sol +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.21; - -import {Test} from "forge-std/Test.sol"; - -import {L1ERC20Bridge} from "contracts/bridge/L1ERC20Bridge.sol"; -import {IL1SharedBridge} from "contracts/bridge/interfaces/IL1SharedBridge.sol"; -import {TestnetERC20Token} from "contracts/dev-contracts/TestnetERC20Token.sol"; -import {FeeOnTransferToken} from "contracts/dev-contracts/FeeOnTransferToken.sol"; -import {ReenterL1ERC20Bridge} from "contracts/dev-contracts/test/ReenterL1ERC20Bridge.sol"; -import {Utils} from "../../Utils/Utils.sol"; - -contract L1Erc20BridgeTest is Test { - L1ERC20Bridge internal bridge; - - ReenterL1ERC20Bridge internal reenterL1ERC20Bridge; - L1ERC20Bridge internal bridgeReenterItself; - - TestnetERC20Token internal token; - TestnetERC20Token internal feeOnTransferToken; - address internal randomSigner; - address internal alice; - address sharedBridgeAddress; - - constructor() { - randomSigner = makeAddr("randomSigner"); - alice = makeAddr("alice"); - - sharedBridgeAddress = makeAddr("shared bridge"); - bridge = new L1ERC20Bridge(IL1SharedBridge(sharedBridgeAddress)); - - 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/L1SharedBridgeAdmin.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeAdmin.t.sol deleted file mode 100644 index af97e3ed2..000000000 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeAdmin.t.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -import {L1SharedBridgeTest} from "./_L1SharedBridge_Shared.t.sol"; - -/// We are testing all the specified revert and require cases. -contract L1SharedBridgeAdminTest is L1SharedBridgeTest { - uint256 internal randomChainId = 123456; - - function testAdminCanInitializeChainGovernance() public { - address randomL2Bridge = makeAddr("randomL2Bridge"); - - vm.prank(admin); - sharedBridge.initializeChainGovernance(randomChainId, randomL2Bridge); - - assertEq(sharedBridge.l2BridgeAddress(randomChainId), randomL2Bridge); - } - - function testAdminCanNotReinitializeChainGovernance() public { - address randomNewBridge = makeAddr("randomNewBridge"); - - vm.expectRevert("Ownable: caller is not the owner"); - vm.prank(admin); - sharedBridge.reinitializeChainGovernance(randomChainId, randomNewBridge); - } -} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol deleted file mode 100644 index 1b785ae84..000000000 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol +++ /dev/null @@ -1,163 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -import {StdStorage, stdStorage} from "forge-std/Test.sol"; -import {Test} from "forge-std/Test.sol"; - -import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; - -import {L1SharedBridge} from "contracts/bridge/L1SharedBridge.sol"; -import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; -import {TestnetERC20Token} from "contracts/dev-contracts/TestnetERC20Token.sol"; - -contract L1SharedBridgeTest is Test { - using stdStorage for StdStorage; - - 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 eraPostUpgradeFirstBatch; - - address owner; - address admin; - address proxyAdmin; - address zkSync; - address alice; - address bob; - uint256 chainId; - uint256 amount = 100; - bytes32 txHash; - - uint256 eraChainId; - address eraDiamondProxy; - address eraErc20BridgeAddress; - - uint256 l2BatchNumber; - uint256 l2MessageIndex; - uint16 l2TxNumberInBatch; - bytes32[] merkleProof; - - uint256 isWithdrawalFinalizedStorageLocation = uint256(8 - 1 + (1 + 49) + 0 + (1 + 49) + 50 + 1 + 50); - - function setUp() public { - owner = makeAddr("owner"); - admin = makeAddr("admin"); - proxyAdmin = makeAddr("proxyAdmin"); - // 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); - eraPostUpgradeFirstBatch = 1; - - chainId = 1; - eraChainId = 9; - eraDiamondProxy = makeAddr("eraDiamondProxy"); - eraErc20BridgeAddress = makeAddr("eraErc20BridgeAddress"); - - token = new TestnetERC20Token("TestnetERC20Token", "TET", 18); - sharedBridgeImpl = new L1SharedBridge({ - _l1WethAddress: l1WethAddress, - _bridgehub: IBridgehub(bridgehubAddress), - _eraChainId: eraChainId, - _eraDiamondProxy: eraDiamondProxy - }); - TransparentUpgradeableProxy sharedBridgeProxy = new TransparentUpgradeableProxy( - address(sharedBridgeImpl), - proxyAdmin, - abi.encodeWithSelector(L1SharedBridge.initialize.selector, owner) - ); - sharedBridge = L1SharedBridge(payable(sharedBridgeProxy)); - vm.prank(owner); - sharedBridge.setL1Erc20Bridge(l1ERC20BridgeAddress); - vm.prank(owner); - sharedBridge.setEraPostDiamondUpgradeFirstBatch(eraPostUpgradeFirstBatch); - vm.prank(owner); - sharedBridge.setEraPostLegacyBridgeUpgradeFirstBatch(eraPostUpgradeFirstBatch); - vm.prank(owner); - sharedBridge.setEraLegacyBridgeLastDepositTime(1, 0); - vm.prank(owner); - sharedBridge.initializeChainGovernance(chainId, l2SharedBridge); - vm.prank(owner); - sharedBridge.initializeChainGovernance(eraChainId, l2SharedBridge); - vm.prank(owner); - sharedBridge.setPendingAdmin(admin); - vm.prank(admin); - sharedBridge.acceptAdmin(); - } - - function _setSharedBridgeDepositHappened(uint256 _chainId, bytes32 _txHash, bytes32 _txDataHash) internal { - stdstore - .target(address(sharedBridge)) - .sig(sharedBridge.depositHappened.selector) - .with_key(_chainId) - .with_key(_txHash) - .checked_write(_txDataHash); - } - - function _setSharedBridgeChainBalance(uint256 _chainId, address _token, uint256 _value) internal { - stdstore - .target(address(sharedBridge)) - .sig(sharedBridge.chainBalance.selector) - .with_key(_chainId) - .with_key(_token) - .checked_write(_value); - } -} diff --git a/l1-contracts/test/foundry/unit/concrete/Executor/Committing.t.sol b/l1-contracts/test/foundry/unit/concrete/Executor/Committing.t.sol deleted file mode 100644 index eb64051c5..000000000 --- a/l1-contracts/test/foundry/unit/concrete/Executor/Committing.t.sol +++ /dev/null @@ -1,841 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -import {Vm} from "forge-std/Test.sol"; -import {Utils, L2_BOOTLOADER_ADDRESS, L2_SYSTEM_CONTEXT_ADDRESS} from "../Utils/Utils.sol"; -import {ExecutorTest} from "./_Executor_Shared.t.sol"; - -import {IExecutor, MAX_NUMBER_OF_BLOBS} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; -import {SystemLogKey} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; -import {POINT_EVALUATION_PRECOMPILE_ADDR} from "contracts/common/Config.sol"; -import {L2_PUBDATA_CHUNK_PUBLISHER_ADDR} from "contracts/common/L2ContractAddresses.sol"; -import {TimeNotReached, BatchNumberMismatch, PubdataCommitmentsTooBig, InvalidPubdataCommitmentsSize, PubdataCommitmentsEmpty, L2TimestampTooBig, EmptyBlobVersionHash, CanOnlyProcessOneBatch, TimestampError, LogAlreadyProcessed, InvalidLogSender, UnexpectedSystemLog, HashMismatch, BatchHashMismatch, ValueMismatch, MissingSystemLogs, InvalidPubdataLength, NonEmptyBlobVersionHash, BlobHashCommitmentError} from "contracts/common/L1ContractErrors.sol"; - -contract CommittingTest is ExecutorTest { - function test_RevertWhen_CommittingWithWrongLastCommittedBatchData() public { - IExecutor.CommitBatchInfo[] memory newCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - newCommitBatchInfoArray[0] = newCommitBatchInfo; - - IExecutor.StoredBatchInfo memory wrongGenesisStoredBatchInfo = genesisStoredBatchInfo; - wrongGenesisStoredBatchInfo.timestamp = 1000; - - vm.prank(validator); - - vm.expectRevert( - abi.encodeWithSelector( - BatchHashMismatch.selector, - keccak256(abi.encode(genesisStoredBatchInfo)), - keccak256(abi.encode(wrongGenesisStoredBatchInfo)) - ) - ); - executor.commitBatches(wrongGenesisStoredBatchInfo, newCommitBatchInfoArray); - } - - function test_RevertWhen_CommittingWithWrongOrderOfBatches() public { - IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; - wrongNewCommitBatchInfo.batchNumber = 2; // wrong batch number - - IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; - - vm.prank(validator); - - vm.expectRevert(abi.encodeWithSelector(BatchNumberMismatch.selector, uint256(1), uint256(2))); - executor.commitBatches(genesisStoredBatchInfo, wrongNewCommitBatchInfoArray); - } - - function test_RevertWhen_CommittingWithWrongNewBatchTimestamp() public { - bytes32 wrongNewBatchTimestamp = Utils.randomBytes32("wrongNewBatchTimestamp"); - bytes[] memory wrongL2Logs = Utils.createSystemLogs(); - - wrongL2Logs[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), - wrongNewBatchTimestamp - ); - - IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; - wrongNewCommitBatchInfo.systemLogs = Utils.encodePacked(wrongL2Logs); - - IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; - - vm.prank(validator); - - vm.expectRevert(TimestampError.selector); - executor.commitBatches(genesisStoredBatchInfo, wrongNewCommitBatchInfoArray); - } - - function test_RevertWhen_CommittingWithTooSmallNewBatchTimestamp() public { - uint256 wrongNewBatchTimestamp = 1; - bytes[] memory wrongL2Logs = Utils.createSystemLogs(); - wrongL2Logs[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(1, 1) - ); - - IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; - wrongNewCommitBatchInfo.systemLogs = Utils.encodePacked(wrongL2Logs); - wrongNewCommitBatchInfo.timestamp = uint64(wrongNewBatchTimestamp); - - IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; - - vm.prank(validator); - - vm.expectRevert(abi.encodeWithSelector(TimeNotReached.selector, 1, 2)); - executor.commitBatches(genesisStoredBatchInfo, wrongNewCommitBatchInfoArray); - } - - function test_RevertWhen_CommittingTooBigLastL2BatchTimestamp() public { - uint64 wrongNewBatchTimestamp = 0xffffffff; - bytes[] memory wrongL2Logs = Utils.createSystemLogs(); - wrongL2Logs[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(wrongNewBatchTimestamp, wrongNewBatchTimestamp) - ); - - IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; - wrongNewCommitBatchInfo.systemLogs = Utils.encodePacked(wrongL2Logs); - wrongNewCommitBatchInfo.timestamp = wrongNewBatchTimestamp; - - IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; - - vm.prank(validator); - - vm.expectRevert(abi.encodeWithSelector(L2TimestampTooBig.selector)); - executor.commitBatches(genesisStoredBatchInfo, wrongNewCommitBatchInfoArray); - } - - function test_RevertWhen_CommittingWithWrongPreviousBatchHash() public { - bytes32 wrongPreviousBatchHash = Utils.randomBytes32("wrongPreviousBatchHash"); - bytes[] memory wrongL2Logs = Utils.createSystemLogs(); - wrongL2Logs[uint256(uint256(SystemLogKey.PREV_BATCH_HASH_KEY))] = Utils.constructL2Log( - true, - L2_SYSTEM_CONTEXT_ADDRESS, - uint256(SystemLogKey.PREV_BATCH_HASH_KEY), - wrongPreviousBatchHash - ); - - IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; - wrongNewCommitBatchInfo.systemLogs = Utils.encodePacked(wrongL2Logs); - - IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; - - vm.prank(validator); - - vm.expectRevert(abi.encodeWithSelector(HashMismatch.selector, wrongPreviousBatchHash, bytes32(0))); - executor.commitBatches(genesisStoredBatchInfo, wrongNewCommitBatchInfoArray); - } - - function test_RevertWhen_CommittingWithoutProcessingSystemContextLog() public { - bytes[] memory wrongL2Logs = Utils.createSystemLogs(); - delete wrongL2Logs[uint256(uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY))]; - - IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; - wrongNewCommitBatchInfo.systemLogs = Utils.encodePacked(wrongL2Logs); - - IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; - - vm.prank(validator); - - vm.expectRevert(abi.encodeWithSelector(MissingSystemLogs.selector, 8191, 8183)); - executor.commitBatches(genesisStoredBatchInfo, wrongNewCommitBatchInfoArray); - } - - function test_RevertWhen_CommittingWithProcessingSystemContextLogTwice() public { - bytes[] memory l2Logs = Utils.createSystemLogs(); - - bytes memory wrongL2Logs = abi.encodePacked( - Utils.encodePacked(l2Logs), - // solhint-disable-next-line func-named-parameters - Utils.constructL2Log( - true, - L2_SYSTEM_CONTEXT_ADDRESS, - uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY), - bytes32("") - ) - ); - - IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; - wrongNewCommitBatchInfo.systemLogs = wrongL2Logs; - - IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; - - vm.prank(validator); - - vm.expectRevert(abi.encodeWithSelector(LogAlreadyProcessed.selector, 3)); - executor.commitBatches(genesisStoredBatchInfo, wrongNewCommitBatchInfoArray); - } - - function test_RevertWhen_UnexpectedL2ToL1Log() public { - address unexpectedAddress = address(0); - bytes[] memory wrongL2Logs = Utils.createSystemLogs(); - wrongL2Logs[uint256(uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY))] = Utils.constructL2Log( - true, - unexpectedAddress, - uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY), - bytes32("") - ); - - IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; - wrongNewCommitBatchInfo.systemLogs = Utils.encodePacked(wrongL2Logs); - - IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; - - vm.prank(validator); - - vm.expectRevert( - abi.encodeWithSelector( - InvalidLogSender.selector, - address(0), - uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY) - ) - ); - executor.commitBatches(genesisStoredBatchInfo, wrongNewCommitBatchInfoArray); - } - - function test_RevertWhen_CommittingWithWrongCanonicalTxHash() public { - bytes32 wrongChainedPriorityHash = Utils.randomBytes32("canonicalTxHash"); - bytes[] memory wrongL2Logs = Utils.createSystemLogs(); - wrongL2Logs[uint256(uint256(SystemLogKey.CHAINED_PRIORITY_TXN_HASH_KEY))] = Utils.constructL2Log( - true, - L2_BOOTLOADER_ADDRESS, - uint256(SystemLogKey.CHAINED_PRIORITY_TXN_HASH_KEY), - wrongChainedPriorityHash - ); - - IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; - wrongNewCommitBatchInfo.systemLogs = Utils.encodePacked(wrongL2Logs); - - IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; - - vm.prank(validator); - - vm.expectRevert(abi.encodeWithSelector(HashMismatch.selector, wrongChainedPriorityHash, keccak256(""))); - executor.commitBatches(genesisStoredBatchInfo, wrongNewCommitBatchInfoArray); - } - - function test_RevertWhen_CommittingWithWrongNumberOfLayer1txs() public { - bytes[] memory wrongL2Logs = Utils.createSystemLogs(); - wrongL2Logs[uint256(uint256(SystemLogKey.NUMBER_OF_LAYER_1_TXS_KEY))] = Utils.constructL2Log( - true, - L2_BOOTLOADER_ADDRESS, - uint256(SystemLogKey.NUMBER_OF_LAYER_1_TXS_KEY), - bytes32(bytes1(0x01)) - ); - - IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; - wrongNewCommitBatchInfo.systemLogs = Utils.encodePacked(wrongL2Logs); - wrongNewCommitBatchInfo.numberOfLayer1Txs = 2; - - IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; - - vm.prank(validator); - - vm.expectRevert(abi.encodeWithSelector(ValueMismatch.selector, uint256(bytes32(bytes1(0x01))), uint256(2))); - executor.commitBatches(genesisStoredBatchInfo, wrongNewCommitBatchInfoArray); - } - - function test_RevertWhen_CommittingWithUnknownSystemLogKey() public { - bytes[] memory l2Logs = Utils.createSystemLogs(); - bytes memory wrongL2Logs = abi.encodePacked( - Utils.encodePacked(l2Logs), - // solhint-disable-next-line func-named-parameters - abi.encodePacked(bytes2(0x0001), bytes2(0x0000), L2_SYSTEM_CONTEXT_ADDRESS, uint256(119), bytes32("")) - ); - - IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; - wrongNewCommitBatchInfo.systemLogs = abi.encodePacked(wrongL2Logs); - - IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; - - vm.prank(validator); - - vm.expectRevert(abi.encodeWithSelector(UnexpectedSystemLog.selector, uint256(119))); - executor.commitBatches(genesisStoredBatchInfo, wrongNewCommitBatchInfoArray); - } - - function test_RevertWhen_SystemLogIsFromIncorrectAddress() public { - bytes32[7] memory values = [ - bytes32(""), - bytes32(0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563), - bytes32(""), - bytes32(""), - bytes32(""), - keccak256(""), - bytes32("") - ]; - - bytes[7] memory errors = [ - bytes.concat("lm"), - bytes.concat("ln"), - bytes.concat("lb"), - bytes.concat("sc"), - bytes.concat("sv"), - bytes.concat("bl"), - bytes.concat("bk") - ]; - - for (uint256 i = 0; i < values.length; i++) { - bytes[] memory wrongL2Logs = Utils.createSystemLogs(); - address wrongAddress = makeAddr("randomAddress"); - wrongL2Logs[i] = Utils.constructL2Log(true, wrongAddress, i, values[i]); - - IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; - wrongNewCommitBatchInfo.systemLogs = Utils.encodePacked(wrongL2Logs); - - IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; - - vm.prank(validator); - - vm.expectRevert(abi.encodeWithSelector(InvalidLogSender.selector, wrongAddress, i)); - executor.commitBatches(genesisStoredBatchInfo, wrongNewCommitBatchInfoArray); - } - } - - function test_RevertWhen_SystemLogIsMissing() public { - for (uint256 i = 0; i < 7; i++) { - bytes[] memory l2Logs = Utils.createSystemLogs(); - delete l2Logs[i]; - - IExecutor.CommitBatchInfo memory wrongNewCommitBatchInfo = newCommitBatchInfo; - wrongNewCommitBatchInfo.systemLogs = Utils.encodePacked(l2Logs); - - IExecutor.CommitBatchInfo[] memory wrongNewCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - wrongNewCommitBatchInfoArray[0] = wrongNewCommitBatchInfo; - - vm.prank(validator); - - uint256 allLogsProcessed = uint256(8191); - vm.expectRevert(abi.encodeWithSelector(MissingSystemLogs.selector, 8191, allLogsProcessed ^ (1 << i))); - executor.commitBatches(genesisStoredBatchInfo, wrongNewCommitBatchInfoArray); - } - } - - function test_SuccessfullyCommitBatch() public { - bytes[] memory correctL2Logs = Utils.createSystemLogs(); - correctL2Logs[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) - ); - correctL2Logs[uint256(SystemLogKey.BLOB_ONE_HASH_KEY)] = Utils.constructL2Log( - true, - L2_PUBDATA_CHUNK_PUBLISHER_ADDR, - uint256(SystemLogKey.BLOB_ONE_HASH_KEY), - 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563 - ); - - IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; - correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); - correctNewCommitBatchInfo.pubdataCommitments = abi.encodePacked( - "\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", - bytes32(uint256(0xbeef)) - ); - - bytes32[] memory blobHashes = new bytes32[](MAX_NUMBER_OF_BLOBS); - blobHashes[0] = 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563; - - bytes32[] memory blobCommitments = new bytes32[](MAX_NUMBER_OF_BLOBS); - blobCommitments[0] = bytes32(uint256(0xbeef)); - - bytes32 expectedBatchCommitment = Utils.createBatchCommitment( - correctNewCommitBatchInfo, - bytes32(""), - blobCommitments, - blobHashes - ); - - IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; - - vm.prank(validator); - - vm.recordLogs(); - - executor.commitBatches(genesisStoredBatchInfo, correctCommitBatchInfoArray); - - Vm.Log[] memory entries = vm.getRecordedLogs(); - - assertEq(entries.length, 1); - assertEq(entries[0].topics[0], keccak256("BlockCommit(uint256,bytes32,bytes32)")); - assertEq(entries[0].topics[1], bytes32(uint256(1))); // batchNumber - assertEq(entries[0].topics[2], correctNewCommitBatchInfo.newStateRoot); // batchHash - assertEq(entries[0].topics[3], expectedBatchCommitment); // commitment - - uint256 totalBatchesCommitted = getters.getTotalBatchesCommitted(); - assertEq(totalBatchesCommitted, 1); - } - - function test_SuccessfullyCommitBatchWithOneBlob() public { - bytes - memory pubdataCommitment = "\x01\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2"; - bytes32 versionedHash1 = 0xf39a869f62e75cf5f0bf914688a6b289caf2049435d8e68c5c5e6d05e44913f3; - - vm.mockCall(blobVersionedHashRetriever, abi.encode(uint256(0)), abi.encode(versionedHash1)); - - vm.mockCall(blobVersionedHashRetriever, abi.encode(uint256(1)), abi.encode(bytes32(0))); - - vm.mockCall( - POINT_EVALUATION_PRECOMPILE_ADDR, - "\xf3\x9a\x86\x9f\x62\xe7\x5c\xf5\xf0\xbf\x91\x46\x88\xa6\xb2\x89\xca\xf2\x04\x94\x35\xd8\xe6\x8c\x5c\x5e\x6d\x05\xe4\x49\x13\xf3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2", - "\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\x10\x00\x73\xed\xa7\x53\x29\x9d\x7d\x48\x33\x39\xd8\x08\x09\xa1\xd8\x05\x53\xbd\xa4\x02\xff\xfe\x5b\xfe\xff\xff\xff\xff\x00\x00\x00\x01" - ); - - bytes[] memory correctL2Logs = Utils.createSystemLogs(); - correctL2Logs[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) - ); - - correctL2Logs[uint256(SystemLogKey.BLOB_ONE_HASH_KEY)] = Utils.constructL2Log( - true, - L2_PUBDATA_CHUNK_PUBLISHER_ADDR, - uint256(SystemLogKey.BLOB_ONE_HASH_KEY), - versionedHash1 - ); - - IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; - correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); - - IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; - correctCommitBatchInfoArray[0].pubdataCommitments = pubdataCommitment; - - vm.prank(validator); - - vm.recordLogs(); - - executor.commitBatches(genesisStoredBatchInfo, correctCommitBatchInfoArray); - - Vm.Log[] memory entries = vm.getRecordedLogs(); - - assertEq(entries.length, 1); - assertEq(entries[0].topics[0], keccak256("BlockCommit(uint256,bytes32,bytes32)")); - assertEq(entries[0].topics[1], bytes32(uint256(1))); // batchNumber - - uint256 totalBatchesCommitted = getters.getTotalBatchesCommitted(); - assertEq(totalBatchesCommitted, 1); - - vm.clearMockedCalls(); - } - - function test_SuccessfullyCommitBatchWithTwoBlob() public { - bytes - memory pubdataCommitment = "\x01\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2"; - bytes32 versionedHash1 = 0xf39a869f62e75cf5f0bf914688a6b289caf2049435d8e68c5c5e6d05e44913f3; - bytes32 versionedHash2 = 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563; - - vm.mockCall(blobVersionedHashRetriever, abi.encode(uint256(0)), abi.encode(versionedHash1)); - - vm.mockCall(blobVersionedHashRetriever, abi.encode(uint256(0)), abi.encode(versionedHash1)); - - vm.mockCall(blobVersionedHashRetriever, abi.encode(uint256(1)), abi.encode(versionedHash2)); - - vm.mockCall(blobVersionedHashRetriever, abi.encode(uint256(2)), abi.encode(bytes32(0))); - - vm.mockCall( - POINT_EVALUATION_PRECOMPILE_ADDR, - "\xf3\x9a\x86\x9f\x62\xe7\x5c\xf5\xf0\xbf\x91\x46\x88\xa6\xb2\x89\xca\xf2\x04\x94\x35\xd8\xe6\x8c\x5c\x5e\x6d\x05\xe4\x49\x13\xf3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2", - "\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\x10\x00\x73\xed\xa7\x53\x29\x9d\x7d\x48\x33\x39\xd8\x08\x09\xa1\xd8\x05\x53\xbd\xa4\x02\xff\xfe\x5b\xfe\xff\xff\xff\xff\x00\x00\x00\x01" - ); - - vm.mockCall( - POINT_EVALUATION_PRECOMPILE_ADDR, - "\x29\x0d\xec\xd9\x54\x8b\x62\xa8\xd6\x03\x45\xa9\x88\x38\x6f\xc8\x4b\xa6\xbc\x95\x48\x40\x08\xf6\x36\x2f\x93\x16\x0e\xf3\xe5\x63\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2", - "\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\x10\x00\x73\xed\xa7\x53\x29\x9d\x7d\x48\x33\x39\xd8\x08\x09\xa1\xd8\x05\x53\xbd\xa4\x02\xff\xfe\x5b\xfe\xff\xff\xff\xff\x00\x00\x00\x01" - ); - - bytes[] memory correctL2Logs = Utils.createSystemLogs(); - correctL2Logs[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) - ); - - correctL2Logs[uint256(SystemLogKey.BLOB_ONE_HASH_KEY)] = Utils.constructL2Log( - true, - L2_PUBDATA_CHUNK_PUBLISHER_ADDR, - uint256(SystemLogKey.BLOB_ONE_HASH_KEY), - versionedHash1 - ); - - correctL2Logs[uint256(SystemLogKey.BLOB_TWO_HASH_KEY)] = Utils.constructL2Log( - true, - L2_PUBDATA_CHUNK_PUBLISHER_ADDR, - uint256(SystemLogKey.BLOB_TWO_HASH_KEY), - versionedHash2 - ); - - IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; - correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); - - IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; - correctCommitBatchInfoArray[0].pubdataCommitments = pubdataCommitment; - - vm.prank(validator); - - vm.recordLogs(); - - executor.commitBatches(genesisStoredBatchInfo, correctCommitBatchInfoArray); - - Vm.Log[] memory entries = vm.getRecordedLogs(); - - assertEq(entries.length, 1); - assertEq(entries[0].topics[0], keccak256("BlockCommit(uint256,bytes32,bytes32)")); - assertEq(entries[0].topics[1], bytes32(uint256(1))); // batchNumber - - uint256 totalBatchesCommitted = getters.getTotalBatchesCommitted(); - assertEq(totalBatchesCommitted, 1); - - vm.clearMockedCalls(); - } - - function test_RevertWhen_CommittingBatchMoreThanOneBatch() public { - IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; - - IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](2); - correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; - correctCommitBatchInfoArray[1] = correctNewCommitBatchInfo; - - vm.prank(validator); - - vm.expectRevert(abi.encodeWithSelector(CanOnlyProcessOneBatch.selector)); - executor.commitBatches(genesisStoredBatchInfo, correctCommitBatchInfoArray); - } - - function test_RevertWhen_EmptyPubdataCommitments() public { - bytes memory pubdataCommitment = "\x01"; - - bytes[] memory correctL2Logs = Utils.createSystemLogs(); - correctL2Logs[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) - ); - - IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; - correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); - - IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; - correctCommitBatchInfoArray[0].pubdataCommitments = pubdataCommitment; - - vm.prank(validator); - - vm.expectRevert(PubdataCommitmentsEmpty.selector); - executor.commitBatches(genesisStoredBatchInfo, correctCommitBatchInfoArray); - } - - function test_RevertWhen_PartialPubdataCommitment() public { - bytes - memory pubdataCommitment = "\x01\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57"; - - bytes[] memory correctL2Logs = Utils.createSystemLogs(); - correctL2Logs[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) - ); - - IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; - correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); - - IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; - correctCommitBatchInfoArray[0].pubdataCommitments = pubdataCommitment; - - vm.prank(validator); - - vm.expectRevert(InvalidPubdataCommitmentsSize.selector); - executor.commitBatches(genesisStoredBatchInfo, correctCommitBatchInfoArray); - } - - function test_RevertWhen_TooManyPubdataCommitments() public { - bytes - memory pubdataCommitment = "\x01\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2\x01\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2"; - - bytes[] memory correctL2Logs = Utils.createSystemLogs(); - correctL2Logs[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) - ); - - IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; - correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); - - IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; - correctCommitBatchInfoArray[0].pubdataCommitments = pubdataCommitment; - - vm.prank(validator); - - vm.expectRevert(PubdataCommitmentsTooBig.selector); - executor.commitBatches(genesisStoredBatchInfo, correctCommitBatchInfoArray); - } - - function test_RevertWhen_NotEnoughPubdataCommitments() public { - bytes - memory pubdataCommitment = "\x01\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2"; - bytes32 versionedHash1 = 0xf39a869f62e75cf5f0bf914688a6b289caf2049435d8e68c5c5e6d05e44913f3; - bytes32 versionedHash2 = 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563; - - vm.mockCall(blobVersionedHashRetriever, abi.encode(uint256(0)), abi.encode(versionedHash1)); - - vm.mockCall(blobVersionedHashRetriever, abi.encode(uint256(1)), abi.encode(versionedHash2)); - - vm.mockCall( - POINT_EVALUATION_PRECOMPILE_ADDR, - "\xf3\x9a\x86\x9f\x62\xe7\x5c\xf5\xf0\xbf\x91\x46\x88\xa6\xb2\x89\xca\xf2\x04\x94\x35\xd8\xe6\x8c\x5c\x5e\x6d\x05\xe4\x49\x13\xf3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2", - "\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\x10\x00\x73\xed\xa7\x53\x29\x9d\x7d\x48\x33\x39\xd8\x08\x09\xa1\xd8\x05\x53\xbd\xa4\x02\xff\xfe\x5b\xfe\xff\xff\xff\xff\x00\x00\x00\x01" - ); - - bytes[] memory correctL2Logs = Utils.createSystemLogs(); - correctL2Logs[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) - ); - - correctL2Logs[uint256(SystemLogKey.BLOB_ONE_HASH_KEY)] = Utils.constructL2Log( - true, - L2_PUBDATA_CHUNK_PUBLISHER_ADDR, - uint256(SystemLogKey.BLOB_ONE_HASH_KEY), - versionedHash1 - ); - - IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; - correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); - - IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; - correctCommitBatchInfoArray[0].pubdataCommitments = pubdataCommitment; - - vm.prank(validator); - - vm.expectRevert(abi.encodeWithSelector(NonEmptyBlobVersionHash.selector, uint256(1))); - executor.commitBatches(genesisStoredBatchInfo, correctCommitBatchInfoArray); - - vm.clearMockedCalls(); - } - - function test_RevertWhen_BlobDoesNotExist() public { - bytes - memory pubdataCommitment = "\x01\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2"; - bytes32 versionedHash1 = 0xf39a869f62e75cf5f0bf914688a6b289caf2049435d8e68c5c5e6d05e44913f3; - - vm.mockCall(blobVersionedHashRetriever, abi.encode(uint256(0)), abi.encode(bytes32(0))); - - bytes[] memory correctL2Logs = Utils.createSystemLogs(); - correctL2Logs[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) - ); - - correctL2Logs[uint256(SystemLogKey.BLOB_ONE_HASH_KEY)] = Utils.constructL2Log( - true, - L2_PUBDATA_CHUNK_PUBLISHER_ADDR, - uint256(SystemLogKey.BLOB_ONE_HASH_KEY), - versionedHash1 - ); - - IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; - correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); - - IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; - correctCommitBatchInfoArray[0].pubdataCommitments = pubdataCommitment; - - vm.prank(validator); - - vm.expectRevert(abi.encodeWithSelector(EmptyBlobVersionHash.selector, 0)); - executor.commitBatches(genesisStoredBatchInfo, correctCommitBatchInfoArray); - - vm.clearMockedCalls(); - } - - function test_RevertWhen_SecondBlobSentWithoutCommitmentData() public { - bytes - memory pubdataCommitment = "\x01\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2"; - bytes32 versionedHash1 = 0xf39a869f62e75cf5f0bf914688a6b289caf2049435d8e68c5c5e6d05e44913f3; - - vm.mockCall(blobVersionedHashRetriever, abi.encode(uint256(0)), abi.encode(versionedHash1)); - - vm.mockCall(blobVersionedHashRetriever, abi.encode(uint256(1)), abi.encode(versionedHash1)); - - vm.mockCall( - POINT_EVALUATION_PRECOMPILE_ADDR, - "\xf3\x9a\x86\x9f\x62\xe7\x5c\xf5\xf0\xbf\x91\x46\x88\xa6\xb2\x89\xca\xf2\x04\x94\x35\xd8\xe6\x8c\x5c\x5e\x6d\x05\xe4\x49\x13\xf3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2", - "\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\x10\x00\x73\xed\xa7\x53\x29\x9d\x7d\x48\x33\x39\xd8\x08\x09\xa1\xd8\x05\x53\xbd\xa4\x02\xff\xfe\x5b\xfe\xff\xff\xff\xff\x00\x00\x00\x01" - ); - - bytes[] memory correctL2Logs = Utils.createSystemLogs(); - correctL2Logs[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) - ); - - correctL2Logs[uint256(SystemLogKey.BLOB_ONE_HASH_KEY)] = Utils.constructL2Log( - true, - L2_PUBDATA_CHUNK_PUBLISHER_ADDR, - uint256(SystemLogKey.BLOB_ONE_HASH_KEY), - versionedHash1 - ); - - IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; - correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); - - IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; - correctCommitBatchInfoArray[0].pubdataCommitments = pubdataCommitment; - - vm.prank(validator); - - vm.expectRevert(abi.encodeWithSelector(NonEmptyBlobVersionHash.selector, uint256(1))); - executor.commitBatches(genesisStoredBatchInfo, correctCommitBatchInfoArray); - - vm.clearMockedCalls(); - } - - function test_RevertWhen_SecondBlobLinearHashZeroWithCommitment() public { - bytes - memory pubdataCommitment = "\x01\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2"; - bytes32 versionedHash1 = 0xf39a869f62e75cf5f0bf914688a6b289caf2049435d8e68c5c5e6d05e44913f3; - bytes32 versionedHash2 = 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563; - - vm.mockCall(blobVersionedHashRetriever, abi.encode(uint256(0)), abi.encode(versionedHash1)); - - vm.mockCall(blobVersionedHashRetriever, abi.encode(uint256(0)), abi.encode(versionedHash1)); - - vm.mockCall(blobVersionedHashRetriever, abi.encode(uint256(1)), abi.encode(versionedHash2)); - - vm.mockCall(blobVersionedHashRetriever, abi.encode(uint256(2)), abi.encode(bytes32(0))); - - vm.mockCall( - POINT_EVALUATION_PRECOMPILE_ADDR, - "\xf3\x9a\x86\x9f\x62\xe7\x5c\xf5\xf0\xbf\x91\x46\x88\xa6\xb2\x89\xca\xf2\x04\x94\x35\xd8\xe6\x8c\x5c\x5e\x6d\x05\xe4\x49\x13\xf3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2", - "\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\x10\x00\x73\xed\xa7\x53\x29\x9d\x7d\x48\x33\x39\xd8\x08\x09\xa1\xd8\x05\x53\xbd\xa4\x02\xff\xfe\x5b\xfe\xff\xff\xff\xff\x00\x00\x00\x01" - ); - - vm.mockCall( - POINT_EVALUATION_PRECOMPILE_ADDR, - "\x29\x0d\xec\xd9\x54\x8b\x62\xa8\xd6\x03\x45\xa9\x88\x38\x6f\xc8\x4b\xa6\xbc\x95\x48\x40\x08\xf6\x36\x2f\x93\x16\x0e\xf3\xe5\x63\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2", - "\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\x10\x00\x73\xed\xa7\x53\x29\x9d\x7d\x48\x33\x39\xd8\x08\x09\xa1\xd8\x05\x53\xbd\xa4\x02\xff\xfe\x5b\xfe\xff\xff\xff\xff\x00\x00\x00\x01" - ); - - bytes[] memory correctL2Logs = Utils.createSystemLogs(); - correctL2Logs[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) - ); - - correctL2Logs[uint256(SystemLogKey.BLOB_ONE_HASH_KEY)] = Utils.constructL2Log( - true, - L2_PUBDATA_CHUNK_PUBLISHER_ADDR, - uint256(SystemLogKey.BLOB_ONE_HASH_KEY), - versionedHash1 - ); - - correctL2Logs[uint256(SystemLogKey.BLOB_TWO_HASH_KEY)] = Utils.constructL2Log( - true, - L2_PUBDATA_CHUNK_PUBLISHER_ADDR, - uint256(SystemLogKey.BLOB_TWO_HASH_KEY), - bytes32(0) - ); - - IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; - correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); - - IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; - correctCommitBatchInfoArray[0].pubdataCommitments = pubdataCommitment; - - vm.prank(validator); - - vm.expectRevert(abi.encodeWithSelector(BlobHashCommitmentError.selector, uint256(1), true, false)); - executor.commitBatches(genesisStoredBatchInfo, correctCommitBatchInfoArray); - } - - function test_RevertWhen_SecondBlobLinearHashNotZeroWithEmptyCommitment() public { - bytes - memory pubdataCommitment = "\x01\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2"; - bytes32 versionedHash1 = 0xf39a869f62e75cf5f0bf914688a6b289caf2049435d8e68c5c5e6d05e44913f3; - - vm.mockCall(blobVersionedHashRetriever, abi.encode(uint256(0)), abi.encode(versionedHash1)); - - vm.mockCall(blobVersionedHashRetriever, abi.encode(uint256(1)), abi.encode(bytes32(0))); - - vm.mockCall( - POINT_EVALUATION_PRECOMPILE_ADDR, - "\xf3\x9a\x86\x9f\x62\xe7\x5c\xf5\xf0\xbf\x91\x46\x88\xa6\xb2\x89\xca\xf2\x04\x94\x35\xd8\xe6\x8c\x5c\x5e\x6d\x05\xe4\x49\x13\xf3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf4\x3d\x53\x8d\x91\xd4\x77\xb0\xf8\xf7\x7e\x19\x52\x48\x7f\x00\xb8\xdf\x41\xda\x90\x5c\x08\x75\xc5\xc9\x9b\xa1\x92\x26\x84\x0d\x0d\x0a\x25\x26\xee\x22\xc7\x96\x60\x65\x7c\xbe\x01\x95\x33\x5b\x44\x69\xbd\x92\x94\x6f\x7f\x74\xae\xc5\xce\xef\x31\xf4\x32\x53\xd4\x08\x96\x72\x65\xfa\x85\x5a\xc8\xa0\x0a\x19\x52\x93\x6e\x0f\xe9\x97\x01\xc0\xa4\x32\xa1\x32\x2c\x45\x67\x24\xf7\xad\xd8\xa5\xb4\x7a\x51\xda\x52\x17\x06\x06\x95\x34\x61\xab\xd7\x5b\x91\x49\xc7\xc7\x91\xf4\x07\xfd\xbc\xf8\x39\x53\x2c\xb1\x08\xe8\xa5\x00\x64\x40\xcf\x21\xbf\x68\x87\x20\x5a\xcf\x44\x3b\x66\x3a\x57\xf2", - "\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\x10\x00\x73\xed\xa7\x53\x29\x9d\x7d\x48\x33\x39\xd8\x08\x09\xa1\xd8\x05\x53\xbd\xa4\x02\xff\xfe\x5b\xfe\xff\xff\xff\xff\x00\x00\x00\x01" - ); - - bytes[] memory correctL2Logs = Utils.createSystemLogs(); - correctL2Logs[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) - ); - - correctL2Logs[uint256(SystemLogKey.BLOB_ONE_HASH_KEY)] = Utils.constructL2Log( - true, - L2_PUBDATA_CHUNK_PUBLISHER_ADDR, - uint256(SystemLogKey.BLOB_ONE_HASH_KEY), - versionedHash1 - ); - - correctL2Logs[uint256(SystemLogKey.BLOB_TWO_HASH_KEY)] = Utils.constructL2Log( - true, - L2_PUBDATA_CHUNK_PUBLISHER_ADDR, - uint256(SystemLogKey.BLOB_TWO_HASH_KEY), - versionedHash1 - ); - - IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; - correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); - - IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; - correctCommitBatchInfoArray[0].pubdataCommitments = pubdataCommitment; - - vm.prank(validator); - - vm.expectRevert(abi.encodeWithSelector(BlobHashCommitmentError.selector, uint256(1), false, true)); - executor.commitBatches(genesisStoredBatchInfo, correctCommitBatchInfoArray); - - vm.clearMockedCalls(); - } -} diff --git a/l1-contracts/test/foundry/unit/concrete/GatewayTransactionFilterer/CheckTransaction.sol b/l1-contracts/test/foundry/unit/concrete/GatewayTransactionFilterer/CheckTransaction.sol new file mode 100644 index 000000000..f9e22907f --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/GatewayTransactionFilterer/CheckTransaction.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {GatewayTransactionFiltererTest} from "./_GatewayTransactionFilterer_Shared.t.sol"; + +import {IGetters} from "contracts/state-transition/chain-interfaces/IGetters.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {IAssetRouterBase} from "contracts/bridge/asset-router/IAssetRouterBase.sol"; +import {AlreadyWhitelisted, InvalidSelector, NotWhitelisted} from "contracts/common/L1ContractErrors.sol"; + +contract CheckTransactionTest is GatewayTransactionFiltererTest { + function test_TransactionAllowedOnlyFromWhitelistedSenderWhichIsNotAssetRouter() public { + bytes memory txCalladata = abi.encodeCall( + IAssetRouterBase.finalizeDeposit, + (uint256(10), bytes32("0x12345"), bytes("0x23456")) + ); + vm.startPrank(owner); + vm.mockCall( + bridgehub, + abi.encodeWithSelector(IBridgehub.ctmAssetIdToAddress.selector), + abi.encode(address(0)) // Return any address + ); + bool isTxAllowed = transactionFiltererProxy.isTransactionAllowed( + sender, + address(0), + 0, + 0, + txCalladata, + address(0) + ); // Other arguments do not make a difference for the test + + assertEq(isTxAllowed, false, "Transaction should not be allowed"); + + transactionFiltererProxy.grantWhitelist(sender); + isTxAllowed = transactionFiltererProxy.isTransactionAllowed(sender, address(0), 0, 0, txCalladata, address(0)); // Other arguments do not make a difference for the test + + assertEq(isTxAllowed, true, "Transaction should be allowed"); + + transactionFiltererProxy.grantWhitelist(assetRouter); + isTxAllowed = transactionFiltererProxy.isTransactionAllowed( + assetRouter, + address(0), + 0, + 0, + txCalladata, + address(0) + ); // Other arguments do not make a difference for the test + + assertEq(isTxAllowed, false, "Transaction should not be allowed"); + + vm.stopPrank(); + } + + function test_TransactionAllowedFromWhitelistedSenderForChainBridging() public { + address stm = address(0x6060606); + bytes memory txCalladata = abi.encodeCall( + IAssetRouterBase.finalizeDeposit, + (uint256(10), bytes32("0x12345"), bytes("0x23456")) + ); + vm.startPrank(owner); + vm.mockCall( + bridgehub, + abi.encodeWithSelector(IBridgehub.ctmAssetIdToAddress.selector), + abi.encode(stm) // Return random address + ); + + transactionFiltererProxy.grantWhitelist(assetRouter); + bool isTxAllowed = transactionFiltererProxy.isTransactionAllowed( + assetRouter, + address(0), + 0, + 0, + txCalladata, + address(0) + ); // Other arguments do not make a difference for the test + + assertEq(isTxAllowed, true, "Transaction should be allowed"); + + vm.stopPrank(); + } + + function test_TransactionFailsWithInvalidSelectorEvenIfTheSenderIsAR() public { + bytes memory txCalladata = abi.encodeCall( + IAssetRouterBase.setAssetHandlerAddressThisChain, + (bytes32("0x12345"), address(0x01234567890123456789)) + ); + vm.prank(owner); + vm.expectRevert( + abi.encodeWithSelector(InvalidSelector.selector, IAssetRouterBase.setAssetHandlerAddressThisChain.selector) + ); + bool isTxAllowed = transactionFiltererProxy.isTransactionAllowed( + assetRouter, + address(0), + 0, + 0, + txCalladata, + address(0) + ); // Other arguments do not make a difference for the test + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/GatewayTransactionFilterer/ManageWhitelist.sol b/l1-contracts/test/foundry/unit/concrete/GatewayTransactionFilterer/ManageWhitelist.sol new file mode 100644 index 000000000..be176e150 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/GatewayTransactionFilterer/ManageWhitelist.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {GatewayTransactionFiltererTest} from "./_GatewayTransactionFilterer_Shared.t.sol"; + +import {AlreadyWhitelisted, NotWhitelisted} from "contracts/common/L1ContractErrors.sol"; + +contract ManageWhitelistTest is GatewayTransactionFiltererTest { + function test_GrantingWhitelistToSender() public { + vm.startPrank(owner); + transactionFiltererProxy.grantWhitelist(sender); + + assertEq( + transactionFiltererProxy.whitelistedSenders(sender), + true, + "Whitelisting of sender was not successful" + ); + + vm.expectRevert(abi.encodeWithSelector(AlreadyWhitelisted.selector, sender)); + transactionFiltererProxy.grantWhitelist(sender); + } + + function test_RevokeWhitelistFromSender() public { + vm.startPrank(owner); + vm.expectRevert(abi.encodeWithSelector(NotWhitelisted.selector, sender)); + transactionFiltererProxy.revokeWhitelist(sender); + + transactionFiltererProxy.grantWhitelist(sender); + transactionFiltererProxy.revokeWhitelist(sender); + + assertEq( + transactionFiltererProxy.whitelistedSenders(sender), + false, + "Revoking the sender from whitelist was not successful" + ); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/GatewayTransactionFilterer/_GatewayTransactionFilterer_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/GatewayTransactionFilterer/_GatewayTransactionFilterer_Shared.t.sol new file mode 100644 index 000000000..1b3646ccb --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/GatewayTransactionFilterer/_GatewayTransactionFilterer_Shared.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.21; + +import {Test} from "forge-std/Test.sol"; + +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; + +import {GatewayTransactionFilterer} from "contracts/transactionFilterer/GatewayTransactionFilterer.sol"; + +contract GatewayTransactionFiltererTest is Test { + GatewayTransactionFilterer internal transactionFiltererProxy; + GatewayTransactionFilterer internal transactionFiltererImplementation; + address internal constant owner = address(0x1010101); + address internal constant admin = address(0x2020202); + address internal constant sender = address(0x3030303); + address internal constant bridgehub = address(0x5050505); + address internal constant assetRouter = address(0x4040404); + + constructor() { + transactionFiltererImplementation = new GatewayTransactionFilterer(IBridgehub(bridgehub), assetRouter); + + transactionFiltererProxy = GatewayTransactionFilterer( + address( + new TransparentUpgradeableProxy( + address(transactionFiltererImplementation), + admin, + abi.encodeCall(GatewayTransactionFilterer.initialize, owner) + ) + ) + ); + } + + // add this to be excluded from coverage report + function test() internal virtual {} +} diff --git a/l1-contracts/test/foundry/unit/concrete/Verifier/VerifierRecursive.t.sol b/l1-contracts/test/foundry/unit/concrete/Verifier/VerifierRecursive.t.sol deleted file mode 100644 index 69bad2303..000000000 --- a/l1-contracts/test/foundry/unit/concrete/Verifier/VerifierRecursive.t.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -import {VerifierTestTest} from "./Verifier.t.sol"; -import {VerifierRecursiveTest} from "contracts/dev-contracts/test/VerifierRecursiveTest.sol"; - -contract VerifierRecursiveTestTest is VerifierTestTest { - function setUp() public override { - super.setUp(); - - recursiveAggregationInput.push(2257920826825449939414463854743099397427742128922725774525544832270890253504); - recursiveAggregationInput.push(9091218701914748532331969127001446391756173432977615061129552313204917562530); - recursiveAggregationInput.push(16188304989094043810949359833767911976672882599560690320245309499206765021563); - recursiveAggregationInput.push(3201093556796962656759050531176732990872300033146738631772984017549903765305); - - verifier = new VerifierRecursiveTest(); - } - - function testMoreThan4WordsRecursiveInput_shouldRevert() public { - uint256[] memory newRecursiveAggregationInput = new uint256[](recursiveAggregationInput.length + 1); - - for (uint256 i = 0; i < recursiveAggregationInput.length; i++) { - newRecursiveAggregationInput[i] = recursiveAggregationInput[i]; - } - newRecursiveAggregationInput[newRecursiveAggregationInput.length - 1] = recursiveAggregationInput[ - recursiveAggregationInput.length - 1 - ]; - - vm.expectRevert(bytes("loadProof: Proof is invalid")); - verifier.verify(publicInputs, serializedProof, newRecursiveAggregationInput); - } - - function testEmptyRecursiveInput_shouldRevert() public { - uint256[] memory newRecursiveAggregationInput; - - vm.expectRevert(bytes("loadProof: Proof is invalid")); - verifier.verify(publicInputs, serializedProof, newRecursiveAggregationInput); - } - - function testInvalidRecursiveInput_shouldRevert() public { - uint256[] memory newRecursiveAggregationInput = new uint256[](4); - newRecursiveAggregationInput[0] = 1; - newRecursiveAggregationInput[1] = 2; - newRecursiveAggregationInput[2] = 1; - newRecursiveAggregationInput[3] = 2; - - vm.expectRevert(bytes("finalPairing: pairing failure")); - verifier.verify(publicInputs, serializedProof, newRecursiveAggregationInput); - } - - function testVerificationKeyHash() public override { - bytes32 verificationKeyHash = verifier.verificationKeyHash(); - assertEq(verificationKeyHash, 0x88b3ddc4ed85974c7e14297dcad4097169440305c05fdb6441ca8dfd77cd7fa7); - } -} 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 deleted file mode 100644 index 2113f3467..000000000 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/RevertBatches.t.sol +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -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 "contracts/common/Config.sol"; -import {IExecutor, SystemLogKey} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; -import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; -import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol"; -import {ExecutorFacet} from "contracts/state-transition/chain-deps/facets/Executor.sol"; -import {IExecutor} from "contracts/state-transition/chain-interfaces/IExecutor.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.getHyperchain(chainId); - - executorFacet = ExecutorFacet(address(newChainAddress)); - gettersFacet = GettersFacet(address(newChainAddress)); - adminFacet = AdminFacet(address(newChainAddress)); - - // Initial setup for logs & commits - vm.stopPrank(); - vm.startPrank(newChainAdmin); - - genesisStoredBatchInfo = IExecutor.StoredBatchInfo({ - batchNumber: 0, - batchHash: bytes32(uint256(0x01)), - indexRepeatedStorageChanges: 1, - numberOfLayer1Txs: 0, - priorityOperationsHash: EMPTY_STRING_KECCAK, - l2LogsTreeRoot: DEFAULT_L2_LOGS_TREE_ROOT_HASH, - timestamp: 0, - commitment: bytes32(uint256(0x01)) - }); - - 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: 1, - 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) - ); - - correctL2Logs[uint256(uint256(SystemLogKey.PREV_BATCH_HASH_KEY))] = Utils.constructL2Log( - true, - L2_SYSTEM_CONTEXT_ADDRESS, - uint256(SystemLogKey.PREV_BATCH_HASH_KEY), - bytes32(uint256(0x01)) - ); - - 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: 1, - 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/SetValidatorTimelock.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetValidatorTimelock.t.sol deleted file mode 100644 index d290a8767..000000000 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetValidatorTimelock.t.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -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/_StateTransitionManager_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/_StateTransitionManager_Shared.t.sol deleted file mode 100644 index 999336642..000000000 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/_StateTransitionManager_Shared.t.sol +++ /dev/null @@ -1,145 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.21; - -import {Test} from "forge-std/Test.sol"; - -import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/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 "contracts/state-transition/chain-deps/facets/Admin.sol"; -import {ExecutorFacet} from "contracts/state-transition/chain-deps/facets/Executor.sol"; -import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; -import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; -import {DiamondInit} from "contracts/state-transition/chain-deps/DiamondInit.sol"; -import {GenesisUpgrade} from "contracts/upgrades/GenesisUpgrade.sol"; -import {InitializeDataNewChain} from "contracts/state-transition/chain-interfaces/IDiamondInit.sol"; -import {StateTransitionManager} from "contracts/state-transition/StateTransitionManager.sol"; -import {StateTransitionManagerInitializeData, ChainCreationParams} from "contracts/state-transition/IStateTransitionManager.sol"; -import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; -import {ZeroAddress} from "contracts/common/L1ContractErrors.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; - address internal testnetVerifier = address(new TestnetVerifier()); - - Diamond.FacetCut[] internal facetCuts; - - function setUp() public { - bridgehub = makeAddr("bridgehub"); - newChainAdmin = makeAddr("chainadmin"); - - vm.startPrank(bridgehub); - stateTransitionManager = new StateTransitionManager(bridgehub, type(uint256).max); - 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() - }) - ); - - ChainCreationParams memory chainCreationParams = ChainCreationParams({ - genesisUpgrade: address(genesisUpgradeContract), - genesisBatchHash: bytes32(uint256(0x01)), - genesisIndexRepeatedStorageChanges: 0x01, - genesisBatchCommitment: bytes32(uint256(0x01)), - diamondCut: getDiamondCutData(address(diamondInit)) - }); - - StateTransitionManagerInitializeData memory stmInitializeDataNoGovernor = StateTransitionManagerInitializeData({ - owner: address(0), - validatorTimelock: validator, - chainCreationParams: chainCreationParams, - protocolVersion: 0 - }); - - vm.expectRevert(ZeroAddress.selector); - new TransparentUpgradeableProxy( - address(stateTransitionManager), - admin, - abi.encodeCall(StateTransitionManager.initialize, stmInitializeDataNoGovernor) - ); - - StateTransitionManagerInitializeData memory stmInitializeData = StateTransitionManagerInitializeData({ - owner: governor, - validatorTimelock: validator, - chainCreationParams: chainCreationParams, - 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(testnetVerifier); - - 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: chainId, - _baseToken: baseToken, - _sharedBridge: sharedBridge, - _admin: newChainAdmin, - _diamondCut: 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/facets/Base/OnlyGovernorOrStateTransitionManager.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyGovernorOrStateTransitionManager.t.sol deleted file mode 100644 index 2c4062d5b..000000000 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyGovernorOrStateTransitionManager.t.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -import {ZkSyncHyperchainBaseTest} from "./_Base_Shared.t.sol"; -import {Unauthorized} from "contracts/common/L1ContractErrors.sol"; - -contract OnlyAdminOrStateTransitionManagerTest is ZkSyncHyperchainBaseTest { - function test_revertWhen_calledByNonAdmin() public { - address nonAdmin = makeAddr("nonAdmin"); - - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, nonAdmin)); - vm.startPrank(nonAdmin); - testBaseFacet.functionWithOnlyAdminOrStateTransitionManagerModifier(); - } - - function test_revertWhen_calledByNonStateTransitionManager() public { - address nonStateTransitionManager = makeAddr("nonStateTransitionManager"); - - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, nonStateTransitionManager)); - 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 deleted file mode 100644 index a93032c90..000000000 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/OnlyStateTransitionManager.t.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -import {ZkSyncHyperchainBaseTest} from "./_Base_Shared.t.sol"; -import {Unauthorized} from "contracts/common/L1ContractErrors.sol"; - -contract OnlyStateTransitionManagerTest is ZkSyncHyperchainBaseTest { - function test_revertWhen_calledByNonStateTransitionManager() public { - address nonStateTransitionManager = makeAddr("nonStateTransitionManager"); - - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, nonStateTransitionManager)); - 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/Getters/GetBaseTokenBridge.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetBaseTokenBridge.t.sol deleted file mode 100644 index db32ca6bd..000000000 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetBaseTokenBridge.t.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -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/GetStateTransitionManager.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetStateTransitionManager.t.sol deleted file mode 100644 index 9b3038f97..000000000 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/GetStateTransitionManager.t.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -import {GettersFacetTest} from "./_Getters_Shared.t.sol"; - -contract GetStateTransitionManagerTest is GettersFacetTest { - function test() public { - address expected = makeAddr("stateTransitionManager"); - 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/PriorityQueueFrontOperation.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/PriorityQueueFrontOperation.t.sol deleted file mode 100644 index ac8ccfeaa..000000000 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/PriorityQueueFrontOperation.t.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -import {GettersFacetTest} from "./_Getters_Shared.t.sol"; -import {PriorityOperation} from "contracts/state-transition/libraries/PriorityQueue.sol"; -import {QueueIsEmpty} from "contracts/common/L1ContractErrors.sol"; - -contract GetPriorityQueueFrontOperationTest is GettersFacetTest { - function test_revertWhen_queueIsEmpty() public { - vm.expectRevert(QueueIsEmpty.selector); - 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/Mailbox/ProvingL2LogsInclusion.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/ProvingL2LogsInclusion.t.sol deleted file mode 100644 index b80409234..000000000 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/ProvingL2LogsInclusion.t.sol +++ /dev/null @@ -1,286 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -import {MailboxTest} from "./_Mailbox_Shared.t.sol"; -import {L2Message, L2Log} from "contracts/common/Messaging.sol"; -import "forge-std/Test.sol"; -import {L2_L1_LOGS_TREE_DEFAULT_LEAF_HASH, L1_GAS_PER_PUBDATA_BYTE, L2_TO_L1_LOG_SERIALIZE_SIZE} from "contracts/common/Config.sol"; -import {L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, L2_BOOTLOADER_ADDRESS} from "contracts/common/L2ContractAddresses.sol"; -import {BatchNotExecuted, HashedLogIsDefault} from "contracts/common/L1ContractErrors.sol"; -import {Merkle} from "contracts/state-transition/libraries/Merkle.sol"; -import {MurkyBase} from "murky/common/MurkyBase.sol"; -import {MerkleTest} from "contracts/dev-contracts/test/MerkleTest.sol"; -import {TxStatus} from "contracts/state-transition/chain-deps/facets/Mailbox.sol"; -import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; -import {MerkleTreeNoSort} from "test/foundry/unit/concrete/state-transition/libraries/Merkle/MerkleTreeNoSort.sol"; - -contract MailboxL2LogsProve is MailboxTest { - bytes32[] elements; - MerkleTest merkle; - MerkleTreeNoSort merkleTree; - bytes data; - uint256 batchNumber; - bool isService; - uint8 shardId; - - function setUp() public virtual { - setupDiamondProxy(); - - data = abi.encodePacked("test data"); - merkleTree = new MerkleTreeNoSort(); - merkle = new MerkleTest(); - batchNumber = gettersFacet.getTotalBatchesExecuted(); - isService = true; - shardId = 0; - } - - function _addHashedLogToMerkleTree( - uint8 _shardId, - bool _isService, - uint16 _txNumberInBatch, - address _sender, - bytes32 _key, - bytes32 _value - ) internal returns (uint256 index) { - elements.push(keccak256(abi.encodePacked(_shardId, _isService, _txNumberInBatch, _sender, _key, _value))); - - index = elements.length - 1; - } - - function test_RevertWhen_batchNumberGreaterThanBatchesExecuted() public { - L2Message memory message = L2Message({txNumberInBatch: 0, sender: sender, data: data}); - bytes32[] memory proof = new bytes32[](0); - - vm.expectRevert(abi.encodeWithSelector(BatchNotExecuted.selector, batchNumber + 1)); - mailboxFacet.proveL2MessageInclusion({ - _batchNumber: batchNumber + 1, - _index: 0, - _message: message, - _proof: proof - }); - } - - function test_success_proveL2MessageInclusion() public { - uint256 firstLogIndex = _addHashedLogToMerkleTree({ - _shardId: 0, - _isService: true, - _txNumberInBatch: 0, - _sender: L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, - _key: bytes32(uint256(uint160(sender))), - _value: keccak256(data) - }); - - uint256 secondLogIndex = _addHashedLogToMerkleTree({ - _shardId: 0, - _isService: true, - _txNumberInBatch: 1, - _sender: L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, - _key: bytes32(uint256(uint160(sender))), - _value: keccak256(data) - }); - - // Calculate the Merkle root - bytes32 root = merkleTree.getRoot(elements); - utilsFacet.util_setL2LogsRootHash(batchNumber, root); - - // Create L2 message - L2Message memory message = L2Message({txNumberInBatch: 0, sender: sender, data: data}); - - // Get Merkle proof for the first element - bytes32[] memory firstLogProof = merkleTree.getProof(elements, firstLogIndex); - - { - // Calculate the root using the Merkle proof - bytes32 leaf = elements[firstLogIndex]; - bytes32 calculatedRoot = merkle.calculateRoot(firstLogProof, firstLogIndex, leaf); - - // Assert that the calculated root matches the expected root - assertEq(calculatedRoot, root); - } - - // Prove L2 message inclusion - bool ret = mailboxFacet.proveL2MessageInclusion(batchNumber, firstLogIndex, message, firstLogProof); - - // Assert that the proof was successful - assertEq(ret, true); - - // Prove L2 message inclusion for wrong leaf - ret = mailboxFacet.proveL2MessageInclusion(batchNumber, secondLogIndex, message, firstLogProof); - - // Assert that the proof has failed - assertEq(ret, false); - } - - function test_success_proveL2LogInclusion() public { - uint256 firstLogIndex = _addHashedLogToMerkleTree({ - _shardId: shardId, - _isService: isService, - _txNumberInBatch: 0, - _sender: L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, - _key: bytes32(uint256(uint160(sender))), - _value: keccak256(data) - }); - - uint256 secondLogIndex = _addHashedLogToMerkleTree({ - _shardId: shardId, - _isService: isService, - _txNumberInBatch: 1, - _sender: L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, - _key: bytes32(uint256(uint160(sender))), - _value: keccak256(data) - }); - - L2Log memory log = L2Log({ - l2ShardId: shardId, - isService: isService, - txNumberInBatch: 1, - sender: L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, - key: bytes32(uint256(uint160(sender))), - value: keccak256(data) - }); - - // Calculate the Merkle root - bytes32 root = merkleTree.getRoot(elements); - // Set root hash for current batch - utilsFacet.util_setL2LogsRootHash(batchNumber, root); - - // Get Merkle proof for the first element - bytes32[] memory secondLogProof = merkleTree.getProof(elements, secondLogIndex); - - { - // Calculate the root using the Merkle proof - bytes32 leaf = elements[secondLogIndex]; - - bytes32 calculatedRoot = merkle.calculateRoot(secondLogProof, secondLogIndex, leaf); - // Assert that the calculated root matches the expected root - assertEq(calculatedRoot, root); - } - - // Prove l2 log inclusion with correct proof - bool ret = mailboxFacet.proveL2LogInclusion({ - _batchNumber: batchNumber, - _index: secondLogIndex, - _proof: secondLogProof, - _log: log - }); - - // Assert that the proof was successful - assertEq(ret, true); - - // Prove l2 log inclusion with wrong proof - ret = mailboxFacet.proveL2LogInclusion({ - _batchNumber: batchNumber, - _index: firstLogIndex, - _proof: secondLogProof, - _log: log - }); - - // Assert that the proof was successful - assertEq(ret, false); - } - - // this is not possible in case of message, because some default values - // are set during translation from message to log - function test_RevertWhen_proveL2LogInclusionDefaultLog() public { - L2Log memory log = L2Log({ - l2ShardId: 0, - isService: false, - txNumberInBatch: 0, - sender: address(0), - key: bytes32(0), - value: bytes32(0) - }); - - uint256 firstLogIndex = _addHashedLogToMerkleTree({ - _shardId: 0, - _isService: true, - _txNumberInBatch: 1, - _sender: L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, - _key: bytes32(uint256(uint160(sender))), - _value: keccak256(data) - }); - - // Add first element to the Merkle tree - elements.push(keccak256(new bytes(L2_TO_L1_LOG_SERIALIZE_SIZE))); - uint256 secondLogIndex = 1; - - // Calculate the Merkle root - bytes32 root = merkleTree.getRoot(elements); - // Set root hash for current batch - utilsFacet.util_setL2LogsRootHash(batchNumber, root); - - // Get Merkle proof for the first element - bytes32[] memory secondLogProof = merkleTree.getProof(elements, secondLogIndex); - - { - // Calculate the root using the Merkle proof - bytes32 leaf = elements[secondLogIndex]; - bytes32 calculatedRoot = merkle.calculateRoot(secondLogProof, secondLogIndex, leaf); - // Assert that the calculated root matches the expected root - assertEq(calculatedRoot, root); - } - - // Prove log inclusion reverts - vm.expectRevert(HashedLogIsDefault.selector); - mailboxFacet.proveL2LogInclusion({ - _batchNumber: batchNumber, - _index: secondLogIndex, - _proof: secondLogProof, - _log: log - }); - } - - function test_success_proveL1ToL2TransactionStatus() public { - bytes32 firstL2TxHash = keccak256("firstL2Transaction"); - bytes32 secondL2TxHash = keccak256("SecondL2Transaction"); - TxStatus txStatus = TxStatus.Success; - - uint256 firstLogIndex = _addHashedLogToMerkleTree({ - _shardId: shardId, - _isService: isService, - _txNumberInBatch: 0, - _sender: L2_BOOTLOADER_ADDRESS, - _key: firstL2TxHash, - _value: bytes32(uint256(txStatus)) - }); - - uint256 secondLogIndex = _addHashedLogToMerkleTree({ - _shardId: shardId, - _isService: isService, - _txNumberInBatch: 1, - _sender: L2_BOOTLOADER_ADDRESS, - _key: secondL2TxHash, - _value: bytes32(uint256(txStatus)) - }); - - // Calculate the Merkle root - bytes32 root = merkleTree.getRoot(elements); - // Set root hash for current batch - utilsFacet.util_setL2LogsRootHash(batchNumber, root); - - // Get Merkle proof for the first element - bytes32[] memory secondLogProof = merkleTree.getProof(elements, secondLogIndex); - - { - // Calculate the root using the Merkle proof - bytes32 leaf = elements[secondLogIndex]; - bytes32 calculatedRoot = merkle.calculateRoot(secondLogProof, secondLogIndex, leaf); - // Assert that the calculated root matches the expected root - assertEq(calculatedRoot, root); - } - - // Prove L1 to L2 transaction status - bool ret = mailboxFacet.proveL1ToL2TransactionStatus({ - _l2TxHash: secondL2TxHash, - _l2BatchNumber: batchNumber, - _l2MessageIndex: secondLogIndex, - _l2TxNumberInBatch: 1, - _merkleProof: secondLogProof, - _status: txStatus - }); - - // Assert that the proof was successful - assertEq(ret, true); - } -} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/TransferEthToSharedBridge.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/TransferEthToSharedBridge.t.sol deleted file mode 100644 index 2bba6bda1..000000000 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/TransferEthToSharedBridge.t.sol +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -import {MailboxTest} from "./_Mailbox_Shared.t.sol"; -import {IL1SharedBridge} from "contracts/bridge/interfaces/IL1SharedBridge.sol"; -import {DummySharedBridge} from "contracts/dev-contracts/test/DummySharedBridge.sol"; -import {OnlyEraSupported, Unauthorized} from "contracts/common/L1ContractErrors.sol"; - -contract MailboxTransferEthToSharedBridge is MailboxTest { - address baseTokenBridgeAddress; - DummySharedBridge l1SharedBridge; - - function setUp() public virtual { - setupDiamondProxy(); - - l1SharedBridge = new DummySharedBridge(keccak256("dummyDepositHash")); - baseTokenBridgeAddress = address(l1SharedBridge); - - utilsFacet.util_setChainId(eraChainId); - utilsFacet.util_setBaseTokenBridge(baseTokenBridgeAddress); - } - - modifier useBaseTokenBridge() { - vm.startPrank(baseTokenBridgeAddress); - _; - vm.stopPrank(); - } - - function test_success_transfer(uint256 randomAmount) public useBaseTokenBridge { - vm.deal(diamondProxy, randomAmount); - - assertEq(address(l1SharedBridge).balance, 0); - assertEq(address(diamondProxy).balance, randomAmount); - mailboxFacet.transferEthToSharedBridge(); - assertEq(address(l1SharedBridge).balance, randomAmount); - assertEq(address(diamondProxy).balance, 0); - } - - function test_RevertWhen_wrongCaller() public { - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, sender)); - vm.prank(sender); - mailboxFacet.transferEthToSharedBridge(); - } - - function test_RevertWhen_hyperchainIsNotEra(uint256 randomChainId) public useBaseTokenBridge { - vm.assume(eraChainId != randomChainId); - utilsFacet.util_setChainId(randomChainId); - - vm.expectRevert(OnlyEraSupported.selector); - mailboxFacet.transferEthToSharedBridge(); - } -} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/Merkle/Merkle.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/Merkle/Merkle.t.sol deleted file mode 100644 index 89514fc99..000000000 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/Merkle/Merkle.t.sol +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -import {Test} from "forge-std/Test.sol"; -import {MerkleTest} from "contracts/dev-contracts/test/MerkleTest.sol"; -import {MerkleTreeNoSort} from "./MerkleTreeNoSort.sol"; -import {MerklePathEmpty, MerkleIndexOutOfBounds, MerklePathOutOfBounds} from "contracts/common/L1ContractErrors.sol"; - -contract MerkleTestTest is Test { - MerkleTreeNoSort merkleTree; - MerkleTest merkleTest; - bytes32[] elements; - bytes32 root; - - function setUp() public { - merkleTree = new MerkleTreeNoSort(); - merkleTest = new MerkleTest(); - - for (uint256 i = 0; i < 65; i++) { - elements.push(keccak256(abi.encodePacked(i))); - } - - root = merkleTree.getRoot(elements); - } - - function testElements(uint256 i) public { - vm.assume(i < elements.length); - bytes32 leaf = elements[i]; - bytes32[] memory proof = merkleTree.getProof(elements, i); - - bytes32 rootFromContract = merkleTest.calculateRoot(proof, i, leaf); - - assertEq(rootFromContract, root); - } - - function testFirstElement() public { - testElements(0); - } - - function testLastElement() public { - testElements(elements.length - 1); - } - - function testEmptyProof_shouldRevert() public { - bytes32 leaf = elements[0]; - bytes32[] memory proof; - - vm.expectRevert(MerklePathEmpty.selector); - merkleTest.calculateRoot(proof, 0, leaf); - } - - function testLeafIndexTooBig_shouldRevert() public { - bytes32 leaf = elements[0]; - bytes32[] memory proof = merkleTree.getProof(elements, 0); - - vm.expectRevert(MerkleIndexOutOfBounds.selector); - merkleTest.calculateRoot(proof, 2 ** 255, leaf); - } - - function testProofLengthTooLarge_shouldRevert() public { - bytes32 leaf = elements[0]; - bytes32[] memory proof = new bytes32[](256); - - vm.expectRevert(MerklePathOutOfBounds.selector); - merkleTest.calculateRoot(proof, 0, leaf); - } -} diff --git a/l1-contracts/test/test_config/constant/hardhat.json b/l1-contracts/test/test_config/constant/hardhat.json index 0e63431f0..7e18d5adf 100644 --- a/l1-contracts/test/test_config/constant/hardhat.json +++ b/l1-contracts/test/test_config/constant/hardhat.json @@ -3,96 +3,96 @@ "name": "DAI", "symbol": "DAI", "decimals": 18, - "address": "0xD6E49dd4fb0CA1549566869725d1820aDEb92Ae9" + "address": "0x2733174391e451C1708050dE4442f2AaF197759C" }, { "name": "wBTC", "symbol": "wBTC", "decimals": 8, - "address": "0xcee1f75F30B6908286Cd003C4228A5D9a2851FA4" + "address": "0x5195459c6a59dA8f5bED6a9E7692d5b5b40A2928" }, { "name": "BAT", "symbol": "BAT", "decimals": 18, - "address": "0x0Bc76A4EfE0748f1697F237fB100741ea6Ceda2d" + "address": "0x18fb101C1f3ab450498fD0D4400e4D8a3c1B9F6c" }, { "name": "GNT", "symbol": "GNT", "decimals": 18, - "address": "0x51ae50BcCEE10ac5BEFFA1E4a64106a5f83bc3F8" + "address": "0x20bABCb488aad652e713fB70BACCD0c8e72de4EF" }, { "name": "MLTT", "symbol": "MLTT", "decimals": 18, - "address": "0xa9c7fEEf8586E17D93A05f873BA65f28f48ED259" + "address": "0xFF9d5a057d09802c371582D1166df728D3b019A4" }, { "name": "DAIK", "symbol": "DAIK", "decimals": 18, - "address": "0x99Efb27598804Aa408A1066550e9d01c45f21b05" + "address": "0xAC5d1395Dd3bA956bB8Ba0e0E8ffe247404fd9c5" }, { "name": "wBTCK", "symbol": "wBTCK", "decimals": 8, - "address": "0x4B701928Da6B3e72775b462A15b8b76ba2d16BbD" + "address": "0x9614c0F8e657eAb74e95B87cA819C6ae1F9d5fe1" }, { "name": "BATK", "symbol": "BATS", "decimals": 18, - "address": "0xf7B03c921dfefB4286b13075BA0335099708368D" + "address": "0x175F95E3c6a30c3D4DDBA32f82427C5d371f67B8" }, { "name": "GNTK", "symbol": "GNTS", "decimals": 18, - "address": "0xc0581Ee28c519533B06cc0aAC1ace98cF63C817b" + "address": "0x205725BE39c54574e64771C3c71c44829E3031dC" }, { "name": "MLTTK", "symbol": "MLTTS", "decimals": 18, - "address": "0xeB6394F2E8DA607b94dBa2Cf345A965d6D9b3aCD" + "address": "0xADDb1960aCAAC7db90E3d20b9621D8b8C0b97405" }, { "name": "DAIL", "symbol": "DAIL", "decimals": 18, - "address": "0x4311643C5eD7cD0813B4E3Ff5428de71c7d7b8bB" + "address": "0x5F0A3258CF075F828a01bEAf63cCea32159B4EcD" }, { "name": "wBTCL", "symbol": "wBTCP", "decimals": 8, - "address": "0x6b3fbfC9Bb89Ab5F11BE782a1f67c1615c2A5fc3" + "address": "0x09405DfB0C61959daA219e9E9907223e7F091587" }, { "name": "BATL", "symbol": "BATW", "decimals": 18, - "address": "0xE003698b7831829843B69D3fB4f9a3133d97b257" + "address": "0xA318F029bc204EaF55A6f480f9Dc6Ef0C235d2D6" }, { "name": "GNTL", "symbol": "GNTW", "decimals": 18, - "address": "0x2417626170675Ccf6022d9db1eFC8f3c59836368" + "address": "0x9910Ed28A31C2b4EE8A7C1F8559Cf1a874e374F2" }, { "name": "MLTTL", "symbol": "MLTTW", "decimals": 18, - "address": "0x28106C39BE5E51C31D9a289313361D86C9bb7C8E" + "address": "0x3bC2c8eF2f110750bfa9aA89D30F3D66c2a03bb9" }, { "name": "Wrapped Ether", "symbol": "WETH", "decimals": 18, - "address": "0x51E83b811930bb4a3aAb3494894ec237Cb6cEc49" + "address": "0x6930c5c3421a666d6642FafBe750B1D0B42197c6" } ] diff --git a/l1-contracts/test/unit_tests/custom_base_token.spec.ts b/l1-contracts/test/unit_tests/custom_base_token.spec.ts index 464298482..2fc87d199 100644 --- a/l1-contracts/test/unit_tests/custom_base_token.spec.ts +++ b/l1-contracts/test/unit_tests/custom_base_token.spec.ts @@ -1,18 +1,19 @@ 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 type { IL1AssetRouter } from "../../typechain/IL1AssetRouter"; +import { IL1AssetRouterFactory } from "../../typechain/IL1AssetRouterFactory"; +import type { IL1NativeTokenVault } from "../../typechain/IL1NativeTokenVault"; +import { IL1NativeTokenVaultFactory } from "../../typechain/IL1NativeTokenVaultFactory"; import { getTokens } from "../../src.ts/deploy-token"; import type { Deployer } from "../../src.ts/deploy"; -import { ADDRESS_ONE, ethTestConfig } from "../../src.ts/utils"; +import { ethTestConfig } from "../../src.ts/utils"; import { initialTestnetDeploymentProcess } from "../../src.ts/deploy-test-process"; import { getCallRevertReason, REQUIRED_L2_GAS_PRICE_PER_PUBDATA } from "./utils"; @@ -22,8 +23,9 @@ describe("Custom base token chain and bridge tests", () => { let randomSigner: ethers.Signer; let deployWallet: Wallet; let deployer: Deployer; - let l1SharedBridge: IL1SharedBridge; + let l1SharedBridge: IL1AssetRouter; let bridgehub: IBridgehub; + let nativeTokenVault: IL1NativeTokenVault; let baseToken: TestnetERC20Token; let baseTokenAddress: string; let altTokenAddress: string; @@ -61,27 +63,20 @@ describe("Custom base token chain and bridge tests", () => { altToken = TestnetERC20TokenFactory.connect(altTokenAddress, owner); // prepare the bridge - l1SharedBridge = IL1SharedBridgeFactory.connect(deployer.addresses.Bridges.SharedBridgeProxy, deployWallet); + l1SharedBridge = IL1AssetRouterFactory.connect(deployer.addresses.Bridges.SharedBridgeProxy, deployWallet); + + nativeTokenVault = IL1NativeTokenVaultFactory.connect( + deployer.addresses.Bridges.NativeTokenVaultProxy, + deployWallet + ); }); it("Should have correct base token", async () => { // we should still be able to deploy the erc20 bridge - const baseTokenAddressInBridgehub = await bridgehub.baseToken(chainId); + const baseTokenAddressInBridgehub = await bridgehub.baseToken(deployer.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 @@ -119,6 +114,7 @@ describe("Custom base token chain and bridge tests", () => { }); it("Should deposit alternative token successfully twoBridges method", async () => { + nativeTokenVault.registerToken(altTokenAddress); const altTokenAmount = ethers.utils.parseUnits("800", 18); const baseTokenAmount = ethers.utils.parseUnits("800", 18); @@ -137,17 +133,22 @@ describe("Custom base token chain and bridge tests", () => { secondBridgeAddress: l1SharedBridge.address, secondBridgeValue: 0, secondBridgeCalldata: ethers.utils.defaultAbiCoder.encode( - ["address", "uint256", "address"], - [altTokenAddress, altTokenAmount, await randomSigner.getAddress()] + ["bytes32", "bytes", "address"], + [ + ethers.utils.hexZeroPad(altTokenAddress, 32), + new ethers.utils.AbiCoder().encode(["uint256"], [altTokenAmount]), + await randomSigner.getAddress(), + ] ), }); }); it("Should revert on finalizing a withdrawal with wrong message length", async () => { + const mailboxFunctionSignature = "0x6c0960f9"; const revertReason = await getCallRevertReason( - l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, "0x", []) + l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, mailboxFunctionSignature, []) ); - expect(revertReason).contains("MalformedMessage"); + expect(revertReason).contains("L2WithdrawalMessageWrongLength"); }); it("Should revert on finalizing a withdrawal with wrong function selector", async () => { diff --git a/l1-contracts/test/unit_tests/gateway.spec.ts b/l1-contracts/test/unit_tests/gateway.spec.ts new file mode 100644 index 000000000..37460e02a --- /dev/null +++ b/l1-contracts/test/unit_tests/gateway.spec.ts @@ -0,0 +1,184 @@ +import { expect } from "chai"; +import * as ethers from "ethers"; +import { Wallet } from "ethers"; +import * as hardhat from "hardhat"; + +import type { Bridgehub } from "../../typechain"; +import { BridgehubFactory } from "../../typechain"; + +import { + initialTestnetDeploymentProcess, + defaultDeployerForTests, + registerZKChainWithBridgeRegistration, +} from "../../src.ts/deploy-test-process"; +import { + ethTestConfig, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + priorityTxMaxGasLimit, + L2_BRIDGEHUB_ADDRESS, +} from "../../src.ts/utils"; +import { SYSTEM_CONFIG } from "../../scripts/utils"; + +import type { Deployer } from "../../src.ts/deploy"; + +describe("Gateway", function () { + let bridgehub: Bridgehub; + // let stateTransition: ChainTypeManager; + let owner: ethers.Signer; + let migratingDeployer: Deployer; + let gatewayDeployer: 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; + const mintChainId = 11; + + 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); + + migratingDeployer = await initialTestnetDeploymentProcess(deployWallet, ownerAddress, gasPrice, []); + // We will use the chain admin as the admin to be closer to the production environment + await migratingDeployer.transferAdminFromDeployerToChainAdmin(); + + chainId = migratingDeployer.chainId; + + bridgehub = BridgehubFactory.connect(migratingDeployer.addresses.Bridgehub.BridgehubProxy, deployWallet); + + gatewayDeployer = await defaultDeployerForTests(deployWallet, ownerAddress); + gatewayDeployer.chainId = 10; + await registerZKChainWithBridgeRegistration( + gatewayDeployer, + false, + [], + gasPrice, + undefined, + gatewayDeployer.chainId.toString() + ); + + // For tests, the chainId is 9 + migratingDeployer.chainId = 9; + }); + + it("Check register synclayer", async () => { + await gatewayDeployer.registerSettlementLayer(); + }); + + it("Check start move chain to synclayer", async () => { + const gasPrice = await owner.provider.getGasPrice(); + await migratingDeployer.moveChainToGateway(gatewayDeployer.chainId.toString(), gasPrice); + expect(await bridgehub.settlementLayer(migratingDeployer.chainId)).to.equal(gatewayDeployer.chainId); + }); + + it("Check l2 registration", async () => { + const ctm = migratingDeployer.chainTypeManagerContract(migratingDeployer.deployWallet); + const gasPrice = await migratingDeployer.deployWallet.provider.getGasPrice(); + const value = ( + await bridgehub.l2TransactionBaseCost(chainId, gasPrice, priorityTxMaxGasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA) + ).mul(10); + + const ctmDeploymentTracker = migratingDeployer.ctmDeploymentTracker(migratingDeployer.deployWallet); + const assetRouter = migratingDeployer.defaultSharedBridge(migratingDeployer.deployWallet); + const assetId = await bridgehub.ctmAssetIdFromChainId(chainId); + + await migratingDeployer.executeUpgrade( + bridgehub.address, + value, + bridgehub.interface.encodeFunctionData("requestL2TransactionTwoBridges", [ + { + chainId, + mintValue: value, + l2Value: 0, + l2GasLimit: priorityTxMaxGasLimit, + l2GasPerPubdataByteLimit: SYSTEM_CONFIG.requiredL2GasPricePerPubdata, + refundRecipient: migratingDeployer.deployWallet.address, + secondBridgeAddress: assetRouter.address, + secondBridgeValue: 0, + secondBridgeCalldata: + "0x02" + + ethers.utils.defaultAbiCoder.encode(["bytes32", "address"], [assetId, L2_BRIDGEHUB_ADDRESS]).slice(2), + }, + ]) + ); + await migratingDeployer.executeUpgrade( + bridgehub.address, + value, + bridgehub.interface.encodeFunctionData("requestL2TransactionTwoBridges", [ + { + chainId, + mintValue: value, + l2Value: 0, + l2GasLimit: priorityTxMaxGasLimit, + l2GasPerPubdataByteLimit: SYSTEM_CONFIG.requiredL2GasPricePerPubdata, + refundRecipient: migratingDeployer.deployWallet.address, + secondBridgeAddress: ctmDeploymentTracker.address, + secondBridgeValue: 0, + secondBridgeCalldata: + "0x01" + ethers.utils.defaultAbiCoder.encode(["address", "address"], [ctm.address, ctm.address]).slice(2), + }, + ]) + ); + // console.log("CTM asset registered in L2 Bridgehub on SL"); + }); + + it("Check start message to L3 on L1", async () => { + const amount = ethers.utils.parseEther("2"); + await bridgehub.requestL2TransactionDirect( + { + chainId: migratingDeployer.chainId, + mintValue: amount, + l2Contract: ethers.constants.AddressZero, + l2Value: 0, + l2Calldata: "0x", + l2GasLimit: priorityTxMaxGasLimit, + l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + factoryDeps: [], + refundRecipient: ethers.constants.AddressZero, + }, + { value: amount } + ); + }); + + it("Check forward message to L3 on SL", async () => { + const tx = { + txType: 255, + from: ethers.constants.AddressZero, + to: ethers.constants.AddressZero, + gasLimit: priorityTxMaxGasLimit, + gasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + maxFeePerGas: 1, + maxPriorityFeePerGas: 0, + paymaster: 0, + // Note, that the priority operation id is used as "nonce" for L1->L2 transactions + nonce: 0, + value: 0, + reserved: [0 as ethers.BigNumberish, 0, 0, 0] as [ + ethers.BigNumberish, + ethers.BigNumberish, + ethers.BigNumberish, + ethers.BigNumberish, + ], + data: "0x", + signature: ethers.constants.HashZero, + factoryDeps: [], + paymasterInput: "0x", + reservedDynamic: "0x", + }; + bridgehub.forwardTransactionOnGateway(mintChainId, tx, [], ethers.constants.HashZero, 0); + }); +}); diff --git a/l1-contracts/test/unit_tests/governance_test.spec.ts b/l1-contracts/test/unit_tests/governance_test.spec.ts index 444e70846..e689def8d 100644 --- a/l1-contracts/test/unit_tests/governance_test.spec.ts +++ b/l1-contracts/test/unit_tests/governance_test.spec.ts @@ -13,17 +13,18 @@ describe("Admin facet tests", function () { before(async () => { const contractFactory = await hardhat.ethers.getContractFactory("AdminFacetTest"); - const contract = await contractFactory.deploy(); + const contract = await contractFactory.deploy(await contractFactory.signer.getChainId()); adminFacetTest = AdminFacetTestFactory.connect(contract.address, contract.signer); - const governanceContract = await contractFactory.deploy(); + const governanceContract = await contractFactory.deploy(await contractFactory.signer.getChainId()); + const governance = GovernanceFactory.connect(governanceContract.address, governanceContract.signer); await adminFacetTest.setPendingAdmin(governance.address); randomSigner = (await hardhat.ethers.getSigners())[1]; }); - it("StateTransitionManager successfully set validator", async () => { + it("ChainTypeManager successfully set validator", async () => { const validatorAddress = randomAddress(); await adminFacetTest.setValidator(validatorAddress, true); @@ -39,7 +40,7 @@ describe("Admin facet tests", function () { expect(revertReason).contains("Unauthorized"); }); - it("StateTransitionManager successfully set porter availability", async () => { + it("ChainTypeManager successfully set porter availability", async () => { await adminFacetTest.setPorterAvailability(true); const porterAvailability = await adminFacetTest.getPorterAvailability(); @@ -51,7 +52,7 @@ describe("Admin facet tests", function () { expect(revertReason).contains("Unauthorized"); }); - it("StateTransitionManager successfully set priority transaction max gas limit", async () => { + it("ChainTypeManager successfully set priority transaction max gas limit", async () => { const gasLimit = "12345678"; await adminFacetTest.setPriorityTxMaxGasLimit(gasLimit); diff --git a/l1-contracts/test/unit_tests/initial_deployment_test.spec.ts b/l1-contracts/test/unit_tests/initial_deployment_test.spec.ts index a70594304..532fd57d1 100644 --- a/l1-contracts/test/unit_tests/initial_deployment_test.spec.ts +++ b/l1-contracts/test/unit_tests/initial_deployment_test.spec.ts @@ -3,22 +3,32 @@ 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 type { Bridgehub, ChainTypeManager, L1NativeTokenVault, L1AssetRouter, L1Nullifier } from "../../typechain"; +import { + BridgehubFactory, + ChainTypeManagerFactory, + L1NativeTokenVaultFactory, + L1AssetRouterFactory, + L1NullifierFactory, +} from "../../typechain"; import { initialTestnetDeploymentProcess } from "../../src.ts/deploy-test-process"; import { ethTestConfig } from "../../src.ts/utils"; import type { Deployer } from "../../src.ts/deploy"; +import { registerZKChain } from "../../src.ts/deploy-process"; -describe("Initial deployment", function () { +describe("Initial deployment test", function () { let bridgehub: Bridgehub; - let stateTransition: StateTransitionManager; + let chainTypeManager: ChainTypeManager; 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 l1NativeTokenVault: L1NativeTokenVault; + let l1AssetRouter: L1AssetRouter; + let l1Nullifier: L1Nullifier; let chainId = process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID || 270; before(async () => { @@ -47,22 +57,53 @@ describe("Initial deployment", function () { // await deploySharedBridgeOnL2ThroughL1(deployer, chainId.toString(), gasPrice); bridgehub = BridgehubFactory.connect(deployer.addresses.Bridgehub.BridgehubProxy, deployWallet); - stateTransition = StateTransitionManagerFactory.connect( + chainTypeManager = ChainTypeManagerFactory.connect( deployer.addresses.StateTransition.StateTransitionProxy, deployWallet ); + l1NativeTokenVault = L1NativeTokenVaultFactory.connect( + deployer.addresses.Bridges.NativeTokenVaultProxy, + deployWallet + ); + l1AssetRouter = L1AssetRouterFactory.connect(deployer.addresses.Bridges.SharedBridgeProxy, deployWallet); + l1Nullifier = L1NullifierFactory.connect(deployer.addresses.Bridges.L1NullifierProxy, 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.getHyperchain(chainId); - expect(stateTransitionAddress1.toLowerCase()).equal(stateTransitionAddress2.toLowerCase()); + const bridgehubAddress1 = deployer.addresses.Bridgehub.BridgehubProxy; + const bridgehubAddress2 = await l1AssetRouter.BRIDGE_HUB(); + const bridgehubAddress3 = await chainTypeManager.BRIDGE_HUB(); + expect(bridgehubAddress1.toLowerCase()).equal(bridgehubAddress2.toLowerCase()); + expect(bridgehubAddress1.toLowerCase()).equal(bridgehubAddress3.toLowerCase()); + + const chainTypeManagerAddress1 = deployer.addresses.StateTransition.StateTransitionProxy; + const chainTypeManagerAddress2 = await bridgehub.chainTypeManager(chainId); + expect(chainTypeManagerAddress1.toLowerCase()).equal(chainTypeManagerAddress2.toLowerCase()); + + const chainAddress2 = await chainTypeManager.getZKChain(chainId); + const chainAddress1 = deployer.addresses.StateTransition.DiamondProxy; + expect(chainAddress1.toLowerCase()).equal(chainAddress2.toLowerCase()); + + const chainAddress3 = await bridgehub.getZKChain(chainId); + expect(chainAddress1.toLowerCase()).equal(chainAddress3.toLowerCase()); + + const assetRouterAddress1 = deployer.addresses.Bridges.SharedBridgeProxy; + const assetRouterAddress2 = await bridgehub.sharedBridge(); + const assetRouterAddress3 = await l1NativeTokenVault.ASSET_ROUTER(); + const assetRouterAddress4 = await l1Nullifier.l1AssetRouter(); + expect(assetRouterAddress1.toLowerCase()).equal(assetRouterAddress2.toLowerCase()); + expect(assetRouterAddress1.toLowerCase()).equal(assetRouterAddress3.toLowerCase()); + expect(assetRouterAddress1.toLowerCase()).equal(assetRouterAddress4.toLowerCase()); + + const ntvAddress1 = deployer.addresses.Bridges.NativeTokenVaultProxy; + const ntvAddress2 = await l1Nullifier.l1NativeTokenVault(); + const ntvAddress3 = await l1AssetRouter.nativeTokenVault(); + expect(ntvAddress1.toLowerCase()).equal(ntvAddress2.toLowerCase()); + expect(ntvAddress1.toLowerCase()).equal(ntvAddress3.toLowerCase()); + }); - const stateTransitionAddress3 = await bridgehub.getHyperchain(chainId); - expect(stateTransitionAddress1.toLowerCase()).equal(stateTransitionAddress3.toLowerCase()); + it("Check L2SharedBridge", async () => { + const gasPrice = await owner.provider.getGasPrice(); + await registerZKChain(deployer, false, [], gasPrice, "", "0x33", true, true); }); }); 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 index 31475e241..e1e17128b 100644 --- a/l1-contracts/test/unit_tests/l1_shared_bridge_test.spec.ts +++ b/l1-contracts/test/unit_tests/l1_shared_bridge_test.spec.ts @@ -1,16 +1,22 @@ 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 type { L1AssetRouter, Bridgehub, L1NativeTokenVault, MockExecutorFacet } from "../../typechain"; +import { + L1AssetRouterFactory, + BridgehubFactory, + TestnetERC20TokenFactory, + MockExecutorFacetFactory, +} from "../../typechain"; +import { L1NativeTokenVaultFactory } from "../../typechain/L1NativeTokenVaultFactory"; import { getTokens } from "../../src.ts/deploy-token"; -import { ADDRESS_ONE, ethTestConfig } from "../../src.ts/utils"; +import { Action, facetCut } from "../../src.ts/diamondCut"; +import { 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"; +import { getCallRevertReason, REQUIRED_L2_GAS_PRICE_PER_PUBDATA, DUMMY_MERKLE_PROOF_START } from "./utils"; describe("Shared Bridge tests", () => { let owner: ethers.Signer; @@ -18,12 +24,14 @@ describe("Shared Bridge tests", () => { let deployWallet: Wallet; let deployer: Deployer; let bridgehub: Bridgehub; - let l1SharedBridge: L1SharedBridge; - let l1SharedBridgeInterface: Interface; - let l1Weth: WETH9; + let l1NativeTokenVault: L1NativeTokenVault; + let proxyAsMockExecutor: MockExecutorFacet; + let l1SharedBridge: L1AssetRouter; let erc20TestToken: ethers.Contract; - const functionSignature = "0x6c0960f9"; + const mailboxFunctionSignature = "0x6c0960f9"; const ERC20functionSignature = "0x11a2ccc1"; + const dummyProof = Array(9).fill(ethers.constants.HashZero); + dummyProof[0] = DUMMY_MERKLE_PROOF_START; let chainId = process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID || 270; @@ -46,39 +54,52 @@ describe("Shared Bridge tests", () => { 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); + // note we can use initialTestnetDeploymentProcess so we don't go into deployment details here - deployer = await initialTestnetDeploymentProcess(deployWallet, ownerAddress, gasPrice, []); + deployer = await initialTestnetDeploymentProcess(deployWallet, ownerAddress, gasPrice, [extraFacet]); chainId = deployer.chainId; // prepare the bridge - l1SharedBridge = L1SharedBridgeFactory.connect(deployer.addresses.Bridges.SharedBridgeProxy, deployWallet); + proxyAsMockExecutor = MockExecutorFacetFactory.connect( + deployer.addresses.StateTransition.DiamondProxy, + mockExecutorContract.signer + ); + + await ( + await proxyAsMockExecutor.saveL2LogsRootHash( + 0, + "0x0000000000000000000000000000000000000000000000000000000000000001" + ) + ).wait(); + + l1SharedBridge = L1AssetRouterFactory.connect(deployer.addresses.Bridges.SharedBridgeProxy, deployWallet); bridgehub = BridgehubFactory.connect(deployer.addresses.Bridgehub.BridgehubProxy, deployWallet); - l1SharedBridgeInterface = new Interface(hardhat.artifacts.readArtifactSync("L1SharedBridge").abi); + l1NativeTokenVault = L1NativeTokenVaultFactory.connect( + deployer.addresses.Bridges.NativeTokenVaultProxy, + deployWallet + ); 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); + await erc20TestToken + .connect(randomSigner) + .approve(l1NativeTokenVault.address, ethers.utils.parseUnits("10000", 18)); - expect(txHash).not.equal(ethers.constants.HashZero); + await l1NativeTokenVault.registerToken(erc20TestToken.address); }); it("Should not allow depositing zero erc20 amount", async () => { const mintValue = ethers.utils.parseEther("0.01"); + await (await erc20TestToken.connect(randomSigner).approve(l1NativeTokenVault.address, mintValue.mul(10))).wait(); + const revertReason = await getCallRevertReason( bridgehub.connect(randomSigner).requestL2TransactionTwoBridges( { @@ -101,12 +122,17 @@ describe("Shared Bridge tests", () => { expect(revertReason).contains("EmptyDeposit"); }); - it("Should deposit successfully", async () => { + it("Should deposit successfully legacy encoding", 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( + + await erc20TestToken.connect(randomSigner).mint(await randomSigner.getAddress(), amount.mul(10)); + + const balanceBefore = await erc20TestToken.balanceOf(await randomSigner.getAddress()); + const balanceNTVBefore = await erc20TestToken.balanceOf(l1NativeTokenVault.address); + + await (await erc20TestToken.connect(randomSigner).approve(l1NativeTokenVault.address, amount.mul(10))).wait(); + await bridgehub.connect(randomSigner).requestL2TransactionTwoBridges( { chainId, mintValue, @@ -118,18 +144,24 @@ describe("Shared Bridge tests", () => { secondBridgeValue: 0, secondBridgeCalldata: new ethers.utils.AbiCoder().encode( ["address", "uint256", "address"], - [l1Weth.address, amount, await randomSigner.getAddress()] + [erc20TestToken.address, amount, await randomSigner.getAddress()] ), }, { value: mintValue } ); + const balanceAfter = await erc20TestToken.balanceOf(await randomSigner.getAddress()); + expect(balanceAfter).equal(balanceBefore.sub(amount)); + const balanceNTVAfter = await erc20TestToken.balanceOf(l1NativeTokenVault.address); + expect(balanceNTVAfter).equal(balanceNTVBefore.add(amount)); }); 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]) + l1SharedBridge + .connect(randomSigner) + .finalizeWithdrawal(chainId, 0, 0, 0, mailboxFunctionSignature, [ethers.constants.HashZero]) ); - expect(revertReason).contains("MalformedMessage"); + expect(revertReason).contains("L2WithdrawalMessageWrongLength"); }); it("Should revert on finalizing a withdrawal with wrong message length", async () => { @@ -141,11 +173,11 @@ describe("Shared Bridge tests", () => { 0, 0, 0, - ethers.utils.hexConcat([ERC20functionSignature, l1SharedBridge.address, ethers.utils.randomBytes(72 + 4)]), + ethers.utils.hexConcat([ERC20functionSignature, l1SharedBridge.address, mailboxFunctionSignature]), [ethers.constants.HashZero] ) ); - expect(revertReason).contains("MalformedMessage"); + expect(revertReason).contains("L2WithdrawalMessageWrongLength"); }); it("Should revert on finalizing a withdrawal with wrong function selector", async () => { @@ -155,35 +187,37 @@ describe("Shared Bridge tests", () => { expect(revertReason).contains("InvalidSelector"); }); - 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 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]) + l1SharedBridge + .connect(randomSigner) + .finalizeWithdrawal(chainId, 0, 0, 0, mailboxFunctionSignature, [ethers.constants.HashZero]) ); - expect(revertReason).contains("MalformedMessage"); + expect(revertReason).contains("L2WithdrawalMessageWrongLength"); }); it("Should revert on finalizing a withdrawal with wrong function signature", async () => { @@ -198,13 +232,13 @@ describe("Shared Bridge tests", () => { it("Should revert on finalizing a withdrawal with wrong batch number", async () => { const l1Receiver = await randomSigner.getAddress(); const l2ToL1message = ethers.utils.hexConcat([ - functionSignature, + mailboxFunctionSignature, l1Receiver, erc20TestToken.address, ethers.constants.HashZero, ]); const revertReason = await getCallRevertReason( - l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 10, 0, 0, l2ToL1message, []) + l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 10, 0, 0, l2ToL1message, dummyProof) ); expect(revertReason).contains("BatchNotExecuted"); }); @@ -212,7 +246,7 @@ describe("Shared Bridge tests", () => { it("Should revert on finalizing a withdrawal with wrong length of proof", async () => { const l1Receiver = await randomSigner.getAddress(); const l2ToL1message = ethers.utils.hexConcat([ - functionSignature, + mailboxFunctionSignature, l1Receiver, erc20TestToken.address, ethers.constants.HashZero, @@ -226,7 +260,7 @@ describe("Shared Bridge tests", () => { it("Should revert on finalizing a withdrawal with wrong proof", async () => { const l1Receiver = await randomSigner.getAddress(); const l2ToL1message = ethers.utils.hexConcat([ - functionSignature, + mailboxFunctionSignature, l1Receiver, erc20TestToken.address, ethers.constants.HashZero, @@ -234,7 +268,7 @@ describe("Shared Bridge tests", () => { const revertReason = await getCallRevertReason( l1SharedBridge .connect(randomSigner) - .finalizeWithdrawal(chainId, 0, 0, 0, l2ToL1message, Array(9).fill(ethers.constants.HashZero)) + .finalizeWithdrawal(chainId, 0, 0, 0, l2ToL1message, [dummyProof[0], dummyProof[1]]) ); expect(revertReason).contains("InvalidProof"); }); 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 b5d97bf7d..5aade6ad9 100644 --- a/l1-contracts/test/unit_tests/l2-upgrade.test.spec.ts +++ b/l1-contracts/test/unit_tests/l2-upgrade.test.spec.ts @@ -5,7 +5,7 @@ import * as ethers from "ethers"; import * as hardhat from "hardhat"; import { hashBytecode } from "zksync-ethers/build/utils"; -import type { AdminFacet, ExecutorFacet, GettersFacet, StateTransitionManager } from "../../typechain"; +import type { AdminFacet, ExecutorFacet, GettersFacet, ChainTypeManager } from "../../typechain"; import { AdminFacetFactory, DummyAdminFacetFactory, @@ -13,7 +13,7 @@ import { DefaultUpgradeFactory, ExecutorFacetFactory, GettersFacetFactory, - StateTransitionManagerFactory, + ChainTypeManagerFactory, } from "../../typechain"; import { Ownable2StepFactory } from "../../typechain/Ownable2StepFactory"; @@ -27,6 +27,7 @@ import { diamondCut, Action, facetCut } from "../../src.ts/diamondCut"; import type { CommitBatchInfo, StoredBatchInfo, CommitBatchInfoWithTimestamp } from "./utils"; import { + encodeCommitBatchesData, L2_BOOTLOADER_ADDRESS, L2_SYSTEM_CONTEXT_ADDRESS, SYSTEM_LOG_KEYS, @@ -39,15 +40,17 @@ import { buildCommitBatchInfoWithUpgrade, makeExecutedEqualCommitted, getBatchStoredInfo, + buildL2DARollupPubdataCommitment, + L2_TO_L1_MESSENGER, } from "./utils"; import { packSemver, unpackStringSemVer, addToProtocolVersion } from "../../scripts/utils"; -describe.only("L2 upgrade test", function () { +describe("L2 upgrade test", function () { let proxyExecutor: ExecutorFacet; let proxyAdmin: AdminFacet; let proxyGetters: GettersFacet; - let stateTransitionManager: StateTransitionManager; + let chainTypeManager: ChainTypeManager; let owner: ethers.Signer; @@ -113,14 +116,14 @@ describe.only("L2 upgrade test", function () { deployWallet ); - stateTransitionManager = StateTransitionManagerFactory.connect( + chainTypeManager = ChainTypeManagerFactory.connect( deployer.addresses.StateTransition.StateTransitionProxy, deployWallet ); await (await dummyAdminFacet.dummySetValidator(await deployWallet.getAddress())).wait(); - // do initial setChainIdUpgrade + // do initial GenesisUpgrade const upgradeTxHash = await proxyGetters.getL2SystemContractsUpgradeTxHash(); batch1InfoChainIdUpgrade = await buildCommitBatchInfoWithUpgrade( genesisStoredBatchInfo(), @@ -133,7 +136,10 @@ describe.only("L2 upgrade test", function () { ); const commitReceipt = await ( - await proxyExecutor.commitBatches(genesisStoredBatchInfo(), [batch1InfoChainIdUpgrade]) + await proxyExecutor.commitBatchesSharedBridge( + chainId, + ...encodeCommitBatchesData(genesisStoredBatchInfo(), [batch1InfoChainIdUpgrade]) + ) ).wait(); const commitment = commitReceipt.events[0].args.commitment; storedBatch1InfoChainIdUpgrade = getBatchStoredInfo(batch1InfoChainIdUpgrade, commitment); @@ -148,7 +154,10 @@ describe.only("L2 upgrade test", function () { }); const commitReceipt = await ( - await proxyExecutor.commitBatches(storedBatch1InfoChainIdUpgrade, [batch2Info]) + await proxyExecutor.commitBatchesSharedBridge( + chainId, + ...encodeCommitBatchesData(storedBatch1InfoChainIdUpgrade, [batch2Info]) + ) ).wait(); const commitment = commitReceipt.events[0].args.commitment; @@ -156,7 +165,7 @@ describe.only("L2 upgrade test", function () { expect(await proxyGetters.getL2SystemContractsUpgradeTxHash()).to.equal(ethers.constants.HashZero); await ( - await executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { + await executeUpgrade(chainId, proxyGetters, chainTypeManager, proxyAdmin, { newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 1, 0), l2ProtocolUpgradeTx: noopUpgradeTransaction, }) @@ -173,7 +182,7 @@ describe.only("L2 upgrade test", function () { const { 0: major, 1: minor, 2: patch } = await proxyGetters.getSemverProtocolVersion(); const bootloaderRevertReason = await getCallRevertReason( - executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, chainTypeManager, proxyAdmin, { newProtocolVersion: packSemver(major, minor, patch + 1), bootloaderHash: ethers.utils.hexlify(hashBytecode(ethers.utils.randomBytes(32))), l2ProtocolUpgradeTx: noopUpgradeTransaction, @@ -182,7 +191,7 @@ describe.only("L2 upgrade test", function () { expect(bootloaderRevertReason).to.contain("PatchUpgradeCantSetBootloader"); const defaultAccountRevertReason = await getCallRevertReason( - executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, chainTypeManager, proxyAdmin, { newProtocolVersion: packSemver(major, minor, patch + 1), defaultAccountHash: ethers.utils.hexlify(hashBytecode(ethers.utils.randomBytes(32))), l2ProtocolUpgradeTx: noopUpgradeTransaction, @@ -200,7 +209,7 @@ describe.only("L2 upgrade test", function () { }); const bootloaderRevertReason = await getCallRevertReason( - executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, chainTypeManager, proxyAdmin, { newProtocolVersion: packSemver(major, minor, patch + 1), l2ProtocolUpgradeTx: someTx, }) @@ -218,7 +227,7 @@ describe.only("L2 upgrade test", function () { }); const bootloaderRevertReason = await getCallRevertReason( - executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, chainTypeManager, proxyAdmin, { newProtocolVersion: newVersion, l2ProtocolUpgradeTx: someTx, }) @@ -229,14 +238,14 @@ describe.only("L2 upgrade test", function () { 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(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { + await executeUpgrade(chainId, proxyGetters, chainTypeManager, proxyAdmin, { upgradeTimestamp: ethers.BigNumber.from(timeNow), l2ProtocolUpgradeTx: noopUpgradeTransaction, }); // Upgrade that was scheduled for the future should not work now const revertReason = await getCallRevertReason( - executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, chainTypeManager, proxyAdmin, { upgradeTimestamp: ethers.BigNumber.from(timeNow).mul(2), l2ProtocolUpgradeTx: noopUpgradeTransaction, }) @@ -249,7 +258,7 @@ describe.only("L2 upgrade test", function () { txType: 255, }); const revertReason = await getCallRevertReason( - executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, chainTypeManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 3, 0), }) @@ -265,7 +274,7 @@ describe.only("L2 upgrade test", function () { }); const revertReason = await getCallRevertReason( - executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, chainTypeManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 4, 0), }) @@ -281,7 +290,7 @@ describe.only("L2 upgrade test", function () { }); const revertReason = await getCallRevertReason( - executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, chainTypeManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, newProtocolVersion: 0, }) @@ -297,7 +306,7 @@ describe.only("L2 upgrade test", function () { }); const revertReason = await getCallRevertReason( - executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, chainTypeManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 10000, 0), }) @@ -313,7 +322,7 @@ describe.only("L2 upgrade test", function () { }); const revertReason = await getCallRevertReason( - executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, chainTypeManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 4, 0), }) @@ -329,7 +338,7 @@ describe.only("L2 upgrade test", function () { }); const revertReason = await getCallRevertReason( - executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, chainTypeManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 4, 0), }) @@ -346,7 +355,7 @@ describe.only("L2 upgrade test", function () { }); const revertReason = await getCallRevertReason( - executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, chainTypeManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 4, 0), }) @@ -355,56 +364,17 @@ describe.only("L2 upgrade test", function () { expect(revertReason).contains("PubdataGreaterThanLimit"); }); - it("Should validate factory deps", async () => { - const myFactoryDep = ethers.utils.hexlify(ethers.utils.randomBytes(32)); - const wrongFactoryDepHash = ethers.utils.hexlify(hashBytecode(ethers.utils.randomBytes(32))); - const wrongTx = buildL2CanonicalTransaction({ - factoryDeps: [wrongFactoryDepHash], - nonce: 4 + initialMinorProtocolVersion, - }); - - const revertReason = await getCallRevertReason( - executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { - l2ProtocolUpgradeTx: wrongTx, - factoryDeps: [myFactoryDep], - newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 4, 0), - }) - ); - - expect(revertReason).contains("L2BytecodeHashMismatch"); - }); - - it("Should validate factory deps length match", async () => { - const myFactoryDep = ethers.utils.hexlify(ethers.utils.randomBytes(32)); - const wrongTx = buildL2CanonicalTransaction({ - factoryDeps: [], - nonce: 4 + initialMinorProtocolVersion, - }); - - const revertReason = await getCallRevertReason( - executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { - l2ProtocolUpgradeTx: wrongTx, - factoryDeps: [myFactoryDep], - newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 4, 0), - }) - ); - - expect(revertReason).contains("UnexpectedNumberOfFactoryDeps"); - }); - it("Should validate factory deps length isn't too large", async () => { - const myFactoryDep = ethers.utils.hexlify(ethers.utils.randomBytes(32)); const randomDepHash = ethers.utils.hexlify(hashBytecode(ethers.utils.randomBytes(32))); const wrongTx = buildL2CanonicalTransaction({ - factoryDeps: Array(33).fill(randomDepHash), + factoryDeps: Array(65).fill(randomDepHash), nonce: 4 + initialMinorProtocolVersion, }); const revertReason = await getCallRevertReason( - executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { + executeUpgrade(chainId, proxyGetters, chainTypeManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, - factoryDeps: Array(33).fill(myFactoryDep), newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 4, 0), }) ); @@ -439,12 +409,11 @@ describe.only("L2 upgrade test", function () { verifierParams: newerVerifierParams, executeUpgradeTx: true, l2ProtocolUpgradeTx: upgradeTx, - factoryDeps: [myFactoryDep], newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 5, 0), }; const upgradeReceipt = await ( - await executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, upgrade) + await executeUpgrade(chainId, proxyGetters, chainTypeManager, proxyAdmin, upgrade) ).wait(); const defaultUpgradeFactory = await hardhat.ethers.getContractFactory("DefaultUpgrade"); @@ -531,7 +500,7 @@ describe.only("L2 upgrade test", function () { }; const upgradeReceipt = await ( - await executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, upgrade) + await executeUpgrade(chainId, proxyGetters, chainTypeManager, proxyAdmin, upgrade) ).wait(); const defaultUpgradeFactory = await hardhat.ethers.getContractFactory("DefaultUpgrade"); @@ -609,29 +578,26 @@ describe.only("L2 upgrade test", function () { newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 5 + 1, 0), }; const revertReason = await getCallRevertReason( - executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, upgrade) - ); - await rollBackToVersion( - addToProtocolVersion(initialProtocolVersion, 5, 1).toString(), - stateTransitionManager, - upgrade + executeUpgrade(chainId, proxyGetters, chainTypeManager, proxyAdmin, upgrade) ); + await rollBackToVersion(addToProtocolVersion(initialProtocolVersion, 5, 1).toString(), chainTypeManager, upgrade); expect(revertReason).to.contains("PreviousUpgradeNotFinalized"); }); - it("Should require that the next commit batches contains an upgrade tx", async () => { - if (!l2UpgradeTxHash) { - throw new Error("Can not perform this test without l2UpgradeTxHash"); - } - - const batch3InfoNoUpgradeTx = await buildCommitBatchInfo(storedBatch2Info, { - batchNumber: 3, - }); - const revertReason = await getCallRevertReason( - proxyExecutor.commitBatches(storedBatch2Info, [batch3InfoNoUpgradeTx]) - ); - expect(revertReason).to.contains("MissingSystemLogs"); - }); + // TODO: restore test + // it("Should require that the next commit batches contains an upgrade tx", async () => { + // if (!l2UpgradeTxHash) { + // throw new Error("Can not perform this test without l2UpgradeTxHash"); + // } + + // const batch3InfoNoUpgradeTx = await buildCommitBatchInfo(storedBatch2Info, { + // batchNumber: 3, + // }); + // const revertReason = await getCallRevertReason( + // proxyExecutor.commitBatchesSharedBridge(chainId, ...encodeCommitBatchesData(storedBatch2Info, [batch3InfoNoUpgradeTx])) + // ); + // expect(revertReason).to.contains("MissingSystemLogs"); + // }); it("Should ensure any additional upgrade logs go to the priority ops hash", async () => { if (!l2UpgradeTxHash) { @@ -639,6 +605,7 @@ describe.only("L2 upgrade test", function () { } const systemLogs = createSystemLogs(); + systemLogs.push( constructL2Log( true, @@ -669,8 +636,12 @@ describe.only("L2 upgrade test", function () { }, systemLogs ); + const revertReason = await getCallRevertReason( - proxyExecutor.commitBatches(storedBatch2Info, [batch3InfoNoUpgradeTx]) + proxyExecutor.commitBatchesSharedBridge( + chainId, + ...encodeCommitBatchesData(storedBatch2Info, [batch3InfoNoUpgradeTx]) + ) ); expect(revertReason).to.contains("LogAlreadyProcessed"); }); @@ -703,7 +674,10 @@ describe.only("L2 upgrade test", function () { ); const revertReason = await getCallRevertReason( - proxyExecutor.commitBatches(storedBatch2Info, [batch3InfoTwoUpgradeTx]) + proxyExecutor.commitBatchesSharedBridge( + chainId, + ...encodeCommitBatchesData(storedBatch2Info, [batch3InfoTwoUpgradeTx]) + ) ); expect(revertReason).to.contains("TxHashMismatch"); }); @@ -735,13 +709,18 @@ describe.only("L2 upgrade test", function () { systemLogs ); - await (await proxyExecutor.commitBatches(storedBatch2Info, [batch3InfoTwoUpgradeTx])).wait(); + await ( + await proxyExecutor.commitBatchesSharedBridge( + chainId, + ...encodeCommitBatchesData(storedBatch2Info, [batch3InfoTwoUpgradeTx]) + ) + ).wait(); expect(await proxyGetters.getL2SystemContractsUpgradeBatchNumber()).to.equal(3); }); it("Should commit successfully when batch was reverted and reupgraded", async () => { - await (await proxyExecutor.revertBatches(2)).wait(); + await (await proxyExecutor.revertBatchesSharedBridge(chainId, 2)).wait(); const timestamp = (await hardhat.ethers.provider.getBlock("latest")).timestamp; const systemLogs = createSystemLogs(); systemLogs.push( @@ -768,7 +747,12 @@ describe.only("L2 upgrade test", function () { systemLogs ); - const commitReceipt = await (await proxyExecutor.commitBatches(storedBatch2Info, [batch3InfoTwoUpgradeTx])).wait(); + const commitReceipt = await ( + await proxyExecutor.commitBatchesSharedBridge( + chainId, + ...encodeCommitBatchesData(storedBatch2Info, [batch3InfoTwoUpgradeTx]) + ) + ).wait(); expect(await proxyGetters.getL2SystemContractsUpgradeBatchNumber()).to.equal(3); const commitment = commitReceipt.events[0].args.commitment; @@ -781,7 +765,7 @@ describe.only("L2 upgrade test", function () { it("Should successfully commit a sequential upgrade", async () => { expect(await proxyGetters.getL2SystemContractsUpgradeBatchNumber()).to.equal(0); await ( - await executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { + await executeUpgrade(chainId, proxyGetters, chainTypeManager, proxyAdmin, { newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 5 + 1, 0), l2ProtocolUpgradeTx: noopUpgradeTransaction, }) @@ -805,7 +789,12 @@ describe.only("L2 upgrade test", function () { systemLogs ); - const commitReceipt = await (await proxyExecutor.commitBatches(storedBatch2Info, [batch4InfoTwoUpgradeTx])).wait(); + const commitReceipt = await ( + await proxyExecutor.commitBatchesSharedBridge( + chainId, + ...encodeCommitBatchesData(storedBatch2Info, [batch4InfoTwoUpgradeTx]) + ) + ).wait(); const commitment = commitReceipt.events[0].args.commitment; const newBatchStoredInfo = getBatchStoredInfo(batch4InfoTwoUpgradeTx, commitment); @@ -820,7 +809,7 @@ describe.only("L2 upgrade test", function () { it("Should successfully commit custom upgrade", async () => { const upgradeReceipt = await ( - await executeCustomUpgrade(chainId, proxyGetters, proxyAdmin, stateTransitionManager, { + await executeCustomUpgrade(chainId, proxyGetters, proxyAdmin, chainTypeManager, { newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 6 + 1, 0), l2ProtocolUpgradeTx: noopUpgradeTransaction, }) @@ -859,7 +848,12 @@ describe.only("L2 upgrade test", function () { systemLogs ); - const commitReceipt = await (await proxyExecutor.commitBatches(storedBatch2Info, [batch5InfoTwoUpgradeTx])).wait(); + const commitReceipt = await ( + await proxyExecutor.commitBatchesSharedBridge( + chainId, + ...encodeCommitBatchesData(storedBatch2Info, [batch5InfoTwoUpgradeTx]) + ) + ).wait(); const commitment = commitReceipt.events[0].args.commitment; const newBatchStoredInfo = getBatchStoredInfo(batch5InfoTwoUpgradeTx, commitment); @@ -876,7 +870,14 @@ async function buildCommitBatchInfo( info: CommitBatchInfoWithTimestamp ): Promise { const timestamp = info.timestamp || (await hardhat.ethers.provider.getBlock("latest")).timestamp; - const systemLogs = createSystemLogs(info.priorityOperationsHash, info.numberOfLayer1Txs, prevInfo.batchHash); + const [fullPubdataCommitment, l1DAOutputHash] = buildL2DARollupPubdataCommitment(ethers.constants.HashZero, "0x"); + + const systemLogs = createSystemLogs( + info.priorityOperationsHash, + info.numberOfLayer1Txs, + prevInfo.batchHash, + l1DAOutputHash + ); systemLogs[SYSTEM_LOG_KEYS.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY] = constructL2Log( true, L2_SYSTEM_CONTEXT_ADDRESS, @@ -891,7 +892,7 @@ async function buildCommitBatchInfo( numberOfLayer1Txs: 0, priorityOperationsHash: EMPTY_STRING_KECCAK, systemLogs: ethers.utils.hexConcat(systemLogs), - pubdataCommitments: `0x${"0".repeat(130)}`, + operatorDAInput: fullPubdataCommitment, bootloaderHeapInitialContentsHash: ethers.utils.randomBytes(32), eventsQueueStateHash: ethers.utils.randomBytes(32), ...info, @@ -904,12 +905,20 @@ async function buildCommitBatchInfoWithCustomLogs( systemLogs: string[] ): Promise { const timestamp = info.timestamp || (await hardhat.ethers.provider.getBlock("latest")).timestamp; + const [fullPubdataCommitment, l1DAOutputHash] = buildL2DARollupPubdataCommitment(ethers.constants.HashZero, "0x"); + 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) ); + systemLogs[SYSTEM_LOG_KEYS.L2_DA_VALIDATOR_OUTPUT_HASH_KEY] = constructL2Log( + true, + L2_TO_L1_MESSENGER, + SYSTEM_LOG_KEYS.L2_DA_VALIDATOR_OUTPUT_HASH_KEY, + l1DAOutputHash + ); return { timestamp, @@ -918,7 +927,7 @@ async function buildCommitBatchInfoWithCustomLogs( numberOfLayer1Txs: 0, priorityOperationsHash: EMPTY_STRING_KECCAK, systemLogs: ethers.utils.hexConcat(systemLogs), - pubdataCommitments: `0x${"0".repeat(130)}`, + operatorDAInput: fullPubdataCommitment, bootloaderHeapInitialContentsHash: ethers.utils.randomBytes(32), eventsQueueStateHash: ethers.utils.randomBytes(32), ...info, @@ -947,7 +956,6 @@ function buildProposeUpgrade(proposedUpgrade: PartialProposedUpgrade): ProposedU l1ContractsUpgradeCalldata: "0x", postUpgradeCalldata: "0x", upgradeTimestamp: ethers.constants.Zero, - factoryDeps: [], newProtocolVersion, ...proposedUpgrade, }; @@ -956,7 +964,7 @@ function buildProposeUpgrade(proposedUpgrade: PartialProposedUpgrade): ProposedU async function executeUpgrade( chainId: BigNumberish, proxyGetters: GettersFacet, - stateTransitionManager: StateTransitionManager, + chainTypeManager: ChainTypeManager, proxyAdmin: AdminFacet, partialUpgrade: Partial, contractFactory?: ethers.ethers.ContractFactory @@ -982,7 +990,7 @@ async function executeUpgrade( const oldProtocolVersion = await proxyGetters.getProtocolVersion(); // This promise will be handled in the tests ( - await stateTransitionManager.setNewVersionUpgrade( + await chainTypeManager.setNewVersionUpgrade( diamondCutData, oldProtocolVersion, 999999999999, @@ -995,7 +1003,7 @@ async function executeUpgrade( // we rollback the protocolVersion ( we don't clear the upgradeHash mapping, but that is ok) async function rollBackToVersion( protocolVersion: string, - stateTransition: StateTransitionManager, + stateTransition: ChainTypeManager, partialUpgrade: Partial ) { partialUpgrade.newProtocolVersion = protocolVersion; @@ -1026,7 +1034,7 @@ async function executeCustomUpgrade( chainId: BigNumberish, proxyGetters: GettersFacet, proxyAdmin: AdminFacet, - stateTransition: StateTransitionManager, + stateTransition: ChainTypeManager, partialUpgrade: Partial, contractFactory?: ethers.ethers.ContractFactory ) { diff --git a/l1-contracts/test/unit_tests/legacy_era_test.spec.ts b/l1-contracts/test/unit_tests/legacy_era_test.spec.ts index 0f4f26bd9..5a0d6e995 100644 --- a/l1-contracts/test/unit_tests/legacy_era_test.spec.ts +++ b/l1-contracts/test/unit_tests/legacy_era_test.spec.ts @@ -1,22 +1,23 @@ import { expect } from "chai"; import { ethers, Wallet } from "ethers"; import * as hardhat from "hardhat"; +import type { BytesLike } from "ethers/lib/utils"; import { Interface } from "ethers/lib/utils"; -import type { Bridgehub, L1SharedBridge, GettersFacet, MockExecutorFacet } from "../../typechain"; +import type { Bridgehub, GettersFacet, MockExecutorFacet } from "../../typechain"; import { - L1SharedBridgeFactory, BridgehubFactory, TestnetERC20TokenFactory, MailboxFacetFactory, GettersFacetFactory, MockExecutorFacetFactory, + L1NullifierFactory, } 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 { 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"; @@ -28,6 +29,8 @@ import { L2_TO_L1_MESSENGER, getCallRevertReason, requestExecuteDirect, + DUMMY_MERKLE_PROOF_START, + DUMMY_MERKLE_PROOF_2_START, } from "./utils"; // This test is mimicking the legacy Era functions. Era's Address was known at the upgrade, so we hardcoded them in the contracts, @@ -41,17 +44,20 @@ describe("Legacy Era tests", function () { let deployer: Deployer; let l1ERC20BridgeAddress: string; let l1ERC20Bridge: IL1ERC20Bridge; - let sharedBridgeProxy: L1SharedBridge; + // let sharedBridgeProxy: L1AssetRouter; let erc20TestToken: ethers.Contract; let bridgehub: Bridgehub; let chainId = "9"; // Hardhat config ERA_CHAIN_ID const functionSignature = "0x11a2ccc1"; + let l2ToL1message: BytesLike; 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; + const dummyProof = Array(9).fill(ethers.constants.HashZero); + dummyProof[0] = DUMMY_MERKLE_PROOF_START; before(async () => { [owner, randomSigner] = await hardhat.ethers.getSigners(); @@ -85,7 +91,7 @@ describe("Legacy Era tests", function () { l1ERC20BridgeAddress = deployer.addresses.Bridges.ERC20BridgeProxy; l1ERC20Bridge = IL1ERC20BridgeFactory.connect(l1ERC20BridgeAddress, deployWallet); - sharedBridgeProxy = L1SharedBridgeFactory.connect(deployer.addresses.Bridges.SharedBridgeProxy, deployWallet); + // sharedBridgeProxy = L1AssetRouterFactory.connect(deployer.addresses.Bridges.SharedBridgeProxy, deployWallet); const tokens = getTokens(); const tokenAddress = tokens.find((token: { symbol: string }) => token.symbol == "DAI")!.address; @@ -94,11 +100,12 @@ describe("Legacy Era tests", function () { await erc20TestToken.mint(await randomSigner.getAddress(), ethers.utils.parseUnits("10000", 18)); await erc20TestToken.connect(randomSigner).approve(l1ERC20BridgeAddress, ethers.utils.parseUnits("10000", 18)); - const sharedBridgeFactory = await hardhat.ethers.getContractFactory("L1SharedBridge"); + const sharedBridgeFactory = await hardhat.ethers.getContractFactory("L1AssetRouter"); const l1WethToken = tokens.find((token: { symbol: string }) => token.symbol == "WETH")!.address; const sharedBridge = await sharedBridgeFactory.deploy( l1WethToken, deployer.addresses.Bridgehub.BridgehubProxy, + deployer.addresses.Bridges.L1NullifierProxy, deployer.chainId, deployer.addresses.StateTransition.DiamondProxy ); @@ -113,9 +120,16 @@ describe("Legacy Era tests", function () { await deployer.executeUpgrade(deployer.addresses.TransparentProxyAdmin, 0, calldata); if (deployer.verbose) { - console.log("L1SharedBridge upgrade sent for testing"); + console.log("L1AssetRouter upgrade sent for testing"); } + const setL1Erc20BridgeCalldata = L1NullifierFactory.connect( + deployer.addresses.Bridges.L1NullifierProxy, + deployWallet + ).interface.encodeFunctionData("setL1Erc20Bridge", [l1ERC20Bridge.address]); + + await deployer.executeUpgrade(deployer.addresses.Bridges.L1NullifierProxy, 0, setL1Erc20BridgeCalldata); + mailbox = MailboxFacetFactory.connect(deployer.addresses.StateTransition.DiamondProxy, deployWallet); getter = GettersFacetFactory.connect(deployer.addresses.StateTransition.DiamondProxy, deployWallet); @@ -123,18 +137,24 @@ describe("Legacy Era tests", function () { 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, - ]); + await ( + await proxyAsMockExecutor.saveL2LogsRootHash( + 1, + "0x0000000000000000000000000000000000000000000000000000000000000001" + ) + ).wait(); - const txHash = await deployer.executeUpgrade(sharedBridgeProxy.address, 0, upgradeCall); + const txExecute = await proxyAsMockExecutor.setExecutedBatches(1); + await txExecute.wait(); - expect(txHash).not.equal(ethers.constants.HashZero); + const l1Receiver = await randomSigner.getAddress(); + l2ToL1message = ethers.utils.hexConcat([ + functionSignature, + l1Receiver, + erc20TestToken.address, + ethers.constants.HashZero, + ]); }); it("Should not allow depositing zero amount", async () => { @@ -153,6 +173,7 @@ describe("Legacy Era tests", function () { l1ERC20Bridge.connect(randomSigner), bridgehub, chainId, + deployer.l1ChainId, depositorAddress, erc20TestToken.address, ethers.utils.parseUnits("800", 18), @@ -161,31 +182,27 @@ describe("Legacy Era tests", function () { }); it("Should revert on finalizing a withdrawal with wrong message length", async () => { + const mailboxFunctionSignature = "0x6c0960f9"; const revertReason = await getCallRevertReason( - l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(0, 0, 0, "0x", [ethers.constants.HashZero]) + l1ERC20Bridge + .connect(randomSigner) + .finalizeWithdrawal(1, 0, 0, mailboxFunctionSignature, [ethers.constants.HashZero]) ); - expect(revertReason).contains("MalformedMessage"); + expect(revertReason).contains("L2WithdrawalMessageWrongLength(4)"); }); 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]) + .finalizeWithdrawal(1, 0, 0, ethers.utils.randomBytes(76), [ethers.constants.HashZero]) ); expect(revertReason).contains("InvalidSelector"); }); 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, []) + l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(10, 0, 0, l2ToL1message, dummyProof) ); expect(revertReason).contains("BatchNotExecuted"); }); @@ -198,24 +215,12 @@ describe("Legacy Era tests", function () { erc20TestToken.address, ethers.constants.HashZero, ]); - const revertReason = await getCallRevertReason( - l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(0, 0, 0, l2ToL1message, []) - ); - expect(revertReason).contains("MerklePathEmpty"); + await expect(l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(0, 0, 0, l2ToL1message, [])).to.be.reverted; }); 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)) + l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(1, 0, 0, l2ToL1message, dummyProof) ); expect(revertReason).contains("InvalidProof"); }); @@ -259,6 +264,7 @@ describe("Legacy Era tests", function () { ); const MERKLE_PROOF = [ + DUMMY_MERKLE_PROOF_2_START, "0x72abee45b59e344af8a6e520241c4744aff26ed411f4c4b00f8af09adada43ba", "0xc3d03eebfd83049991ea3d3e358b6712e7aa2e2e63dc2d4b438987cec28ac8d0", "0xe3697c7f33c31a9b0f0aeb8542287d0d21e8c4cf82163d0c44c7a98aa11aa111", @@ -271,7 +277,7 @@ describe("Legacy Era tests", function () { ]; let L2_LOGS_TREE_ROOT = HASHED_LOG; - for (let i = 0; i < MERKLE_PROOF.length; i++) { + for (let i = 1; i < MERKLE_PROOF.length; i++) { L2_LOGS_TREE_ROOT = ethers.utils.keccak256(L2_LOGS_TREE_ROOT + MERKLE_PROOF[i].slice(2)); } @@ -281,7 +287,7 @@ describe("Legacy Era tests", function () { it("Reverts when proof is invalid", async () => { const invalidProof = [...MERKLE_PROOF]; - invalidProof[0] = "0x72abee45b59e344af8a6e520241c4744aff26ed411f4c4b00f8af09adada43bb"; + invalidProof[1] = "0x72abee45b59e344af8a6e520241c4744aff26ed411f4c4b00f8af09adada43bb"; const revertReason = await getCallRevertReason( mailbox.finalizeEthWithdrawal(BLOCK_NUMBER, MESSAGE_INDEX, TX_NUMBER_IN_BLOCK, MESSAGE, invalidProof) @@ -308,7 +314,6 @@ describe("Legacy Era tests", function () { 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); diff --git a/l1-contracts/test/unit_tests/mailbox_test.spec.ts b/l1-contracts/test/unit_tests/mailbox_test.spec.ts index 5bb874381..c78cc646d 100644 --- a/l1-contracts/test/unit_tests/mailbox_test.spec.ts +++ b/l1-contracts/test/unit_tests/mailbox_test.spec.ts @@ -105,7 +105,7 @@ describe("Mailbox tests", function () { ) ); - expect(revertReason).contains("MalformedBytecode"); + expect(revertReason).contains("LengthIsNotDivisibleBy32(63)"); }); it("Should not accept bytecode of even length in words", async () => { @@ -199,7 +199,10 @@ describe("Mailbox tests", function () { before(async () => { const mailboxTestContractFactory = await hardhat.ethers.getContractFactory("MailboxFacetTest"); - const mailboxTestContract = await mailboxTestContractFactory.deploy(chainId); + const mailboxTestContract = await mailboxTestContractFactory.deploy( + chainId, + await mailboxTestContractFactory.signer.getChainId() + ); testContract = MailboxFacetTestFactory.connect(mailboxTestContract.address, mailboxTestContract.signer); // Generating 10 more gas prices for test suit @@ -329,7 +332,7 @@ describe("Mailbox tests", function () { for (const [refundRecipient, externallyOwned] of refundRecipients) { const result = await sendTransaction(refundRecipient); - const [, event2] = (await result.transaction.wait()).logs; + const [, , event2] = (await result.transaction.wait()).logs; const parsedEvent = mailbox.interface.parseLog(event2); expect(parsedEvent.name).to.equal("NewPriorityRequest"); diff --git a/l1-contracts/test/unit_tests/proxy_test.spec.ts b/l1-contracts/test/unit_tests/proxy_test.spec.ts index e63abe0bb..836942760 100644 --- a/l1-contracts/test/unit_tests/proxy_test.spec.ts +++ b/l1-contracts/test/unit_tests/proxy_test.spec.ts @@ -45,7 +45,7 @@ describe("Diamond proxy tests", function () { diamondInit = DiamondInitFactory.connect(diamondInitContract.address, diamondInitContract.signer); const adminFactory = await hardhat.ethers.getContractFactory("AdminFacet"); - const adminContract = await adminFactory.deploy(); + const adminContract = await adminFactory.deploy(await owner.getChainId(), ethers.constants.AddressZero); adminFacet = AdminFacetFactory.connect(adminContract.address, adminContract.signer); const gettersFacetFactory = await hardhat.ethers.getContractFactory("GettersFacet"); @@ -53,16 +53,19 @@ describe("Diamond proxy tests", function () { gettersFacet = GettersFacetFactory.connect(gettersFacetContract.address, gettersFacetContract.signer); const mailboxFacetFactory = await hardhat.ethers.getContractFactory("MailboxFacet"); - const mailboxFacetContract = await mailboxFacetFactory.deploy(chainId); + const mailboxFacetContract = await mailboxFacetFactory.deploy(chainId, await owner.getChainId()); mailboxFacet = MailboxFacetFactory.connect(mailboxFacetContract.address, mailboxFacetContract.signer); const executorFactory = await hardhat.ethers.getContractFactory("ExecutorFacet"); - const executorContract = await executorFactory.deploy(); + const executorContract = await executorFactory.deploy(await owner.getChainId()); executorFacet = ExecutorFacetFactory.connect(executorContract.address, executorContract.signer); const diamondProxyTestFactory = await hardhat.ethers.getContractFactory("DiamondProxyTest"); const diamondProxyTestContract = await diamondProxyTestFactory.deploy(); + const dummyBridgehubFactory = await hardhat.ethers.getContractFactory("DummyBridgehub"); + const dummyBridgehub = await dummyBridgehubFactory.deploy(); + diamondProxyTest = DiamondProxyTestFactory.connect( diamondProxyTestContract.address, diamondProxyTestContract.signer @@ -84,12 +87,12 @@ describe("Diamond proxy tests", function () { const diamondInitCalldata = diamondInit.interface.encodeFunctionData("initialize", [ { chainId, - bridgehub: "0x0000000000000000000000000000000000000001", - stateTransitionManager: await owner.getAddress(), + bridgehub: dummyBridgehub.address, + chainTypeManager: await owner.getAddress(), protocolVersion: 0, admin: governorAddress, validatorTimelock: governorAddress, - baseToken: "0x0000000000000000000000000000000000000001", + baseTokenAssetId: "0x0000000000000000000000000000000000000000000000000000000000000001", baseTokenBridge: "0x0000000000000000000000000000000000000001", storedBatchZero: "0x02c775f0a90abf7a0e8043f2fdc38f0580ca9f9996a895d05a501bfeaa3b2e21", verifier: "0x0000000000000000000000000000000000000001", @@ -134,14 +137,14 @@ describe("Diamond proxy tests", function () { const proxyAsERC20 = TestnetERC20TokenFactory.connect(proxy.address, proxy.signer); const revertReason = await getCallRevertReason(proxyAsERC20.transfer(proxyAsERC20.address, 0)); - expect(revertReason).contains("InvalidSelector"); + expect(revertReason).contains("F"); }); it("check that proxy reject data with no selector", async () => { const dataWithoutSelector = "0x1122"; const revertReason = await getCallRevertReason(proxy.fallback({ data: dataWithoutSelector })); - expect(revertReason).contains("MalformedCalldata"); + expect(revertReason).contains("Ut"); }); it("should freeze the diamond storage", async () => { @@ -178,7 +181,7 @@ describe("Diamond proxy tests", function () { data: executorFacetSelector3 + "0000000000000000000000000000000000000000000000000000000000000000", }) ); - expect(revertReason).contains("FacetIsFrozen"); + expect(revertReason).contains("q1"); }); it("should be able to call an unfreezable facet when diamondStorage is frozen", async () => { diff --git a/l1-contracts/test/unit_tests/utils.ts b/l1-contracts/test/unit_tests/utils.ts index 3e21d3c81..1114ed5f4 100644 --- a/l1-contracts/test/unit_tests/utils.ts +++ b/l1-contracts/test/unit_tests/utils.ts @@ -2,7 +2,6 @@ import * as hardhat from "hardhat"; import type { BigNumberish, BytesLike } from "ethers"; import { BigNumber, ethers } from "ethers"; import type { Address } from "zksync-ethers/build/types"; -import { REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT } from "zksync-ethers/build/utils"; import type { IBridgehub } from "../../typechain/IBridgehub"; import type { IL1ERC20Bridge } from "../../typechain/IL1ERC20Bridge"; @@ -11,8 +10,16 @@ import type { IMailbox } from "../../typechain/IMailbox"; import type { ExecutorFacet } from "../../typechain"; import type { FeeParams, L2CanonicalTransaction } from "../../src.ts/utils"; -import { ADDRESS_ONE, PubdataPricingMode, EMPTY_STRING_KECCAK } from "../../src.ts/utils"; +import { + ADDRESS_ONE, + PubdataPricingMode, + EMPTY_STRING_KECCAK, + STORED_BATCH_INFO_ABI_STRING, + COMMIT_BATCH_INFO_ABI_STRING, + PRIORITY_OPS_BATCH_INFO_ABI_STRING, +} from "../../src.ts/utils"; import { packSemver } from "../../scripts/utils"; +import { keccak256, hexConcat, defaultAbiCoder } from "ethers/lib/utils"; export const CONTRACTS_GENESIS_PROTOCOL_VERSION = packSemver(0, 21, 0).toString(); // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -20,6 +27,8 @@ export const IERC20_INTERFACE = require("@openzeppelin/contracts-v4/build/contra export const DEFAULT_REVERT_REASON = "VM did not revert"; export const DEFAULT_L2_LOGS_TREE_ROOT_HASH = "0x0000000000000000000000000000000000000000000000000000000000000000"; +export const DUMMY_MERKLE_PROOF_START = "0x0101000000000000000000000000000000000000000000000000000000000000"; +export const DUMMY_MERKLE_PROOF_2_START = "0x0109000000000000000000000000000000000000000000000000000000000000"; export const L2_SYSTEM_CONTEXT_ADDRESS = "0x000000000000000000000000000000000000800b"; export const L2_BOOTLOADER_ADDRESS = "0x0000000000000000000000000000000000008001"; export const L2_KNOWN_CODE_STORAGE_ADDRESS = "0x0000000000000000000000000000000000008004"; @@ -28,7 +37,6 @@ export const L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR = "0x000000000000000000000000000 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; @@ -38,18 +46,15 @@ export function randomAddress() { export enum SYSTEM_LOG_KEYS { L2_TO_L1_LOGS_TREE_ROOT_KEY, - TOTAL_L2_TO_L1_PUBDATA_KEY, - STATE_DIFF_HASH_KEY, PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY, - PREV_BATCH_HASH_KEY, CHAINED_PRIORITY_TXN_HASH_KEY, NUMBER_OF_LAYER_1_TXS_KEY, - BLOB_ONE_HASH_KEY, - BLOB_TWO_HASH_KEY, - BLOB_THREE_HASH_KEY, - BLOB_FOUR_HASH_KEY, - BLOB_FIVE_HASH_KEY, - BLOB_SIX_HASH_KEY, + // Note, that it is important that `PREV_BATCH_HASH_KEY` has position + // `4` since it is the same as it was in the previous protocol version and + // it is the only one that is emitted before the system contracts are upgraded. + PREV_BATCH_HASH_KEY, + L2_DA_VALIDATOR_OUTPUT_HASH_KEY, + USED_L2_DA_VALIDATOR_ADDRESS_KEY, EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY, } @@ -210,24 +215,17 @@ export function constructL2Log(isService: boolean, sender: string, key: number | export function createSystemLogs( chainedPriorityTxHashKey?: BytesLike, numberOfLayer1Txs?: BigNumberish, - previousBatchHash?: BytesLike + previousBatchHash?: BytesLike, + l2DaValidatorOutputHash?: 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_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, @@ -240,27 +238,24 @@ export function createSystemLogs( 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, - PUBDATA_CHUNK_PUBLISHER_ADDRESS, - SYSTEM_LOG_KEYS.BLOB_THREE_HASH_KEY, - ethers.constants.HashZero + L2_SYSTEM_CONTEXT_ADDRESS, + SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, + previousBatchHash ? ethers.utils.hexlify(previousBatchHash) : ethers.constants.HashZero ), constructL2Log( true, - PUBDATA_CHUNK_PUBLISHER_ADDRESS, - SYSTEM_LOG_KEYS.BLOB_FOUR_HASH_KEY, - ethers.constants.HashZero + L2_TO_L1_MESSENGER, + SYSTEM_LOG_KEYS.L2_DA_VALIDATOR_OUTPUT_HASH_KEY, + l2DaValidatorOutputHash ? ethers.utils.hexlify(l2DaValidatorOutputHash) : ethers.constants.HashZero ), constructL2Log( true, - PUBDATA_CHUNK_PUBLISHER_ADDRESS, - SYSTEM_LOG_KEYS.BLOB_FIVE_HASH_KEY, - ethers.constants.HashZero + L2_TO_L1_MESSENGER, + SYSTEM_LOG_KEYS.USED_L2_DA_VALIDATOR_ADDRESS_KEY, + process.env.CONTRACTS_L2_DA_VALIDATOR_ADDR ), - constructL2Log(true, PUBDATA_CHUNK_PUBLISHER_ADDRESS, SYSTEM_LOG_KEYS.BLOB_SIX_HASH_KEY, ethers.constants.HashZero), ]; } @@ -268,12 +263,11 @@ export function createSystemLogsWithUpgrade( chainedPriorityTxHashKey?: BytesLike, numberOfLayer1Txs?: BigNumberish, upgradeTxHash?: string, - previousBatchHash?: string + previousBatchHash?: string, + l2DaValidatorOutputHash?: 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_SYSTEM_CONTEXT_ADDRESS, @@ -298,27 +292,18 @@ export function createSystemLogsWithUpgrade( 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, - PUBDATA_CHUNK_PUBLISHER_ADDRESS, - SYSTEM_LOG_KEYS.BLOB_THREE_HASH_KEY, - ethers.constants.HashZero - ), constructL2Log( true, - PUBDATA_CHUNK_PUBLISHER_ADDRESS, - SYSTEM_LOG_KEYS.BLOB_FOUR_HASH_KEY, - ethers.constants.HashZero + L2_TO_L1_MESSENGER, + SYSTEM_LOG_KEYS.L2_DA_VALIDATOR_OUTPUT_HASH_KEY, + ethers.utils.hexlify(l2DaValidatorOutputHash) || ethers.constants.HashZero ), constructL2Log( true, - PUBDATA_CHUNK_PUBLISHER_ADDRESS, - SYSTEM_LOG_KEYS.BLOB_FIVE_HASH_KEY, - ethers.constants.HashZero + L2_TO_L1_MESSENGER, + SYSTEM_LOG_KEYS.USED_L2_DA_VALIDATOR_ADDRESS_KEY, + process.env.CONTRACTS_L2_DA_VALIDATOR_ADDR || ethers.constants.AddressZero ), - constructL2Log(true, PUBDATA_CHUNK_PUBLISHER_ADDRESS, SYSTEM_LOG_KEYS.BLOB_SIX_HASH_KEY, ethers.constants.HashZero), constructL2Log( true, L2_BOOTLOADER_ADDRESS, @@ -383,13 +368,20 @@ export interface CommitBatchInfo { bootloaderHeapInitialContentsHash: BytesLike; eventsQueueStateHash: BytesLike; systemLogs: BytesLike; - pubdataCommitments: BytesLike; + operatorDAInput: BytesLike; +} + +export interface PriorityOpsBatchInfo { + leftPath: Array; + rightPath: Array; + itemHashes: Array; } export async function depositERC20( bridge: IL1ERC20Bridge, bridgehubContract: IBridgehub, chainId: string, + l1ChainId: number, l2Receiver: string, l1Token: string, amount: ethers.BigNumber, @@ -397,7 +389,7 @@ export async function depositERC20( l2RefundRecipient = ethers.constants.AddressZero ) { const gasPrice = await bridge.provider.getGasPrice(); - const gasPerPubdata = REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT; + const gasPerPubdata = REQUIRED_L2_GAS_PRICE_PER_PUBDATA; const neededValue = await bridgehubContract.l2TransactionBaseCost(chainId, gasPrice, l2GasLimit, gasPerPubdata); const ethIsBaseToken = (await bridgehubContract.baseToken(chainId)) == ADDRESS_ONE; @@ -406,7 +398,7 @@ export async function depositERC20( l1Token, amount, l2GasLimit, - REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA, l2RefundRecipient, { value: ethIsBaseToken ? neededValue : 0, @@ -421,7 +413,7 @@ export function buildL2CanonicalTransaction(tx: Partial) from: ethers.constants.AddressZero, to: ethers.constants.AddressZero, gasLimit: 5000000, - gasPerPubdataByteLimit: REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, + gasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, maxFeePerGas: 0, maxPriorityFeePerGas: 0, paymaster: 0, @@ -441,17 +433,60 @@ export type CommitBatchInfoWithTimestamp = Partial & { batchNumber: BigNumberish; }; +function padStringWithZeroes(str: string, lenBytes: number): string { + const strLen = lenBytes * 2; + if (str.length > strLen) { + throw new Error("String is too long"); + } + const paddingLength = strLen - str.length; + return str + "0".repeat(paddingLength); +} + +// Returns a pair of strings: +// - the expected pubdata commitemnt +// - the required rollup l2 da hash output +export function buildL2DARollupPubdataCommitment(stateDiffHash: string, fullPubdata: string): [string, string] { + const BLOB_SIZE_BYTES = 126_976; + const fullPubdataHash = ethers.utils.keccak256(fullPubdata); + if (ethers.utils.arrayify(fullPubdata).length > BLOB_SIZE_BYTES) { + throw new Error("Too much pubdata"); + } + const blobsProvided = 1; + + const blobLinearHash = keccak256(padStringWithZeroes(fullPubdata, BLOB_SIZE_BYTES)); + + const l1DAOutput = ethers.utils.hexConcat([ + stateDiffHash, + fullPubdataHash, + ethers.utils.hexlify(blobsProvided), + blobLinearHash, + ]); + const l1DAOutputHash = ethers.utils.keccak256(l1DAOutput); + + // After the header the 00 byte is for "calldata" mode. + // Then, there is the full pubdata. + // Then, there are 32 bytes for blob commitment. They must have at least one non-zero byte, + // so it will be the last one. + const fullPubdataCommitment = `${l1DAOutput}00${fullPubdata.slice(2)}${"0".repeat(62)}01`; + + return [fullPubdataCommitment, l1DAOutputHash]; +} + export async function buildCommitBatchInfoWithUpgrade( prevInfo: StoredBatchInfo, info: CommitBatchInfoWithTimestamp, upgradeTxHash: string ): Promise { const timestamp = info.timestamp || (await hardhat.ethers.provider.getBlock("latest")).timestamp; + + const [fullPubdataCommitment, l1DAOutputHash] = buildL2DARollupPubdataCommitment(ethers.constants.HashZero, "0x"); + const systemLogs = createSystemLogsWithUpgrade( info.priorityOperationsHash, info.numberOfLayer1Txs, upgradeTxHash, - ethers.utils.hexlify(prevInfo.batchHash) + ethers.utils.hexlify(prevInfo.batchHash), + l1DAOutputHash ); systemLogs[SYSTEM_LOG_KEYS.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY] = constructL2Log( true, @@ -467,7 +502,7 @@ export async function buildCommitBatchInfoWithUpgrade( numberOfLayer1Txs: 0, priorityOperationsHash: EMPTY_STRING_KECCAK, systemLogs: ethers.utils.hexConcat(systemLogs), - pubdataCommitments: `0x${"0".repeat(130)}`, + operatorDAInput: fullPubdataCommitment, bootloaderHeapInitialContentsHash: ethers.utils.randomBytes(32), eventsQueueStateHash: ethers.utils.randomBytes(32), ...info, @@ -483,13 +518,13 @@ export async function makeExecutedEqualCommitted( batchesToExecute = [...batchesToProve, ...batchesToExecute]; await ( - await proxyExecutor.proveBatches(prevBatchInfo, batchesToProve, { - recursiveAggregationInput: [], - serializedProof: [], - }) + await proxyExecutor.proveBatchesSharedBridge(0, ...encodeProveBatchesData(prevBatchInfo, batchesToProve, [])) ).wait(); - await (await proxyExecutor.executeBatches(batchesToExecute)).wait(); + const dummyMerkleProofs = batchesToExecute.map(() => ({ leftPath: [], rightPath: [], itemHashes: [] })); + await ( + await proxyExecutor.executeBatchesSharedBridge(0, ...encodeExecuteBatchesData(batchesToExecute, dummyMerkleProofs)) + ).wait(); } export function getBatchStoredInfo(commitInfo: CommitBatchInfo, commitment: string): StoredBatchInfo { @@ -504,3 +539,40 @@ export function getBatchStoredInfo(commitInfo: CommitBatchInfo, commitment: stri commitment: commitment, }; } + +export function encodeCommitBatchesData( + storedBatchInfo: StoredBatchInfo, + commitBatchInfos: Array +): [BigNumberish, BigNumberish, string] { + const encodedCommitDataWithoutVersion = defaultAbiCoder.encode( + [STORED_BATCH_INFO_ABI_STRING, `${COMMIT_BATCH_INFO_ABI_STRING}[]`], + [storedBatchInfo, commitBatchInfos] + ); + const commitData = hexConcat(["0x00", encodedCommitDataWithoutVersion]); + return [commitBatchInfos[0].batchNumber, commitBatchInfos[commitBatchInfos.length - 1].batchNumber, commitData]; +} + +export function encodeProveBatchesData( + prevBatch: StoredBatchInfo, + committedBatches: Array, + proof: Array +): [BigNumberish, BigNumberish, string] { + const encodedProveDataWithoutVersion = defaultAbiCoder.encode( + [STORED_BATCH_INFO_ABI_STRING, `${STORED_BATCH_INFO_ABI_STRING}[]`, "uint256[]"], + [prevBatch, committedBatches, proof] + ); + const proveData = hexConcat(["0x00", encodedProveDataWithoutVersion]); + return [committedBatches[0].batchNumber, committedBatches[committedBatches.length - 1].batchNumber, proveData]; +} + +export function encodeExecuteBatchesData( + batchesData: Array, + priorityOpsBatchInfo: Array +): [BigNumberish, BigNumberish, string] { + const encodedExecuteDataWithoutVersion = defaultAbiCoder.encode( + [`${STORED_BATCH_INFO_ABI_STRING}[]`, `${PRIORITY_OPS_BATCH_INFO_ABI_STRING}[]`], + [batchesData, priorityOpsBatchInfo] + ); + const executeData = hexConcat(["0x00", encodedExecuteDataWithoutVersion]); + return [batchesData[0].batchNumber, batchesData[batchesData.length - 1].batchNumber, executeData]; +} diff --git a/l1-contracts/test/unit_tests/validator_timelock_test.spec.ts b/l1-contracts/test/unit_tests/validator_timelock_test.spec.ts deleted file mode 100644 index af3c951ff..000000000 --- a/l1-contracts/test/unit_tests/validator_timelock_test.spec.ts +++ /dev/null @@ -1,280 +0,0 @@ -import { expect } from "chai"; -import { ethers } from "ethers"; -import * as hardhat from "hardhat"; -import type { DummyExecutor, ValidatorTimelock, DummyStateTransitionManager } from "../../typechain"; -import { DummyExecutorFactory, ValidatorTimelockFactory, DummyStateTransitionManagerFactory } from "../../typechain"; -import { getCallRevertReason } from "./utils"; - -describe("ValidatorTimelock tests", function () { - let owner: ethers.Signer; - let validator: ethers.Signer; - let randomSigner: ethers.Signer; - let validatorTimelock: ValidatorTimelock; - let dummyExecutor: DummyExecutor; - let dummyStateTransitionManager: DummyStateTransitionManager; - const chainId: number = 270; - - const MOCK_PROOF_INPUT = { - recursiveAggregationInput: [], - serializedProof: [], - }; - - function getMockCommitBatchInfo(batchNumber: number, timestamp: number = 0) { - return { - batchNumber, - timestamp, - indexRepeatedStorageChanges: 0, - newStateRoot: ethers.constants.HashZero, - numberOfLayer1Txs: 0, - priorityOperationsHash: ethers.constants.HashZero, - bootloaderHeapInitialContentsHash: ethers.utils.randomBytes(32), - eventsQueueStateHash: ethers.utils.randomBytes(32), - systemLogs: [], - pubdataCommitments: - "0x00290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - }; - } - - function getMockStoredBatchInfo(batchNumber: number, timestamp: number = 0) { - return { - batchNumber, - batchHash: ethers.constants.HashZero, - indexRepeatedStorageChanges: 0, - numberOfLayer1Txs: 0, - priorityOperationsHash: ethers.constants.HashZero, - l2LogsTreeRoot: ethers.constants.HashZero, - timestamp, - commitment: ethers.constants.HashZero, - }; - } - - before(async () => { - [owner, validator, randomSigner] = await hardhat.ethers.getSigners(); - - const dummyExecutorFactory = await hardhat.ethers.getContractFactory("DummyExecutor"); - const dummyExecutorContract = await dummyExecutorFactory.deploy(); - dummyExecutor = DummyExecutorFactory.connect(dummyExecutorContract.address, dummyExecutorContract.signer); - - const dummyStateTransitionManagerFactory = await hardhat.ethers.getContractFactory("DummyStateTransitionManager"); - const dummyStateTransitionManagerContract = await dummyStateTransitionManagerFactory.deploy(); - dummyStateTransitionManager = DummyStateTransitionManagerFactory.connect( - dummyStateTransitionManagerContract.address, - dummyStateTransitionManagerContract.signer - ); - - const setSTtx = await dummyStateTransitionManager.setHyperchain(chainId, dummyExecutor.address); - await setSTtx.wait(); - - const validatorTimelockFactory = await hardhat.ethers.getContractFactory("ValidatorTimelock"); - const validatorTimelockContract = await validatorTimelockFactory.deploy(await owner.getAddress(), 0, chainId); - 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.getHyperchain(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 () => { - const revertReason = await getCallRevertReason( - validatorTimelock.connect(randomSigner).commitBatches(getMockStoredBatchInfo(0), [getMockCommitBatchInfo(1)]) - ); - - expect(revertReason).contains("Unauthorized"); - }); - - it("Should revert if non-validator proves batches", async () => { - const revertReason = await getCallRevertReason( - validatorTimelock - .connect(randomSigner) - .proveBatches(getMockStoredBatchInfo(0), [getMockStoredBatchInfo(1)], MOCK_PROOF_INPUT) - ); - - expect(revertReason).contains("Unauthorized"); - }); - - it("Should revert if non-validator revert batches", async () => { - const revertReason = await getCallRevertReason(validatorTimelock.connect(randomSigner).revertBatches(1)); - - expect(revertReason).contains("Unauthorized"); - }); - - it("Should revert if non-validator executes batches", async () => { - const revertReason = await getCallRevertReason( - validatorTimelock.connect(randomSigner).executeBatches([getMockStoredBatchInfo(1)]) - ); - - expect(revertReason).contains("Unauthorized"); - }); - - it("Should revert if not chain governor sets validator", async () => { - const revertReason = await getCallRevertReason( - validatorTimelock.connect(randomSigner).addValidator(chainId, await randomSigner.getAddress()) - ); - - expect(revertReason).contains("Unauthorized"); - }); - - it("Should revert if non-owner sets execution delay", async () => { - const revertReason = await getCallRevertReason(validatorTimelock.connect(randomSigner).setExecutionDelay(1000)); - - expect(revertReason).equal("Ownable: caller is not the owner"); - }); - - it("Should successfully set the validator", async () => { - const validatorAddress = await validator.getAddress(); - await validatorTimelock.connect(owner).addValidator(chainId, validatorAddress); - - expect(await validatorTimelock.validators(chainId, validatorAddress)).equal(true); - }); - - it("Should successfully set the execution delay", async () => { - await validatorTimelock.connect(owner).setExecutionDelay(10); // set to 10 seconds - - expect(await validatorTimelock.executionDelay()).equal(10); - }); - - it("Should successfully commit batches", async () => { - await validatorTimelock - .connect(validator) - .commitBatchesSharedBridge(chainId, getMockStoredBatchInfo(0), [getMockCommitBatchInfo(1)]); - - expect(await dummyExecutor.getTotalBatchesCommitted()).equal(1); - }); - - it("Should successfully prove batches", async () => { - await validatorTimelock - .connect(validator) - .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).executeBatchesSharedBridge(chainId, [getMockStoredBatchInfo(1)]) - ); - - expect(revertReason).contains("TimeNotReached"); - }); - - it("Should successfully revert batches", async () => { - 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(chainId, 1)); - - await validatorTimelock - .connect(validator) - .commitBatchesSharedBridge(chainId, getMockStoredBatchInfo(0), [getMockCommitBatchInfo(1)]); - - await validatorTimelock - .connect(validator) - .proveBatchesSharedBridge(chainId, getMockStoredBatchInfo(0), [getMockStoredBatchInfo(1)], MOCK_PROOF_INPUT); - - 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).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) - .commitBatchesSharedBridge(chainId, getMockStoredBatchInfo(0), [getMockCommitBatchInfo(2)]) - ); - - // Error should be forwarded from the DummyExecutor - expect(revertReason).equal("DummyExecutor: Invalid last committed batch number"); - }); - - // Test case to check if proving batches with invalid batchNumber fails - it("Should revert if validator tries to prove batches with invalid batchNumber", async () => { - const revertReason = await getCallRevertReason( - validatorTimelock - .connect(validator) - .proveBatchesSharedBridge(chainId, getMockStoredBatchInfo(0), [getMockStoredBatchInfo(2, 1)], MOCK_PROOF_INPUT) - ); - - expect(revertReason).equal("DummyExecutor: Invalid previous batch number"); - }); - - 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).executeBatchesSharedBridge(chainId, [getMockStoredBatchInfo(2)]) - ); - - expect(revertReason).equal("DummyExecutor 2: Can"); - }); - - // These tests primarily needed to make gas statistics be more accurate. - - it("Should commit multiple batches in one transaction", async () => { - await validatorTimelock - .connect(validator) - .commitBatchesSharedBridge(chainId, getMockStoredBatchInfo(1), [ - getMockCommitBatchInfo(2), - getMockCommitBatchInfo(3), - getMockCommitBatchInfo(4), - getMockCommitBatchInfo(5), - getMockCommitBatchInfo(6), - getMockCommitBatchInfo(7), - getMockCommitBatchInfo(8), - ]); - - expect(await dummyExecutor.getTotalBatchesCommitted()).equal(8); - }); - - it("Should prove multiple batches in one transactions", async () => { - for (let i = 1; i < 8; i++) { - await validatorTimelock - .connect(validator) - .proveBatchesSharedBridge( - chainId, - getMockStoredBatchInfo(i), - [getMockStoredBatchInfo(i + 1)], - MOCK_PROOF_INPUT - ); - - expect(await dummyExecutor.getTotalBatchesVerified()).equal(i + 1); - } - }); - - it("Should execute multiple batches in multiple transactions", async () => { - await hardhat.network.provider.send("hardhat_mine", ["0x2", "0xc"]); //mine 2 batches with intervals of 12 seconds - await validatorTimelock - .connect(validator) - .executeBatchesSharedBridge(chainId, [ - getMockStoredBatchInfo(2), - getMockStoredBatchInfo(3), - getMockStoredBatchInfo(4), - getMockStoredBatchInfo(5), - getMockStoredBatchInfo(6), - getMockStoredBatchInfo(7), - getMockStoredBatchInfo(8), - ]); - - expect(await dummyExecutor.getTotalBatchesExecuted()).equal(8); - }); -}); diff --git a/l2-contracts/contracts/ForceDeployUpgrader.sol b/l2-contracts/contracts/ForceDeployUpgrader.sol index 46f9e4fd8..a7de60a2a 100644 --- a/l2-contracts/contracts/ForceDeployUpgrader.sol +++ b/l2-contracts/contracts/ForceDeployUpgrader.sol @@ -6,7 +6,7 @@ import {IContractDeployer, DEPLOYER_SYSTEM_CONTRACT} from "./L2ContractHelper.so /// @custom:security-contact security@matterlabs.dev /// @notice The contract that calls force deployment during the L2 system contract upgrade. -/// @notice It is supposed to be used as an implementation of the ComplexUpgrader. +/// @notice It is supposed to be used inherited by an implementation of the ComplexUpgrader. (but it is not useful in itself) contract ForceDeployUpgrader { /// @notice A function that performs force deploy /// @param _forceDeployments The force deployments to perform. diff --git a/l2-contracts/contracts/L2ContractHelper.sol b/l2-contracts/contracts/L2ContractHelper.sol index 842d4b4f4..bbb615327 100644 --- a/l2-contracts/contracts/L2ContractHelper.sol +++ b/l2-contracts/contracts/L2ContractHelper.sol @@ -2,6 +2,9 @@ // We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. pragma solidity ^0.8.20; +import {EfficientCall} from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/EfficientCall.sol"; +import {MalformedBytecode, BytecodeError} from "./errors/L2ContractErrors.sol"; + /** * @author Matter Labs * @custom:security-contact security@matterlabs.dev @@ -19,7 +22,7 @@ interface IL2Messenger { /// @notice Sends an arbitrary length message to L1. /// @param _message The variable length message to be sent to L1. /// @return Returns the keccak256 hashed value of the message. - function sendToL1(bytes memory _message) external returns (bytes32); + function sendToL1(bytes calldata _message) external returns (bytes32); } /** @@ -51,6 +54,19 @@ interface IContractDeployer { /// @param _bytecodeHash the bytecodehash of the new contract to be deployed /// @param _input the calldata to be sent to the constructor of the new contract function create2(bytes32 _salt, bytes32 _bytecodeHash, bytes calldata _input) external returns (address); + + /// @notice Calculates the address of a create2 contract deployment + /// @param _sender The address of the sender. + /// @param _bytecodeHash The bytecode hash of the new contract to be deployed. + /// @param _salt a unique value to create the deterministic address of the new contract + /// @param _input the calldata to be sent to the constructor of the new contract + /// @return newAddress The derived address of the account. + function getNewAddressCreate2( + address _sender, + bytes32 _bytecodeHash, + bytes32 _salt, + bytes calldata _input + ) external view returns (address newAddress); } /** @@ -65,16 +81,61 @@ interface IBaseToken { function withdrawWithMessage(address _l1Receiver, bytes memory _additionalData) external payable; } +/** + * @author Matter Labs + * @custom:security-contact security@matterlabs.dev + * @notice The interface for the Compressor contract, responsible for verifying the correctness of + * the compression of the state diffs and bytecodes. + */ +interface ICompressor { + /// @notice Verifies that the compression of state diffs has been done correctly for the {_stateDiffs} param. + /// @param _numberOfStateDiffs The number of state diffs being checked. + /// @param _enumerationIndexSize Number of bytes used to represent an enumeration index for repeated writes. + /// @param _stateDiffs Encoded full state diff structs. See the first dev comment below for encoding. + /// @param _compressedStateDiffs The compressed state diffs + function verifyCompressedStateDiffs( + uint256 _numberOfStateDiffs, + uint256 _enumerationIndexSize, + bytes calldata _stateDiffs, + bytes calldata _compressedStateDiffs + ) external returns (bytes32 stateDiffHash); +} + +/** + * @author Matter Labs + * @custom:security-contact security@matterlabs.dev + * @notice Interface for contract responsible chunking pubdata into the appropriate size for EIP-4844 blobs. + */ +interface IPubdataChunkPublisher { + /// @notice Chunks pubdata into pieces that can fit into blobs. + /// @param _pubdata The total l2 to l1 pubdata that will be sent via L1 blobs. + /// @dev Note: This is an early implementation, in the future we plan to support up to 16 blobs per l1 batch. + function chunkPubdataToBlobs(bytes calldata _pubdata) external pure returns (bytes32[] memory blobLinearHashes); +} + uint160 constant SYSTEM_CONTRACTS_OFFSET = 0x8000; // 2^15 +/// @dev The offset from which the built-in, but user space contracts are located. +uint160 constant USER_CONTRACTS_OFFSET = 0x10000; // 2^16 + address constant BOOTLOADER_ADDRESS = address(SYSTEM_CONTRACTS_OFFSET + 0x01); address constant MSG_VALUE_SYSTEM_CONTRACT = address(SYSTEM_CONTRACTS_OFFSET + 0x09); address constant DEPLOYER_SYSTEM_CONTRACT = address(SYSTEM_CONTRACTS_OFFSET + 0x06); +address constant L2_BRIDGEHUB_ADDRESS = address(USER_CONTRACTS_OFFSET + 0x02); + +uint256 constant L1_CHAIN_ID = 1; + IL2Messenger constant L2_MESSENGER = IL2Messenger(address(SYSTEM_CONTRACTS_OFFSET + 0x08)); IBaseToken constant L2_BASE_TOKEN_ADDRESS = IBaseToken(address(SYSTEM_CONTRACTS_OFFSET + 0x0a)); +ICompressor constant COMPRESSOR_CONTRACT = ICompressor(address(SYSTEM_CONTRACTS_OFFSET + 0x0e)); + +IPubdataChunkPublisher constant PUBDATA_CHUNK_PUBLISHER = IPubdataChunkPublisher( + address(SYSTEM_CONTRACTS_OFFSET + 0x11) +); + /** * @author Matter Labs * @custom:security-contact security@matterlabs.dev @@ -112,6 +173,66 @@ library L2ContractHelper { return address(uint160(uint256(data))); } + + /// @notice Validate the bytecode format and calculate its hash. + /// @param _bytecode The bytecode to hash. + /// @return hashedBytecode The 32-byte hash of the bytecode. + /// Note: The function reverts the execution if the bytecode has non expected format: + /// - Bytecode bytes length is not a multiple of 32 + /// - Bytecode bytes length is not less than 2^21 bytes (2^16 words) + /// - Bytecode words length is not odd + function hashL2BytecodeCalldata(bytes calldata _bytecode) internal view returns (bytes32 hashedBytecode) { + // Note that the length of the bytecode must be provided in 32-byte words. + if (_bytecode.length % 32 != 0) { + revert MalformedBytecode(BytecodeError.Length); + } + + uint256 bytecodeLenInWords = _bytecode.length / 32; + // bytecode length must be less than 2^16 words + if (bytecodeLenInWords >= 2 ** 16) { + revert MalformedBytecode(BytecodeError.NumberOfWords); + } + // bytecode length in words must be odd + if (bytecodeLenInWords % 2 == 0) { + revert MalformedBytecode(BytecodeError.WordsMustBeOdd); + } + hashedBytecode = + EfficientCall.sha(_bytecode) & + 0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + // Setting the version of the hash + hashedBytecode = (hashedBytecode | bytes32(uint256(1 << 248))); + // Setting the length + hashedBytecode = hashedBytecode | bytes32(bytecodeLenInWords << 224); + } + + /// @notice Validate the bytecode format and calculate its hash. + /// @param _bytecode The bytecode to hash. + /// @return hashedBytecode The 32-byte hash of the bytecode. + /// Note: The function reverts the execution if the bytecode has non expected format: + /// - Bytecode bytes length is not a multiple of 32 + /// - Bytecode bytes length is not less than 2^21 bytes (2^16 words) + /// - Bytecode words length is not odd + function hashL2Bytecode(bytes memory _bytecode) internal pure returns (bytes32 hashedBytecode) { + // Note that the length of the bytecode must be provided in 32-byte words. + if (_bytecode.length % 32 != 0) { + revert MalformedBytecode(BytecodeError.Length); + } + + uint256 bytecodeLenInWords = _bytecode.length / 32; + // bytecode length must be less than 2^16 words + if (bytecodeLenInWords >= 2 ** 16) { + revert MalformedBytecode(BytecodeError.NumberOfWords); + } + // bytecode length in words must be odd + if (bytecodeLenInWords % 2 == 0) { + revert MalformedBytecode(BytecodeError.WordsMustBeOdd); + } + hashedBytecode = sha256(_bytecode) & 0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + // Setting the version of the hash + hashedBytecode = (hashedBytecode | bytes32(uint256(1 << 248))); + // Setting the length + hashedBytecode = hashedBytecode | bytes32(bytecodeLenInWords << 224); + } } /// @notice Structure used to represent a ZKsync transaction. diff --git a/l2-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol b/l2-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol deleted file mode 100644 index 84d412245..000000000 --- a/l2-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. -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/IL1SharedBridge.sol b/l2-contracts/contracts/bridge/interfaces/IL1SharedBridge.sol deleted file mode 100644 index cc468ab87..000000000 --- a/l2-contracts/contracts/bridge/interfaces/IL1SharedBridge.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MIT -// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. -pragma solidity ^0.8.20; - -/// @title L1 Bridge contract interface -/// @author Matter Labs -/// @custom:security-contact security@matterlabs.dev -interface IL1SharedBridge { - function finalizeWithdrawal( - uint256 _chainId, - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes calldata _message, - bytes32[] calldata _merkleProof - ) external; -} diff --git a/l2-contracts/contracts/data-availability/AvailL2DAValidator.sol b/l2-contracts/contracts/data-availability/AvailL2DAValidator.sol new file mode 100644 index 000000000..6462a5e42 --- /dev/null +++ b/l2-contracts/contracts/data-availability/AvailL2DAValidator.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IL2DAValidator} from "../interfaces/IL2DAValidator.sol"; +import {StateDiffL2DAValidator} from "./StateDiffL2DAValidator.sol"; + +/// Avail L2 DA validator. It will create a commitment to the pubdata that can later be verified during settlement. +contract AvailL2DAValidator is IL2DAValidator, StateDiffL2DAValidator { + function validatePubdata( + // The rolling hash of the user L2->L1 logs. + bytes32, + // The root hash of the user L2->L1 logs. + bytes32, + // The chained hash of the L2->L1 messages + bytes32 _chainedMessagesHash, + // The chained hash of uncompressed bytecodes sent to L1 + bytes32 _chainedBytecodesHash, + // Operator data, that is related to the DA itself + bytes calldata _totalL2ToL1PubdataAndStateDiffs + ) external returns (bytes32 outputHash) { + (bytes32 stateDiffHash, bytes calldata _totalPubdata, ) = _produceStateDiffPubdata( + _chainedMessagesHash, + _chainedBytecodesHash, + _totalL2ToL1PubdataAndStateDiffs + ); + + bytes32 fullPubdataHash = keccak256(_totalPubdata); + outputHash = keccak256(abi.encodePacked(stateDiffHash, fullPubdataHash)); + } +} diff --git a/l2-contracts/contracts/data-availability/DAErrors.sol b/l2-contracts/contracts/data-availability/DAErrors.sol new file mode 100644 index 000000000..c3f032d2a --- /dev/null +++ b/l2-contracts/contracts/data-availability/DAErrors.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +enum PubdataField { + MsgHash, + Bytecode, + StateDiffCompressionVersion, + ExtraData +} + +error ReconstructionMismatch(PubdataField, bytes32 expected, bytes32 actual); diff --git a/l2-contracts/contracts/data-availability/RollupL2DAValidator.sol b/l2-contracts/contracts/data-availability/RollupL2DAValidator.sol new file mode 100644 index 000000000..febedf625 --- /dev/null +++ b/l2-contracts/contracts/data-availability/RollupL2DAValidator.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IL2DAValidator} from "../interfaces/IL2DAValidator.sol"; +import {StateDiffL2DAValidator} from "./StateDiffL2DAValidator.sol"; +import {PUBDATA_CHUNK_PUBLISHER} from "../L2ContractHelper.sol"; + +import {SafeCast} from "@openzeppelin/contracts-v4/utils/math/SafeCast.sol"; +import {EfficientCall} from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/EfficientCall.sol"; + +import {ReconstructionMismatch, PubdataField} from "./DAErrors.sol"; + +/// Rollup DA validator. It will publish data that would allow to use either calldata or blobs. +contract RollupL2DAValidator is IL2DAValidator, StateDiffL2DAValidator { + function validatePubdata( + // The rolling hash of the user L2->L1 logs. + bytes32, + // The root hash of the user L2->L1 logs. + bytes32, + // The chained hash of the L2->L1 messages + bytes32 _chainedMessagesHash, + // The chained hash of uncompressed bytecodes sent to L1 + bytes32 _chainedBytecodesHash, + // Operator data, that is related to the DA itself + bytes calldata _totalL2ToL1PubdataAndStateDiffs + ) external returns (bytes32 outputHash) { + (bytes32 stateDiffHash, bytes calldata _totalPubdata, bytes calldata leftover) = _produceStateDiffPubdata( + _chainedMessagesHash, + _chainedBytecodesHash, + _totalL2ToL1PubdataAndStateDiffs + ); + + /// Check for calldata strict format + if (leftover.length != 0) { + revert ReconstructionMismatch(PubdataField.ExtraData, bytes32(0), bytes32(leftover.length)); + } + + // The preimage under the hash `outputHash` is expected to be in the following format: + // - First 32 bytes are the hash of the uncompressed state diff. + // - Then, there is a 32-byte hash of the full pubdata. + // - Then, there is the 1-byte number of blobs published. + // - Then, there are linear hashes of the published blobs, 32 bytes each. + + bytes32[] memory blobLinearHashes = PUBDATA_CHUNK_PUBLISHER.chunkPubdataToBlobs(_totalPubdata); + + outputHash = keccak256( + abi.encodePacked( + stateDiffHash, + EfficientCall.keccak(_totalPubdata), + SafeCast.toUint8(blobLinearHashes.length), + blobLinearHashes + ) + ); + } +} diff --git a/l2-contracts/contracts/data-availability/StateDiffL2DAValidator.sol b/l2-contracts/contracts/data-availability/StateDiffL2DAValidator.sol new file mode 100644 index 000000000..249924058 --- /dev/null +++ b/l2-contracts/contracts/data-availability/StateDiffL2DAValidator.sol @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {ReconstructionMismatch, PubdataField} from "./DAErrors.sol"; +import {COMPRESSOR_CONTRACT, L2ContractHelper} from "../L2ContractHelper.sol"; + +import {EfficientCall} from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/EfficientCall.sol"; + +/// @dev The current version of state diff compression being used. +uint256 constant STATE_DIFF_COMPRESSION_VERSION_NUMBER = 1; + +uint256 constant L2_TO_L1_LOG_SERIALIZE_SIZE = 88; + +/// @dev Each state diff consists of 156 bytes of actual data and 116 bytes of unused padding, needed for circuit efficiency. +uint256 constant STATE_DIFF_ENTRY_SIZE = 272; + +/// A library that could be used by any L2 DA validator to produce standard state-diff-based +/// DA output. +abstract contract StateDiffL2DAValidator { + /// @notice Validates, that the operator provided the correct preimages for logs, messages, and bytecodes. + /// @return uncompressedStateDiffHash the hash of the uncompressed state diffs + /// @return totalL2Pubdata total pubdata that should be sent to L1. + /// @return leftoverSuffix the suffix left after pubdata and uncompressed state diffs. + /// On Era or other "vanilla" rollups it is empty, but it can be used for providing additional data by the operator, + /// e.g. DA committee signatures, etc. + function _produceStateDiffPubdata( + bytes32 _chainedMessagesHash, + bytes32 _chainedBytecodesHash, + bytes calldata _totalL2ToL1PubdataAndStateDiffs + ) + internal + virtual + returns (bytes32 uncompressedStateDiffHash, bytes calldata totalL2Pubdata, bytes calldata leftoverSuffix) + { + uint256 calldataPtr = 0; + + /// Check logs + uint32 numberOfL2ToL1Logs = uint32(bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4])); + calldataPtr += 4 + numberOfL2ToL1Logs * L2_TO_L1_LOG_SERIALIZE_SIZE; + + /// Check messages + uint32 numberOfMessages = uint32(bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4])); + calldataPtr += 4; + bytes32 reconstructedChainedMessagesHash; + for (uint256 i = 0; i < numberOfMessages; ++i) { + uint32 currentMessageLength = uint32(bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4])); + calldataPtr += 4; + bytes32 hashedMessage = EfficientCall.keccak( + _totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + currentMessageLength] + ); + calldataPtr += currentMessageLength; + reconstructedChainedMessagesHash = keccak256(abi.encode(reconstructedChainedMessagesHash, hashedMessage)); + } + if (reconstructedChainedMessagesHash != _chainedMessagesHash) { + revert ReconstructionMismatch(PubdataField.MsgHash, _chainedMessagesHash, reconstructedChainedMessagesHash); + } + + /// Check bytecodes + uint32 numberOfBytecodes = uint32(bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4])); + calldataPtr += 4; + bytes32 reconstructedChainedL1BytecodesRevealDataHash; + for (uint256 i = 0; i < numberOfBytecodes; ++i) { + uint32 currentBytecodeLength = uint32( + bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4]) + ); + calldataPtr += 4; + reconstructedChainedL1BytecodesRevealDataHash = keccak256( + abi.encode( + reconstructedChainedL1BytecodesRevealDataHash, + L2ContractHelper.hashL2BytecodeCalldata( + _totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + currentBytecodeLength] + ) + ) + ); + calldataPtr += currentBytecodeLength; + } + if (reconstructedChainedL1BytecodesRevealDataHash != _chainedBytecodesHash) { + revert ReconstructionMismatch( + PubdataField.Bytecode, + _chainedBytecodesHash, + reconstructedChainedL1BytecodesRevealDataHash + ); + } + + /// Check State Diffs + /// encoding is as follows: + /// header (1 byte version, 3 bytes total len of compressed, 1 byte enumeration index size) + /// body (`compressedStateDiffSize` bytes, 4 bytes number of state diffs, `numberOfStateDiffs` * `STATE_DIFF_ENTRY_SIZE` bytes for the uncompressed state diffs) + /// encoded state diffs: [20bytes address][32bytes key][32bytes derived key][8bytes enum index][32bytes initial value][32bytes final value] + if ( + uint256(uint8(bytes1(_totalL2ToL1PubdataAndStateDiffs[calldataPtr]))) != + STATE_DIFF_COMPRESSION_VERSION_NUMBER + ) { + revert ReconstructionMismatch( + PubdataField.StateDiffCompressionVersion, + bytes32(STATE_DIFF_COMPRESSION_VERSION_NUMBER), + bytes32(uint256(uint8(bytes1(_totalL2ToL1PubdataAndStateDiffs[calldataPtr])))) + ); + } + ++calldataPtr; + + uint24 compressedStateDiffSize = uint24(bytes3(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 3])); + calldataPtr += 3; + + uint8 enumerationIndexSize = uint8(bytes1(_totalL2ToL1PubdataAndStateDiffs[calldataPtr])); + ++calldataPtr; + + bytes calldata compressedStateDiffs = _totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + + compressedStateDiffSize]; + calldataPtr += compressedStateDiffSize; + + totalL2Pubdata = _totalL2ToL1PubdataAndStateDiffs[:calldataPtr]; + + uint32 numberOfStateDiffs = uint32(bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4])); + calldataPtr += 4; + + bytes calldata stateDiffs = _totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + + (numberOfStateDiffs * STATE_DIFF_ENTRY_SIZE)]; + + uncompressedStateDiffHash = COMPRESSOR_CONTRACT.verifyCompressedStateDiffs( + numberOfStateDiffs, + enumerationIndexSize, + stateDiffs, + compressedStateDiffs + ); + + calldataPtr += numberOfStateDiffs * STATE_DIFF_ENTRY_SIZE; + + leftoverSuffix = _totalL2ToL1PubdataAndStateDiffs[calldataPtr:]; + } +} diff --git a/l2-contracts/contracts/data-availability/ValidiumL2DAValidator.sol b/l2-contracts/contracts/data-availability/ValidiumL2DAValidator.sol new file mode 100644 index 000000000..5930131fc --- /dev/null +++ b/l2-contracts/contracts/data-availability/ValidiumL2DAValidator.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IL2DAValidator} from "../interfaces/IL2DAValidator.sol"; + +/// Rollup DA validator. It will publish data that would allow to use either calldata or blobs. +contract ValidiumL2DAValidator is IL2DAValidator { + function validatePubdata( + // The rolling hash of the user L2->L1 logs. + bytes32, + // The root hash of the user L2->L1 logs. + bytes32, + // The chained hash of the L2->L1 messages + bytes32, + // The chained hash of uncompressed bytecodes sent to L1 + bytes32, + // Operator data, that is related to the DA itself + bytes calldata + ) external returns (bytes32 outputHash) { + // Since we do not need to publish anything to L1, we can just return 0. + // Note, that Rollup validator sends the hash of uncompressed state diffs, since the + // correctness of the publish pubdata depends on it. However Validium doesn't sent anything, + // so we don't need to publish even that. + outputHash = bytes32(0); + } +} diff --git a/l2-contracts/contracts/dev-contracts/DevL2SharedBridge.sol b/l2-contracts/contracts/dev-contracts/DevL2SharedBridge.sol deleted file mode 100644 index fda1c5932..000000000 --- a/l2-contracts/contracts/dev-contracts/DevL2SharedBridge.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {L2SharedBridge} from "../bridge/L2SharedBridge.sol"; -import {L2StandardERC20} from "../bridge/L2StandardERC20.sol"; -import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; - -/// @author Matter Labs -/// @notice The implementation of the shared bridge that allows setting legacy bridge. Must only be used in local testing environments. -contract DevL2SharedBridge is L2SharedBridge { - constructor(uint256 _eraChainId) L2SharedBridge(_eraChainId) {} - - function initializeDevBridge( - address _l1SharedBridge, - address _l1Bridge, - bytes32 _l2TokenProxyBytecodeHash, - address _aliasedOwner - ) external reinitializer(2) { - l1SharedBridge = _l1SharedBridge; - - address l2StandardToken = address(new L2StandardERC20{salt: bytes32(0)}()); - l2TokenBeacon = new UpgradeableBeacon{salt: bytes32(0)}(l2StandardToken); - l2TokenProxyBytecodeHash = _l2TokenProxyBytecodeHash; - l2TokenBeacon.transferOwnership(_aliasedOwner); - - // Unfortunately the `l1Bridge` is not an internal variable in the parent contract. - // To keep the changes to the production code minimal, we'll just manually set the variable here. - assembly { - sstore(4, _l1Bridge) - } - } -} diff --git a/l2-contracts/contracts/bridge/interfaces/IL2StandardToken.sol b/l2-contracts/contracts/dev-contracts/IL2StandardToken.sol similarity index 74% rename from l2-contracts/contracts/bridge/interfaces/IL2StandardToken.sol rename to l2-contracts/contracts/dev-contracts/IL2StandardToken.sol index 6fefafa63..f42672575 100644 --- a/l2-contracts/contracts/bridge/interfaces/IL2StandardToken.sol +++ b/l2-contracts/contracts/dev-contracts/IL2StandardToken.sol @@ -1,6 +1,6 @@ +pragma solidity ^0.8.0; + // SPDX-License-Identifier: MIT -// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. -pragma solidity ^0.8.20; interface IL2StandardToken { event BridgeInitialize(address indexed l1Token, string name, string symbol, uint8 decimals); diff --git a/l2-contracts/contracts/dev-contracts/IL2WETH.sol b/l2-contracts/contracts/dev-contracts/IL2WETH.sol new file mode 100644 index 000000000..9a76c332e --- /dev/null +++ b/l2-contracts/contracts/dev-contracts/IL2WETH.sol @@ -0,0 +1,15 @@ +pragma solidity ^0.8.0; + +// SPDX-License-Identifier: MIT + +interface IL2WETH { + event Initialize(string name, string symbol, uint8 decimals); + + function deposit() external payable; + + function withdraw(uint256 _amount) external; + + function depositTo(address _to) external payable; + + function withdrawTo(address _to, uint256 _amount) external; +} diff --git a/l2-contracts/contracts/dev-contracts/L2WETH.sol b/l2-contracts/contracts/dev-contracts/L2WETH.sol new file mode 100644 index 000000000..92ca54e04 --- /dev/null +++ b/l2-contracts/contracts/dev-contracts/L2WETH.sol @@ -0,0 +1,94 @@ +pragma solidity ^0.8.0; + +// SPDX-License-Identifier: MIT + +import "@openzeppelin/contracts-upgradeable-v4/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol"; +import "./IL2WETH.sol"; +import "./IL2StandardToken.sol"; + +/// @author Matter Labs +/// @notice The canonical implementation of the WETH token. +/// @dev The idea is to replace the legacy WETH9 (which has well-known issues) with something better. +/// This implementation has the following differences from the WETH9: +/// - It does not have a silent fallback method and will revert if it's called for a method it hasn't implemented. +/// - It implements `receive` method to allow users to deposit ether directly. +/// - It implements `permit` method to allow users to sign a message instead of calling `approve`. +/// +/// 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 { + /// @dev Contract is expected to be used as proxy implementation. + constructor() { + // Disable initialization to prevent Parity hack. + _disableInitializers(); + } + + /// @notice Initializes a contract token for later use. Expected to be used in the proxy. + /// @dev Stores the L1 address of the bridge and set `name`/`symbol`/`decimals` getters. + /// @param name_ The name of the token. + /// @param symbol_ The symbol of the token. + /// Note: The decimals are hardcoded to 18, the same as on Ether. + function initialize(string memory name_, string memory symbol_) external initializer { + // Set decoded values for name and symbol. + __ERC20_init_unchained(name_, symbol_); + + // Set the name for EIP-712 signature. + __ERC20Permit_init(name_); + + emit Initialize(name_, symbol_, 18); + } + + /// @notice Function for minting tokens on L2, is implemented †o be compatible with StandardToken interface. + /// @dev Should be never called because the WETH should be collateralized with Ether. + /// Note: Use `deposit`/`depositTo` methods instead. + function bridgeMint( + address, // _to + uint256 // _amount + ) external override { + revert("bridgeMint is not implemented"); + } + + /// @dev Burn tokens from a given account and send the same amount of Ether to the bridge. + /// @param _from The account from which tokens will be burned. + /// @param _amount The amount that will be burned. + /// @notice Should be called by the bridge before withdrawing tokens to L1. + function bridgeBurn(address _from, uint256 _amount) external override { + revert("bridgeBurn is not implemented yet"); + } + + function l2Bridge() external view returns (address) { + revert("l2Bridge is not implemented yet"); + } + + function l1Address() external view returns (address) { + revert("l1Address is not implemented yet"); + } + + /// @notice Deposit Ether to mint WETH. + function deposit() external payable override { + depositTo(msg.sender); + } + + /// @notice Withdraw WETH to get Ether. + function withdraw(uint256 _amount) external override { + withdrawTo(msg.sender, _amount); + } + + /// @notice Deposit Ether to mint WETH to a given account. + function depositTo(address _to) public payable override { + _mint(_to, msg.value); + } + + /// @notice Withdraw WETH to get Ether to a given account. + function withdrawTo(address _to, uint256 _amount) public override { + _burn(msg.sender, _amount); + + (bool success, ) = _to.call{value: _amount}(""); + require(success, "Failed withdrawal"); + } + + /// @dev Fallback function to allow receiving Ether. + receive() external payable { + depositTo(msg.sender); + } +} diff --git a/l2-contracts/contracts/dev-contracts/VerifierRecursiveTest.sol b/l2-contracts/contracts/dev-contracts/VerifierRecursiveTest.sol new file mode 100644 index 000000000..2b1da08f0 --- /dev/null +++ b/l2-contracts/contracts/dev-contracts/VerifierRecursiveTest.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {Verifier} from "../verifier/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 + mstore(VK_GATE_SETUP_0_X_SLOT, 0x046e45fd137982bd0f6cf731b4650d2d520e8d675827744e1edf1308583599bb) + mstore(VK_GATE_SETUP_0_Y_SLOT, 0x177f14d16b716d4298be5e07b83add3fb61ff1ee08dce19f9a54fa8f04937f7e) + mstore(VK_GATE_SETUP_1_X_SLOT, 0x169ad5156d25b56f7b67ea6382f88b845ed5bae5b91aacfe51d8f0716afff2fb) + mstore(VK_GATE_SETUP_1_Y_SLOT, 0x2406e3268e4d5fa672142998ecf834034638a4a6f8b5e90205552c6aa1dde163) + mstore(VK_GATE_SETUP_2_X_SLOT, 0x05fd0ce0fdc590938d29c738c8dc956b32ca8e69c3babfbb49dc1c13a6d9a8d4) + mstore(VK_GATE_SETUP_2_Y_SLOT, 0x0a27dac323a04dd319d9805be879875c95063d0a55c96214cd45c913fba84460) + mstore(VK_GATE_SETUP_3_X_SLOT, 0x0d58a2a86b208a4976beb9bfd918514d448656e0ee66175eb344a4a17bba99f8) + mstore(VK_GATE_SETUP_3_Y_SLOT, 0x215fa609a1a425b84c9dc218c6cf999596d9eba6d35597ad7aaf2d077a6616ed) + mstore(VK_GATE_SETUP_4_X_SLOT, 0x1a26e6deccf91174ab13613363eb4939680828f0c6031f5039f9e6f264afa68c) + mstore(VK_GATE_SETUP_4_Y_SLOT, 0x1f5b2d6bffac1839edfd02cd0e41acc411f0ecbf6c5c4b1da0e12b68b99cb25d) + mstore(VK_GATE_SETUP_5_X_SLOT, 0x09b71be2e8a45dcbe7654cf369c4f1f2e7eab4b97869a469fb7a149d989f7226) + mstore(VK_GATE_SETUP_5_Y_SLOT, 0x197e1e2cefbd4f99558b89ca875e01fec0f14f05e5128bd869c87d6bf2f307fa) + mstore(VK_GATE_SETUP_6_X_SLOT, 0x0d7cef745da686fd44760403d452d72be504bb41b0a7f4fbe973a07558893871) + mstore(VK_GATE_SETUP_6_Y_SLOT, 0x1e9a863307cdfd3fdcf119f72279ddfda08b6f23c3672e8378dbb9d548734c29) + mstore(VK_GATE_SETUP_7_X_SLOT, 0x16af3f5d978446fdb37d84f5cf12e59f5c1088bde23f8260c0bb6792c5f78e99) + mstore(VK_GATE_SETUP_7_Y_SLOT, 0x167d3aeee50c0e53fd1e8a33941a806a34cfae5dc8b66578486e5d7207b5d546) + + // gate selectors commitments + mstore(VK_GATE_SELECTORS_0_X_SLOT, 0x1addc8e154c74bed403dc19558096ce22f1ceb2c656a2a5e85e56d2be6580ed1) + mstore(VK_GATE_SELECTORS_0_Y_SLOT, 0x1420d38f0ef206828efc36d0f5ad2b4d85fe768097f358fc671b7b3ec0239234) + mstore(VK_GATE_SELECTORS_1_X_SLOT, 0x2d5c06d0c8aa6a3520b8351f82341affcbb1a0bf27bceb9bab175e3e1d38cf47) + mstore(VK_GATE_SELECTORS_1_Y_SLOT, 0x0ff8d923a0374308147f6dd4fc513f6d0640f5df699f4836825ef460df3f8d6a) + + // permutation commitments + mstore(VK_PERMUTATION_0_X_SLOT, 0x1de8943a8f67d9f6fcbda10a1f37a82de9e9ffd0a0102ea5ce0ce6dd13b4031b) + mstore(VK_PERMUTATION_0_Y_SLOT, 0x1e04b0824853ab5d7c3412a217a1c5b88a2b4011be7e7e849485be8ed7332e41) + mstore(VK_PERMUTATION_1_X_SLOT, 0x2aa1817b9cc40b6cc7a7b3f832f3267580f9fb8e539666c00541e1a77e34a3da) + mstore(VK_PERMUTATION_1_Y_SLOT, 0x0edb3cde226205b01212fc1861303c49ef3ff66f060b5833dc9a3f661ef31dd9) + mstore(VK_PERMUTATION_2_X_SLOT, 0x13f5ae93c8eccc1455a0095302923442d4b0b3c8233d66ded99ffcf2ad641c27) + mstore(VK_PERMUTATION_2_Y_SLOT, 0x2dd42d42ccdea8b1901435ace12bc9e52c7dbbeb409d20c517ba942ed0cc7519) + mstore(VK_PERMUTATION_3_X_SLOT, 0x1a15a70a016be11af71e46e9c8a8d31ece32a7e657ae90356dd9535e6566645f) + mstore(VK_PERMUTATION_3_Y_SLOT, 0x0381d23e115521c6fc233c5346f79a6777bfa8871b7ee623d990cdcb5d8c3ce1) + + // lookup tables commitments + mstore(VK_LOOKUP_TABLE_0_X_SLOT, 0x2c513ed74d9d57a5ec901e074032741036353a2c4513422e96e7b53b302d765b) + mstore(VK_LOOKUP_TABLE_0_Y_SLOT, 0x04dd964427e430f16004076d708c0cb21e225056cc1d57418cfbd3d472981468) + mstore(VK_LOOKUP_TABLE_1_X_SLOT, 0x1ea83e5e65c6f8068f4677e2911678cf329b28259642a32db1f14b8347828aac) + mstore(VK_LOOKUP_TABLE_1_Y_SLOT, 0x1d22bc884a2da4962a893ba8de13f57aaeb785ed52c5e686994839cab8f7475d) + mstore(VK_LOOKUP_TABLE_2_X_SLOT, 0x0b2e7212d0d9cff26d0bdf3d79b2cac029a25dfeb1cafdf49e2349d7db348d89) + mstore(VK_LOOKUP_TABLE_2_Y_SLOT, 0x1301f9b252419ea240eb67fda720ca0b16d92364027285f95e9b1349490fa283) + mstore(VK_LOOKUP_TABLE_3_X_SLOT, 0x02f7b99fdfa5b418548c2d777785820e02383cfc87e7085e280a375a358153bf) + mstore(VK_LOOKUP_TABLE_3_Y_SLOT, 0x09d004fe08dc4d19c382df36fad22ef676185663543703e6a4b40203e50fd8a6) + + // lookup selector commitment + mstore(VK_LOOKUP_SELECTOR_X_SLOT, 0x1641f5d312e6f62720b1e6cd1d1be5bc0e69d10d20a12dc97ff04e2107e10ccc) + mstore(VK_LOOKUP_SELECTOR_Y_SLOT, 0x277f435d376acc3261ef9d5748e6705086214daf46d04edc80fbd657f8d9e73d) + + // table type commitment + mstore(VK_LOOKUP_TABLE_TYPE_X_SLOT, 0x1b5f1cfddd6713cf25d9e6850a1b3fe80d6ef7fe2c67248f25362d5f9b31893c) + mstore(VK_LOOKUP_TABLE_TYPE_Y_SLOT, 0x0945076de03a0d240067e5f02b8fc11eaa589df3343542576eb59fdb3ecb57e0) + + // flag for using recursive part + mstore(VK_RECURSIVE_FLAG_SLOT, 1) + } + } +} diff --git a/l2-contracts/contracts/dev-contracts/VerifierTest.sol b/l2-contracts/contracts/dev-contracts/VerifierTest.sol new file mode 100644 index 000000000..9c2db1c84 --- /dev/null +++ b/l2-contracts/contracts/dev-contracts/VerifierTest.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {Verifier} from "../verifier/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 + mstore(VK_GATE_SETUP_0_X_SLOT, 0x046e45fd137982bd0f6cf731b4650d2d520e8d675827744e1edf1308583599bb) + mstore(VK_GATE_SETUP_0_Y_SLOT, 0x177f14d16b716d4298be5e07b83add3fb61ff1ee08dce19f9a54fa8f04937f7e) + mstore(VK_GATE_SETUP_1_X_SLOT, 0x169ad5156d25b56f7b67ea6382f88b845ed5bae5b91aacfe51d8f0716afff2fb) + mstore(VK_GATE_SETUP_1_Y_SLOT, 0x2406e3268e4d5fa672142998ecf834034638a4a6f8b5e90205552c6aa1dde163) + mstore(VK_GATE_SETUP_2_X_SLOT, 0x05fd0ce0fdc590938d29c738c8dc956b32ca8e69c3babfbb49dc1c13a6d9a8d4) + mstore(VK_GATE_SETUP_2_Y_SLOT, 0x0a27dac323a04dd319d9805be879875c95063d0a55c96214cd45c913fba84460) + mstore(VK_GATE_SETUP_3_X_SLOT, 0x0d58a2a86b208a4976beb9bfd918514d448656e0ee66175eb344a4a17bba99f8) + mstore(VK_GATE_SETUP_3_Y_SLOT, 0x215fa609a1a425b84c9dc218c6cf999596d9eba6d35597ad7aaf2d077a6616ed) + mstore(VK_GATE_SETUP_4_X_SLOT, 0x1a26e6deccf91174ab13613363eb4939680828f0c6031f5039f9e6f264afa68c) + mstore(VK_GATE_SETUP_4_Y_SLOT, 0x1f5b2d6bffac1839edfd02cd0e41acc411f0ecbf6c5c4b1da0e12b68b99cb25d) + mstore(VK_GATE_SETUP_5_X_SLOT, 0x09b71be2e8a45dcbe7654cf369c4f1f2e7eab4b97869a469fb7a149d989f7226) + mstore(VK_GATE_SETUP_5_Y_SLOT, 0x197e1e2cefbd4f99558b89ca875e01fec0f14f05e5128bd869c87d6bf2f307fa) + mstore(VK_GATE_SETUP_6_X_SLOT, 0x0d7cef745da686fd44760403d452d72be504bb41b0a7f4fbe973a07558893871) + mstore(VK_GATE_SETUP_6_Y_SLOT, 0x1e9a863307cdfd3fdcf119f72279ddfda08b6f23c3672e8378dbb9d548734c29) + mstore(VK_GATE_SETUP_7_X_SLOT, 0x16af3f5d978446fdb37d84f5cf12e59f5c1088bde23f8260c0bb6792c5f78e99) + mstore(VK_GATE_SETUP_7_Y_SLOT, 0x167d3aeee50c0e53fd1e8a33941a806a34cfae5dc8b66578486e5d7207b5d546) + + // gate selectors commitments + mstore(VK_GATE_SELECTORS_0_X_SLOT, 0x1addc8e154c74bed403dc19558096ce22f1ceb2c656a2a5e85e56d2be6580ed1) + mstore(VK_GATE_SELECTORS_0_Y_SLOT, 0x1420d38f0ef206828efc36d0f5ad2b4d85fe768097f358fc671b7b3ec0239234) + mstore(VK_GATE_SELECTORS_1_X_SLOT, 0x2d5c06d0c8aa6a3520b8351f82341affcbb1a0bf27bceb9bab175e3e1d38cf47) + mstore(VK_GATE_SELECTORS_1_Y_SLOT, 0x0ff8d923a0374308147f6dd4fc513f6d0640f5df699f4836825ef460df3f8d6a) + + // permutation commitments + mstore(VK_PERMUTATION_0_X_SLOT, 0x1de8943a8f67d9f6fcbda10a1f37a82de9e9ffd0a0102ea5ce0ce6dd13b4031b) + mstore(VK_PERMUTATION_0_Y_SLOT, 0x1e04b0824853ab5d7c3412a217a1c5b88a2b4011be7e7e849485be8ed7332e41) + mstore(VK_PERMUTATION_1_X_SLOT, 0x2aa1817b9cc40b6cc7a7b3f832f3267580f9fb8e539666c00541e1a77e34a3da) + mstore(VK_PERMUTATION_1_Y_SLOT, 0x0edb3cde226205b01212fc1861303c49ef3ff66f060b5833dc9a3f661ef31dd9) + mstore(VK_PERMUTATION_2_X_SLOT, 0x13f5ae93c8eccc1455a0095302923442d4b0b3c8233d66ded99ffcf2ad641c27) + mstore(VK_PERMUTATION_2_Y_SLOT, 0x2dd42d42ccdea8b1901435ace12bc9e52c7dbbeb409d20c517ba942ed0cc7519) + mstore(VK_PERMUTATION_3_X_SLOT, 0x1a15a70a016be11af71e46e9c8a8d31ece32a7e657ae90356dd9535e6566645f) + mstore(VK_PERMUTATION_3_Y_SLOT, 0x0381d23e115521c6fc233c5346f79a6777bfa8871b7ee623d990cdcb5d8c3ce1) + + // lookup tables commitments + mstore(VK_LOOKUP_TABLE_0_X_SLOT, 0x2c513ed74d9d57a5ec901e074032741036353a2c4513422e96e7b53b302d765b) + mstore(VK_LOOKUP_TABLE_0_Y_SLOT, 0x04dd964427e430f16004076d708c0cb21e225056cc1d57418cfbd3d472981468) + mstore(VK_LOOKUP_TABLE_1_X_SLOT, 0x1ea83e5e65c6f8068f4677e2911678cf329b28259642a32db1f14b8347828aac) + mstore(VK_LOOKUP_TABLE_1_Y_SLOT, 0x1d22bc884a2da4962a893ba8de13f57aaeb785ed52c5e686994839cab8f7475d) + mstore(VK_LOOKUP_TABLE_2_X_SLOT, 0x0b2e7212d0d9cff26d0bdf3d79b2cac029a25dfeb1cafdf49e2349d7db348d89) + mstore(VK_LOOKUP_TABLE_2_Y_SLOT, 0x1301f9b252419ea240eb67fda720ca0b16d92364027285f95e9b1349490fa283) + mstore(VK_LOOKUP_TABLE_3_X_SLOT, 0x02f7b99fdfa5b418548c2d777785820e02383cfc87e7085e280a375a358153bf) + mstore(VK_LOOKUP_TABLE_3_Y_SLOT, 0x09d004fe08dc4d19c382df36fad22ef676185663543703e6a4b40203e50fd8a6) + + // lookup selector commitment + mstore(VK_LOOKUP_SELECTOR_X_SLOT, 0x1641f5d312e6f62720b1e6cd1d1be5bc0e69d10d20a12dc97ff04e2107e10ccc) + mstore(VK_LOOKUP_SELECTOR_Y_SLOT, 0x277f435d376acc3261ef9d5748e6705086214daf46d04edc80fbd657f8d9e73d) + + // table type commitment + mstore(VK_LOOKUP_TABLE_TYPE_X_SLOT, 0x1b5f1cfddd6713cf25d9e6850a1b3fe80d6ef7fe2c67248f25362d5f9b31893c) + mstore(VK_LOOKUP_TABLE_TYPE_Y_SLOT, 0x0945076de03a0d240067e5f02b8fc11eaa589df3343542576eb59fdb3ecb57e0) + + // flag for using recursive part + mstore(VK_RECURSIVE_FLAG_SLOT, 0) + } + } +} diff --git a/l2-contracts/contracts/errors/L2ContractErrors.sol b/l2-contracts/contracts/errors/L2ContractErrors.sol index c5177f3eb..89e548ea0 100644 --- a/l2-contracts/contracts/errors/L2ContractErrors.sol +++ b/l2-contracts/contracts/errors/L2ContractErrors.sol @@ -4,35 +4,33 @@ pragma solidity ^0.8.20; // 0x1f73225f error AddressMismatch(address expected, address supplied); +// 0x1294e9e1 +error AssetIdMismatch(bytes32 expected, bytes32 supplied); // 0x5e85ae73 error AmountMustBeGreaterThanZero(); -// 0xb4f54111 -error DeployFailed(); // 0x7138356f error EmptyAddress(); -// 0x1c25715b -error EmptyBytes32(); // 0x1bdfd505 error FailedToTransferTokens(address tokenContract, address to, uint256 amount); // 0x2a1b2dd8 error InsufficientAllowance(uint256 providedAllowance, uint256 requiredAmount); -// 0xcbd9d2e0 -error InvalidCaller(address); // 0xb4fa3fb3 error InvalidInput(); -// 0x0ac76f01 -error NonSequentialVersion(); // 0x8e4a23d6 error Unauthorized(address); -// 0x6e128399 -error Unimplemented(); -// 0xa4dde386 -error UnimplementedMessage(string message); // 0xff15b069 error UnsupportedPaymasterFlow(); // 0x750b219c error WithdrawFailed(); +// 0xcea34703 +error MalformedBytecode(BytecodeError); + +enum BytecodeError { + Version, + NumberOfWords, + Length, + WordsMustBeOdd, + DictionaryLength +} // 0xd92e233d error ZeroAddress(); - -string constant BRIDGE_MINT_NOT_IMPLEMENTED = "bridgeMint is not implemented! Use deposit/depositTo methods instead."; diff --git a/l2-contracts/contracts/interfaces/IL2DAValidator.sol b/l2-contracts/contracts/interfaces/IL2DAValidator.sol new file mode 100644 index 000000000..1e053307d --- /dev/null +++ b/l2-contracts/contracts/interfaces/IL2DAValidator.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +interface IL2DAValidator { + function validatePubdata( + // The rolling hash of the user L2->L1 logs. + bytes32 _chainedLogsHash, + // The root hash of the user L2->L1 logs. + bytes32 _logsRootHash, + // The chained hash of the L2->L1 messages + bytes32 _chainedMessagesHash, + // The chained hash of uncompressed bytecodes sent to L1 + bytes32 _chainedBytecodesHash, + // Same operator input + bytes calldata _totalL2ToL1PubdataAndStateDiffs + ) external returns (bytes32 outputHash); +} diff --git a/l2-contracts/contracts/verifier/TestnetVerifier.sol b/l2-contracts/contracts/verifier/TestnetVerifier.sol new file mode 100644 index 000000000..808fa70db --- /dev/null +++ b/l2-contracts/contracts/verifier/TestnetVerifier.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {Verifier} from "./Verifier.sol"; +import {IVerifier} from "./chain-interfaces/IVerifier.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice Modified version of the main verifier contract for the testnet environment +/// @dev This contract is used to skip the zkp verification for the testnet environment. +/// If the proof is not empty, it will verify it using the main verifier contract, +/// otherwise, it will skip the verification. +contract TestnetVerifier is Verifier { + constructor(uint256 _l1ChainId) { + assert(_l1ChainId != 1); + } + + /// @dev Verifies a zk-SNARK proof, skipping the verification if the proof is empty. + /// @inheritdoc IVerifier + function verify(uint256[] calldata _publicInputs, uint256[] calldata _proof) public view override returns (bool) { + // We allow skipping the zkp verification for the test(net) environment + // If the proof is not empty, verify it, otherwise, skip the verification + if (_proof.length == 0) { + return true; + } + + return super.verify(_publicInputs, _proof); + } +} diff --git a/l2-contracts/contracts/verifier/Verifier.sol b/l2-contracts/contracts/verifier/Verifier.sol new file mode 100644 index 000000000..dd4eaff55 --- /dev/null +++ b/l2-contracts/contracts/verifier/Verifier.sol @@ -0,0 +1,1711 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IVerifier} from "./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 ZK chain circuits. +/// @dev Contract was generated from a verification key with a hash of 0x14f97b81e54b35fe673d8708cc1a19e1ea5b5e348e12d31e39824ed4f42bbca2 +/// @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: +/// * Original Plonk Article: https://eprint.iacr.org/2019/953.pdf +/// * Original LookUp Article: https://eprint.iacr.org/2020/315.pdf +/// * Plonk for ZKsync v1.1: https://github.com/matter-labs/solidity_plonk_verifier/raw/recursive/bellman_vk_codegen_recursive/RecursivePlonkUnrolledForEthereum.pdf +/// The notation used in the code is the same as in the papers. +/* solhint-enable max-line-length */ +contract Verifier is IVerifier { + /*////////////////////////////////////////////////////////////// + Verification keys + //////////////////////////////////////////////////////////////*/ + + // Memory slots from 0x000 to 0x200 are reserved for intermediate computations and call to precompiles. + + uint256 internal constant VK_GATE_SETUP_0_X_SLOT = 0x200 + 0x000; + uint256 internal constant VK_GATE_SETUP_0_Y_SLOT = 0x200 + 0x020; + uint256 internal constant VK_GATE_SETUP_1_X_SLOT = 0x200 + 0x040; + uint256 internal constant VK_GATE_SETUP_1_Y_SLOT = 0x200 + 0x060; + uint256 internal constant VK_GATE_SETUP_2_X_SLOT = 0x200 + 0x080; + uint256 internal constant VK_GATE_SETUP_2_Y_SLOT = 0x200 + 0x0a0; + uint256 internal constant VK_GATE_SETUP_3_X_SLOT = 0x200 + 0x0c0; + uint256 internal constant VK_GATE_SETUP_3_Y_SLOT = 0x200 + 0x0e0; + uint256 internal constant VK_GATE_SETUP_4_X_SLOT = 0x200 + 0x100; + uint256 internal constant VK_GATE_SETUP_4_Y_SLOT = 0x200 + 0x120; + uint256 internal constant VK_GATE_SETUP_5_X_SLOT = 0x200 + 0x140; + uint256 internal constant VK_GATE_SETUP_5_Y_SLOT = 0x200 + 0x160; + uint256 internal constant VK_GATE_SETUP_6_X_SLOT = 0x200 + 0x180; + uint256 internal constant VK_GATE_SETUP_6_Y_SLOT = 0x200 + 0x1a0; + uint256 internal constant VK_GATE_SETUP_7_X_SLOT = 0x200 + 0x1c0; + uint256 internal constant VK_GATE_SETUP_7_Y_SLOT = 0x200 + 0x1e0; + + uint256 internal constant VK_GATE_SELECTORS_0_X_SLOT = 0x200 + 0x200; + uint256 internal constant VK_GATE_SELECTORS_0_Y_SLOT = 0x200 + 0x220; + uint256 internal constant VK_GATE_SELECTORS_1_X_SLOT = 0x200 + 0x240; + uint256 internal constant VK_GATE_SELECTORS_1_Y_SLOT = 0x200 + 0x260; + + uint256 internal constant VK_PERMUTATION_0_X_SLOT = 0x200 + 0x280; + uint256 internal constant VK_PERMUTATION_0_Y_SLOT = 0x200 + 0x2a0; + uint256 internal constant VK_PERMUTATION_1_X_SLOT = 0x200 + 0x2c0; + uint256 internal constant VK_PERMUTATION_1_Y_SLOT = 0x200 + 0x2e0; + uint256 internal constant VK_PERMUTATION_2_X_SLOT = 0x200 + 0x300; + uint256 internal constant VK_PERMUTATION_2_Y_SLOT = 0x200 + 0x320; + uint256 internal constant VK_PERMUTATION_3_X_SLOT = 0x200 + 0x340; + uint256 internal constant VK_PERMUTATION_3_Y_SLOT = 0x200 + 0x360; + + uint256 internal constant VK_LOOKUP_SELECTOR_X_SLOT = 0x200 + 0x380; + uint256 internal constant VK_LOOKUP_SELECTOR_Y_SLOT = 0x200 + 0x3a0; + + uint256 internal constant VK_LOOKUP_TABLE_0_X_SLOT = 0x200 + 0x3c0; + uint256 internal constant VK_LOOKUP_TABLE_0_Y_SLOT = 0x200 + 0x3e0; + uint256 internal constant VK_LOOKUP_TABLE_1_X_SLOT = 0x200 + 0x400; + uint256 internal constant VK_LOOKUP_TABLE_1_Y_SLOT = 0x200 + 0x420; + uint256 internal constant VK_LOOKUP_TABLE_2_X_SLOT = 0x200 + 0x440; + uint256 internal constant VK_LOOKUP_TABLE_2_Y_SLOT = 0x200 + 0x460; + uint256 internal constant VK_LOOKUP_TABLE_3_X_SLOT = 0x200 + 0x480; + uint256 internal constant VK_LOOKUP_TABLE_3_Y_SLOT = 0x200 + 0x4a0; + + uint256 internal constant VK_LOOKUP_TABLE_TYPE_X_SLOT = 0x200 + 0x4c0; + uint256 internal constant VK_LOOKUP_TABLE_TYPE_Y_SLOT = 0x200 + 0x4e0; + + uint256 internal constant VK_RECURSIVE_FLAG_SLOT = 0x200 + 0x500; + + /*////////////////////////////////////////////////////////////// + Proof + //////////////////////////////////////////////////////////////*/ + + uint256 internal constant PROOF_PUBLIC_INPUT = 0x200 + 0x520 + 0x000; + + uint256 internal constant PROOF_STATE_POLYS_0_X_SLOT = 0x200 + 0x520 + 0x020; + uint256 internal constant PROOF_STATE_POLYS_0_Y_SLOT = 0x200 + 0x520 + 0x040; + uint256 internal constant PROOF_STATE_POLYS_1_X_SLOT = 0x200 + 0x520 + 0x060; + uint256 internal constant PROOF_STATE_POLYS_1_Y_SLOT = 0x200 + 0x520 + 0x080; + uint256 internal constant PROOF_STATE_POLYS_2_X_SLOT = 0x200 + 0x520 + 0x0a0; + uint256 internal constant PROOF_STATE_POLYS_2_Y_SLOT = 0x200 + 0x520 + 0x0c0; + uint256 internal constant PROOF_STATE_POLYS_3_X_SLOT = 0x200 + 0x520 + 0x0e0; + uint256 internal constant PROOF_STATE_POLYS_3_Y_SLOT = 0x200 + 0x520 + 0x100; + + uint256 internal constant PROOF_COPY_PERMUTATION_GRAND_PRODUCT_X_SLOT = 0x200 + 0x520 + 0x120; + uint256 internal constant PROOF_COPY_PERMUTATION_GRAND_PRODUCT_Y_SLOT = 0x200 + 0x520 + 0x140; + + uint256 internal constant PROOF_LOOKUP_S_POLY_X_SLOT = 0x200 + 0x520 + 0x160; + uint256 internal constant PROOF_LOOKUP_S_POLY_Y_SLOT = 0x200 + 0x520 + 0x180; + + uint256 internal constant PROOF_LOOKUP_GRAND_PRODUCT_X_SLOT = 0x200 + 0x520 + 0x1a0; + uint256 internal constant PROOF_LOOKUP_GRAND_PRODUCT_Y_SLOT = 0x200 + 0x520 + 0x1c0; + + uint256 internal constant PROOF_QUOTIENT_POLY_PARTS_0_X_SLOT = 0x200 + 0x520 + 0x1e0; + uint256 internal constant PROOF_QUOTIENT_POLY_PARTS_0_Y_SLOT = 0x200 + 0x520 + 0x200; + uint256 internal constant PROOF_QUOTIENT_POLY_PARTS_1_X_SLOT = 0x200 + 0x520 + 0x220; + uint256 internal constant PROOF_QUOTIENT_POLY_PARTS_1_Y_SLOT = 0x200 + 0x520 + 0x240; + uint256 internal constant PROOF_QUOTIENT_POLY_PARTS_2_X_SLOT = 0x200 + 0x520 + 0x260; + uint256 internal constant PROOF_QUOTIENT_POLY_PARTS_2_Y_SLOT = 0x200 + 0x520 + 0x280; + uint256 internal constant PROOF_QUOTIENT_POLY_PARTS_3_X_SLOT = 0x200 + 0x520 + 0x2a0; + uint256 internal constant PROOF_QUOTIENT_POLY_PARTS_3_Y_SLOT = 0x200 + 0x520 + 0x2c0; + + uint256 internal constant PROOF_STATE_POLYS_0_OPENING_AT_Z_SLOT = 0x200 + 0x520 + 0x2e0; + uint256 internal constant PROOF_STATE_POLYS_1_OPENING_AT_Z_SLOT = 0x200 + 0x520 + 0x300; + uint256 internal constant PROOF_STATE_POLYS_2_OPENING_AT_Z_SLOT = 0x200 + 0x520 + 0x320; + uint256 internal constant PROOF_STATE_POLYS_3_OPENING_AT_Z_SLOT = 0x200 + 0x520 + 0x340; + + uint256 internal constant PROOF_STATE_POLYS_3_OPENING_AT_Z_OMEGA_SLOT = 0x200 + 0x520 + 0x360; + uint256 internal constant PROOF_GATE_SELECTORS_0_OPENING_AT_Z_SLOT = 0x200 + 0x520 + 0x380; + + uint256 internal constant PROOF_COPY_PERMUTATION_POLYS_0_OPENING_AT_Z_SLOT = 0x200 + 0x520 + 0x3a0; + uint256 internal constant PROOF_COPY_PERMUTATION_POLYS_1_OPENING_AT_Z_SLOT = 0x200 + 0x520 + 0x3c0; + uint256 internal constant PROOF_COPY_PERMUTATION_POLYS_2_OPENING_AT_Z_SLOT = 0x200 + 0x520 + 0x3e0; + + uint256 internal constant PROOF_COPY_PERMUTATION_GRAND_PRODUCT_OPENING_AT_Z_OMEGA_SLOT = 0x200 + 0x520 + 0x400; + uint256 internal constant PROOF_LOOKUP_S_POLY_OPENING_AT_Z_OMEGA_SLOT = 0x200 + 0x520 + 0x420; + uint256 internal constant PROOF_LOOKUP_GRAND_PRODUCT_OPENING_AT_Z_OMEGA_SLOT = 0x200 + 0x520 + 0x440; + uint256 internal constant PROOF_LOOKUP_T_POLY_OPENING_AT_Z_SLOT = 0x200 + 0x520 + 0x460; + uint256 internal constant PROOF_LOOKUP_T_POLY_OPENING_AT_Z_OMEGA_SLOT = 0x200 + 0x520 + 0x480; + uint256 internal constant PROOF_LOOKUP_SELECTOR_POLY_OPENING_AT_Z_SLOT = 0x200 + 0x520 + 0x4a0; + uint256 internal constant PROOF_LOOKUP_TABLE_TYPE_POLY_OPENING_AT_Z_SLOT = 0x200 + 0x520 + 0x4c0; + uint256 internal constant PROOF_QUOTIENT_POLY_OPENING_AT_Z_SLOT = 0x200 + 0x520 + 0x4e0; + uint256 internal constant PROOF_LINEARISATION_POLY_OPENING_AT_Z_SLOT = 0x200 + 0x520 + 0x500; + + uint256 internal constant PROOF_OPENING_PROOF_AT_Z_X_SLOT = 0x200 + 0x520 + 0x520; + uint256 internal constant PROOF_OPENING_PROOF_AT_Z_Y_SLOT = 0x200 + 0x520 + 0x540; + uint256 internal constant PROOF_OPENING_PROOF_AT_Z_OMEGA_X_SLOT = 0x200 + 0x520 + 0x560; + uint256 internal constant PROOF_OPENING_PROOF_AT_Z_OMEGA_Y_SLOT = 0x200 + 0x520 + 0x580; + + uint256 internal constant PROOF_RECURSIVE_PART_P1_X_SLOT = 0x200 + 0x520 + 0x5a0; + uint256 internal constant PROOF_RECURSIVE_PART_P1_Y_SLOT = 0x200 + 0x520 + 0x5c0; + + uint256 internal constant PROOF_RECURSIVE_PART_P2_X_SLOT = 0x200 + 0x520 + 0x5e0; + uint256 internal constant PROOF_RECURSIVE_PART_P2_Y_SLOT = 0x200 + 0x520 + 0x600; + + /*////////////////////////////////////////////////////////////// + Transcript slot + //////////////////////////////////////////////////////////////*/ + + uint256 internal constant TRANSCRIPT_BEGIN_SLOT = 0x200 + 0x520 + 0x620 + 0x00; + uint256 internal constant TRANSCRIPT_DST_BYTE_SLOT = 0x200 + 0x520 + 0x620 + 0x03; + uint256 internal constant TRANSCRIPT_STATE_0_SLOT = 0x200 + 0x520 + 0x620 + 0x04; + uint256 internal constant TRANSCRIPT_STATE_1_SLOT = 0x200 + 0x520 + 0x620 + 0x24; + uint256 internal constant TRANSCRIPT_CHALLENGE_SLOT = 0x200 + 0x520 + 0x620 + 0x44; + + /*////////////////////////////////////////////////////////////// + Partial verifier state + //////////////////////////////////////////////////////////////*/ + + uint256 internal constant STATE_ALPHA_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x000; + uint256 internal constant STATE_BETA_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x020; + uint256 internal constant STATE_GAMMA_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x040; + uint256 internal constant STATE_POWER_OF_ALPHA_2_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x060; + uint256 internal constant STATE_POWER_OF_ALPHA_3_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x080; + uint256 internal constant STATE_POWER_OF_ALPHA_4_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x0a0; + uint256 internal constant STATE_POWER_OF_ALPHA_5_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x0c0; + uint256 internal constant STATE_POWER_OF_ALPHA_6_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x0e0; + uint256 internal constant STATE_POWER_OF_ALPHA_7_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x100; + uint256 internal constant STATE_POWER_OF_ALPHA_8_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x120; + uint256 internal constant STATE_ETA_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x140; + uint256 internal constant STATE_BETA_LOOKUP_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x160; + uint256 internal constant STATE_GAMMA_LOOKUP_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x180; + uint256 internal constant STATE_BETA_PLUS_ONE_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x1a0; + uint256 internal constant STATE_BETA_GAMMA_PLUS_GAMMA_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x1c0; + uint256 internal constant STATE_V_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x1e0; + uint256 internal constant STATE_U_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x200; + uint256 internal constant STATE_Z_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x220; + uint256 internal constant STATE_Z_MINUS_LAST_OMEGA_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x240; + uint256 internal constant STATE_L_0_AT_Z_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x260; + uint256 internal constant STATE_L_N_MINUS_ONE_AT_Z_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x280; + uint256 internal constant STATE_Z_IN_DOMAIN_SIZE = 0x200 + 0x520 + 0x620 + 0x80 + 0x2a0; + + /*////////////////////////////////////////////////////////////// + Queries + //////////////////////////////////////////////////////////////*/ + + uint256 internal constant QUERIES_BUFFER_POINT_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x2c0 + 0x00; + + uint256 internal constant QUERIES_AT_Z_0_X_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x2c0 + 0x40; + uint256 internal constant QUERIES_AT_Z_0_Y_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x2c0 + 0x60; + uint256 internal constant QUERIES_AT_Z_1_X_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x2c0 + 0x80; + uint256 internal constant QUERIES_AT_Z_1_Y_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x2c0 + 0xa0; + + uint256 internal constant QUERIES_T_POLY_AGGREGATED_X_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x2c0 + 0xc0; + uint256 internal constant QUERIES_T_POLY_AGGREGATED_Y_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x2c0 + 0xe0; + + /*////////////////////////////////////////////////////////////// + Aggregated commitment + //////////////////////////////////////////////////////////////*/ + + uint256 internal constant AGGREGATED_AT_Z_X_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x2c0 + 0x100 + 0x00; + uint256 internal constant AGGREGATED_AT_Z_Y_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x2c0 + 0x100 + 0x20; + + uint256 internal constant AGGREGATED_AT_Z_OMEGA_X_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x2c0 + 0x100 + 0x40; + uint256 internal constant AGGREGATED_AT_Z_OMEGA_Y_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x2c0 + 0x100 + 0x60; + + uint256 internal constant AGGREGATED_OPENING_AT_Z_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x2c0 + 0x100 + 0x80; + uint256 internal constant AGGREGATED_OPENING_AT_Z_OMEGA_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x2c0 + 0x100 + 0xa0; + + /*////////////////////////////////////////////////////////////// + Pairing data + //////////////////////////////////////////////////////////////*/ + + uint256 internal constant PAIRING_BUFFER_POINT_X_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x2c0 + 0x100 + 0xc0 + 0x00; + uint256 internal constant PAIRING_BUFFER_POINT_Y_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x2c0 + 0x100 + 0xc0 + 0x20; + + uint256 internal constant PAIRING_PAIR_WITH_GENERATOR_X_SLOT = + 0x200 + 0x520 + 0x620 + 0x80 + 0x2c0 + 0x100 + 0xc0 + 0x40; + uint256 internal constant PAIRING_PAIR_WITH_GENERATOR_Y_SLOT = + 0x200 + 0x520 + 0x620 + 0x80 + 0x2c0 + 0x100 + 0xc0 + 0x60; + + uint256 internal constant PAIRING_PAIR_WITH_X_X_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x2c0 + 0x100 + 0x100 + 0x80; + uint256 internal constant PAIRING_PAIR_WITH_X_Y_SLOT = 0x200 + 0x520 + 0x620 + 0x80 + 0x2c0 + 0x100 + 0x100 + 0xa0; + + /*////////////////////////////////////////////////////////////// + Slots for scalar multiplication optimizations + //////////////////////////////////////////////////////////////*/ + + uint256 internal constant COPY_PERMUTATION_FIRST_AGGREGATED_COMMITMENT_COEFF = + 0x200 + 0x520 + 0x620 + 0x80 + 0x2c0 + 0x100 + 0x100 + 0xc0; + uint256 internal constant LOOKUP_GRAND_PRODUCT_FIRST_AGGREGATED_COMMITMENT_COEFF = + 0x200 + 0x520 + 0x620 + 0x80 + 0x2c0 + 0x100 + 0x100 + 0xe0; + uint256 internal constant LOOKUP_S_FIRST_AGGREGATED_COMMITMENT_COEFF = + 0x200 + 0x520 + 0x620 + 0x80 + 0x2c0 + 0x100 + 0x100 + 0x100; + + /*////////////////////////////////////////////////////////////// + Constants + //////////////////////////////////////////////////////////////*/ + + uint256 internal constant OMEGA = 0x1951441010b2b95a6e47a6075066a50a036f5ba978c050f2821df86636c0facb; + uint256 internal constant DOMAIN_SIZE = 0x1000000; // 2^24 + uint256 internal constant Q_MOD = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + uint256 internal constant R_MOD = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + + /// @dev flip of 0xe000000000000000000000000000000000000000000000000000000000000000; + uint256 internal constant FR_MASK = 0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + + // non residues + uint256 internal constant NON_RESIDUES_0 = 0x05; + uint256 internal constant NON_RESIDUES_1 = 0x07; + uint256 internal constant NON_RESIDUES_2 = 0x0a; + + // trusted setup g2 elements + uint256 internal constant G2_ELEMENTS_0_X1 = 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2; + uint256 internal constant G2_ELEMENTS_0_X2 = 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed; + uint256 internal constant G2_ELEMENTS_0_Y1 = 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b; + uint256 internal constant G2_ELEMENTS_0_Y2 = 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa; + uint256 internal constant G2_ELEMENTS_1_X1 = 0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1; + uint256 internal constant G2_ELEMENTS_1_X2 = 0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0; + uint256 internal constant G2_ELEMENTS_1_Y1 = 0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4; + uint256 internal constant G2_ELEMENTS_1_Y2 = 0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55; + + /// @inheritdoc IVerifier + function verificationKeyHash() external pure returns (bytes32 vkHash) { + _loadVerificationKey(); + + assembly { + let start := VK_GATE_SETUP_0_X_SLOT + let end := VK_RECURSIVE_FLAG_SLOT + let length := add(sub(end, start), 0x20) + + vkHash := keccak256(start, length) + } + } + + /// @notice Load verification keys to memory in runtime. + /// @dev The constants are loaded into memory in a specific layout declared in the constants starting from + /// `VK_` prefix. + /// NOTE: Function may corrupt the memory state if some memory was used before this function was called. + /// The VK consists of commitments to setup polynomials: + /// [q_a], [q_b], [q_c], [q_d], - main gate setup commitments + /// [q_{d_next}], [q_ab], [q_ac], [q_const] / + /// [main_gate_selector], [custom_gate_selector] - gate selectors commitments + /// [sigma_0], [sigma_1], [sigma_2], [sigma_3] - permutation polynomials commitments + /// [lookup_selector] - lookup selector commitment + /// [col_0], [col_1], [col_2], [col_3] - lookup columns commitments + /// [table_type] - lookup table type commitment + function _loadVerificationKey() internal pure virtual { + assembly { + // gate setup commitments + mstore(VK_GATE_SETUP_0_X_SLOT, 0x110deb1e0863737f9a3d7b4de641a03aa00a77bc9f1a05acc9d55b76ab9fdd4d) + mstore(VK_GATE_SETUP_0_Y_SLOT, 0x2c9dc252441e9298b7f6df6335a252517b7bccb924adf537b87c5cd3383fd7a9) + mstore(VK_GATE_SETUP_1_X_SLOT, 0x04659caf7b05471ba5ba85b1ab62267aa6c456836e625f169f7119d55b9462d2) + mstore(VK_GATE_SETUP_1_Y_SLOT, 0x0ea63403692148d2ad22189a1e5420076312f4d46e62036a043a6b0b84d5b410) + mstore(VK_GATE_SETUP_2_X_SLOT, 0x0e6696d09d65fce1e42805be03fca1f14aea247281f688981f925e77d4ce2291) + mstore(VK_GATE_SETUP_2_Y_SLOT, 0x0228f6cf8fe20c1e07e5b78bf8c41d50e55975a126d22a198d1e56acd4bbb3dd) + mstore(VK_GATE_SETUP_3_X_SLOT, 0x14685dafe340b1dec5eafcd5e7faddaf24f3781ddc53309cc25d0b42c00541dd) + mstore(VK_GATE_SETUP_3_Y_SLOT, 0x0e651cff9447cb360198899b80fa23e89ec13bc94ff161729aa841d2b55ea5be) + mstore(VK_GATE_SETUP_4_X_SLOT, 0x16e9ef76cb68f2750eb0ee72382dd9911a982308d0ab10ef94dada13c382ae73) + mstore(VK_GATE_SETUP_4_Y_SLOT, 0x22e404bc91350f3bc7daad1d1025113742436983c85eac5ab7b42221a181b81e) + mstore(VK_GATE_SETUP_5_X_SLOT, 0x0d9b29613037a5025655c82b143d2b7449c98f3aea358307c8529249cc54f3b9) + mstore(VK_GATE_SETUP_5_Y_SLOT, 0x15b3c4c946ad1babfc4c03ff7c2423fd354af3a9305c499b7fb3aaebe2fee746) + mstore(VK_GATE_SETUP_6_X_SLOT, 0x2a4cb6c495dbc7201142cc773da895ae2046e790073988fb850aca6aead27b8a) + mstore(VK_GATE_SETUP_6_Y_SLOT, 0x28ef9200c3cb67da82030520d640292014f5f7c2e2909da608812e04671a3acf) + mstore(VK_GATE_SETUP_7_X_SLOT, 0x283344a1ab3e55ecfd904d0b8e9f4faea338df5a4ead2fa9a42f0e103da40abc) + mstore(VK_GATE_SETUP_7_Y_SLOT, 0x223b37b83b9687512d322993edd70e508dd80adb10bcf7321a3cc8a44c269521) + + // gate selectors commitments + mstore(VK_GATE_SELECTORS_0_X_SLOT, 0x1f67f0ba5f7e837bc680acb4e612ebd938ad35211aa6e05b96cad19e66b82d2d) + mstore(VK_GATE_SELECTORS_0_Y_SLOT, 0x2820641a84d2e8298ac2ac42bd4b912c0c37f768ecc83d3a29e7c720763d15a1) + mstore(VK_GATE_SELECTORS_1_X_SLOT, 0x0353257957562270292a17860ca8e8827703f828f440ee004848b1e23fdf9de2) + mstore(VK_GATE_SELECTORS_1_Y_SLOT, 0x305f4137fee253dff8b2bfe579038e8f25d5bd217865072af5d89fc8800ada24) + + // permutation commitments + mstore(VK_PERMUTATION_0_X_SLOT, 0x13a600154b369ff3237706d00948e465ee1c32c7a6d3e18bccd9c4a15910f2e5) + mstore(VK_PERMUTATION_0_Y_SLOT, 0x138aa24fbf4cdddc75114811b3d59040394c218ecef3eb46ef9bd646f7e53776) + mstore(VK_PERMUTATION_1_X_SLOT, 0x277fff1f80c409357e2d251d79f6e3fd2164b755ce69cfd72de5c690289df662) + mstore(VK_PERMUTATION_1_Y_SLOT, 0x25235588e28c70eea3e35531c80deac25cd9b53ea3f98993f120108bc7abf670) + mstore(VK_PERMUTATION_2_X_SLOT, 0x0990e07a9b001048b947d0e5bd6157214c7359b771f01bf52bd771ba563a900e) + mstore(VK_PERMUTATION_2_Y_SLOT, 0x05e5fb090dd40914c8606d875e301167ae3047d684a02b44d9d36f1eaf43d0b4) + mstore(VK_PERMUTATION_3_X_SLOT, 0x1d4656690b33299db5631401a282afab3e16c78ee2c9ad9efea628171dcbc6bc) + mstore(VK_PERMUTATION_3_Y_SLOT, 0x0ebda2ebe582f601f813ec1e3970d13ef1500c742a85cce9b7f190f333de03b0) + + // lookup tables commitments + mstore(VK_LOOKUP_TABLE_0_X_SLOT, 0x2c513ed74d9d57a5ec901e074032741036353a2c4513422e96e7b53b302d765b) + mstore(VK_LOOKUP_TABLE_0_Y_SLOT, 0x04dd964427e430f16004076d708c0cb21e225056cc1d57418cfbd3d472981468) + mstore(VK_LOOKUP_TABLE_1_X_SLOT, 0x1ea83e5e65c6f8068f4677e2911678cf329b28259642a32db1f14b8347828aac) + mstore(VK_LOOKUP_TABLE_1_Y_SLOT, 0x1d22bc884a2da4962a893ba8de13f57aaeb785ed52c5e686994839cab8f7475d) + mstore(VK_LOOKUP_TABLE_2_X_SLOT, 0x0b2e7212d0d9cff26d0bdf3d79b2cac029a25dfeb1cafdf49e2349d7db348d89) + mstore(VK_LOOKUP_TABLE_2_Y_SLOT, 0x1301f9b252419ea240eb67fda720ca0b16d92364027285f95e9b1349490fa283) + mstore(VK_LOOKUP_TABLE_3_X_SLOT, 0x02f7b99fdfa5b418548c2d777785820e02383cfc87e7085e280a375a358153bf) + mstore(VK_LOOKUP_TABLE_3_Y_SLOT, 0x09d004fe08dc4d19c382df36fad22ef676185663543703e6a4b40203e50fd8a6) + + // lookup selector commitment + mstore(VK_LOOKUP_SELECTOR_X_SLOT, 0x2f4d347c7fb61daaadfff881e24f4b5dcfdc0d70a95bcb148168b90ef93e0007) + mstore(VK_LOOKUP_SELECTOR_Y_SLOT, 0x2322632465ba8e28cd0a4befd813ea85a972f4f6fa8e8603cf5d062dbcb14065) + + // table type commitment + mstore(VK_LOOKUP_TABLE_TYPE_X_SLOT, 0x1e3c9fc98c118e4bc34f1f93d214a5d86898e980c40d8e2c180c6ada377a7467) + mstore(VK_LOOKUP_TABLE_TYPE_Y_SLOT, 0x2260a13535c35a15c173f5e5797d4b675b55d164a9995bfb7624971324bd84a8) + + // flag for using recursive part + mstore(VK_RECURSIVE_FLAG_SLOT, 0) + } + } + + /// @inheritdoc IVerifier + function verify( + uint256[] calldata, // _publicInputs + uint256[] calldata // _proof + ) public view virtual returns (bool) { + // No memory was accessed yet, so keys can be loaded into the right place and not corrupt any other memory. + _loadVerificationKey(); + + // Beginning of the big inline assembly block that makes all the verification work. + // Note: We use the custom memory layout, so the return value should be returned from the assembly, not + // Solidity code. + assembly { + /*////////////////////////////////////////////////////////////// + Utils + //////////////////////////////////////////////////////////////*/ + + /// @dev Reverts execution with a provided revert reason. + /// @param len The byte length of the error message string, which is expected to be no more than 32. + /// @param reason The 1-word revert reason string, encoded in ASCII. + function revertWithMessage(len, reason) { + // "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)"))) + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + // Data offset + mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020) + // Length of revert string + mstore(0x24, len) + // Revert reason + mstore(0x44, reason) + // Revert + revert(0x00, 0x64) + } + + /// @dev Performs modular exponentiation using the formula (value ^ power) mod R_MOD. + function modexp(value, power) -> res { + res := 1 + for { + + } gt(power, 0) { + + } { + if mod(power, 2) { + res := mulmod(res, value, R_MOD) + } + value := mulmod(value, value, R_MOD) + power := shr(1, power) + } + } + + /// @dev Performs a point multiplication operation and stores the result in a given memory destination. + function pointMulIntoDest(point, s, dest) { + mstore(0x00, mload(point)) + mstore(0x20, mload(add(point, 0x20))) + mstore(0x40, s) + if iszero(staticcall(gas(), 7, 0, 0x60, dest, 0x40)) { + revertWithMessage(30, "pointMulIntoDest: ecMul failed") + } + } + + /// @dev Performs a point addition operation and stores the result in a given memory destination. + function pointAddIntoDest(p1, p2, dest) { + mstore(0x00, mload(p1)) + mstore(0x20, mload(add(p1, 0x20))) + mstore(0x40, mload(p2)) + mstore(0x60, mload(add(p2, 0x20))) + if iszero(staticcall(gas(), 6, 0x00, 0x80, dest, 0x40)) { + revertWithMessage(30, "pointAddIntoDest: ecAdd failed") + } + } + + /// @dev Performs a point subtraction operation and updates the first point with the result. + function pointSubAssign(p1, p2) { + mstore(0x00, mload(p1)) + mstore(0x20, mload(add(p1, 0x20))) + mstore(0x40, mload(p2)) + mstore(0x60, sub(Q_MOD, mload(add(p2, 0x20)))) + if iszero(staticcall(gas(), 6, 0x00, 0x80, p1, 0x40)) { + revertWithMessage(28, "pointSubAssign: ecAdd failed") + } + } + + /// @dev Performs a point addition operation and updates the first point with the result. + function pointAddAssign(p1, p2) { + mstore(0x00, mload(p1)) + mstore(0x20, mload(add(p1, 0x20))) + mstore(0x40, mload(p2)) + mstore(0x60, mload(add(p2, 0x20))) + if iszero(staticcall(gas(), 6, 0x00, 0x80, p1, 0x40)) { + revertWithMessage(28, "pointAddAssign: ecAdd failed") + } + } + + /// @dev Performs a point multiplication operation and then adds the result to the destination point. + function pointMulAndAddIntoDest(point, s, dest) { + mstore(0x00, mload(point)) + mstore(0x20, mload(add(point, 0x20))) + mstore(0x40, s) + let success := staticcall(gas(), 7, 0, 0x60, 0, 0x40) + + mstore(0x40, mload(dest)) + mstore(0x60, mload(add(dest, 0x20))) + success := and(success, staticcall(gas(), 6, 0x00, 0x80, dest, 0x40)) + + if iszero(success) { + revertWithMessage(22, "pointMulAndAddIntoDest") + } + } + + /// @dev Negates an elliptic curve point by changing the sign of the y-coordinate. + function pointNegate(point) { + let pY := mload(add(point, 0x20)) + switch pY + case 0 { + if mload(point) { + revertWithMessage(26, "pointNegate: invalid point") + } + } + default { + mstore(add(point, 0x20), sub(Q_MOD, pY)) + } + } + + /*////////////////////////////////////////////////////////////// + Transcript helpers + //////////////////////////////////////////////////////////////*/ + + /// @dev Updates the transcript state with a new challenge value. + function updateTranscript(value) { + mstore8(TRANSCRIPT_DST_BYTE_SLOT, 0x00) + mstore(TRANSCRIPT_CHALLENGE_SLOT, value) + let newState0 := keccak256(TRANSCRIPT_BEGIN_SLOT, 0x64) + mstore8(TRANSCRIPT_DST_BYTE_SLOT, 0x01) + let newState1 := keccak256(TRANSCRIPT_BEGIN_SLOT, 0x64) + mstore(TRANSCRIPT_STATE_1_SLOT, newState1) + mstore(TRANSCRIPT_STATE_0_SLOT, newState0) + } + + /// @dev Retrieves a transcript challenge. + function getTranscriptChallenge(numberOfChallenge) -> challenge { + mstore8(TRANSCRIPT_DST_BYTE_SLOT, 0x02) + mstore(TRANSCRIPT_CHALLENGE_SLOT, shl(224, numberOfChallenge)) + challenge := and(keccak256(TRANSCRIPT_BEGIN_SLOT, 0x48), FR_MASK) + } + + /*////////////////////////////////////////////////////////////// + 1. Load Proof + //////////////////////////////////////////////////////////////*/ + + /// @dev This function loads a zk-SNARK proof, ensures it's properly formatted, and stores it in memory. + /// It ensures the number of inputs and the elliptic curve point's validity. + /// Note: It does NOT reject inputs that exceed these module sizes, but rather wraps them within the + /// module bounds. + /// The proof consists of: + /// 1. Public input: (1 field element from F_r) + /// + /// 2. Polynomial commitments (elliptic curve points over F_q): + /// [a], [b], [c], [d] - state polynomials commitments + /// [z_perm] - copy-permutation grand product commitment + /// [s] - polynomial for lookup argument commitment + /// [z_lookup] - lookup grand product commitment + /// [t_0], [t_1], [t_2], [t_3] - quotient polynomial parts commitments + /// [W], [W'] - proof openings commitments + /// + /// 3. Polynomial evaluations at z and z*omega (field elements from F_r): + /// t(z) - quotient polynomial opening + /// a(z), b(z), c(z), d(z), d(z*omega) - state polynomials openings + /// main_gate_selector(z) - main gate selector opening + /// sigma_0(z), sigma_1(z), sigma_2(z) - permutation polynomials openings + /// z_perm(z*omega) - copy-permutation grand product opening + /// z_lookup(z*omega) - lookup grand product opening + /// lookup_selector(z) - lookup selector opening + /// s(x*omega), t(z*omega), table_type(z) - lookup argument polynomial openings + /// r(z) - linearisation polynomial opening + /// + /// 4. Recursive proof (0 or 2 elliptic curve points over F_q) + function loadProof() { + // 1. Load public input + let offset := calldataload(0x04) + let publicInputLengthInWords := calldataload(add(offset, 0x04)) + let isValid := eq(publicInputLengthInWords, 1) // We expect only one public input + mstore(PROOF_PUBLIC_INPUT, and(calldataload(add(offset, 0x24)), FR_MASK)) + + // 2. Load the proof (except for the recursive part) + offset := calldataload(0x24) + let proofLengthInWords := calldataload(add(offset, 0x04)) + + // Check the proof length depending on whether the recursive part is present + let expectedProofLength + switch mload(VK_RECURSIVE_FLAG_SLOT) + case 0 { + expectedProofLength := 44 + } + default { + expectedProofLength := 48 + } + isValid := and(eq(proofLengthInWords, expectedProofLength), isValid) + + // PROOF_STATE_POLYS_0 + { + let x := mod(calldataload(add(offset, 0x024)), Q_MOD) + let y := mod(calldataload(add(offset, 0x044)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + isValid := and(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD)), isValid) + mstore(PROOF_STATE_POLYS_0_X_SLOT, x) + mstore(PROOF_STATE_POLYS_0_Y_SLOT, y) + } + // PROOF_STATE_POLYS_1 + { + let x := mod(calldataload(add(offset, 0x064)), Q_MOD) + let y := mod(calldataload(add(offset, 0x084)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + isValid := and(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD)), isValid) + mstore(PROOF_STATE_POLYS_1_X_SLOT, x) + mstore(PROOF_STATE_POLYS_1_Y_SLOT, y) + } + // PROOF_STATE_POLYS_2 + { + let x := mod(calldataload(add(offset, 0x0a4)), Q_MOD) + let y := mod(calldataload(add(offset, 0x0c4)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + isValid := and(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD)), isValid) + mstore(PROOF_STATE_POLYS_2_X_SLOT, x) + mstore(PROOF_STATE_POLYS_2_Y_SLOT, y) + } + // PROOF_STATE_POLYS_3 + { + let x := mod(calldataload(add(offset, 0x0e4)), Q_MOD) + let y := mod(calldataload(add(offset, 0x104)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + isValid := and(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD)), isValid) + mstore(PROOF_STATE_POLYS_3_X_SLOT, x) + mstore(PROOF_STATE_POLYS_3_Y_SLOT, y) + } + // PROOF_COPY_PERMUTATION_GRAND_PRODUCT + { + let x := mod(calldataload(add(offset, 0x124)), Q_MOD) + let y := mod(calldataload(add(offset, 0x144)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + isValid := and(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD)), isValid) + mstore(PROOF_COPY_PERMUTATION_GRAND_PRODUCT_X_SLOT, x) + mstore(PROOF_COPY_PERMUTATION_GRAND_PRODUCT_Y_SLOT, y) + } + // PROOF_LOOKUP_S_POLY + { + let x := mod(calldataload(add(offset, 0x164)), Q_MOD) + let y := mod(calldataload(add(offset, 0x184)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + isValid := and(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD)), isValid) + mstore(PROOF_LOOKUP_S_POLY_X_SLOT, x) + mstore(PROOF_LOOKUP_S_POLY_Y_SLOT, y) + } + // PROOF_LOOKUP_GRAND_PRODUCT + { + let x := mod(calldataload(add(offset, 0x1a4)), Q_MOD) + let y := mod(calldataload(add(offset, 0x1c4)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + isValid := and(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD)), isValid) + mstore(PROOF_LOOKUP_GRAND_PRODUCT_X_SLOT, x) + mstore(PROOF_LOOKUP_GRAND_PRODUCT_Y_SLOT, y) + } + // PROOF_QUOTIENT_POLY_PARTS_0 + { + let x := mod(calldataload(add(offset, 0x1e4)), Q_MOD) + let y := mod(calldataload(add(offset, 0x204)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + isValid := and(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD)), isValid) + mstore(PROOF_QUOTIENT_POLY_PARTS_0_X_SLOT, x) + mstore(PROOF_QUOTIENT_POLY_PARTS_0_Y_SLOT, y) + } + // PROOF_QUOTIENT_POLY_PARTS_1 + { + let x := mod(calldataload(add(offset, 0x224)), Q_MOD) + let y := mod(calldataload(add(offset, 0x244)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + isValid := and(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD)), isValid) + mstore(PROOF_QUOTIENT_POLY_PARTS_1_X_SLOT, x) + mstore(PROOF_QUOTIENT_POLY_PARTS_1_Y_SLOT, y) + } + // PROOF_QUOTIENT_POLY_PARTS_2 + { + let x := mod(calldataload(add(offset, 0x264)), Q_MOD) + let y := mod(calldataload(add(offset, 0x284)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + isValid := and(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD)), isValid) + mstore(PROOF_QUOTIENT_POLY_PARTS_2_X_SLOT, x) + mstore(PROOF_QUOTIENT_POLY_PARTS_2_Y_SLOT, y) + } + // PROOF_QUOTIENT_POLY_PARTS_3 + { + let x := mod(calldataload(add(offset, 0x2a4)), Q_MOD) + let y := mod(calldataload(add(offset, 0x2c4)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + isValid := and(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD)), isValid) + mstore(PROOF_QUOTIENT_POLY_PARTS_3_X_SLOT, x) + mstore(PROOF_QUOTIENT_POLY_PARTS_3_Y_SLOT, y) + } + + mstore(PROOF_STATE_POLYS_0_OPENING_AT_Z_SLOT, mod(calldataload(add(offset, 0x2e4)), R_MOD)) + mstore(PROOF_STATE_POLYS_1_OPENING_AT_Z_SLOT, mod(calldataload(add(offset, 0x304)), R_MOD)) + mstore(PROOF_STATE_POLYS_2_OPENING_AT_Z_SLOT, mod(calldataload(add(offset, 0x324)), R_MOD)) + mstore(PROOF_STATE_POLYS_3_OPENING_AT_Z_SLOT, mod(calldataload(add(offset, 0x344)), R_MOD)) + + mstore(PROOF_STATE_POLYS_3_OPENING_AT_Z_OMEGA_SLOT, mod(calldataload(add(offset, 0x364)), R_MOD)) + mstore(PROOF_GATE_SELECTORS_0_OPENING_AT_Z_SLOT, mod(calldataload(add(offset, 0x384)), R_MOD)) + + mstore(PROOF_COPY_PERMUTATION_POLYS_0_OPENING_AT_Z_SLOT, mod(calldataload(add(offset, 0x3a4)), R_MOD)) + mstore(PROOF_COPY_PERMUTATION_POLYS_1_OPENING_AT_Z_SLOT, mod(calldataload(add(offset, 0x3c4)), R_MOD)) + mstore(PROOF_COPY_PERMUTATION_POLYS_2_OPENING_AT_Z_SLOT, mod(calldataload(add(offset, 0x3e4)), R_MOD)) + + mstore( + PROOF_COPY_PERMUTATION_GRAND_PRODUCT_OPENING_AT_Z_OMEGA_SLOT, + mod(calldataload(add(offset, 0x404)), R_MOD) + ) + mstore(PROOF_LOOKUP_S_POLY_OPENING_AT_Z_OMEGA_SLOT, mod(calldataload(add(offset, 0x424)), R_MOD)) + mstore(PROOF_LOOKUP_GRAND_PRODUCT_OPENING_AT_Z_OMEGA_SLOT, mod(calldataload(add(offset, 0x444)), R_MOD)) + mstore(PROOF_LOOKUP_T_POLY_OPENING_AT_Z_SLOT, mod(calldataload(add(offset, 0x464)), R_MOD)) + mstore(PROOF_LOOKUP_T_POLY_OPENING_AT_Z_OMEGA_SLOT, mod(calldataload(add(offset, 0x484)), R_MOD)) + mstore(PROOF_LOOKUP_SELECTOR_POLY_OPENING_AT_Z_SLOT, mod(calldataload(add(offset, 0x4a4)), R_MOD)) + mstore(PROOF_LOOKUP_TABLE_TYPE_POLY_OPENING_AT_Z_SLOT, mod(calldataload(add(offset, 0x4c4)), R_MOD)) + mstore(PROOF_QUOTIENT_POLY_OPENING_AT_Z_SLOT, mod(calldataload(add(offset, 0x4e4)), R_MOD)) + mstore(PROOF_LINEARISATION_POLY_OPENING_AT_Z_SLOT, mod(calldataload(add(offset, 0x504)), R_MOD)) + + // PROOF_OPENING_PROOF_AT_Z + { + let x := mod(calldataload(add(offset, 0x524)), Q_MOD) + let y := mod(calldataload(add(offset, 0x544)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + isValid := and(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD)), isValid) + mstore(PROOF_OPENING_PROOF_AT_Z_X_SLOT, x) + mstore(PROOF_OPENING_PROOF_AT_Z_Y_SLOT, y) + } + // PROOF_OPENING_PROOF_AT_Z_OMEGA + { + let x := mod(calldataload(add(offset, 0x564)), Q_MOD) + let y := mod(calldataload(add(offset, 0x584)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + isValid := and(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD)), isValid) + mstore(PROOF_OPENING_PROOF_AT_Z_OMEGA_X_SLOT, x) + mstore(PROOF_OPENING_PROOF_AT_Z_OMEGA_Y_SLOT, y) + } + + // 3. Load the recursive part of the proof + if mload(VK_RECURSIVE_FLAG_SLOT) { + // recursive part should be consist of 2 points + + // PROOF_RECURSIVE_PART_P1 + { + let x := mod(calldataload(add(offset, 0x5a4)), Q_MOD) + let y := mod(calldataload(add(offset, 0x5c4)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + isValid := and(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD)), isValid) + mstore(PROOF_RECURSIVE_PART_P1_X_SLOT, x) + mstore(PROOF_RECURSIVE_PART_P1_Y_SLOT, y) + } + // PROOF_RECURSIVE_PART_P2 + { + let x := mod(calldataload(add(offset, 0x5e4)), Q_MOD) + let y := mod(calldataload(add(offset, 0x604)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + isValid := and(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD)), isValid) + mstore(PROOF_RECURSIVE_PART_P2_X_SLOT, x) + mstore(PROOF_RECURSIVE_PART_P2_Y_SLOT, y) + } + } + + // Revert if a proof is not valid + if iszero(isValid) { + revertWithMessage(27, "loadProof: Proof is invalid") + } + } + + /*////////////////////////////////////////////////////////////// + 2. Transcript initialization + //////////////////////////////////////////////////////////////*/ + + /// @notice Recomputes all challenges + /// @dev The process is the following: + /// Commit: PI, [a], [b], [c], [d] + /// Get: eta + /// Commit: [s] + /// Get: beta, gamma + /// Commit: [z_perm] + /// Get: beta', gamma' + /// Commit: [z_lookup] + /// Get: alpha + /// Commit: [t_0], [t_1], [t_2], [t_3] + /// Get: z + /// Commit: t(z), a(z), b(z), c(z), d(z), d(z*omega), + /// main_gate_selector(z), + /// sigma_0(z), sigma_1(z), sigma_2(z), + /// z_perm(z*omega), + /// t(z), lookup_selector(z), table_type(z), + /// s(x*omega), z_lookup(z*omega), t(z*omega), + /// r(z) + /// Get: v + /// Commit: [W], [W'] + /// Get: u + function initializeTranscript() { + // Round 1 + updateTranscript(mload(PROOF_PUBLIC_INPUT)) + updateTranscript(mload(PROOF_STATE_POLYS_0_X_SLOT)) + updateTranscript(mload(PROOF_STATE_POLYS_0_Y_SLOT)) + updateTranscript(mload(PROOF_STATE_POLYS_1_X_SLOT)) + updateTranscript(mload(PROOF_STATE_POLYS_1_Y_SLOT)) + updateTranscript(mload(PROOF_STATE_POLYS_2_X_SLOT)) + updateTranscript(mload(PROOF_STATE_POLYS_2_Y_SLOT)) + updateTranscript(mload(PROOF_STATE_POLYS_3_X_SLOT)) + updateTranscript(mload(PROOF_STATE_POLYS_3_Y_SLOT)) + + mstore(STATE_ETA_SLOT, getTranscriptChallenge(0)) + + // Round 1.5 + updateTranscript(mload(PROOF_LOOKUP_S_POLY_X_SLOT)) + updateTranscript(mload(PROOF_LOOKUP_S_POLY_Y_SLOT)) + + mstore(STATE_BETA_SLOT, getTranscriptChallenge(1)) + mstore(STATE_GAMMA_SLOT, getTranscriptChallenge(2)) + + // Round 2 + updateTranscript(mload(PROOF_COPY_PERMUTATION_GRAND_PRODUCT_X_SLOT)) + updateTranscript(mload(PROOF_COPY_PERMUTATION_GRAND_PRODUCT_Y_SLOT)) + + mstore(STATE_BETA_LOOKUP_SLOT, getTranscriptChallenge(3)) + mstore(STATE_GAMMA_LOOKUP_SLOT, getTranscriptChallenge(4)) + + // Round 2.5 + updateTranscript(mload(PROOF_LOOKUP_GRAND_PRODUCT_X_SLOT)) + updateTranscript(mload(PROOF_LOOKUP_GRAND_PRODUCT_Y_SLOT)) + + mstore(STATE_ALPHA_SLOT, getTranscriptChallenge(5)) + + // Round 3 + updateTranscript(mload(PROOF_QUOTIENT_POLY_PARTS_0_X_SLOT)) + updateTranscript(mload(PROOF_QUOTIENT_POLY_PARTS_0_Y_SLOT)) + updateTranscript(mload(PROOF_QUOTIENT_POLY_PARTS_1_X_SLOT)) + updateTranscript(mload(PROOF_QUOTIENT_POLY_PARTS_1_Y_SLOT)) + updateTranscript(mload(PROOF_QUOTIENT_POLY_PARTS_2_X_SLOT)) + updateTranscript(mload(PROOF_QUOTIENT_POLY_PARTS_2_Y_SLOT)) + updateTranscript(mload(PROOF_QUOTIENT_POLY_PARTS_3_X_SLOT)) + updateTranscript(mload(PROOF_QUOTIENT_POLY_PARTS_3_Y_SLOT)) + + { + let z := getTranscriptChallenge(6) + + mstore(STATE_Z_SLOT, z) + mstore(STATE_Z_IN_DOMAIN_SIZE, modexp(z, DOMAIN_SIZE)) + } + + // Round 4 + updateTranscript(mload(PROOF_QUOTIENT_POLY_OPENING_AT_Z_SLOT)) + + updateTranscript(mload(PROOF_STATE_POLYS_0_OPENING_AT_Z_SLOT)) + updateTranscript(mload(PROOF_STATE_POLYS_1_OPENING_AT_Z_SLOT)) + updateTranscript(mload(PROOF_STATE_POLYS_2_OPENING_AT_Z_SLOT)) + updateTranscript(mload(PROOF_STATE_POLYS_3_OPENING_AT_Z_SLOT)) + + updateTranscript(mload(PROOF_STATE_POLYS_3_OPENING_AT_Z_OMEGA_SLOT)) + updateTranscript(mload(PROOF_GATE_SELECTORS_0_OPENING_AT_Z_SLOT)) + + updateTranscript(mload(PROOF_COPY_PERMUTATION_POLYS_0_OPENING_AT_Z_SLOT)) + updateTranscript(mload(PROOF_COPY_PERMUTATION_POLYS_1_OPENING_AT_Z_SLOT)) + updateTranscript(mload(PROOF_COPY_PERMUTATION_POLYS_2_OPENING_AT_Z_SLOT)) + + updateTranscript(mload(PROOF_COPY_PERMUTATION_GRAND_PRODUCT_OPENING_AT_Z_OMEGA_SLOT)) + updateTranscript(mload(PROOF_LOOKUP_T_POLY_OPENING_AT_Z_SLOT)) + updateTranscript(mload(PROOF_LOOKUP_SELECTOR_POLY_OPENING_AT_Z_SLOT)) + updateTranscript(mload(PROOF_LOOKUP_TABLE_TYPE_POLY_OPENING_AT_Z_SLOT)) + updateTranscript(mload(PROOF_LOOKUP_S_POLY_OPENING_AT_Z_OMEGA_SLOT)) + updateTranscript(mload(PROOF_LOOKUP_GRAND_PRODUCT_OPENING_AT_Z_OMEGA_SLOT)) + updateTranscript(mload(PROOF_LOOKUP_T_POLY_OPENING_AT_Z_OMEGA_SLOT)) + updateTranscript(mload(PROOF_LINEARISATION_POLY_OPENING_AT_Z_SLOT)) + + mstore(STATE_V_SLOT, getTranscriptChallenge(7)) + + // Round 5 + updateTranscript(mload(PROOF_OPENING_PROOF_AT_Z_X_SLOT)) + updateTranscript(mload(PROOF_OPENING_PROOF_AT_Z_Y_SLOT)) + updateTranscript(mload(PROOF_OPENING_PROOF_AT_Z_OMEGA_X_SLOT)) + updateTranscript(mload(PROOF_OPENING_PROOF_AT_Z_OMEGA_Y_SLOT)) + + mstore(STATE_U_SLOT, getTranscriptChallenge(8)) + } + + /*////////////////////////////////////////////////////////////// + 3. Verifying quotient evaluation + //////////////////////////////////////////////////////////////*/ + + /// @notice Compute linearisation polynomial's constant term: r_0 + /// @dev To save a verifier scalar multiplication, we split linearisation polynomial + /// into its constant and non-constant terms. The constant term is computed with the formula: + /// + /// r_0 = alpha^0 * L_0(z) * PI * q_{main selector}(z) + r(z) -- main gate contribution + /// + /// - alpha^4 * z_perm(z*omega)(sigma_0(z) * beta + gamma + a(z)) \ + /// (sigma_1(z) * beta + gamma + b(z)) | + /// (sigma_2(z) * beta + gamma + c(z)) | - permutation contribution + /// (sigma_3(z) + gamma) | + /// - alpha^5 * L_0(z) / + /// + /// + alpha^6 * (s(z*omega) * beta' + gamma' (beta' + 1)) \ + /// * (z - omega^{n-1}) * z_lookup(z*omega) | - lookup contribution + /// - alpha^7 * L_0(z) | + /// - alpha^8 * L_{n-1}(z) * (gamma' (beta' + 1))^{n-1} / + /// + /// In the end we should check that t(z)*Z_H(z) = r(z) + r_0! + function verifyQuotientEvaluation() { + // Compute power of alpha + { + let alpha := mload(STATE_ALPHA_SLOT) + let currentAlpha := mulmod(alpha, alpha, R_MOD) + mstore(STATE_POWER_OF_ALPHA_2_SLOT, currentAlpha) + currentAlpha := mulmod(currentAlpha, alpha, R_MOD) + mstore(STATE_POWER_OF_ALPHA_3_SLOT, currentAlpha) + currentAlpha := mulmod(currentAlpha, alpha, R_MOD) + mstore(STATE_POWER_OF_ALPHA_4_SLOT, currentAlpha) + currentAlpha := mulmod(currentAlpha, alpha, R_MOD) + mstore(STATE_POWER_OF_ALPHA_5_SLOT, currentAlpha) + currentAlpha := mulmod(currentAlpha, alpha, R_MOD) + mstore(STATE_POWER_OF_ALPHA_6_SLOT, currentAlpha) + currentAlpha := mulmod(currentAlpha, alpha, R_MOD) + mstore(STATE_POWER_OF_ALPHA_7_SLOT, currentAlpha) + currentAlpha := mulmod(currentAlpha, alpha, R_MOD) + mstore(STATE_POWER_OF_ALPHA_8_SLOT, currentAlpha) + } + + // z + let stateZ := mload(STATE_Z_SLOT) + // L_0(z) + mstore(STATE_L_0_AT_Z_SLOT, evaluateLagrangePolyOutOfDomain(0, stateZ)) + // L_{n-1}(z) + mstore(STATE_L_N_MINUS_ONE_AT_Z_SLOT, evaluateLagrangePolyOutOfDomain(sub(DOMAIN_SIZE, 1), stateZ)) + // L_0(z) * PI + let stateT := mulmod(mload(STATE_L_0_AT_Z_SLOT), mload(PROOF_PUBLIC_INPUT), R_MOD) + + // Compute main gate contribution + let result := mulmod(stateT, mload(PROOF_GATE_SELECTORS_0_OPENING_AT_Z_SLOT), R_MOD) + + // Compute permutation contribution + result := addmod(result, permutationQuotientContribution(), R_MOD) + + // Compute lookup contribution + result := addmod(result, lookupQuotientContribution(), R_MOD) + + // Check that r(z) + r_0 = t(z) * Z_H(z) + result := addmod(mload(PROOF_LINEARISATION_POLY_OPENING_AT_Z_SLOT), result, R_MOD) + + let vanishing := addmod(mload(STATE_Z_IN_DOMAIN_SIZE), sub(R_MOD, 1), R_MOD) + let lhs := mulmod(mload(PROOF_QUOTIENT_POLY_OPENING_AT_Z_SLOT), vanishing, R_MOD) + if iszero(eq(lhs, result)) { + revertWithMessage(27, "invalid quotient evaluation") + } + } + + /// @notice Evaluating L_{polyNum}(at) out of domain + /// @dev L_i is a Lagrange polynomial for our domain such that: + /// L_i(omega^i) = 1 and L_i(omega^j) = 0 for all j != i + function evaluateLagrangePolyOutOfDomain(polyNum, at) -> res { + let omegaPower := 1 + if polyNum { + omegaPower := modexp(OMEGA, polyNum) + } + + res := addmod(modexp(at, DOMAIN_SIZE), sub(R_MOD, 1), R_MOD) + + // Vanishing polynomial can not be zero at point `at` + if iszero(res) { + revertWithMessage(28, "invalid vanishing polynomial") + } + res := mulmod(res, omegaPower, R_MOD) + let denominator := addmod(at, sub(R_MOD, omegaPower), R_MOD) + denominator := mulmod(denominator, DOMAIN_SIZE, R_MOD) + denominator := modexp(denominator, sub(R_MOD, 2)) + res := mulmod(res, denominator, R_MOD) + } + + /// @notice Compute permutation contribution to linearisation polynomial's constant term + function permutationQuotientContribution() -> res { + // res = alpha^4 * z_perm(z*omega) + res := mulmod( + mload(STATE_POWER_OF_ALPHA_4_SLOT), + mload(PROOF_COPY_PERMUTATION_GRAND_PRODUCT_OPENING_AT_Z_OMEGA_SLOT), + R_MOD + ) + + { + let gamma := mload(STATE_GAMMA_SLOT) + let beta := mload(STATE_BETA_SLOT) + + let factorMultiplier + { + // res *= sigma_0(z) * beta + gamma + a(z) + factorMultiplier := mulmod(mload(PROOF_COPY_PERMUTATION_POLYS_0_OPENING_AT_Z_SLOT), beta, R_MOD) + factorMultiplier := addmod(factorMultiplier, gamma, R_MOD) + factorMultiplier := addmod( + factorMultiplier, + mload(PROOF_STATE_POLYS_0_OPENING_AT_Z_SLOT), + R_MOD + ) + res := mulmod(res, factorMultiplier, R_MOD) + } + { + // res *= sigma_1(z) * beta + gamma + b(z) + factorMultiplier := mulmod(mload(PROOF_COPY_PERMUTATION_POLYS_1_OPENING_AT_Z_SLOT), beta, R_MOD) + factorMultiplier := addmod(factorMultiplier, gamma, R_MOD) + factorMultiplier := addmod( + factorMultiplier, + mload(PROOF_STATE_POLYS_1_OPENING_AT_Z_SLOT), + R_MOD + ) + res := mulmod(res, factorMultiplier, R_MOD) + } + { + // res *= sigma_2(z) * beta + gamma + c(z) + factorMultiplier := mulmod(mload(PROOF_COPY_PERMUTATION_POLYS_2_OPENING_AT_Z_SLOT), beta, R_MOD) + factorMultiplier := addmod(factorMultiplier, gamma, R_MOD) + factorMultiplier := addmod( + factorMultiplier, + mload(PROOF_STATE_POLYS_2_OPENING_AT_Z_SLOT), + R_MOD + ) + res := mulmod(res, factorMultiplier, R_MOD) + } + + // res *= sigma_3(z) + gamma + res := mulmod(res, addmod(mload(PROOF_STATE_POLYS_3_OPENING_AT_Z_SLOT), gamma, R_MOD), R_MOD) + } + + // res = -res + res := sub(R_MOD, res) + + // -= L_0(z) * alpha^5 + let l0AtZ := mload(STATE_L_0_AT_Z_SLOT) + l0AtZ := mulmod(l0AtZ, mload(STATE_POWER_OF_ALPHA_5_SLOT), R_MOD) + res := addmod(res, sub(R_MOD, l0AtZ), R_MOD) + } + + /// @notice Compute lookup contribution to linearisation polynomial's constant term + function lookupQuotientContribution() -> res { + let betaLookup := mload(STATE_BETA_LOOKUP_SLOT) + let gammaLookup := mload(STATE_GAMMA_LOOKUP_SLOT) + let betaPlusOne := addmod(betaLookup, 1, R_MOD) + let betaGamma := mulmod(betaPlusOne, gammaLookup, R_MOD) + + mstore(STATE_BETA_PLUS_ONE_SLOT, betaPlusOne) + mstore(STATE_BETA_GAMMA_PLUS_GAMMA_SLOT, betaGamma) + + // res = alpha^6 * (s(z*omega) * beta' + gamma' (beta' + 1)) * z_lookup(z*omega) + res := mulmod(mload(PROOF_LOOKUP_S_POLY_OPENING_AT_Z_OMEGA_SLOT), betaLookup, R_MOD) + res := addmod(res, betaGamma, R_MOD) + res := mulmod(res, mload(PROOF_LOOKUP_GRAND_PRODUCT_OPENING_AT_Z_OMEGA_SLOT), R_MOD) + res := mulmod(res, mload(STATE_POWER_OF_ALPHA_6_SLOT), R_MOD) + + // res *= z - omega^{n-1} + { + let lastOmega := modexp(OMEGA, sub(DOMAIN_SIZE, 1)) + let zMinusLastOmega := addmod(mload(STATE_Z_SLOT), sub(R_MOD, lastOmega), R_MOD) + mstore(STATE_Z_MINUS_LAST_OMEGA_SLOT, zMinusLastOmega) + res := mulmod(res, zMinusLastOmega, R_MOD) + } + + // res -= alpha^7 * L_{0}(z) + { + let intermediateValue := mulmod( + mload(STATE_L_0_AT_Z_SLOT), + mload(STATE_POWER_OF_ALPHA_7_SLOT), + R_MOD + ) + res := addmod(res, sub(R_MOD, intermediateValue), R_MOD) + } + + // res -= alpha^8 * L_{n-1}(z) * (gamma' (beta' + 1))^{n-1} + { + let lnMinusOneAtZ := mload(STATE_L_N_MINUS_ONE_AT_Z_SLOT) + let betaGammaPowered := modexp(betaGamma, sub(DOMAIN_SIZE, 1)) + let alphaPower8 := mload(STATE_POWER_OF_ALPHA_8_SLOT) + + let subtrahend := mulmod(mulmod(lnMinusOneAtZ, betaGammaPowered, R_MOD), alphaPower8, R_MOD) + res := addmod(res, sub(R_MOD, subtrahend), R_MOD) + } + } + + /// @notice Compute main gate contribution to linearisation polynomial commitment multiplied by v + function mainGateLinearisationContributionWithV( + dest, + stateOpening0AtZ, + stateOpening1AtZ, + stateOpening2AtZ, + stateOpening3AtZ + ) { + // += a(z) * [q_a] + pointMulIntoDest(VK_GATE_SETUP_0_X_SLOT, stateOpening0AtZ, dest) + // += b(z) * [q_b] + pointMulAndAddIntoDest(VK_GATE_SETUP_1_X_SLOT, stateOpening1AtZ, dest) + // += c(z) * [q_c] + pointMulAndAddIntoDest(VK_GATE_SETUP_2_X_SLOT, stateOpening2AtZ, dest) + // += d(z) * [q_d] + pointMulAndAddIntoDest(VK_GATE_SETUP_3_X_SLOT, stateOpening3AtZ, dest) + // += a(z) * b(z) * [q_ab] + pointMulAndAddIntoDest(VK_GATE_SETUP_4_X_SLOT, mulmod(stateOpening0AtZ, stateOpening1AtZ, R_MOD), dest) + // += a(z) * c(z) * [q_ac] + pointMulAndAddIntoDest(VK_GATE_SETUP_5_X_SLOT, mulmod(stateOpening0AtZ, stateOpening2AtZ, R_MOD), dest) + // += [q_const] + pointAddAssign(dest, VK_GATE_SETUP_6_X_SLOT) + // += d(z*omega) * [q_{d_next}] + pointMulAndAddIntoDest(VK_GATE_SETUP_7_X_SLOT, mload(PROOF_STATE_POLYS_3_OPENING_AT_Z_OMEGA_SLOT), dest) + + // *= v * main_gate_selector(z) + let coeff := mulmod(mload(PROOF_GATE_SELECTORS_0_OPENING_AT_Z_SLOT), mload(STATE_V_SLOT), R_MOD) + pointMulIntoDest(dest, coeff, dest) + } + + /// @notice Compute custom gate contribution to linearisation polynomial commitment multiplied by v + function addAssignRescueCustomGateLinearisationContributionWithV( + dest, + stateOpening0AtZ, + stateOpening1AtZ, + stateOpening2AtZ, + stateOpening3AtZ + ) { + let accumulator + let intermediateValue + // = alpha * (a(z)^2 - b(z)) + accumulator := mulmod(stateOpening0AtZ, stateOpening0AtZ, R_MOD) + accumulator := addmod(accumulator, sub(R_MOD, stateOpening1AtZ), R_MOD) + accumulator := mulmod(accumulator, mload(STATE_ALPHA_SLOT), R_MOD) + // += alpha^2 * (b(z)^2 - c(z)) + intermediateValue := mulmod(stateOpening1AtZ, stateOpening1AtZ, R_MOD) + intermediateValue := addmod(intermediateValue, sub(R_MOD, stateOpening2AtZ), R_MOD) + intermediateValue := mulmod(intermediateValue, mload(STATE_POWER_OF_ALPHA_2_SLOT), R_MOD) + accumulator := addmod(accumulator, intermediateValue, R_MOD) + // += alpha^3 * (c(z) * a(z) - d(z)) + intermediateValue := mulmod(stateOpening2AtZ, stateOpening0AtZ, R_MOD) + intermediateValue := addmod(intermediateValue, sub(R_MOD, stateOpening3AtZ), R_MOD) + intermediateValue := mulmod(intermediateValue, mload(STATE_POWER_OF_ALPHA_3_SLOT), R_MOD) + accumulator := addmod(accumulator, intermediateValue, R_MOD) + + // *= v * [custom_gate_selector] + accumulator := mulmod(accumulator, mload(STATE_V_SLOT), R_MOD) + pointMulAndAddIntoDest(VK_GATE_SELECTORS_1_X_SLOT, accumulator, dest) + } + + /// @notice Compute copy-permutation contribution to linearisation polynomial commitment multiplied by v + function addAssignPermutationLinearisationContributionWithV( + dest, + stateOpening0AtZ, + stateOpening1AtZ, + stateOpening2AtZ, + stateOpening3AtZ + ) { + // alpha^4 + let factor := mload(STATE_POWER_OF_ALPHA_4_SLOT) + // Calculate the factor + { + // *= (a(z) + beta * z + gamma) + let zMulBeta := mulmod(mload(STATE_Z_SLOT), mload(STATE_BETA_SLOT), R_MOD) + let gamma := mload(STATE_GAMMA_SLOT) + + let intermediateValue := addmod(addmod(zMulBeta, gamma, R_MOD), stateOpening0AtZ, R_MOD) + factor := mulmod(factor, intermediateValue, R_MOD) + + // (b(z) + beta * z * k0 + gamma) + intermediateValue := addmod( + addmod(mulmod(zMulBeta, NON_RESIDUES_0, R_MOD), gamma, R_MOD), + stateOpening1AtZ, + R_MOD + ) + factor := mulmod(factor, intermediateValue, R_MOD) + + // (c(z) + beta * z * k1 + gamma) + intermediateValue := addmod( + addmod(mulmod(zMulBeta, NON_RESIDUES_1, R_MOD), gamma, R_MOD), + stateOpening2AtZ, + R_MOD + ) + factor := mulmod(factor, intermediateValue, R_MOD) + + // (d(z) + beta * z * k2 + gamma) + intermediateValue := addmod( + addmod(mulmod(zMulBeta, NON_RESIDUES_2, R_MOD), gamma, R_MOD), + stateOpening3AtZ, + R_MOD + ) + factor := mulmod(factor, intermediateValue, R_MOD) + } + + // += alpha^5 * L_0(z) + let l0AtZ := mload(STATE_L_0_AT_Z_SLOT) + factor := addmod(factor, mulmod(l0AtZ, mload(STATE_POWER_OF_ALPHA_5_SLOT), R_MOD), R_MOD) + + // Here we can optimize one scalar multiplication by aggregating coefficients near [z_perm] during + // computing [F] + // We will sum them and add and make one scalar multiplication: (coeff1 + coeff2) * [z_perm] + factor := mulmod(factor, mload(STATE_V_SLOT), R_MOD) + mstore(COPY_PERMUTATION_FIRST_AGGREGATED_COMMITMENT_COEFF, factor) + + // alpha^4 * beta * z_perm(z*omega) + factor := mulmod(mload(STATE_POWER_OF_ALPHA_4_SLOT), mload(STATE_BETA_SLOT), R_MOD) + factor := mulmod(factor, mload(PROOF_COPY_PERMUTATION_GRAND_PRODUCT_OPENING_AT_Z_OMEGA_SLOT), R_MOD) + { + // *= (a(z) + beta * sigma_0(z) + gamma) + let beta := mload(STATE_BETA_SLOT) + let gamma := mload(STATE_GAMMA_SLOT) + + let intermediateValue := addmod( + addmod( + mulmod(mload(PROOF_COPY_PERMUTATION_POLYS_0_OPENING_AT_Z_SLOT), beta, R_MOD), + gamma, + R_MOD + ), + stateOpening0AtZ, + R_MOD + ) + factor := mulmod(factor, intermediateValue, R_MOD) + + // *= (b(z) + beta * sigma_1(z) + gamma) + intermediateValue := addmod( + addmod( + mulmod(mload(PROOF_COPY_PERMUTATION_POLYS_1_OPENING_AT_Z_SLOT), beta, R_MOD), + gamma, + R_MOD + ), + stateOpening1AtZ, + R_MOD + ) + factor := mulmod(factor, intermediateValue, R_MOD) + + // *= (c(z) + beta * sigma_2(z) + gamma) + intermediateValue := addmod( + addmod( + mulmod(mload(PROOF_COPY_PERMUTATION_POLYS_2_OPENING_AT_Z_SLOT), beta, R_MOD), + gamma, + R_MOD + ), + stateOpening2AtZ, + R_MOD + ) + factor := mulmod(factor, intermediateValue, R_MOD) + } + + // *= v * [sigma_3] + factor := mulmod(factor, mload(STATE_V_SLOT), R_MOD) + pointMulIntoDest(VK_PERMUTATION_3_X_SLOT, factor, QUERIES_BUFFER_POINT_SLOT) + + pointSubAssign(dest, QUERIES_BUFFER_POINT_SLOT) + } + + /// @notice Compute lookup contribution to linearisation polynomial commitment multiplied by v + function addAssignLookupLinearisationContributionWithV( + dest, + stateOpening0AtZ, + stateOpening1AtZ, + stateOpening2AtZ + ) { + // alpha^6 * v * z_lookup(z*omega) * (z - omega^{n-1}) * [s] + let factor := mload(PROOF_LOOKUP_GRAND_PRODUCT_OPENING_AT_Z_OMEGA_SLOT) + factor := mulmod(factor, mload(STATE_POWER_OF_ALPHA_6_SLOT), R_MOD) + factor := mulmod(factor, mload(STATE_Z_MINUS_LAST_OMEGA_SLOT), R_MOD) + factor := mulmod(factor, mload(STATE_V_SLOT), R_MOD) + + // Here we can optimize one scalar multiplication by aggregating coefficients near [s] during + // computing [F] + // We will sum them and add and make one scalar multiplication: (coeff1 + coeff2) * [s] + mstore(LOOKUP_S_FIRST_AGGREGATED_COMMITMENT_COEFF, factor) + + // gamma(1 + beta) + t(x) + beta * t(x*omega) + factor := mload(PROOF_LOOKUP_T_POLY_OPENING_AT_Z_OMEGA_SLOT) + factor := mulmod(factor, mload(STATE_BETA_LOOKUP_SLOT), R_MOD) + factor := addmod(factor, mload(PROOF_LOOKUP_T_POLY_OPENING_AT_Z_SLOT), R_MOD) + factor := addmod(factor, mload(STATE_BETA_GAMMA_PLUS_GAMMA_SLOT), R_MOD) + + // *= (gamma + f(z)) + // We should use fact that f(x) = + // lookup_selector(x) * (a(x) + eta * b(x) + eta^2 * c(x) + eta^3 * table_type(x)) + // to restore f(z) + let fReconstructed + { + fReconstructed := stateOpening0AtZ + let eta := mload(STATE_ETA_SLOT) + let currentEta := eta + + fReconstructed := addmod(fReconstructed, mulmod(currentEta, stateOpening1AtZ, R_MOD), R_MOD) + currentEta := mulmod(currentEta, eta, R_MOD) + fReconstructed := addmod(fReconstructed, mulmod(currentEta, stateOpening2AtZ, R_MOD), R_MOD) + currentEta := mulmod(currentEta, eta, R_MOD) + + // add type of table + fReconstructed := addmod( + fReconstructed, + mulmod(mload(PROOF_LOOKUP_TABLE_TYPE_POLY_OPENING_AT_Z_SLOT), currentEta, R_MOD), + R_MOD + ) + fReconstructed := mulmod(fReconstructed, mload(PROOF_LOOKUP_SELECTOR_POLY_OPENING_AT_Z_SLOT), R_MOD) + fReconstructed := addmod(fReconstructed, mload(STATE_GAMMA_LOOKUP_SLOT), R_MOD) + } + // *= -alpha^6 * (beta + 1) * (z - omega^{n-1}) + factor := mulmod(factor, fReconstructed, R_MOD) + factor := mulmod(factor, mload(STATE_BETA_PLUS_ONE_SLOT), R_MOD) + factor := sub(R_MOD, factor) + factor := mulmod(factor, mload(STATE_POWER_OF_ALPHA_6_SLOT), R_MOD) + + factor := mulmod(factor, mload(STATE_Z_MINUS_LAST_OMEGA_SLOT), R_MOD) + + // += alpha^7 * L_0(z) + factor := addmod( + factor, + mulmod(mload(STATE_L_0_AT_Z_SLOT), mload(STATE_POWER_OF_ALPHA_7_SLOT), R_MOD), + R_MOD + ) + + // += alpha^8 * L_{n-1}(z) + factor := addmod( + factor, + mulmod(mload(STATE_L_N_MINUS_ONE_AT_Z_SLOT), mload(STATE_POWER_OF_ALPHA_8_SLOT), R_MOD), + R_MOD + ) + + // Here we can optimize one scalar multiplication by aggregating coefficients near [z_lookup] during + // computing [F] + // We will sum them and add and make one scalar multiplication: (coeff1 + coeff2) * [z_lookup] + factor := mulmod(factor, mload(STATE_V_SLOT), R_MOD) + mstore(LOOKUP_GRAND_PRODUCT_FIRST_AGGREGATED_COMMITMENT_COEFF, factor) + } + + /*////////////////////////////////////////////////////////////// + 4. Prepare queries + //////////////////////////////////////////////////////////////*/ + + /// @dev Here we compute the first and second parts of batched polynomial commitment + /// We use the formula: + /// [D0] = [t_0] + z^n * [t_1] + z^{2n} * [t_2] + z^{3n} * [t_3] + /// and + /// [D1] = main_gate_selector(z) * ( \ + /// a(z) * [q_a] + b(z) * [q_b] + c(z) * [q_c] + d(z) * [q_d] + | - main gate contribution + /// a(z) * b(z) * [q_ab] + a(z) * c(z) * [q_ac] + | + /// [q_const] + d(z*omega) * [q_{d_next}]) / + /// + /// + alpha * [custom_gate_selector] * ( \ + /// (a(z)^2 - b(z)) + | - custom gate contribution + /// (b(z)^2 - c(z)) * alpha + | + /// (a(z)*c(z) - d(z)) * alpha^2 ) / + /// + /// + alpha^4 * [z_perm] * \ + /// (a(z) + beta * z + gamma) * | + /// (b(z) + beta * z * k0 + gamma) * | + /// (c(z) + beta * z * k1 + gamma) * | + /// (d(z) + beta * z * k2 + gamma) | - permutation contribution + /// - alpha^4 * z_perm(z*omega) * beta * [sigma_3] * | + /// (a(z) + beta * sigma_0(z) + gamma) * | + /// (b(z) + beta * sigma_1(z) + gamma) * | + /// (c(z) + beta * sigma_2(z) + gamma) * | + /// + alpha^5 * L_0(z) * [z_perm] / + /// + /// - alpha^6 * (1 + beta') * (gamma' + f(z)) * (z - omega^{n-1}) * \ + /// (gamma'(1 + beta') + t(z) + beta' * t(z*omega)) * [z_lookup] | + /// + alpha^6 * z_lookup(z*omega) * (z - omega^{n-1}) * [s] | - lookup contribution + /// + alpha^7 * L_0(z) * [z_lookup] | + /// + alpha^8 * L_{n-1}(z) * [z_lookup] / + function prepareQueries() { + // Calculate [D0] + { + let zInDomainSize := mload(STATE_Z_IN_DOMAIN_SIZE) + let currentZ := zInDomainSize + + mstore(QUERIES_AT_Z_0_X_SLOT, mload(PROOF_QUOTIENT_POLY_PARTS_0_X_SLOT)) + mstore(QUERIES_AT_Z_0_Y_SLOT, mload(PROOF_QUOTIENT_POLY_PARTS_0_Y_SLOT)) + + pointMulAndAddIntoDest(PROOF_QUOTIENT_POLY_PARTS_1_X_SLOT, currentZ, QUERIES_AT_Z_0_X_SLOT) + currentZ := mulmod(currentZ, zInDomainSize, R_MOD) + + pointMulAndAddIntoDest(PROOF_QUOTIENT_POLY_PARTS_2_X_SLOT, currentZ, QUERIES_AT_Z_0_X_SLOT) + currentZ := mulmod(currentZ, zInDomainSize, R_MOD) + + pointMulAndAddIntoDest(PROOF_QUOTIENT_POLY_PARTS_3_X_SLOT, currentZ, QUERIES_AT_Z_0_X_SLOT) + } + + // Calculate v * [D1] + // We are going to multiply all the points in the sum by v to save + // one scalar multiplication during [F] computation + { + let stateOpening0AtZ := mload(PROOF_STATE_POLYS_0_OPENING_AT_Z_SLOT) + let stateOpening1AtZ := mload(PROOF_STATE_POLYS_1_OPENING_AT_Z_SLOT) + let stateOpening2AtZ := mload(PROOF_STATE_POLYS_2_OPENING_AT_Z_SLOT) + let stateOpening3AtZ := mload(PROOF_STATE_POLYS_3_OPENING_AT_Z_SLOT) + + mainGateLinearisationContributionWithV( + QUERIES_AT_Z_1_X_SLOT, + stateOpening0AtZ, + stateOpening1AtZ, + stateOpening2AtZ, + stateOpening3AtZ + ) + + addAssignRescueCustomGateLinearisationContributionWithV( + QUERIES_AT_Z_1_X_SLOT, + stateOpening0AtZ, + stateOpening1AtZ, + stateOpening2AtZ, + stateOpening3AtZ + ) + + addAssignPermutationLinearisationContributionWithV( + QUERIES_AT_Z_1_X_SLOT, + stateOpening0AtZ, + stateOpening1AtZ, + stateOpening2AtZ, + stateOpening3AtZ + ) + + addAssignLookupLinearisationContributionWithV( + QUERIES_AT_Z_1_X_SLOT, + stateOpening0AtZ, + stateOpening1AtZ, + stateOpening2AtZ + ) + } + + // Also we should restore [t] for future computations + // [t] = [col_0] + eta*[col_1] + eta^2*[col_2] + eta^3*[col_3] + { + mstore(QUERIES_T_POLY_AGGREGATED_X_SLOT, mload(VK_LOOKUP_TABLE_0_X_SLOT)) + mstore(QUERIES_T_POLY_AGGREGATED_Y_SLOT, mload(VK_LOOKUP_TABLE_0_Y_SLOT)) + + let eta := mload(STATE_ETA_SLOT) + let currentEta := eta + + pointMulAndAddIntoDest(VK_LOOKUP_TABLE_1_X_SLOT, currentEta, QUERIES_T_POLY_AGGREGATED_X_SLOT) + currentEta := mulmod(currentEta, eta, R_MOD) + + pointMulAndAddIntoDest(VK_LOOKUP_TABLE_2_X_SLOT, currentEta, QUERIES_T_POLY_AGGREGATED_X_SLOT) + currentEta := mulmod(currentEta, eta, R_MOD) + + pointMulAndAddIntoDest(VK_LOOKUP_TABLE_3_X_SLOT, currentEta, QUERIES_T_POLY_AGGREGATED_X_SLOT) + } + } + + /*////////////////////////////////////////////////////////////// + 5. Prepare aggregated commitment + //////////////////////////////////////////////////////////////*/ + + /// @dev Here we compute aggregated commitment for the final pairing + /// We use the formula: + /// [E] = ( t(z) + v * r(z) + /// + v^2*a(z) + v^3*b(z) + v^4*c(z) + v^5*d(z) + /// + v^6*main_gate_selector(z) + /// + v^7*sigma_0(z) + v^8*sigma_1(z) + v^9*sigma_2(z) + /// + v^10*t(z) + v^11*lookup_selector(z) + v^12*table_type(z) + /// + u * (v^13*z_perm(z*omega) + v^14*d(z*omega) + /// + v^15*s(z*omega) + v^16*z_lookup(z*omega) + v^17*t(z*omega) + /// ) + /// ) * [1] + /// and + /// [F] = [D0] + v * [D1] + /// + v^2*[a] + v^3*[b] + v^4*[c] + v^5*[d] + /// + v^6*[main_gate_selector] + /// + v^7*[sigma_0] + v^8*[sigma_1] + v^9*[sigma_2] + /// + v^10*[t] + v^11*[lookup_selector] + v^12*[table_type] + /// + u * ( v^13*[z_perm] + v^14*[d] + /// + v^15*[s] + v^16*[z_lookup] + v^17*[t] + /// ) + function prepareAggregatedCommitment() { + // Here we compute parts of [E] and [F] without u multiplier + let aggregationChallenge := 1 + let firstDCoeff + let firstTCoeff + + mstore(AGGREGATED_AT_Z_X_SLOT, mload(QUERIES_AT_Z_0_X_SLOT)) + mstore(AGGREGATED_AT_Z_Y_SLOT, mload(QUERIES_AT_Z_0_Y_SLOT)) + let aggregatedOpeningAtZ := mload(PROOF_QUOTIENT_POLY_OPENING_AT_Z_SLOT) + { + function updateAggregationChallenge( + queriesCommitmentPoint, + valueAtZ, + curAggregationChallenge, + curAggregatedOpeningAtZ + ) -> newAggregationChallenge, newAggregatedOpeningAtZ { + newAggregationChallenge := mulmod(curAggregationChallenge, mload(STATE_V_SLOT), R_MOD) + pointMulAndAddIntoDest(queriesCommitmentPoint, newAggregationChallenge, AGGREGATED_AT_Z_X_SLOT) + newAggregatedOpeningAtZ := addmod( + curAggregatedOpeningAtZ, + mulmod(newAggregationChallenge, mload(valueAtZ), R_MOD), + R_MOD + ) + } + + // We don't need to multiply by v, because we have already computed v * [D1] + pointAddIntoDest(AGGREGATED_AT_Z_X_SLOT, QUERIES_AT_Z_1_X_SLOT, AGGREGATED_AT_Z_X_SLOT) + aggregationChallenge := mulmod(aggregationChallenge, mload(STATE_V_SLOT), R_MOD) + aggregatedOpeningAtZ := addmod( + aggregatedOpeningAtZ, + mulmod(aggregationChallenge, mload(PROOF_LINEARISATION_POLY_OPENING_AT_Z_SLOT), R_MOD), + R_MOD + ) + + aggregationChallenge, aggregatedOpeningAtZ := updateAggregationChallenge( + PROOF_STATE_POLYS_0_X_SLOT, + PROOF_STATE_POLYS_0_OPENING_AT_Z_SLOT, + aggregationChallenge, + aggregatedOpeningAtZ + ) + aggregationChallenge, aggregatedOpeningAtZ := updateAggregationChallenge( + PROOF_STATE_POLYS_1_X_SLOT, + PROOF_STATE_POLYS_1_OPENING_AT_Z_SLOT, + aggregationChallenge, + aggregatedOpeningAtZ + ) + aggregationChallenge, aggregatedOpeningAtZ := updateAggregationChallenge( + PROOF_STATE_POLYS_2_X_SLOT, + PROOF_STATE_POLYS_2_OPENING_AT_Z_SLOT, + aggregationChallenge, + aggregatedOpeningAtZ + ) + + // Here we can optimize one scalar multiplication by aggregating coefficients near [d] + // We will sum them and add and make one scalar multiplication: (coeff1 + coeff2) * [d] + aggregationChallenge := mulmod(aggregationChallenge, mload(STATE_V_SLOT), R_MOD) + firstDCoeff := aggregationChallenge + aggregatedOpeningAtZ := addmod( + aggregatedOpeningAtZ, + mulmod(aggregationChallenge, mload(PROOF_STATE_POLYS_3_OPENING_AT_Z_SLOT), R_MOD), + R_MOD + ) + + aggregationChallenge, aggregatedOpeningAtZ := updateAggregationChallenge( + VK_GATE_SELECTORS_0_X_SLOT, + PROOF_GATE_SELECTORS_0_OPENING_AT_Z_SLOT, + aggregationChallenge, + aggregatedOpeningAtZ + ) + aggregationChallenge, aggregatedOpeningAtZ := updateAggregationChallenge( + VK_PERMUTATION_0_X_SLOT, + PROOF_COPY_PERMUTATION_POLYS_0_OPENING_AT_Z_SLOT, + aggregationChallenge, + aggregatedOpeningAtZ + ) + aggregationChallenge, aggregatedOpeningAtZ := updateAggregationChallenge( + VK_PERMUTATION_1_X_SLOT, + PROOF_COPY_PERMUTATION_POLYS_1_OPENING_AT_Z_SLOT, + aggregationChallenge, + aggregatedOpeningAtZ + ) + aggregationChallenge, aggregatedOpeningAtZ := updateAggregationChallenge( + VK_PERMUTATION_2_X_SLOT, + PROOF_COPY_PERMUTATION_POLYS_2_OPENING_AT_Z_SLOT, + aggregationChallenge, + aggregatedOpeningAtZ + ) + + // Here we can optimize one scalar multiplication by aggregating coefficients near [t] + // We will sum them and add and make one scalar multiplication: (coeff1 + coeff2) * [t] + aggregationChallenge := mulmod(aggregationChallenge, mload(STATE_V_SLOT), R_MOD) + firstTCoeff := aggregationChallenge + aggregatedOpeningAtZ := addmod( + aggregatedOpeningAtZ, + mulmod(aggregationChallenge, mload(PROOF_LOOKUP_T_POLY_OPENING_AT_Z_SLOT), R_MOD), + R_MOD + ) + + aggregationChallenge, aggregatedOpeningAtZ := updateAggregationChallenge( + VK_LOOKUP_SELECTOR_X_SLOT, + PROOF_LOOKUP_SELECTOR_POLY_OPENING_AT_Z_SLOT, + aggregationChallenge, + aggregatedOpeningAtZ + ) + aggregationChallenge, aggregatedOpeningAtZ := updateAggregationChallenge( + VK_LOOKUP_TABLE_TYPE_X_SLOT, + PROOF_LOOKUP_TABLE_TYPE_POLY_OPENING_AT_Z_SLOT, + aggregationChallenge, + aggregatedOpeningAtZ + ) + } + mstore(AGGREGATED_OPENING_AT_Z_SLOT, aggregatedOpeningAtZ) + + // Here we compute parts of [E] and [F] with u multiplier + aggregationChallenge := mulmod(aggregationChallenge, mload(STATE_V_SLOT), R_MOD) + + let copyPermutationCoeff := addmod( + mload(COPY_PERMUTATION_FIRST_AGGREGATED_COMMITMENT_COEFF), + mulmod(aggregationChallenge, mload(STATE_U_SLOT), R_MOD), + R_MOD + ) + + pointMulIntoDest( + PROOF_COPY_PERMUTATION_GRAND_PRODUCT_X_SLOT, + copyPermutationCoeff, + AGGREGATED_AT_Z_OMEGA_X_SLOT + ) + let aggregatedOpeningAtZOmega := mulmod( + mload(PROOF_COPY_PERMUTATION_GRAND_PRODUCT_OPENING_AT_Z_OMEGA_SLOT), + aggregationChallenge, + R_MOD + ) + + { + function updateAggregationChallenge( + queriesCommitmentPoint, + valueAtZ_Omega, + previousCoeff, + curAggregationChallenge, + curAggregatedOpeningAtZ_Omega + ) -> newAggregationChallenge, newAggregatedOpeningAtZ_Omega { + newAggregationChallenge := mulmod(curAggregationChallenge, mload(STATE_V_SLOT), R_MOD) + let finalCoeff := addmod( + previousCoeff, + mulmod(newAggregationChallenge, mload(STATE_U_SLOT), R_MOD), + R_MOD + ) + pointMulAndAddIntoDest(queriesCommitmentPoint, finalCoeff, AGGREGATED_AT_Z_OMEGA_X_SLOT) + newAggregatedOpeningAtZ_Omega := addmod( + curAggregatedOpeningAtZ_Omega, + mulmod(newAggregationChallenge, mload(valueAtZ_Omega), R_MOD), + R_MOD + ) + } + + aggregationChallenge, aggregatedOpeningAtZOmega := updateAggregationChallenge( + PROOF_STATE_POLYS_3_X_SLOT, + PROOF_STATE_POLYS_3_OPENING_AT_Z_OMEGA_SLOT, + firstDCoeff, + aggregationChallenge, + aggregatedOpeningAtZOmega + ) + aggregationChallenge, aggregatedOpeningAtZOmega := updateAggregationChallenge( + PROOF_LOOKUP_S_POLY_X_SLOT, + PROOF_LOOKUP_S_POLY_OPENING_AT_Z_OMEGA_SLOT, + mload(LOOKUP_S_FIRST_AGGREGATED_COMMITMENT_COEFF), + aggregationChallenge, + aggregatedOpeningAtZOmega + ) + aggregationChallenge, aggregatedOpeningAtZOmega := updateAggregationChallenge( + PROOF_LOOKUP_GRAND_PRODUCT_X_SLOT, + PROOF_LOOKUP_GRAND_PRODUCT_OPENING_AT_Z_OMEGA_SLOT, + mload(LOOKUP_GRAND_PRODUCT_FIRST_AGGREGATED_COMMITMENT_COEFF), + aggregationChallenge, + aggregatedOpeningAtZOmega + ) + aggregationChallenge, aggregatedOpeningAtZOmega := updateAggregationChallenge( + QUERIES_T_POLY_AGGREGATED_X_SLOT, + PROOF_LOOKUP_T_POLY_OPENING_AT_Z_OMEGA_SLOT, + firstTCoeff, + aggregationChallenge, + aggregatedOpeningAtZOmega + ) + } + mstore(AGGREGATED_OPENING_AT_Z_OMEGA_SLOT, aggregatedOpeningAtZOmega) + + // Now we can merge both parts and get [E] and [F] + let u := mload(STATE_U_SLOT) + + // [F] + pointAddIntoDest( + AGGREGATED_AT_Z_X_SLOT, + AGGREGATED_AT_Z_OMEGA_X_SLOT, + PAIRING_PAIR_WITH_GENERATOR_X_SLOT + ) + + // [E] = (aggregatedOpeningAtZ + u * aggregatedOpeningAtZOmega) * [1] + let aggregatedValue := addmod( + mulmod(mload(AGGREGATED_OPENING_AT_Z_OMEGA_SLOT), u, R_MOD), + mload(AGGREGATED_OPENING_AT_Z_SLOT), + R_MOD + ) + + mstore(PAIRING_BUFFER_POINT_X_SLOT, 1) + mstore(PAIRING_BUFFER_POINT_Y_SLOT, 2) + pointMulIntoDest(PAIRING_BUFFER_POINT_X_SLOT, aggregatedValue, PAIRING_BUFFER_POINT_X_SLOT) + } + + /*////////////////////////////////////////////////////////////// + 5. Pairing + //////////////////////////////////////////////////////////////*/ + + /// @notice Checks the final pairing + /// @dev We should check the equation: + /// e([W] + u * [W'], [x]_2) = e(z * [W] + u * z * omega * [W'] + [F] - [E], [1]_2), + /// where [F] and [E] were computed previously + /// + /// Also we need to check that e([P1], [x]_2) = e([P2], [1]_2) + /// if we have the recursive part of the proof + /// where [P1] and [P2] are parts of the recursive proof + /// + /// We can aggregate both pairings into one for gas optimization: + /// e([W] + u * [W'] + u^2 * [P1], [x]_2) = + /// e(z * [W] + u * z * omega * [W'] + [F] - [E] + u^2 * [P2], [1]_2) + /// + /// u is a valid challenge for such aggregation, + /// because [P1] and [P2] are used in PI + function finalPairing() { + let u := mload(STATE_U_SLOT) + let z := mload(STATE_Z_SLOT) + let zOmega := mulmod(mload(STATE_Z_SLOT), OMEGA, R_MOD) + + // [F] - [E] + pointSubAssign(PAIRING_PAIR_WITH_GENERATOR_X_SLOT, PAIRING_BUFFER_POINT_X_SLOT) + + // +z * [W] + u * z * omega * [W'] + pointMulAndAddIntoDest(PROOF_OPENING_PROOF_AT_Z_X_SLOT, z, PAIRING_PAIR_WITH_GENERATOR_X_SLOT) + pointMulAndAddIntoDest( + PROOF_OPENING_PROOF_AT_Z_OMEGA_X_SLOT, + mulmod(zOmega, u, R_MOD), + PAIRING_PAIR_WITH_GENERATOR_X_SLOT + ) + + // [W] + u * [W'] + mstore(PAIRING_PAIR_WITH_X_X_SLOT, mload(PROOF_OPENING_PROOF_AT_Z_X_SLOT)) + mstore(PAIRING_PAIR_WITH_X_Y_SLOT, mload(PROOF_OPENING_PROOF_AT_Z_Y_SLOT)) + pointMulAndAddIntoDest(PROOF_OPENING_PROOF_AT_Z_OMEGA_X_SLOT, u, PAIRING_PAIR_WITH_X_X_SLOT) + pointNegate(PAIRING_PAIR_WITH_X_X_SLOT) + + // Add recursive proof part if needed + if mload(VK_RECURSIVE_FLAG_SLOT) { + let uu := mulmod(u, u, R_MOD) + pointMulAndAddIntoDest(PROOF_RECURSIVE_PART_P1_X_SLOT, uu, PAIRING_PAIR_WITH_GENERATOR_X_SLOT) + pointMulAndAddIntoDest(PROOF_RECURSIVE_PART_P2_X_SLOT, uu, PAIRING_PAIR_WITH_X_X_SLOT) + } + + // Calculate pairing + { + mstore(0x000, mload(PAIRING_PAIR_WITH_GENERATOR_X_SLOT)) + mstore(0x020, mload(PAIRING_PAIR_WITH_GENERATOR_Y_SLOT)) + + mstore(0x040, G2_ELEMENTS_0_X1) + mstore(0x060, G2_ELEMENTS_0_X2) + mstore(0x080, G2_ELEMENTS_0_Y1) + mstore(0x0a0, G2_ELEMENTS_0_Y2) + + mstore(0x0c0, mload(PAIRING_PAIR_WITH_X_X_SLOT)) + mstore(0x0e0, mload(PAIRING_PAIR_WITH_X_Y_SLOT)) + + mstore(0x100, G2_ELEMENTS_1_X1) + mstore(0x120, G2_ELEMENTS_1_X2) + mstore(0x140, G2_ELEMENTS_1_Y1) + mstore(0x160, G2_ELEMENTS_1_Y2) + + let success := staticcall(gas(), 8, 0, 0x180, 0x00, 0x20) + if iszero(success) { + revertWithMessage(32, "finalPairing: precompile failure") + } + if iszero(mload(0)) { + revertWithMessage(29, "finalPairing: pairing failure") + } + } + } + + /*////////////////////////////////////////////////////////////// + Verification + //////////////////////////////////////////////////////////////*/ + + // Step 1: Load the proof and check the correctness of its parts + loadProof() + + // Step 2: Recompute all the challenges with the transcript + initializeTranscript() + + // Step 3: Check the quotient equality + verifyQuotientEvaluation() + + // Step 4: Compute queries [D0] and v * [D1] + prepareQueries() + + // Step 5: Compute [E] and [F] + prepareAggregatedCommitment() + + // Step 6: Check the final pairing with aggregated recursive proof + finalPairing() + + mstore(0, true) + return(0, 32) + } + } +} diff --git a/l2-contracts/contracts/verifier/chain-interfaces/IVerifier.sol b/l2-contracts/contracts/verifier/chain-interfaces/IVerifier.sol new file mode 100644 index 000000000..fe5e2af2c --- /dev/null +++ b/l2-contracts/contracts/verifier/chain-interfaces/IVerifier.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. +pragma solidity ^0.8.21; + +/// @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 +interface IVerifier { + /// @dev Verifies a zk-SNARK proof. + /// @return A boolean value indicating whether the zk-SNARK proof is valid. + /// Note: The function may revert execution instead of returning false in some cases. + function verify(uint256[] calldata _publicInputs, uint256[] calldata _proof) external view returns (bool); + + /// @notice Calculates a keccak256 hash of the runtime loaded verification keys. + /// @return vkHash The keccak256 hash of the loaded verification keys. + function verificationKeyHash() external pure returns (bytes32); +} diff --git a/l2-contracts/foundry.toml b/l2-contracts/foundry.toml index deb178a12..cbc072ca8 100644 --- a/l2-contracts/foundry.toml +++ b/l2-contracts/foundry.toml @@ -2,12 +2,26 @@ src = "contracts" out = "out" libs = ["lib"] +test = "test/foundry" +solc_version = "0.8.24" cache_path = "cache-forge" +via_ir = true evm_version = "paris" +ignored_error_codes = ["missing-receive-ether", "code-size"] +ignored_warnings_from = ["test", "contracts/dev-contracts"] remappings = [ + "forge-std/=lib/forge-std/src/", + "foundry-test/=test/foundry/", "@openzeppelin/contracts-v4/=lib/openzeppelin-contracts-v4/contracts/", "@openzeppelin/contracts-upgradeable-v4/=lib/openzeppelin-contracts-upgradeable-v4/contracts/", + "@matterlabs/zksync-contracts/=lib/@matterlabs/zksync-contracts/", +] +fs_permissions = [ + { access = "read", path = "zkout" }, + { access = "read", path = "../system-contracts/bootloader/build/artifacts" }, + { access = "read", path = "../system-contracts/artifacts-zk/contracts-preprocessed" } ] -[profile.default.zksync] -zksolc = "1.5.0" +[profile.default.zksync] +enable_eravm_extensions = true +zksolc = "1.5.7" diff --git a/l2-contracts/hardhat.config.ts b/l2-contracts/hardhat.config.ts index 282ab7b96..073b0db2f 100644 --- a/l2-contracts/hardhat.config.ts +++ b/l2-contracts/hardhat.config.ts @@ -1,6 +1,7 @@ import "@matterlabs/hardhat-zksync-solc"; import "@matterlabs/hardhat-zksync-verify"; import "@nomicfoundation/hardhat-chai-matchers"; +import "@matterlabs/hardhat-zksync-node"; import "@nomiclabs/hardhat-ethers"; import "hardhat-typechain"; @@ -12,7 +13,7 @@ if (!process.env.CHAIN_ETH_NETWORK) { export default { zksolc: { - version: "1.5.0", + version: "1.5.7", compilerSource: "binary", settings: { isSystem: true, diff --git a/l2-contracts/lib/@matterlabs b/l2-contracts/lib/@matterlabs new file mode 120000 index 000000000..beffd09fc --- /dev/null +++ b/l2-contracts/lib/@matterlabs @@ -0,0 +1 @@ +../../lib/@matterlabs \ No newline at end of file diff --git a/l2-contracts/package.json b/l2-contracts/package.json index fdaa06327..a4ba33227 100644 --- a/l2-contracts/package.json +++ b/l2-contracts/package.json @@ -6,6 +6,8 @@ "@matterlabs/hardhat-zksync-deploy": "^0.7.0", "@matterlabs/hardhat-zksync-solc": "^0.3.15", "@matterlabs/hardhat-zksync-verify": "^0.4.0", + "@matterlabs/hardhat-zksync-node": "^1.2.0", + "@matterlabs/zksync-contracts": "^0.6.1", "@nomicfoundation/hardhat-chai-matchers": "^1.0.6", "@nomicfoundation/hardhat-ethers": "^3.0.4", "@nomicfoundation/hardhat-verify": "^1.1.0", @@ -32,12 +34,16 @@ }, "scripts": { "build": "hardhat compile", + "build:foundry": "forge build --zksync", + "test:foundry": "forge test --zksync --gas-limit 2000000000", "clean": "hardhat clean", "test": "hardhat test", + "test-node": "hardhat node-zksync --tag 0.1.0-alpha.29", "verify": "hardhat run src/verify.ts", "deploy-testnet-paymaster-through-l1": "ts-node src/deploy-testnet-paymaster-through-l1.ts", "deploy-force-deploy-upgrader-through-l1": "ts-node src/deploy-force-deploy-upgrader-through-l1.ts", "deploy-shared-bridge-on-l2-through-l1": "ts-node src/deploy-shared-bridge-on-l2-through-l1.ts", + "deploy-l2-da-validator-on-l2-through-l1": "ts-node src/deploy-l2-da-validator-on-l2-through-l1.ts", "publish-bridge-preimages": "ts-node src/publish-bridge-preimages.ts", "deploy-l2-weth": "ts-node src/deploy-l2-weth.ts", "upgrade-bridge-contracts": "ts-node src/upgrade-bridge-impl.ts", diff --git a/l2-contracts/src/deploy-l2-da-validator-on-l2-through-l1.ts b/l2-contracts/src/deploy-l2-da-validator-on-l2-through-l1.ts new file mode 100644 index 000000000..a9bfb7eca --- /dev/null +++ b/l2-contracts/src/deploy-l2-da-validator-on-l2-through-l1.ts @@ -0,0 +1,118 @@ +import { Command } from "commander"; +import type { BigNumberish } from "ethers"; +import { ethers, Wallet } from "ethers"; +import { formatUnits, parseUnits } from "ethers/lib/utils"; +import { computeL2Create2Address, create2DeployFromL1, provider, priorityTxMaxGasLimit } from "./utils"; + +import { ethTestConfig } from "./deploy-utils"; + +import { Deployer } from "../../l1-contracts/src.ts/deploy"; +import { GAS_MULTIPLIER } from "../../l1-contracts/scripts/utils"; +import { AdminFacetFactory } from "../../l1-contracts/typechain"; +import * as hre from "hardhat"; + +async function deployContractOnL2ThroughL1( + deployer: Deployer, + name: string, + chainId: string, + gasPrice: BigNumberish +): Promise { + const bytecode = hre.artifacts.readArtifactSync(name).bytecode; + const address = computeL2Create2Address( + deployer.deployWallet, + bytecode, + // Empty constructor data + "0x", + ethers.constants.HashZero + ); + + const tx = await create2DeployFromL1( + chainId, + deployer.deployWallet, + bytecode, + "0x", + ethers.constants.HashZero, + priorityTxMaxGasLimit, + gasPrice + ); + + await tx.wait(); + + return address; +} + +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("--local-legacy-bridge-testing") + .option("--gas-price ") + .option("--nonce ") + .option("--erc20-bridge ") + .option("--validium-mode") + .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`); + + let l2DaValidatorAddress; + let l1DaValidatorAddress; + if (cmd.validiumMode) { + l2DaValidatorAddress = await deployContractOnL2ThroughL1(deployer, "ValidiumL2DAValidator", chainId, gasPrice); + l1DaValidatorAddress = deployer.addresses.ValidiumL1DAValidator; + } else { + l2DaValidatorAddress = await deployContractOnL2ThroughL1(deployer, "RollupL2DAValidator", chainId, gasPrice); + l1DaValidatorAddress = deployer.addresses.RollupL1DAValidator; + } + + console.log(`CONTRACTS_L1_DA_VALIDATOR_ADDR=${l1DaValidatorAddress}`); + console.log(`CONTRACTS_L2_DA_VALIDATOR_ADDR=${l2DaValidatorAddress}`); + + const adminFacetInterface = new AdminFacetFactory().interface; + + console.log("Setting the DA Validator pair on diamond proxy"); + console.log("Who is called: ", deployer.addresses.StateTransition.DiamondProxy); + await deployer.executeChainAdminMulticall([ + { + target: deployer.addresses.StateTransition.DiamondProxy, + data: adminFacetInterface.encodeFunctionData("setDAValidatorPair", [ + l1DaValidatorAddress, + l2DaValidatorAddress, + ]), + value: 0, + }, + ]); + }); + + 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 deleted file mode 100644 index 613fcaf8c..000000000 --- a/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts +++ /dev/null @@ -1,276 +0,0 @@ -import { Command } from "commander"; -import type { BigNumberish } from "ethers"; -import { ethers, Wallet } from "ethers"; -import { formatUnits, Interface, parseUnits, defaultAbiCoder } from "ethers/lib/utils"; -import { - computeL2Create2Address, - create2DeployFromL1, - provider, - priorityTxMaxGasLimit, - hashL2Bytecode, - applyL1ToL2Alias, - publishBytecodeFromL1, -} from "./utils"; - -import { ethTestConfig } from "./deploy-utils"; - -import { Deployer } from "../../l1-contracts/src.ts/deploy"; -import { GAS_MULTIPLIER } from "../../l1-contracts/scripts/utils"; -import * as hre from "hardhat"; - -export const L2_SHARED_BRIDGE_ABI = hre.artifacts.readArtifactSync("L2SharedBridge").abi; -export const L2_STANDARD_TOKEN_PROXY_BYTECODE = hre.artifacts.readArtifactSync("BeaconProxy").bytecode; - -export async function publishL2SharedBridgeDependencyBytecodesOnL2( - deployer: Deployer, - chainId: string, - gasPrice: BigNumberish -) { - if (deployer.verbose) { - console.log("Providing necessary L2 bytecodes"); - } - - const L2_STANDARD_ERC20_PROXY_FACTORY_BYTECODE = hre.artifacts.readArtifactSync("UpgradeableBeacon").bytecode; - const L2_STANDARD_ERC20_IMPLEMENTATION_BYTECODE = hre.artifacts.readArtifactSync("L2StandardERC20").bytecode; - - await publishBytecodeFromL1( - chainId, - deployer.deployWallet, - [ - L2_STANDARD_ERC20_PROXY_FACTORY_BYTECODE, - L2_STANDARD_ERC20_IMPLEMENTATION_BYTECODE, - L2_STANDARD_TOKEN_PROXY_BYTECODE, - ], - gasPrice - ); - - if (deployer.verbose) { - console.log("Bytecodes published on L2"); - } -} - -export async function deploySharedBridgeImplOnL2ThroughL1( - deployer: Deployer, - chainId: string, - gasPrice: BigNumberish, - localLegacyBridgeTesting: boolean = false -) { - if (deployer.verbose) { - console.log("Deploying L2SharedBridge Implementation"); - } - const eraChainId = process.env.CONTRACTS_ERA_CHAIN_ID; - - const l2SharedBridgeImplementationBytecode = localLegacyBridgeTesting - ? hre.artifacts.readArtifactSync("DevL2SharedBridge").bytecode - : hre.artifacts.readArtifactSync("L2SharedBridge").bytecode; - - if (!l2SharedBridgeImplementationBytecode) { - throw new Error("l2SharedBridgeImplementationBytecode not found"); - } - if (deployer.verbose) { - console.log("l2SharedBridgeImplementationBytecode loaded"); - - console.log("Computing L2SharedBridge Implementation Address"); - } - const l2SharedBridgeImplAddress = computeL2Create2Address( - deployer.deployWallet, - l2SharedBridgeImplementationBytecode, - defaultAbiCoder.encode(["uint256"], [eraChainId]), - ethers.constants.HashZero - ); - deployer.addresses.Bridges.L2SharedBridgeImplementation = l2SharedBridgeImplAddress; - 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, - l2SharedBridgeImplementationBytecode, - defaultAbiCoder.encode(["uint256"], [eraChainId]), - ethers.constants.HashZero, - priorityTxMaxGasLimit, - gasPrice, - [L2_STANDARD_TOKEN_PROXY_BYTECODE] - ); - - await tx2.wait(); - if (deployer.verbose) { - console.log("Deployed L2SharedBridge Implementation"); - console.log(`CONTRACTS_L2_SHARED_BRIDGE_IMPL_ADDR=${l2SharedBridgeImplAddress}`); - } -} - -export async function deploySharedBridgeProxyOnL2ThroughL1( - deployer: Deployer, - chainId: string, - gasPrice: BigNumberish, - localLegacyBridgeTesting: boolean = false -) { - const l1SharedBridge = deployer.defaultSharedBridge(deployer.deployWallet); - if (deployer.verbose) { - console.log("Deploying L2SharedBridge Proxy"); - } - /// prepare proxyInitializationParams - const l2GovernorAddress = applyL1ToL2Alias(deployer.addresses.Governance); - - let proxyInitializationParams; - if (localLegacyBridgeTesting) { - const l2SharedBridgeInterface = new Interface(hre.artifacts.readArtifactSync("DevL2SharedBridge").abi); - proxyInitializationParams = l2SharedBridgeInterface.encodeFunctionData("initializeDevBridge", [ - l1SharedBridge.address, - deployer.addresses.Bridges.ERC20BridgeProxy, - hashL2Bytecode(L2_STANDARD_TOKEN_PROXY_BYTECODE), - l2GovernorAddress, - ]); - } else { - const l2SharedBridgeInterface = new Interface(hre.artifacts.readArtifactSync("L2SharedBridge").abi); - proxyInitializationParams = l2SharedBridgeInterface.encodeFunctionData("initialize", [ - l1SharedBridge.address, - deployer.addresses.Bridges.ERC20BridgeProxy, - hashL2Bytecode(L2_STANDARD_TOKEN_PROXY_BYTECODE), - l2GovernorAddress, - ]); - } - - /// prepare constructor data - const l2SharedBridgeProxyConstructorData = ethers.utils.arrayify( - new ethers.utils.AbiCoder().encode( - ["address", "address", "bytes"], - [deployer.addresses.Bridges.L2SharedBridgeImplementation, l2GovernorAddress, proxyInitializationParams] - ) - ); - - /// loading TransparentUpgradeableProxy bytecode - const L2_SHARED_BRIDGE_PROXY_BYTECODE = hre.artifacts.readArtifactSync( - "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol:TransparentUpgradeableProxy" - ).bytecode; - - /// compute L2SharedBridgeProxy address - const l2SharedBridgeProxyAddress = computeL2Create2Address( - deployer.deployWallet, - L2_SHARED_BRIDGE_PROXY_BYTECODE, - l2SharedBridgeProxyConstructorData, - ethers.constants.HashZero - ); - deployer.addresses.Bridges.L2SharedBridgeProxy = l2SharedBridgeProxyAddress; - - /// 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}`); - } -} - -export async function initializeChainGovernance(deployer: Deployer, chainId: string) { - const l1SharedBridge = deployer.defaultSharedBridge(deployer.deployWallet); - - if (deployer.verbose) { - console.log("Initializing chain governance"); - } - await deployer.executeUpgrade( - l1SharedBridge.address, - 0, - l1SharedBridge.interface.encodeFunctionData("initializeChainGovernance", [ - chainId, - deployer.addresses.Bridges.L2SharedBridgeProxy, - ]) - ); - - if (deployer.verbose) { - console.log("L2 shared bridge address registered on L1 via governance"); - } -} - -export async function deploySharedBridgeOnL2ThroughL1( - deployer: Deployer, - chainId: string, - gasPrice: BigNumberish, - localLegacyBridgeTesting: boolean, - skipInitializeChainGovernance: boolean -) { - await publishL2SharedBridgeDependencyBytecodesOnL2(deployer, chainId, gasPrice); - await deploySharedBridgeImplOnL2ThroughL1(deployer, chainId, gasPrice, localLegacyBridgeTesting); - await deploySharedBridgeProxyOnL2ThroughL1(deployer, chainId, gasPrice, localLegacyBridgeTesting); - if (!skipInitializeChainGovernance) { - await initializeChainGovernance(deployer, chainId); - } -} - -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("--local-legacy-bridge-testing") - .option("--gas-price ") - .option("--nonce ") - .option("--erc20-bridge ") - .option("--skip-initialize-chain-governance ") - .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`); - - const skipInitializeChainGovernance = - !!cmd.skipInitializeChainGovernance && cmd.skipInitializeChainGovernance === "true"; - if (skipInitializeChainGovernance) { - console.log("Initialization of the chain governance will be skipped"); - } - - await deploySharedBridgeOnL2ThroughL1( - deployer, - chainId, - gasPrice, - cmd.localLegacyBridgeTesting, - skipInitializeChainGovernance - ); - }); - - 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/upgrade-consistency-checker.ts b/l2-contracts/src/upgrade-consistency-checker.ts index 8bebe197d..da2ebcc29 100644 --- a/l2-contracts/src/upgrade-consistency-checker.ts +++ b/l2-contracts/src/upgrade-consistency-checker.ts @@ -10,7 +10,7 @@ import { Provider } from "zksync-ethers"; // Things that still have to be manually double checked: // 1. Contracts must be verified. -// 2. Getter methods in STM. +// 2. Getter methods in CTM. // List the contracts that should become the upgrade targets const l2BridgeImplAddr = "0x470afaacce2acdaefcc662419b74c79d76c914ae"; diff --git a/l2-contracts/src/utils.ts b/l2-contracts/src/utils.ts index 0a479a540..fc89ba668 100644 --- a/l2-contracts/src/utils.ts +++ b/l2-contracts/src/utils.ts @@ -12,8 +12,8 @@ import { ethers } from "ethers"; import type { Provider } from "zksync-ethers"; import { REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, sleep } from "zksync-ethers/build/utils"; -import { ERC20Factory } from "../../l1-contracts/typechain"; import { IERC20Factory } from "../typechain/IERC20Factory"; +import { IL1NativeTokenVaultFactory } from "../../l1-contracts/typechain/IL1NativeTokenVaultFactory"; export const provider = web3Provider(); @@ -103,13 +103,39 @@ export async function create2DeployFromL1( gasPrice?: ethers.BigNumberish, extraFactoryDeps?: ethers.BytesLike[] ) { - const bridgehubAddress = deployedAddressesFromEnv().Bridgehub.BridgehubProxy; - const bridgehub = IBridgehubFactory.connect(bridgehubAddress, wallet); - - const deployerSystemContracts = new Interface(artifacts.readArtifactSync("IContractDeployer").abi); + const deployerSystemContracts = new Interface( + artifacts.readArtifactSync("contracts/L2ContractHelper.sol:IContractDeployer").abi + ); const bytecodeHash = hashL2Bytecode(bytecode); const calldata = deployerSystemContracts.encodeFunctionData("create2", [create2Salt, bytecodeHash, constructor]); + + const factoryDeps = extraFactoryDeps ? [bytecode, ...extraFactoryDeps] : [bytecode]; + return await requestL2TransactionDirect( + chainId, + wallet, + DEPLOYER_SYSTEM_CONTRACT_ADDRESS, + calldata, + l2GasLimit, + gasPrice, + factoryDeps + ); +} + +export async function requestL2TransactionDirect( + chainId: ethers.BigNumberish, + wallet: ethers.Wallet, + l2Contract: string, + calldata: string, + l2GasLimit: ethers.BigNumberish, + gasPrice?: ethers.BigNumberish, + factoryDeps?: ethers.BytesLike[] +) { + const deployedAddresses = deployedAddressesFromEnv(); + const bridgehubAddress = deployedAddresses.Bridgehub.BridgehubProxy; + const bridgehub = IBridgehubFactory.connect(bridgehubAddress, wallet); + const ntv = IL1NativeTokenVaultFactory.connect(deployedAddresses.Bridges.NativeTokenVaultProxy, wallet); gasPrice ??= await bridgehub.provider.getGasPrice(); + const expectedCost = await bridgehub.l2TransactionBaseCost( chainId, gasPrice, @@ -117,8 +143,9 @@ export async function create2DeployFromL1( REQUIRED_L2_GAS_PRICE_PER_PUBDATA ); - const baseTokenAddress = await bridgehub.baseToken(chainId); - const baseTokenBridge = deployedAddressesFromEnv().Bridges.SharedBridgeProxy; + const baseTokenAssetId = await bridgehub.baseTokenAssetId(chainId); + const baseTokenAddress = await ntv.tokenAddress(baseTokenAssetId); + const baseTokenBridge = deployedAddresses.Bridges.SharedBridgeProxy; const baseToken = IERC20Factory.connect(baseTokenAddress, wallet); const ethIsBaseToken = ADDRESS_ONE == baseTokenAddress; @@ -126,17 +153,16 @@ export async function create2DeployFromL1( const tx = await baseToken.approve(baseTokenBridge, expectedCost); await tx.wait(); } - const factoryDeps = extraFactoryDeps ? [bytecode, ...extraFactoryDeps] : [bytecode]; return await bridgehub.requestL2TransactionDirect( { chainId, - l2Contract: DEPLOYER_SYSTEM_CONTRACT_ADDRESS, + l2Contract: l2Contract, mintValue: expectedCost, l2Value: 0, l2Calldata: calldata, l2GasLimit, l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - factoryDeps: factoryDeps, + factoryDeps: factoryDeps ?? [], refundRecipient: wallet.address, }, { value: ethIsBaseToken ? expectedCost : 0, gasPrice } @@ -170,44 +196,15 @@ export async function publishBytecodeFromL1( factoryDeps: ethers.BytesLike[], gasPrice?: ethers.BigNumberish ) { - const deployedAddresses = deployedAddressesFromEnv(); - const bridgehubAddress = deployedAddresses.Bridgehub.BridgehubProxy; - const bridgehub = IBridgehubFactory.connect(bridgehubAddress, wallet); - - const requiredValueToPublishBytecodes = await bridgehub.l2TransactionBaseCost( + return await requestL2TransactionDirect( chainId, - gasPrice, + wallet, + ethers.constants.AddressZero, + "0x", priorityTxMaxGasLimit, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA - ); - - const baseToken = deployedAddresses.BaseToken; - const ethIsBaseToken = ADDRESS_ONE == baseToken; - if (!ethIsBaseToken) { - const erc20 = ERC20Factory.connect(baseToken, wallet); - - const approveTx = await erc20.approve( - deployedAddresses.Bridges.SharedBridgeProxy, - requiredValueToPublishBytecodes.add(requiredValueToPublishBytecodes) - ); - await approveTx.wait(1); - } - const nonce = await wallet.getTransactionCount(); - 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: factoryDeps, - refundRecipient: wallet.address, - }, - { gasPrice, nonce, value: ethIsBaseToken ? requiredValueToPublishBytecodes : 0 } + gasPrice, + factoryDeps ); - await tx1.wait(); } export async function awaitPriorityOps( @@ -271,3 +268,9 @@ export async function getL1TxInfo( value: neededValue.toString(), }; } + +const LOCAL_NETWORKS = ["localhost", "hardhat", "localhostL2"]; + +export function isCurrentNetworkLocal(): boolean { + return LOCAL_NETWORKS.includes(process.env.CHAIN_ETH_NETWORK); +} diff --git a/l2-contracts/src/verify.ts b/l2-contracts/src/verify.ts index a45c83795..e69e974d9 100644 --- a/l2-contracts/src/verify.ts +++ b/l2-contracts/src/verify.ts @@ -1,5 +1,6 @@ // hardhat import should be the first import in the file import * as hardhat from "hardhat"; +import { isCurrentNetworkLocal } from "./utils"; // eslint-disable-next-line @typescript-eslint/no-explicit-any function verifyPromise(address: string, constructorArguments?: Array, libraries?: object): Promise { @@ -12,7 +13,7 @@ function verifyPromise(address: string, constructorArguments?: Array, libra } async function main() { - if (process.env.CHAIN_ETH_NETWORK == "localhost") { + if (isCurrentNetworkLocal()) { console.log("Skip contract verification on localhost"); return; } diff --git a/l2-contracts/test/consensusRegistry.test.ts b/l2-contracts/test/consensusRegistry.test.ts index 66c0309bd..e6c4fa639 100644 --- a/l2-contracts/test/consensusRegistry.test.ts +++ b/l2-contracts/test/consensusRegistry.test.ts @@ -49,8 +49,8 @@ describe("ConsensusRegistry", function () { // Prepare the node list. const numNodes = 10; for (let i = 0; i < numNodes; i++) { - const node = makeRandomNode(provider); - const nodeEntry = makeRandomNodeEntry(node, i); + const node = makeRandomNode(); + const nodeEntry = makeRandomNodeEntry(node, i + 1); nodes.push(node); nodeEntries.push(nodeEntry); } @@ -73,11 +73,14 @@ describe("ConsensusRegistry", function () { await ( await registry.add( nodeEntries[i].ownerAddr, + true, nodeEntries[i].validatorWeight, nodeEntries[i].validatorPubKey, nodeEntries[i].validatorPoP, + true, nodeEntries[i].attesterWeight, - nodeEntries[i].attesterPubKey + nodeEntries[i].attesterPubKey, + { gasLimit } ) ).wait(); } @@ -130,9 +133,11 @@ describe("ConsensusRegistry", function () { .connect(nonOwner) .add( ethers.Wallet.createRandom().address, + true, 0, { a: new Uint8Array(32), b: new Uint8Array(32), c: new Uint8Array(32) }, { a: new Uint8Array(32), b: new Uint8Array(16) }, + true, 0, { tag: new Uint8Array(1), x: new Uint8Array(32) }, { gasLimit } @@ -140,20 +145,36 @@ describe("ConsensusRegistry", function () { ).to.be.reverted; }); - it("Should allow owner to deactivate", async function () { + it("Should allow owner to deactivate attester", async function () { + const nodeOwner = nodeEntries[0].ownerAddr; + expect((await registry.nodes(nodeOwner)).attesterLatest.active).to.equal(true); + + await (await registry.connect(owner).deactivateAttester(nodeOwner, { gasLimit })).wait(); + expect((await registry.nodes(nodeOwner)).attesterLatest.active).to.equal(false); + + // Restore state. + await (await registry.connect(owner).activateAttester(nodeOwner, { gasLimit })).wait(); + }); + + it("Should allow owner to deactivate validator", async function () { const nodeOwner = nodeEntries[0].ownerAddr; expect((await registry.nodes(nodeOwner)).validatorLatest.active).to.equal(true); - await (await registry.connect(owner).deactivate(nodeOwner, { gasLimit })).wait(); + await (await registry.connect(owner).deactivateValidator(nodeOwner, { gasLimit })).wait(); expect((await registry.nodes(nodeOwner)).validatorLatest.active).to.equal(false); // Restore state. - await (await registry.connect(owner).activate(nodeOwner, { gasLimit })).wait(); + await (await registry.connect(owner).activateValidator(nodeOwner, { gasLimit })).wait(); + }); + + it("Should not allow nonOwner, nonNodeOwner to deactivate attester", async function () { + const nodeOwner = nodeEntries[0].ownerAddr; + await expect(registry.connect(nonOwner).deactivateAttester(nodeOwner, { gasLimit })).to.be.reverted; }); - it("Should not allow nonOwner, nonNodeOwner to deactivate", async function () { + it("Should not allow nonOwner, nonNodeOwner to deactivate validator", async function () { const nodeOwner = nodeEntries[0].ownerAddr; - await expect(registry.connect(nonOwner).deactivate(nodeOwner, { gasLimit })).to.be.reverted; + await expect(registry.connect(nonOwner).deactivateValidator(nodeOwner, { gasLimit })).to.be.reverted; }); it("Should change validator weight", async function () { @@ -209,13 +230,15 @@ describe("ConsensusRegistry", function () { }); it("Should not allow to add a node with a validator public key which already exist", async function () { - const newEntry = makeRandomNodeEntry(makeRandomNode(), 0); + const newEntry = makeRandomNodeEntry(makeRandomNode(), 1); await expect( registry.add( newEntry.ownerAddr, + true, newEntry.validatorWeight, nodeEntries[0].validatorPubKey, newEntry.validatorPoP, + true, newEntry.attesterWeight, newEntry.attesterPubKey, { gasLimit } @@ -224,13 +247,15 @@ describe("ConsensusRegistry", function () { }); it("Should not allow to add a node with an attester public key which already exist", async function () { - const newEntry = makeRandomNodeEntry(makeRandomNode(), 0); + const newEntry = makeRandomNodeEntry(makeRandomNode(), 1); await expect( registry.add( newEntry.ownerAddr, + true, newEntry.validatorWeight, newEntry.validatorPubKey, newEntry.validatorPoP, + true, newEntry.attesterWeight, nodeEntries[0].attesterPubKey, { gasLimit } @@ -284,7 +309,8 @@ describe("ConsensusRegistry", function () { const entry = nodeEntries[idx]; // Deactivate attribute. - await (await registry.deactivate(entry.ownerAddr, { gasLimit })).wait(); + await (await registry.deactivateAttester(entry.ownerAddr, { gasLimit })).wait(); + await (await registry.deactivateValidator(entry.ownerAddr, { gasLimit })).wait(); // Verify no change. expect((await registry.getAttesterCommittee()).length).to.equal(nodes.length); @@ -301,7 +327,8 @@ describe("ConsensusRegistry", function () { expect((await registry.getValidatorCommittee()).length).to.equal(nodes.length - 1); // Restore state. - await (await registry.activate(entry.ownerAddr, { gasLimit })).wait(); + await (await registry.activateAttester(entry.ownerAddr, { gasLimit })).wait(); + await (await registry.activateValidator(entry.ownerAddr, { gasLimit })).wait(); await (await registry.commitAttesterCommittee({ gasLimit })).wait(); await (await registry.commitValidatorCommittee({ gasLimit })).wait(); }); @@ -332,9 +359,11 @@ describe("ConsensusRegistry", function () { await ( await registry.add( entry.ownerAddr, + true, entry.validatorWeight, entry.validatorPubKey, entry.validatorPoP, + true, entry.attesterWeight, entry.attesterPubKey ) @@ -426,9 +455,11 @@ describe("ConsensusRegistry", function () { await ( await registry.add( entry.ownerAddr, + true, entry.validatorWeight, entry.validatorPubKey, entry.validatorPoP, + true, entry.attesterWeight, entry.attesterPubKey ) diff --git a/l2-contracts/test/erc20.test.ts b/l2-contracts/test/erc20.test.ts deleted file mode 100644 index 5f1f21a30..000000000 --- a/l2-contracts/test/erc20.test.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; -import { expect } from "chai"; -import { ethers } from "ethers"; -import * as hre from "hardhat"; -import { Provider, Wallet } from "zksync-ethers"; -import { hashBytecode } from "zksync-ethers/build/utils"; -import { unapplyL1ToL2Alias } from "./test-utils"; -import { L2SharedBridgeFactory, L2StandardERC20Factory } from "../typechain"; -import type { L2SharedBridge, L2StandardERC20 } from "../typechain"; - -const richAccount = [ - { - address: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", - privateKey: "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110", - }, - { - address: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", - privateKey: "0xac1e735be8536c6534bb4f17f06f6afc73b2b5ba84ac2cfb12f7461b20c0bbe3", - }, - { - address: "0x0D43eB5B8a47bA8900d84AA36656c92024e9772e", - privateKey: "0xd293c684d884d56f8d6abd64fc76757d3664904e309a0645baf8522ab6366d9e", - }, -]; - -describe("ERC20Bridge", function () { - const provider = new Provider(hre.config.networks.localhost.url); - const deployerWallet = new Wallet(richAccount[0].privateKey, provider); - const governorWallet = new Wallet(richAccount[1].privateKey, provider); - - // We need to emulate a L1->L2 transaction from the L1 bridge to L2 counterpart. - // It is a bit easier to use EOA and it is sufficient for the tests. - const l1BridgeWallet = new Wallet(richAccount[2].privateKey, provider); - - // We won't actually deploy an L1 token in these tests, but we need some address for it. - const L1_TOKEN_ADDRESS = "0x1111000000000000000000000000000000001111"; - - const testChainId = 9; - - let erc20Bridge: L2SharedBridge; - let erc20Token: L2StandardERC20; - - before("Deploy token and bridge", async function () { - const deployer = new Deployer(hre, deployerWallet); - - // While we formally don't need to deploy the token and the beacon proxy, it is a neat way to have the bytecode published - const l2TokenImplAddress = await deployer.deploy(await deployer.loadArtifact("L2StandardERC20")); - const l2Erc20TokenBeacon = await deployer.deploy( - await deployer.loadArtifact("@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol:UpgradeableBeacon"), - [l2TokenImplAddress.address] - ); - await deployer.deploy( - await deployer.loadArtifact("@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol:BeaconProxy"), - [l2Erc20TokenBeacon.address, "0x"] - ); - - const beaconProxyBytecodeHash = hashBytecode( - (await deployer.loadArtifact("@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol:BeaconProxy")).bytecode - ); - - const erc20BridgeImpl = await deployer.deploy(await deployer.loadArtifact("L2SharedBridge"), [testChainId]); - const bridgeInitializeData = erc20BridgeImpl.interface.encodeFunctionData("initialize", [ - unapplyL1ToL2Alias(l1BridgeWallet.address), - ethers.constants.AddressZero, - beaconProxyBytecodeHash, - governorWallet.address, - ]); - - const erc20BridgeProxy = await deployer.deploy( - await deployer.loadArtifact( - "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol:TransparentUpgradeableProxy" - ), - [erc20BridgeImpl.address, governorWallet.address, bridgeInitializeData] - ); - - erc20Bridge = L2SharedBridgeFactory.connect(erc20BridgeProxy.address, deployerWallet); - }); - - it("Should finalize deposit ERC20 deposit", async function () { - const erc20BridgeWithL1Bridge = L2SharedBridgeFactory.connect(erc20Bridge.address, l1BridgeWallet); - - const l1Depositor = ethers.Wallet.createRandom(); - const l2Receiver = ethers.Wallet.createRandom(); - - const tx = await ( - await erc20BridgeWithL1Bridge.finalizeDeposit( - // Depositor and l2Receiver can be any here - l1Depositor.address, - l2Receiver.address, - L1_TOKEN_ADDRESS, - 100, - encodedTokenData("TestToken", "TT", 18) - ) - ).wait(); - - const l2TokenAddress = tx.events.find((event) => event.event === "FinalizeDeposit").args.l2Token; - - // Checking the correctness of the balance: - erc20Token = L2StandardERC20Factory.connect(l2TokenAddress, deployerWallet); - expect(await erc20Token.balanceOf(l2Receiver.address)).to.equal(100); - expect(await erc20Token.totalSupply()).to.equal(100); - expect(await erc20Token.name()).to.equal("TestToken"); - expect(await erc20Token.symbol()).to.equal("TT"); - expect(await erc20Token.decimals()).to.equal(18); - }); - - it("Governance should be able to reinitialize the token", async () => { - const erc20TokenWithGovernor = L2StandardERC20Factory.connect(erc20Token.address, governorWallet); - - await ( - await erc20TokenWithGovernor.reinitializeToken( - { - ignoreName: false, - ignoreSymbol: false, - ignoreDecimals: false, - }, - "TestTokenNewName", - "TTN", - 2 - ) - ).wait(); - - expect(await erc20Token.name()).to.equal("TestTokenNewName"); - expect(await erc20Token.symbol()).to.equal("TTN"); - // The decimals should stay the same - expect(await erc20Token.decimals()).to.equal(18); - }); - - it("Governance should not be able to skip initializer versions", async () => { - const erc20TokenWithGovernor = L2StandardERC20Factory.connect(erc20Token.address, governorWallet); - - await expect( - erc20TokenWithGovernor.reinitializeToken( - { - ignoreName: false, - ignoreSymbol: false, - ignoreDecimals: false, - }, - "TestTokenNewName", - "TTN", - 20, - { gasLimit: 10000000 } - ) - ).to.be.reverted; - }); -}); - -function encodedTokenData(name: string, symbol: string, decimals: number) { - const abiCoder = ethers.utils.defaultAbiCoder; - const encodedName = abiCoder.encode(["string"], [name]); - const encodedSymbol = abiCoder.encode(["string"], [symbol]); - const encodedDecimals = abiCoder.encode(["uint8"], [decimals]); - - return abiCoder.encode(["bytes", "bytes", "bytes"], [encodedName, encodedSymbol, encodedDecimals]); -} diff --git a/l2-contracts/test/foundry/unit/data-availability/RollupL2DAValidator.t.sol b/l2-contracts/test/foundry/unit/data-availability/RollupL2DAValidator.t.sol new file mode 100644 index 000000000..5a56e7118 --- /dev/null +++ b/l2-contracts/test/foundry/unit/data-availability/RollupL2DAValidator.t.sol @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +// solhint-disable gas-custom-errors + +import {Test} from "forge-std/Test.sol"; + +import {TestStateDiffComposer} from "./TestStateDiffComposer.sol"; + +import {RollupL2DAValidator} from "contracts/data-availability/RollupL2DAValidator.sol"; +import {STATE_DIFF_ENTRY_SIZE} from "contracts/data-availability/StateDiffL2DAValidator.sol"; +import {ReconstructionMismatch, PubdataField} from "contracts/data-availability/DAErrors.sol"; + +import {COMPRESSOR_CONTRACT, PUBDATA_CHUNK_PUBLISHER} from "contracts/L2ContractHelper.sol"; + +import {console2 as console} from "forge-std/Script.sol"; + +contract RollupL2DAValidatorTest is Test { + RollupL2DAValidator internal l2DAValidator; + TestStateDiffComposer internal composer; + + function setUp() public { + l2DAValidator = new RollupL2DAValidator(); + composer = new TestStateDiffComposer(); + + bytes memory emptyArray = new bytes(0); + + // Setting dummy state diffs, so it works fine. + composer.setDummyStateDiffs(1, 0, 64, emptyArray, 0, emptyArray); + + bytes memory verifyCompressedStateDiffsData = abi.encodeCall( + COMPRESSOR_CONTRACT.verifyCompressedStateDiffs, + (0, 64, emptyArray, emptyArray) + ); + vm.mockCall(address(COMPRESSOR_CONTRACT), verifyCompressedStateDiffsData, new bytes(32)); + + bytes memory chunkPubdataToBlobsData = abi.encodeCall( + PUBDATA_CHUNK_PUBLISHER.chunkPubdataToBlobs, + (emptyArray) + ); + vm.mockCall(address(PUBDATA_CHUNK_PUBLISHER), chunkPubdataToBlobsData, new bytes(32)); + } + + function finalizeAndCall(bytes memory revertMessage) internal returns (bytes32) { + bytes32 rollingMessagesHash = composer.correctRollingMessagesHash(); + bytes32 rollingBytecodeHash = composer.correctRollingBytecodesHash(); + bytes memory totalL2ToL1PubdataAndStateDiffs = composer.generateTotalStateDiffsAndPubdata(); + + if (revertMessage.length > 0) { + vm.expectRevert(revertMessage); + } + return + l2DAValidator.validatePubdata( + bytes32(0), + bytes32(0), + rollingMessagesHash, + rollingBytecodeHash, + totalL2ToL1PubdataAndStateDiffs + ); + } + + function test_incorrectChainMessagesHash() public { + composer.appendAMessage("message", true, false); + + bytes memory revertMessage = abi.encodeWithSelector( + ReconstructionMismatch.selector, + PubdataField.MsgHash, + composer.correctRollingMessagesHash(), + composer.currentRollingMessagesHash() + ); + finalizeAndCall(revertMessage); + } + + function test_incorrectChainBytecodeHash() public { + composer.appendBytecode(new bytes(32), true, false); + + bytes memory revertMessage = abi.encodeWithSelector( + ReconstructionMismatch.selector, + PubdataField.Bytecode, + composer.correctRollingBytecodesHash(), + composer.currentRollingBytecodesHash() + ); + finalizeAndCall(revertMessage); + } + + function test_incorrectStateDiffVersion() public { + composer.setDummyStateDiffs(2, 0, 64, new bytes(0), 0, new bytes(0)); + + bytes memory revertMessage = abi.encodeWithSelector( + ReconstructionMismatch.selector, + PubdataField.StateDiffCompressionVersion, + bytes32(uint256(1)), + bytes32(uint256(2)) + ); + finalizeAndCall(revertMessage); + } + + function test_nonZeroLeftOver() public { + composer.setDummyStateDiffs(1, 0, 64, new bytes(0), 0, new bytes(32)); + + bytes memory revertMessage = abi.encodeWithSelector( + ReconstructionMismatch.selector, + PubdataField.ExtraData, + bytes32(0), + bytes32(uint256(32)) + ); + finalizeAndCall(revertMessage); + } + + function test_fullCorrectCompression() public { + composer.appendAMessage("message", true, true); + composer.appendBytecode(new bytes(32), true, true); + + uint256 numberOfStateDiffs = 1; + // Just some non-zero array, the structure does not matter here. + bytes memory compressedStateDiffs = new bytes(12); + bytes memory uncompressedStateDiffs = new bytes(STATE_DIFF_ENTRY_SIZE * numberOfStateDiffs); + + composer.setDummyStateDiffs( + 1, + uint24(compressedStateDiffs.length), + 64, + compressedStateDiffs, + uint32(numberOfStateDiffs), + uncompressedStateDiffs + ); + + bytes32 stateDiffsHash = keccak256(uncompressedStateDiffs); + bytes memory verifyCompressedStateDiffsData = abi.encodeCall( + COMPRESSOR_CONTRACT.verifyCompressedStateDiffs, + (numberOfStateDiffs, 64, uncompressedStateDiffs, compressedStateDiffs) + ); + vm.mockCall(address(COMPRESSOR_CONTRACT), verifyCompressedStateDiffsData, abi.encodePacked(stateDiffsHash)); + + bytes memory totalPubdata = composer.getTotalPubdata(); + bytes32 blobHash = keccak256(totalPubdata); + bytes32[] memory blobHashes = new bytes32[](1); + blobHashes[0] = blobHash; + bytes memory chunkPubdataToBlobsData = abi.encodeCall( + PUBDATA_CHUNK_PUBLISHER.chunkPubdataToBlobs, + (totalPubdata) + ); + vm.mockCall(address(PUBDATA_CHUNK_PUBLISHER), chunkPubdataToBlobsData, abi.encode(blobHashes)); + + bytes32 operatorDAHash = finalizeAndCall(new bytes(0)); + + bytes32 expectedOperatorDAHash = keccak256( + abi.encodePacked(stateDiffsHash, keccak256(totalPubdata), uint8(blobHashes.length), blobHashes) + ); + + assertEq(operatorDAHash, expectedOperatorDAHash); + } +} diff --git a/l2-contracts/test/foundry/unit/data-availability/TestStateDiffComposer.sol b/l2-contracts/test/foundry/unit/data-availability/TestStateDiffComposer.sol new file mode 100644 index 000000000..1806ed255 --- /dev/null +++ b/l2-contracts/test/foundry/unit/data-availability/TestStateDiffComposer.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT + +import {L2_TO_L1_LOG_SERIALIZE_SIZE} from "contracts/data-availability/StateDiffL2DAValidator.sol"; + +import {L2ContractHelper} from "contracts/L2ContractHelper.sol"; + +/// @notice The contract that is used in testing to compose the pubdata needed for the +/// state diff DA validator. +contract TestStateDiffComposer { + // The following two are always correct + // as these qre expected to be already checked by the L1Messenger + uint256 internal logsNumber; + bytes internal logs; + + uint256 internal messagesNumber; + bytes internal messages; + bytes32 public currentRollingMessagesHash; + bytes32 public correctRollingMessagesHash; + + uint256 internal bytecodesNumber; + bytes internal bytecodes; + bytes32 public currentRollingBytecodesHash; + bytes32 public correctRollingBytecodesHash; + + bytes internal uncomressedStateDiffsPart; + bytes internal compressedStateDiffsPart; + + function appendALog() public { + // This function is not fully implemented, i.e. we do not insert the correct + // content of the log. The reason for that is that it is not needed for the + // testing + + ++logsNumber; + logs = bytes.concat(logs, new bytes(L2_TO_L1_LOG_SERIALIZE_SIZE)); + } + + function appendAMessage(bytes memory message, bool includeToArray, bool includeToCorrectHash) public { + if (includeToArray) { + ++messagesNumber; + messages = bytes.concat(messages, bytes4(uint32(message.length)), message); + currentRollingMessagesHash = keccak256(abi.encode(currentRollingMessagesHash, keccak256(message))); + } + + if (includeToCorrectHash) { + correctRollingMessagesHash = keccak256(abi.encode(correctRollingMessagesHash, keccak256(message))); + } + } + + function appendBytecode(bytes memory bytecode, bool includeToArray, bool includeToCorrectHash) public { + if (includeToArray) { + ++bytecodesNumber; + bytecodes = bytes.concat(bytecodes, bytes4(uint32(bytecode.length)), bytecode); + currentRollingBytecodesHash = keccak256( + abi.encode(currentRollingBytecodesHash, L2ContractHelper.hashL2Bytecode(bytecode)) + ); + } + if (includeToCorrectHash) { + correctRollingBytecodesHash = keccak256( + abi.encode(correctRollingBytecodesHash, L2ContractHelper.hashL2Bytecode(bytecode)) + ); + } + } + + function setDummyStateDiffs( + uint8 _version, + uint24 _compressedStateDiffSize, + uint8 _enumIndexSize, + bytes memory _compressedStateDiffs, + uint32 _numberOfStateDiffs, + bytes memory _stateDiffs + ) public { + compressedStateDiffsPart = abi.encodePacked( + _version, + _compressedStateDiffSize, + _enumIndexSize, + _compressedStateDiffs + ); + + uncomressedStateDiffsPart = abi.encodePacked(_numberOfStateDiffs, _stateDiffs); + } + + function getTotalPubdata() public returns (bytes memory _totalPubdata) { + _totalPubdata = abi.encodePacked( + uint32(logsNumber), + logs, + uint32(messagesNumber), + messages, + uint32(bytecodesNumber), + bytecodes, + compressedStateDiffsPart + ); + } + + function generateTotalStateDiffsAndPubdata() public returns (bytes memory _totalL2ToL1PubdataAndStateDiffs) { + _totalL2ToL1PubdataAndStateDiffs = abi.encodePacked(getTotalPubdata(), uncomressedStateDiffsPart); + } +} diff --git a/l2-contracts/test/foundry/unit/data-availability/ValidiumL2DAValidator.t.sol b/l2-contracts/test/foundry/unit/data-availability/ValidiumL2DAValidator.t.sol new file mode 100644 index 000000000..a0ab30720 --- /dev/null +++ b/l2-contracts/test/foundry/unit/data-availability/ValidiumL2DAValidator.t.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {ValidiumL2DAValidator} from "contracts/data-availability/ValidiumL2DAValidator.sol"; + +contract L2ValidiumDAValidatorTest is Test { + function test_callValidiumDAValidator(address depositor, address receiver, uint256 amount) internal { + ValidiumL2DAValidator validator = new ValidiumL2DAValidator(); + + bytes32 outputHash = validator.validatePubdata(bytes32(0), bytes32(0), bytes32(0), bytes32(0), hex""); + + assertEq(outputHash, bytes32(0)); + } +} diff --git a/l2-contracts/test/foundry/unit/verifier/Verifier.t.sol b/l2-contracts/test/foundry/unit/verifier/Verifier.t.sol new file mode 100644 index 000000000..39b7ad944 --- /dev/null +++ b/l2-contracts/test/foundry/unit/verifier/Verifier.t.sol @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; + +import {Script, console2 as console} from "forge-std/Script.sol"; + +import {Verifier} from "contracts/verifier/Verifier.sol"; +import {VerifierTest} from "contracts/dev-contracts/VerifierTest.sol"; + +contract VerifierCaller { + Verifier public verifier; + + constructor(Verifier _verifier) { + verifier = _verifier; + } + + function verify( + uint256[] memory publicInputs, + uint256[] memory serializedProof + ) public view returns (bool result, uint256 gasUsed) { + uint256 gasBefore = gasleft(); + result = verifier.verify(publicInputs, serializedProof); + gasUsed = gasBefore - gasleft(); + } +} + +contract VerifierTestTest is Test { + uint256 Q_MOD = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + uint256 R_MOD = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + + uint256[] public publicInputs; + uint256[] public serializedProof; + + Verifier public verifier; + + function setUp() public virtual { + publicInputs.push(17257057577815541751225964212897374444694342989384539141520877492729); + + serializedProof.push(10032255692304426541958487424837706541667730769782503366592797609781788557424); + serializedProof.push(11856023086316274558845067687080284266010851703055534566998849536424959073766); + serializedProof.push(1946976494418613232642071265529572704802622739887191787991738703483400525159); + serializedProof.push(1328106069458824013351862477593422369726189688844441245167676630500797673929); + serializedProof.push(15488976127650523079605218040232167291115155239002840072043251018873550258833); + serializedProof.push(4352460820258659596860226525221943504756149602617718032378962471842121872064); + serializedProof.push(10499239305859992443759785453270906003243074359959242371675950941500942473773); + serializedProof.push(21347231097799123231227724221565041889687686131480556177475242020711996173235); + serializedProof.push(21448274562455512652922184359722637546669181231038098300951155169465175447933); + serializedProof.push(5224615512030263722410009061780530125927659699046094954022444377569738464640); + serializedProof.push(457781538876079938778845275495204146302569607395268192839148474821758081582); + serializedProof.push(18861735728246155975127314860333796285284072325207684293054713266899263027595); + serializedProof.push(16303944945368742900183889655415585360236645961122617249176044814801835577336); + serializedProof.push(13035945439947210396602249585896632733250124877036427100939804737514358838409); + serializedProof.push(5344210729159253547334947774998425118220137275601995670629358314205854915831); + serializedProof.push(5798533246034358556434877465898581616792677631188370022078168611592512620805); + serializedProof.push(17389657286129893116489015409587246992530648956814855147744210777822507444908); + serializedProof.push(2287244647342394712608648573347732257083870498255199596324312699868511383792); + serializedProof.push(4008043766112513713076111464601725311991199944328610186851424132679188418647); + serializedProof.push(1192776719848445147414966176395169615865534126881763324071908049917030138759); + serializedProof.push(21297794452895123333253856666749932934399762330444876027734824957603009458926); + serializedProof.push(17125994169200693606182326100834606153690416627082476471630567824088261322122); + serializedProof.push(13696978282153979214307382954559709118587582183649354744253374201589715565327); + serializedProof.push(19885518441500677676836488338931187143852666523909650686513498826535451677070); + serializedProof.push(1205434280320863211046275554464591162919269140938371417889032165323835178587); + serializedProof.push(17633172995805911347980792921300006225132501482343225088847242025756974009163); + serializedProof.push(16438080406761371143473961144300947125022788905488819913014533292593141026205); + serializedProof.push(5069081552536259237104332491140391551180511112980430307676595350165020188468); + serializedProof.push(21217317205917200275887696442048162383709998732382676029165079037795626916156); + serializedProof.push(19474466610515117278975027596198570980840609656738255347763182823792179771539); + serializedProof.push(9744176601826774967534277982058590459006781888895542911226406188087317156914); + serializedProof.push(13171230402193025939763214267878900142876558410430734782028402821166810894141); + serializedProof.push(11775403006142607980192261369108550982244126464568678337528680604943636677964); + serializedProof.push(6903612341636669639883555213872265187697278660090786759295896380793937349335); + serializedProof.push(10197105415769290664169006387603164525075746474380469980600306405504981186043); + serializedProof.push(10143152486514437388737642096964118742712576889537781270260677795662183637771); + serializedProof.push(7662095231333811948165764727904932118187491073896301295018543320499906824310); + serializedProof.push(929422796511992741418500336817719055655694499787310043166783539202506987065); + serializedProof.push(13837024938095280064325737989251964639823205065380219552242839155123572433059); + serializedProof.push(11738888513780631372636453609299803548810759208935038785934252961078387526204); + serializedProof.push(16528875312985292109940444015943812939751717229020635856725059316776921546668); + serializedProof.push(17525167117689648878398809303253004706004801107861280044640132822626802938868); + serializedProof.push(7419167499813234488108910149511390953153207250610705609008080038658070088540); + serializedProof.push(11628425014048216611195735618191126626331446742771562481735017471681943914146); + + verifier = new VerifierTest(); + } + + function testShouldVerify() public view { + bool success = verifier.verify(publicInputs, serializedProof); + assert(success); + } + + function testShouldVerifyWithGas() public { + // `gas snapshot` does not work well with zksync setup, so in order to obtain the amount of + // zkevm gas consumed we do the following: + // - Deploy a VerifierCaller contract, which would execute in zkevm context + // - Call the verify function from the VerifierCaller contract and return the gas used + + VerifierCaller caller = new VerifierCaller(verifier); + (bool success, uint256 gasUsed) = caller.verify(publicInputs, serializedProof); + assert(success); + + console.log("Gas used: %d", gasUsed); + } + + function testShouldVerifyWithDirtyBits() public view { + uint256[] memory newPublicInputs = publicInputs; + newPublicInputs[0] += uint256(bytes32(0xe000000000000000000000000000000000000000000000000000000000000000)); + + bool success = verifier.verify(newPublicInputs, serializedProof); + assert(success); + } + + function testEllipticCurvePointsOverModulo() public view { + uint256[] memory newSerializedProof = serializedProof; + newSerializedProof[0] += Q_MOD; + newSerializedProof[1] += Q_MOD; + newSerializedProof[1] += Q_MOD; + + bool success = verifier.verify(publicInputs, newSerializedProof); + assert(success); + } + + function testFrOverModulo() public view { + uint256[] memory newSerializedProof = serializedProof; + newSerializedProof[22] += R_MOD; + + bool success = verifier.verify(publicInputs, newSerializedProof); + assert(success); + } + + function testMoreThanOnePublicInput_shouldRevert() public { + uint256[] memory newPublicInputs = new uint256[](2); + newPublicInputs[0] = publicInputs[0]; + newPublicInputs[1] = publicInputs[0]; + + vm.expectRevert(bytes("loadProof: Proof is invalid")); + verifier.verify(newPublicInputs, serializedProof); + } + + function testEmptyPublicInput_shouldRevert() public { + uint256[] memory newPublicInputs; + + vm.expectRevert(bytes("loadProof: Proof is invalid")); + verifier.verify(newPublicInputs, serializedProof); + } + + function testMoreThan44WordsProof_shouldRevert() public { + uint256[] memory newSerializedProof = new uint256[](serializedProof.length + 1); + + for (uint256 i = 0; i < serializedProof.length; i++) { + newSerializedProof[i] = serializedProof[i]; + } + newSerializedProof[newSerializedProof.length - 1] = serializedProof[serializedProof.length - 1]; + + vm.expectRevert(bytes("loadProof: Proof is invalid")); + verifier.verify(publicInputs, newSerializedProof); + } + + function testEmptyProof_shouldRevert() public { + uint256[] memory newSerializedProof; + + vm.expectRevert(bytes("loadProof: Proof is invalid")); + verifier.verify(publicInputs, newSerializedProof); + } + + function testLongerProofInput_shouldRevert() public { + uint256[] memory newSerializedProof = new uint256[](serializedProof.length + 1); + for (uint256 i = 0; i < serializedProof.length; i++) { + newSerializedProof[i] = serializedProof[i]; + } + newSerializedProof[newSerializedProof.length - 1] = publicInputs[0]; + + vm.expectRevert(bytes("loadProof: Proof is invalid")); + verifier.verify(publicInputs, newSerializedProof); + } + + function testEllipticCurvePointAtInfinity_shouldRevert() public { + uint256[] memory newSerializedProof = serializedProof; + newSerializedProof[0] = 0; + newSerializedProof[1] = 0; + + vm.expectRevert(bytes("loadProof: Proof is invalid")); + verifier.verify(publicInputs, newSerializedProof); + } + + function testInvalidPublicInput_shouldRevert() public { + uint256[] memory newPublicInputs = publicInputs; + newPublicInputs[0] = 0; + + vm.expectRevert(bytes("invalid quotient evaluation")); + verifier.verify(newPublicInputs, serializedProof); + } + + function testVerificationKeyHash() public virtual { + bytes32 verificationKeyHash = verifier.verificationKeyHash(); + assertEq(verificationKeyHash, 0x6625fa96781746787b58306d414b1e25bd706d37d883a9b3acf57b2bd5e0de52); + } +} diff --git a/l2-contracts/test/foundry/unit/verifier/VerifierRecursive.t.sol b/l2-contracts/test/foundry/unit/verifier/VerifierRecursive.t.sol new file mode 100644 index 000000000..df43a07ed --- /dev/null +++ b/l2-contracts/test/foundry/unit/verifier/VerifierRecursive.t.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {VerifierTestTest} from "./Verifier.t.sol"; +import {VerifierRecursiveTest} from "contracts/dev-contracts/VerifierRecursiveTest.sol"; + +contract VerifierRecursiveTestTest is VerifierTestTest { + function setUp() public override { + super.setUp(); + + serializedProof.push(2257920826825449939414463854743099397427742128922725774525544832270890253504); + serializedProof.push(9091218701914748532331969127001446391756173432977615061129552313204917562530); + serializedProof.push(16188304989094043810949359833767911976672882599560690320245309499206765021563); + serializedProof.push(3201093556796962656759050531176732990872300033146738631772984017549903765305); + + verifier = new VerifierRecursiveTest(); + } + + function testMoreThan4WordsRecursiveInput_shouldRevert() public { + uint256[] memory newSerializedProof = new uint256[](serializedProof.length + 1); + + for (uint256 i = 0; i < serializedProof.length; i++) { + newSerializedProof[i] = serializedProof[i]; + } + newSerializedProof[newSerializedProof.length - 1] = serializedProof[serializedProof.length - 1]; + + vm.expectRevert(bytes("loadProof: Proof is invalid")); + verifier.verify(publicInputs, newSerializedProof); + } + + function testEmptyRecursiveInput_shouldRevert() public { + uint256[] memory newSerializedProof = new uint256[](serializedProof.length - 4); + for (uint256 i = 0; i < newSerializedProof.length; i++) { + newSerializedProof[i] = serializedProof[i]; + } + + vm.expectRevert(bytes("loadProof: Proof is invalid")); + verifier.verify(publicInputs, newSerializedProof); + } + + function testInvalidRecursiveInput_shouldRevert() public { + uint256[] memory newSerializedProof = serializedProof; + newSerializedProof[newSerializedProof.length - 4] = 1; + newSerializedProof[newSerializedProof.length - 3] = 2; + newSerializedProof[newSerializedProof.length - 2] = 1; + newSerializedProof[newSerializedProof.length - 1] = 2; + + vm.expectRevert(bytes("finalPairing: pairing failure")); + verifier.verify(publicInputs, newSerializedProof); + } + + function testVerificationKeyHash() public override { + bytes32 verificationKeyHash = verifier.verificationKeyHash(); + assertEq(verificationKeyHash, 0x88b3ddc4ed85974c7e14297dcad4097169440305c05fdb6441ca8dfd77cd7fa7); + } +} diff --git a/l2-contracts/test/test-utils.ts b/l2-contracts/test/test-utils.ts deleted file mode 100644 index c62d76c11..000000000 --- a/l2-contracts/test/test-utils.ts +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index 3e8489077..000000000 --- a/l2-contracts/test/weth.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; -import { expect } from "chai"; -import { ethers } from "ethers"; -import * as hre from "hardhat"; -import { Provider, Wallet } from "zksync-ethers"; -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", - privateKey: "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110", -}; - -const eth18 = ethers.utils.parseEther("18"); -const testChainId = 9; - -describe("WETH token & WETH bridge", function () { - const provider = new Provider(hre.config.networks.localhost.url); - const wallet = new Wallet(richAccount.privateKey, provider); - 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("L2WrappedBaseToken")); - const wethBridgeImpl = await deployer.deploy(await deployer.loadArtifact("L2SharedBridge"), [testChainId]); - const randomAddress = ethers.utils.hexlify(ethers.utils.randomBytes(20)); - - const wethTokenProxy = await deployer.deploy( - await deployer.loadArtifact( - "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol:TransparentUpgradeableProxy" - ), - [wethTokenImpl.address, randomAddress, "0x"] - ); - const wethBridgeProxy = await deployer.deploy( - await deployer.loadArtifact( - "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol:TransparentUpgradeableProxy" - ), - [wethBridgeImpl.address, randomAddress, "0x"] - ); - - wethToken = L2WrappedBaseTokenFactory.connect(wethTokenProxy.address, wallet); - wethBridge = L2SharedBridgeFactory.connect(wethBridgeProxy.address, wallet); - - // await wethToken.initialize(); - await wethToken.initializeV2("Wrapped Ether", "WETH", wethBridge.address, randomAddress); - - // await wethBridge.initialize(randomAddress, randomAddress, wethToken.address); - }); - - it("Should deposit WETH by calling deposit()", async function () { - await wethToken.deposit({ value: eth18 }).then((tx) => tx.wait()); - expect(await wethToken.balanceOf(wallet.address)).to.equal(eth18); - }); - - it("Should deposit WETH by sending", async function () { - await wallet - .sendTransaction({ - to: wethToken.address, - value: eth18, - }) - .then((tx) => tx.wait()); - expect(await wethToken.balanceOf(wallet.address)).to.equal(eth18.mul(2)); - }); - - it("Should fail depositing with random calldata", async function () { - await expect( - wallet.sendTransaction({ - data: ethers.utils.randomBytes(36), - to: wethToken.address, - value: eth18, - gasLimit: 100_000, - }) - ).to.be.reverted; - }); - - it("Should withdraw WETH to L2 ETH", async function () { - await wethToken.withdraw(eth18).then((tx) => tx.wait()); - expect(await wethToken.balanceOf(wallet.address)).to.equal(eth18); - }); - - // 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); - await wethToken.depositTo(anotherWallet.address, { value: eth18 }).then((tx) => tx.wait()); - expect(await wethToken.balanceOf(anotherWallet.address)).to.equal(eth18); - }); - - it("Should withdraw WETH to another account", async 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(eth18.div(2)); - }); - - it("Should fail withdrawing with insufficient balance", async function () { - await expect(wethToken.withdraw(1, { 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(await wethToken.bridgeMint(wallet.address, eth18, { gasLimit: 1_000_000 })).to.be.reverted; - }); - - it("Should fail calling bridgeBurn() directly", async function () { - await expect(wethToken.bridgeBurn(wallet.address, eth18, { gasLimit: 100_000 })).to.be.reverted; - }); -}); diff --git a/lib/@matterlabs/zksync-contracts b/lib/@matterlabs/zksync-contracts new file mode 160000 index 000000000..b8449bf9c --- /dev/null +++ b/lib/@matterlabs/zksync-contracts @@ -0,0 +1 @@ +Subproject commit b8449bf9c819098cc8bfee0549ff5094456be51d diff --git a/package.json b/package.json index 5aeef0ff2..b17e3cb21 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "packages": [ "l1-contracts", "l2-contracts", + "da-contracts", "system-contracts", "gas-bound-caller" ], @@ -38,6 +39,7 @@ "l1": "yarn workspace l1-contracts", "l2": "yarn workspace l2-contracts", "sc": "yarn workspace system-contracts", + "da": "yarn workspace da-contracts", "gas-bound-caller": "yarn workspace gas-bound-caller" } } diff --git a/system-contracts/README.md b/system-contracts/README.md index 4058a356b..95006fe79 100644 --- a/system-contracts/README.md +++ b/system-contracts/README.md @@ -148,12 +148,6 @@ Since scripts, READMEs, etc., are code that is not subject to audits, these are branch. The rest of the release branches as well as the `dev` branch should merge `main` to synchronize with these changes. -## License - -The ZKsync Era system-contracts are distributed under the terms of the MIT license. - -See [LICENSE-MIT](LICENSE-MIT) for details. - ## Official Links - [Website](https://zksync.io/) diff --git a/system-contracts/SystemContractsHashes.json b/system-contracts/SystemContractsHashes.json index 18da55393..fc639d6b4 100644 --- a/system-contracts/SystemContractsHashes.json +++ b/system-contracts/SystemContractsHashes.json @@ -3,49 +3,49 @@ "contractName": "AccountCodeStorage", "bytecodePath": "artifacts-zk/contracts-preprocessed/AccountCodeStorage.sol/AccountCodeStorage.json", "sourceCodePath": "contracts-preprocessed/AccountCodeStorage.sol", - "bytecodeHash": "0x0100005d05a277543946914759aa4a6c403604b828f80d00b900c669c3d224e1", + "bytecodeHash": "0x0100005df535e7d1e6f3933b26076778d9c44fd6e7faf546732f08290d8c8f94", "sourceCodeHash": "0x2e0e09d57a04bd1e722d8bf8c6423fdf3f8bca44e5e8c4f6684f987794be066e" }, { "contractName": "BootloaderUtilities", "bytecodePath": "artifacts-zk/contracts-preprocessed/BootloaderUtilities.sol/BootloaderUtilities.json", "sourceCodePath": "contracts-preprocessed/BootloaderUtilities.sol", - "bytecodeHash": "0x010007c7bb63f64649098bf75f4baa588db20f445b4d20b7cca972d5d8f973ce", + "bytecodeHash": "0x010007c72b7f29a0e1954ee4c65e6598d0934d33c692faedd7ac4fe30b508fa3", "sourceCodeHash": "0x0f1213c4b95acb71f4ab5d4082cc1aeb2bd5017e1cccd46afc66e53268609d85" }, { "contractName": "ComplexUpgrader", "bytecodePath": "artifacts-zk/contracts-preprocessed/ComplexUpgrader.sol/ComplexUpgrader.json", "sourceCodePath": "contracts-preprocessed/ComplexUpgrader.sol", - "bytecodeHash": "0x0100004da9f3aa5e4febcc53522cb7ee6949369fde25dd79e977752b82b9fd5d", - "sourceCodeHash": "0x796046a914fb676ba2bbd337b2924311ee2177ce54571c18a2c3945755c83614" + "bytecodeHash": "0x010000bf6fcec0995b82b1c51133a507c8f63111234530b69fe7dadaae0c8172", + "sourceCodeHash": "0xfcc74aefbc7cbde7945c29bad0e47527ac443bd6b75251a4ae520e28c714af37" }, { "contractName": "Compressor", "bytecodePath": "artifacts-zk/contracts-preprocessed/Compressor.sol/Compressor.json", "sourceCodePath": "contracts-preprocessed/Compressor.sol", - "bytecodeHash": "0x0100014fb4f05ae09288cbcf4fa7a09ca456910f6e69be5ac2c2dfc8d71d1576", - "sourceCodeHash": "0xc6f7cd8b21aae52ed3dd5083c09b438a7af142a4ecda6067c586770e8be745a5" + "bytecodeHash": "0x0100014b2cac967629cb05fb59a5c77cb5a077b74c50521ed9216a59511bf182", + "sourceCodeHash": "0x7240b5fb2ea8e184522e731fb14f764ebae52b8a69d1870a55daedac9a3ed617" }, { "contractName": "ContractDeployer", "bytecodePath": "artifacts-zk/contracts-preprocessed/ContractDeployer.sol/ContractDeployer.json", "sourceCodePath": "contracts-preprocessed/ContractDeployer.sol", - "bytecodeHash": "0x010004e5d52d692822d5c54ac87de3297f39be0e4a6f72f2830ae5ac856684ee", - "sourceCodeHash": "0x82f81fbf5fb007a9cac97462d50907ca5d7a1af62d82d2645e093ed8647a5209" + "bytecodeHash": "0x010004e5a266e697bb45bc90ff310dcb293725006146ff83e46bde8f3c6b44fa", + "sourceCodeHash": "0x92bc09da23ed9d86ba7a84f0dbf48503c99582ae58cdbebbdcc5f14ea1fcf014" }, { "contractName": "Create2Factory", "bytecodePath": "artifacts-zk/contracts-preprocessed/Create2Factory.sol/Create2Factory.json", "sourceCodePath": "contracts-preprocessed/Create2Factory.sol", - "bytecodeHash": "0x010000495bd172e90725e6bfafe73e36a288d616d4673f5347eeb819a78bf546", - "sourceCodeHash": "0x114d9322a9ca654989f3e0b3b21f1311dbc4db84f443d054cd414f6414d84de3" + "bytecodeHash": "0x010000493a391e65a70dea42442132cf7c7001dac94388b9c4218ce9b1491b57", + "sourceCodeHash": "0x97392413259e6aae5187768cefd734507460ae818d6975709cc9b4e15a9af906" }, { "contractName": "DefaultAccount", "bytecodePath": "artifacts-zk/contracts-preprocessed/DefaultAccount.sol/DefaultAccount.json", "sourceCodePath": "contracts-preprocessed/DefaultAccount.sol", - "bytecodeHash": "0x0100055dba11508480be023137563caec69debc85f826cb3a4b68246a7cabe30", + "bytecodeHash": "0x0100052307b3b66ef67935255483d39b3c8dcdb47fdf94dddca11ebe8271afe6", "sourceCodeHash": "0xebffe840ebbd9329edb1ebff8ca50f6935e7dabcc67194a896fcc2e968d46dfb" }, { @@ -59,57 +59,85 @@ "contractName": "ImmutableSimulator", "bytecodePath": "artifacts-zk/contracts-preprocessed/ImmutableSimulator.sol/ImmutableSimulator.json", "sourceCodePath": "contracts-preprocessed/ImmutableSimulator.sol", - "bytecodeHash": "0x01000039785a8e0d342a49b6b6c6e156801b816434d93bee85d33f56d56b4f9a", + "bytecodeHash": "0x0100003946a9e538157e73717201b8cd17af70998602a3692b0ac1eff6ad850e", "sourceCodeHash": "0x9659e69f7db09e8f60a8bb95314b1ed26afcc689851665cf27f5408122f60c98" }, { "contractName": "KnownCodesStorage", "bytecodePath": "artifacts-zk/contracts-preprocessed/KnownCodesStorage.sol/KnownCodesStorage.json", "sourceCodePath": "contracts-preprocessed/KnownCodesStorage.sol", - "bytecodeHash": "0x0100006f0f209c9e6d06b1327db1257b15fa7a8b9864ee5ccd12cd3f8bc40ac9", + "bytecodeHash": "0x0100006f1ab2c7415de3914a2b9c53942cd3ff6471f698e7383b59f51e33e4d3", "sourceCodeHash": "0xb39b5b81168653e0c5062f7b8e1d6d15a4e186df3317f192f0cb2fc3a74f5448" }, { "contractName": "L1Messenger", "bytecodePath": "artifacts-zk/contracts-preprocessed/L1Messenger.sol/L1Messenger.json", "sourceCodePath": "contracts-preprocessed/L1Messenger.sol", - "bytecodeHash": "0x010002955993e8ff8190e388e94a6bb791fbe9c6388e5011c52cb587a4ebf05e", - "sourceCodeHash": "0xa8768fdaac6d8804782f14e2a51bbe2b6be31dee9103b6d02d149ea8dc46eb6a" + "bytecodeHash": "0x010001f74f7e45f40e1acbae30507ef94ea2775026a6ba0d0eb38cce10e4a472", + "sourceCodeHash": "0xe97846e4ff5f1cfffd6a454f5ad278deecf6fd7a67525908dea9af877dc822a9" }, { "contractName": "L2BaseToken", "bytecodePath": "artifacts-zk/contracts-preprocessed/L2BaseToken.sol/L2BaseToken.json", "sourceCodePath": "contracts-preprocessed/L2BaseToken.sol", - "bytecodeHash": "0x01000103174a90beadc2cffe3e81bdb7b8a576174e888809c1953175fd3015b4", + "bytecodeHash": "0x01000103bbfa393b49b9f8a7adcfedf1273b7928750f3ea8798347dfd8ca0d6f", "sourceCodeHash": "0x8bdd2b4d0b53dba84c9f0af250bbaa2aad10b3de6747bba957f0bd3721090dfa" }, + { + "contractName": "L2GatewayUpgrade", + "bytecodePath": "artifacts-zk/contracts-preprocessed/L2GatewayUpgrade.sol/L2GatewayUpgrade.json", + "sourceCodePath": "contracts-preprocessed/L2GatewayUpgrade.sol", + "bytecodeHash": "0x0100038b3b4065d2682996020e14177a9b4632e054b6718f68d46ff13c012b20", + "sourceCodeHash": "0x9248f46f491b8853da77e8f9787cfc1a136abee90fde18a3b8f47dcb8859c63c" + }, + { + "contractName": "L2GatewayUpgradeHelper", + "bytecodePath": "artifacts-zk/contracts-preprocessed/L2GatewayUpgradeHelper.sol/L2GatewayUpgradeHelper.json", + "sourceCodePath": "contracts-preprocessed/L2GatewayUpgradeHelper.sol", + "bytecodeHash": "0x010000071330ec1656098ed33e28b475e101394550c02907d7ee2abbae9b762e", + "sourceCodeHash": "0xd1c42c4d338697b8effbfe22a0f07d8d9c5a06c8ec8f45deae77765af48a355b" + }, + { + "contractName": "L2GenesisUpgrade", + "bytecodePath": "artifacts-zk/contracts-preprocessed/L2GenesisUpgrade.sol/L2GenesisUpgrade.json", + "sourceCodePath": "contracts-preprocessed/L2GenesisUpgrade.sol", + "bytecodeHash": "0x010001b386e0ed48ce9fbaad09c7865a58c28c8350d9bc9446b3beaee4aee999", + "sourceCodeHash": "0x2aaddd8a8ef3f56b4f4e6ba52c0035572145b0ea562fbf218a2eb5fc462f988d" + }, { "contractName": "MsgValueSimulator", "bytecodePath": "artifacts-zk/contracts-preprocessed/MsgValueSimulator.sol/MsgValueSimulator.json", "sourceCodePath": "contracts-preprocessed/MsgValueSimulator.sol", - "bytecodeHash": "0x0100005da36075675b98f85fe90df11c1d526f6b12925da3a55a8b9c02aaac5f", + "bytecodeHash": "0x0100005df63cf8940e407a67346b406dcddf4788cba9792ecd6a0edb8d8b3bd8", "sourceCodeHash": "0x082f3dcbc2fe4d93706c86aae85faa683387097d1b676e7ebd00f71ee0f13b71" }, { "contractName": "NonceHolder", "bytecodePath": "artifacts-zk/contracts-preprocessed/NonceHolder.sol/NonceHolder.json", "sourceCodePath": "contracts-preprocessed/NonceHolder.sol", - "bytecodeHash": "0x010000d97de8c14cd36b1ce06cd7f44a09f6093ec8eb4041629c0fc2116d0c73", + "bytecodeHash": "0x010000d9e79c30aeda9b823f1a0161c7637ed50848e6287e2a34e37cf2e7e4e8", "sourceCodeHash": "0xcd0c0366effebf2c98c58cf96322cc242a2d1c675620ef5514b7ed1f0a869edc" }, { "contractName": "PubdataChunkPublisher", "bytecodePath": "artifacts-zk/contracts-preprocessed/PubdataChunkPublisher.sol/PubdataChunkPublisher.json", "sourceCodePath": "contracts-preprocessed/PubdataChunkPublisher.sol", - "bytecodeHash": "0x010000470e396f376539289b7975b6866914a8a0994008a02987edac8be81db7", - "sourceCodeHash": "0xd7161e2c8092cf57b43c6220bc605c0e7e540bddcde1af24e2d90f75633b098e" + "bytecodeHash": "0x01000049377ba719b2d7493420854f12ebe67b75e21338777fb22b73e58ec057", + "sourceCodeHash": "0x398b1b9325b39d4c31e672866d4cbdf1cab453fae8d29f438262d921d427f094" + }, + { + "contractName": "SloadContract", + "bytecodePath": "artifacts-zk/contracts-preprocessed/SloadContract.sol/SloadContract.json", + "sourceCodePath": "contracts-preprocessed/SloadContract.sol", + "bytecodeHash": "0x01000011322222fc9712efbf569d3477e80ca5903c708b395142e88f654641b4", + "sourceCodeHash": "0xb4debf46dd512f6cf4fb349bf2ce680f73fe25eb3550590261f31b8f00f78c03" }, { "contractName": "SystemContext", "bytecodePath": "artifacts-zk/contracts-preprocessed/SystemContext.sol/SystemContext.json", "sourceCodePath": "contracts-preprocessed/SystemContext.sol", - "bytecodeHash": "0x010001a5eabf9e28288b7ab7e1db316148021347460cfb4314570956867d5af5", - "sourceCodeHash": "0xf308743981ef5cea2f7a3332b8e51695a5e47e811a63974437fc1cceee475e7a" + "bytecodeHash": "0x0100017f235b172e9a808764229a777b027e179eacc88a7ea48ef81cb193630a", + "sourceCodeHash": "0x22406893d61abd477ce071dce506cf2534cca7b7717d015769fc8af1f1b80e06" }, { "contractName": "EventWriter", @@ -178,35 +206,35 @@ "contractName": "bootloader_test", "bytecodePath": "bootloader/build/artifacts/bootloader_test.yul.zbin", "sourceCodePath": "bootloader/build/bootloader_test.yul", - "bytecodeHash": "0x010003cbe67434b2848054322cbc311385851bbfbf70d17f92cd5f1f9836b25e", - "sourceCodeHash": "0x006fdf461899dec5fdb34301c23e6819eb93e275907cbfc67d73fccfb47cae68" + "bytecodeHash": "0x010003d74b175f9f9721d181430a694a7d1e6714a523072141a4a96010e2ae42", + "sourceCodeHash": "0xf44341122d04745a6935f5070a3e53d99ef4e8d1ff8c8e4337e0ea4dfe31a69b" }, { "contractName": "fee_estimate", "bytecodePath": "bootloader/build/artifacts/fee_estimate.yul.zbin", "sourceCodePath": "bootloader/build/fee_estimate.yul", - "bytecodeHash": "0x0100092d9b8ae575bca569f68fe60fbef1dc7d4aad6aa02cc4836f56afcf0a38", - "sourceCodeHash": "0x8a858319bac2924a3dee778218a7fe5e23898db0d87b02d7b783f94c5a02d257" + "bytecodeHash": "0x01000931efb40296b642759fe7e440e92dcff1be45ef1288e78b72c88c629f8b", + "sourceCodeHash": "0xae273f1f71c1fd425d781f8286fde074274664c14b7f48265169679bef8c9e49" }, { "contractName": "gas_test", "bytecodePath": "bootloader/build/artifacts/gas_test.yul.zbin", "sourceCodePath": "bootloader/build/gas_test.yul", - "bytecodeHash": "0x010008b3e1b8bdd393f2f8d6f5c994c8b9c287df7310dee75d0c52a245fc7cb1", - "sourceCodeHash": "0x89f5ad470f10e755fa57b82507518e571c24409a328bc33aeba26e9518ad1c3e" + "bytecodeHash": "0x010008b754dd6c69f0f480639f44e73ab7fd933b2929c0d000725687ccb1222f", + "sourceCodeHash": "0x8bd6c1bf35cf6204967ea50d98da4088524502a9363c57c9e4b1d0a0d31372d8" }, { "contractName": "playground_batch", "bytecodePath": "bootloader/build/artifacts/playground_batch.yul.zbin", "sourceCodePath": "bootloader/build/playground_batch.yul", - "bytecodeHash": "0x01000933061c23d700f3f647c45068e22f5506ff33bb516ac13f11069b163986", - "sourceCodeHash": "0x769448c4fd2b65c43d758ca5f34dd29d9b9dd3000fd0ec89cffcaf8d365a64fd" + "bytecodeHash": "0x01000935c8fb0c8b01322788e8f82e13344199d56139becd3e5392a682f5f43f", + "sourceCodeHash": "0xaabdd037c9ebc7541c8a847ebb4dd16624eb18450e4faec65026992b86d8f294" }, { "contractName": "proved_batch", "bytecodePath": "bootloader/build/artifacts/proved_batch.yul.zbin", "sourceCodePath": "bootloader/build/proved_batch.yul", - "bytecodeHash": "0x010008c3be57ae5800e077b6c2056d9d75ad1a7b4f0ce583407961cc6fe0b678", - "sourceCodeHash": "0x908bc6ddb34ef89b125e9637239a1149deacacd91255781d82a65a542a39036e" + "bytecodeHash": "0x010008e5b883de8897598e83d383e332b87d09164363816c15f22353c3cd910d", + "sourceCodeHash": "0xf8f84fe15ef78e170f6649f12920266c9afe3d29b867d2f5fd28b672bd13e9a3" } ] diff --git a/system-contracts/bootloader/bootloader.yul b/system-contracts/bootloader/bootloader.yul index e995e7151..8933c492b 100644 --- a/system-contracts/bootloader/bootloader.yul +++ b/system-contracts/bootloader/bootloader.yul @@ -193,7 +193,7 @@ object "Bootloader" { /// @dev The maximum number of new factory deps that are allowed in a transaction function MAX_NEW_FACTORY_DEPS() -> ret { - ret := 32 + ret := 64 } /// @dev Besides the factory deps themselves, we also need another 4 slots for: @@ -403,7 +403,7 @@ object "Bootloader" { ret := add(TX_DESCRIPTION_BEGIN_BYTE(), mul(MAX_TRANSACTIONS_IN_BATCH(), TX_DESCRIPTION_SIZE())) } - /// @dev The memory page consists of 59000000 / 32 VM words. + /// @dev The memory page consists of 63800000 / 32 VM words. /// Each execution result is a single boolean, but /// for the sake of simplicity we will spend 32 bytes on each /// of those for now. @@ -1094,13 +1094,13 @@ object "Bootloader" { gasPerPubdata, basePubdataSpent ) -> canonicalL1TxHash, gasUsedOnPreparation { + let gasBeforePreparation := gas() + debugLog("gasBeforePreparation", gasBeforePreparation) + let innerTxDataOffset := add(txDataOffset, 32) setPubdataInfo(gasPerPubdata, basePubdataSpent) - let gasBeforePreparation := gas() - debugLog("gasBeforePreparation", gasBeforePreparation) - // Even though the smart contracts on L1 should make sure that the L1->L2 provide enough gas to generate the hash // we should still be able to do it even if this protection layer fails. canonicalL1TxHash := getCanonicalL1TxHash(txDataOffset) @@ -2473,7 +2473,7 @@ object "Bootloader" { let ptr := NEW_FACTORY_DEPS_BEGIN_BYTE() // Selector - mstore(ptr, {{MARK_BATCH_AS_REPUBLISHED_SELECTOR}}) + mstore(ptr, {{MARK_FACTORY_DEPS_SELECTOR}}) ptr := add(ptr, 32) // Saving whether the dependencies should be sent on L1 @@ -2676,15 +2676,23 @@ object "Bootloader" { function l1MessengerPublishingCall() { let ptr := OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_BEGIN_BYTE() debugLog("Publishing batch data to L1", 0) + + setHook(VM_HOOK_PUBDATA_REQUESTED()) + // First slot (only last 4 bytes) -- selector mstore(ptr, {{PUBLISH_PUBDATA_SELECTOR}}) - // Second slot -- offset - mstore(add(ptr, 32), 32) - setHook(VM_HOOK_PUBDATA_REQUESTED()) + // Second slot is occupied by the address of the L2 DA validator. + // The operator can provide any one it wants. It will be the responsibility of the + // L1Messenger system contract to send the corresponding log to L1. + // + // Third slot -- offset. The correct value must be equal to 64 + assertEq(mload(add(ptr, 64)), 64, "offset for L1Messenger is not 64") + // Third slot -- length of pubdata - let len := mload(add(ptr, 64)) - // 4 bytes for selector, 32 bytes for array offset and 32 bytes for array length - let fullLen := add(len, 68) + let len := mload(add(ptr, 96)) + // 4 bytes for selector, 32 bytes for ABI-encoded L2 DA validator address, + // 32 bytes for array offset and 32 bytes for array length + let fullLen := add(len, 100) // ptr + 28 because the function selector only takes up the last 4 bytes in the first slot. let success := call( @@ -3892,17 +3900,17 @@ object "Bootloader" { /// @dev Log key used by Executor.sol for processing. See Constants.sol::SystemLogKey enum function chainedPriorityTxnHashLogKey() -> ret { - ret := 5 + ret := 2 } /// @dev Log key used by Executor.sol for processing. See Constants.sol::SystemLogKey enum function numberOfLayer1TxsLogKey() -> ret { - ret := 6 + ret := 3 } /// @dev Log key used by Executor.sol for processing. See Constants.sol::SystemLogKey enum function protocolUpgradeTxHashKey() -> ret { - ret := 13 + ret := 7 } //////////////////////////////////////////////////////////////////////////// diff --git a/system-contracts/bootloader/tests/bootloader/bootloader_test.yul b/system-contracts/bootloader/tests/bootloader/bootloader_test.yul index ed506fcea..9e620fccf 100644 --- a/system-contracts/bootloader/tests/bootloader/bootloader_test.yul +++ b/system-contracts/bootloader/tests/bootloader/bootloader_test.yul @@ -105,7 +105,7 @@ function TEST_systemLogKeys() { let numberOfLayer1TxsLogKey := numberOfLayer1TxsLogKey() let protocolUpgradeTxHashKey := protocolUpgradeTxHashKey() - testing_assertEq(chainedPriorityTxnHashLogKey, 5, "Invalid priority txn hash log key") - testing_assertEq(numberOfLayer1TxsLogKey, 6, "Invalid num layer 1 txns log key") - testing_assertEq(protocolUpgradeTxHashKey, 13, "Invalid protocol upgrade txn hash log key") + testing_assertEq(chainedPriorityTxnHashLogKey, 3, "Invalid priority txn hash log key") + testing_assertEq(numberOfLayer1TxsLogKey, 4, "Invalid num layer 1 txns log key") + testing_assertEq(protocolUpgradeTxHashKey, 7, "Invalid protocol upgrade txn hash log key") } diff --git a/system-contracts/contracts/ComplexUpgrader.sol b/system-contracts/contracts/ComplexUpgrader.sol index a69545148..8c965d6a5 100644 --- a/system-contracts/contracts/ComplexUpgrader.sol +++ b/system-contracts/contracts/ComplexUpgrader.sol @@ -3,7 +3,8 @@ pragma solidity 0.8.24; import {IComplexUpgrader} from "./interfaces/IComplexUpgrader.sol"; -import {FORCE_DEPLOYER} from "./Constants.sol"; +import {ForceDeployment} from "./interfaces/IContractDeployer.sol"; +import {FORCE_DEPLOYER, DEPLOYER_SYSTEM_CONTRACT} from "./Constants.sol"; import {Unauthorized, AddressHasNoCode} from "./SystemContractErrors.sol"; /** @@ -13,17 +14,42 @@ import {Unauthorized, AddressHasNoCode} from "./SystemContractErrors.sol"; * this logic should be deployed into the user space and then this contract will delegatecall to the deployed contract. */ contract ComplexUpgrader is IComplexUpgrader { + /// @notice Ensures that only the `FORCE_DEPLOYER` can call the function. + /// @dev Note that it is vital to put this modifier at the start of *each* function, + /// since even temporary anauthorized access can be dangerous. + modifier onlyForceDeployer() { + // Note, that it is not + if (msg.sender != FORCE_DEPLOYER) { + revert Unauthorized(msg.sender); + } + _; + } + /// @notice Executes an upgrade process by delegating calls to another contract. /// @dev This function allows only the `FORCE_DEPLOYER` to initiate the upgrade. /// If the delegate call fails, the function will revert the transaction, returning the error message /// provided by the delegated contract. + /// @param _forceDeployments the list of initial deployments that should be performed before the upgrade. + /// They would typically, though not necessarily include the deployment of the upgrade implementation itself. /// @param _delegateTo the address of the contract to which the calls will be delegated /// @param _calldata the calldata to be delegate called in the `_delegateTo` contract - function upgrade(address _delegateTo, bytes calldata _calldata) external payable { - if (msg.sender != FORCE_DEPLOYER) { - revert Unauthorized(msg.sender); - } + function forceDeployAndUpgrade( + ForceDeployment[] calldata _forceDeployments, + address _delegateTo, + bytes calldata _calldata + ) external payable override onlyForceDeployer { + DEPLOYER_SYSTEM_CONTRACT.forceDeployOnAddresses(_forceDeployments); + upgrade(_delegateTo, _calldata); + } + + /// @notice Executes an upgrade process by delegating calls to another contract. + /// @dev This function allows only the `FORCE_DEPLOYER` to initiate the upgrade. + /// If the delegate call fails, the function will revert the transaction, returning the error message + /// provided by the delegated contract. + /// @param _delegateTo the address of the contract to which the calls will be delegated + /// @param _calldata the calldata to be delegate called in the `_delegateTo` contract + function upgrade(address _delegateTo, bytes calldata _calldata) public payable onlyForceDeployer { if (_delegateTo.code.length == 0) { revert AddressHasNoCode(_delegateTo); } diff --git a/system-contracts/contracts/Compressor.sol b/system-contracts/contracts/Compressor.sol index d4c52f989..f74b0a03d 100644 --- a/system-contracts/contracts/Compressor.sol +++ b/system-contracts/contracts/Compressor.sol @@ -117,7 +117,7 @@ contract Compressor is ICompressor, SystemContractBase { uint256 _enumerationIndexSize, bytes calldata _stateDiffs, bytes calldata _compressedStateDiffs - ) external onlyCallFrom(address(L1_MESSENGER_CONTRACT)) returns (bytes32 stateDiffHash) { + ) external view returns (bytes32 stateDiffHash) { // We do not enforce the operator to use the optimal, i.e. the minimally possible _enumerationIndexSize. // We do enforce however, that the _enumerationIndexSize is not larger than 8 bytes long, which is the // maximal ever possible size for enumeration index. diff --git a/system-contracts/contracts/Constants.sol b/system-contracts/contracts/Constants.sol index 00145480b..4d1d5dad8 100644 --- a/system-contracts/contracts/Constants.sol +++ b/system-contracts/contracts/Constants.sol @@ -8,12 +8,15 @@ import {IContractDeployer} from "./interfaces/IContractDeployer.sol"; import {IKnownCodesStorage} from "./interfaces/IKnownCodesStorage.sol"; import {IImmutableSimulator} from "./interfaces/IImmutableSimulator.sol"; import {IBaseToken} from "./interfaces/IBaseToken.sol"; +import {IBridgehub} from "./interfaces/IBridgehub.sol"; import {IL1Messenger} from "./interfaces/IL1Messenger.sol"; import {ISystemContext} from "./interfaces/ISystemContext.sol"; import {ICompressor} from "./interfaces/ICompressor.sol"; import {IComplexUpgrader} from "./interfaces/IComplexUpgrader.sol"; import {IBootloaderUtilities} from "./interfaces/IBootloaderUtilities.sol"; import {IPubdataChunkPublisher} from "./interfaces/IPubdataChunkPublisher.sol"; +import {IMessageRoot} from "./interfaces/IMessageRoot.sol"; +import {ICreate2Factory} from "./interfaces/ICreate2Factory.sol"; /// @dev All the system contracts introduced by ZKsync have their addresses /// started from 2^15 in order to avoid collision with Ethereum precompiles. @@ -24,10 +27,14 @@ uint160 constant SYSTEM_CONTRACTS_OFFSET = {{SYSTEM_CONTRACTS_OFFSET}}; // 2^15 /// mainnet. uint160 constant REAL_SYSTEM_CONTRACTS_OFFSET = 0x8000; + /// @dev All the system contracts must be located in the kernel space, /// i.e. their addresses must be below 2^16. uint160 constant MAX_SYSTEM_CONTRACT_ADDRESS = 0xffff; // 2^16 - 1 +/// @dev The offset from which the built-in, but user space contracts are located. +uint160 constant USER_CONTRACTS_OFFSET = MAX_SYSTEM_CONTRACT_ADDRESS + 1; + address constant ECRECOVER_SYSTEM_CONTRACT = address(0x01); address constant SHA256_SYSTEM_CONTRACT = address(0x02); address constant ECADD_SYSTEM_CONTRACT = address(0x06); @@ -67,6 +74,17 @@ address constant MSG_VALUE_SYSTEM_CONTRACT = address(SYSTEM_CONTRACTS_OFFSET + 0 IBaseToken constant BASE_TOKEN_SYSTEM_CONTRACT = IBaseToken(address(SYSTEM_CONTRACTS_OFFSET + 0x0a)); IBaseToken constant REAL_BASE_TOKEN_SYSTEM_CONTRACT = IBaseToken(address(REAL_SYSTEM_CONTRACTS_OFFSET + 0x0a)); +ICreate2Factory constant L2_CREATE2_FACTORY = ICreate2Factory(address(USER_CONTRACTS_OFFSET)); +address constant L2_ASSET_ROUTER = address(USER_CONTRACTS_OFFSET + 0x03); +IBridgehub constant L2_BRIDGE_HUB = IBridgehub(address(USER_CONTRACTS_OFFSET + 0x02)); +address constant L2_NATIVE_TOKEN_VAULT_ADDR = address(USER_CONTRACTS_OFFSET + 0x04); +IMessageRoot constant L2_MESSAGE_ROOT = IMessageRoot(address(USER_CONTRACTS_OFFSET + 0x05)); +// Note, that on its own this contract does not provide much functionality, but having it on a constant address +// serves as a convenient storage for its bytecode to be accessible via `extcodehash`. +address constant SLOAD_CONTRACT_ADDRESS = address(USER_CONTRACTS_OFFSET + 0x06); + +address constant WRAPPED_BASE_TOKEN_IMPL_ADDRESS = address(USER_CONTRACTS_OFFSET + 0x07); + // Hardcoded because even for tests we should keep the address. (Instead `SYSTEM_CONTRACTS_OFFSET + 0x10`) // Precompile call depends on it. // And we don't want to mock this contract. @@ -107,18 +125,15 @@ uint256 constant STATE_DIFF_ENTRY_SIZE = 272; enum SystemLogKey { L2_TO_L1_LOGS_TREE_ROOT_KEY, - TOTAL_L2_TO_L1_PUBDATA_KEY, - STATE_DIFF_HASH_KEY, PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY, - PREV_BATCH_HASH_KEY, CHAINED_PRIORITY_TXN_HASH_KEY, NUMBER_OF_LAYER_1_TXS_KEY, - BLOB_ONE_HASH_KEY, - BLOB_TWO_HASH_KEY, - BLOB_THREE_HASH_KEY, - BLOB_FOUR_HASH_KEY, - BLOB_FIVE_HASH_KEY, - BLOB_SIX_HASH_KEY, + // Note, that it is important that `PREV_BATCH_HASH_KEY` has position + // `4` since it is the same as it was in the previous protocol version and + // it is the only one that is emitted before the system contracts are upgraded. + PREV_BATCH_HASH_KEY, + L2_DA_VALIDATOR_OUTPUT_HASH_KEY, + USED_L2_DA_VALIDATOR_ADDRESS_KEY, EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY } diff --git a/system-contracts/contracts/ContractDeployer.sol b/system-contracts/contracts/ContractDeployer.sol index 8e9bda169..ce87a58a6 100644 --- a/system-contracts/contracts/ContractDeployer.sol +++ b/system-contracts/contracts/ContractDeployer.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.24; import {ImmutableData} from "./interfaces/IImmutableSimulator.sol"; -import {IContractDeployer} from "./interfaces/IContractDeployer.sol"; +import {IContractDeployer, ForceDeployment} 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, BASE_TOKEN_SYSTEM_CONTRACT, IMMUTABLE_SIMULATOR_SYSTEM_CONTRACT, COMPLEX_UPGRADER_CONTRACT} from "./Constants.sol"; import {Utils} from "./libraries/Utils.sol"; @@ -198,20 +198,6 @@ contract ContractDeployer is IContractDeployer, SystemContractBase { return newAddress; } - /// @notice A struct that describes a forced deployment on an address - struct ForceDeployment { - // The bytecode hash to put on an address - bytes32 bytecodeHash; - // The address on which to deploy the bytecodehash to - address newAddress; - // Whether to run the constructor on the force deployment - bool callConstructor; - // The value with which to initialize a contract - uint256 value; - // The constructor calldata - bytes input; - } - /// @notice The method that can be used to forcefully deploy a contract. /// @param _deployment Information about the forced deployment. /// @param _sender The `msg.sender` inside the constructor call. @@ -240,7 +226,7 @@ contract ContractDeployer is IContractDeployer, SystemContractBase { /// @notice This method is to be used only during an upgrade to set bytecodes on specific addresses. /// @dev We do not require `onlySystemCall` here, since the method is accessible only /// by `FORCE_DEPLOYER`. - function forceDeployOnAddresses(ForceDeployment[] calldata _deployments) external payable { + function forceDeployOnAddresses(ForceDeployment[] calldata _deployments) external payable override { if (msg.sender != FORCE_DEPLOYER && msg.sender != address(COMPLEX_UPGRADER_CONTRACT)) { revert Unauthorized(msg.sender); } diff --git a/system-contracts/contracts/Create2Factory.sol b/system-contracts/contracts/Create2Factory.sol index 868de66fa..8541e3406 100644 --- a/system-contracts/contracts/Create2Factory.sol +++ b/system-contracts/contracts/Create2Factory.sol @@ -5,11 +5,12 @@ pragma solidity 0.8.24; import {REAL_DEPLOYER_SYSTEM_CONTRACT} from "./Constants.sol"; import {EfficientCall} from "./libraries/EfficientCall.sol"; import {IContractDeployer} from "./interfaces/IContractDeployer.sol"; +import {ICreate2Factory} from "./interfaces/ICreate2Factory.sol"; /// @custom:security-contact security@matterlabs.dev /// @author Matter Labs /// @notice The contract that can be used for deterministic contract deployment. -contract Create2Factory { +contract Create2Factory is ICreate2Factory { /// @notice Function that calls the `create2` method of the `ContractDeployer` contract. /// @dev This function accepts the same parameters as the `create2` function of the ContractDeployer system contract, /// so that we could efficiently relay the calldata. diff --git a/system-contracts/contracts/L1Messenger.sol b/system-contracts/contracts/L1Messenger.sol index f9578b19b..1f0cfe8e5 100644 --- a/system-contracts/contracts/L1Messenger.sol +++ b/system-contracts/contracts/L1Messenger.sol @@ -2,13 +2,15 @@ pragma solidity 0.8.24; -import {IL1Messenger, L2ToL1Log, L2_L1_LOGS_TREE_DEFAULT_LEAF_HASH, L2_TO_L1_LOG_SERIALIZE_SIZE, STATE_DIFF_COMPRESSION_VERSION_NUMBER} from "./interfaces/IL1Messenger.sol"; +import {IL1Messenger, L2ToL1Log, L2_L1_LOGS_TREE_DEFAULT_LEAF_HASH, L2_TO_L1_LOG_SERIALIZE_SIZE} from "./interfaces/IL1Messenger.sol"; + import {SystemContractBase} from "./abstract/SystemContractBase.sol"; import {SystemContractHelper} from "./libraries/SystemContractHelper.sol"; import {EfficientCall} from "./libraries/EfficientCall.sol"; import {Utils} from "./libraries/Utils.sol"; -import {SystemLogKey, SYSTEM_CONTEXT_CONTRACT, KNOWN_CODE_STORAGE_CONTRACT, COMPRESSOR_CONTRACT, STATE_DIFF_ENTRY_SIZE, L2_TO_L1_LOGS_MERKLE_TREE_LEAVES, PUBDATA_CHUNK_PUBLISHER, COMPUTATIONAL_PRICE_FOR_PUBDATA} from "./Constants.sol"; +import {SystemLogKey, SYSTEM_CONTEXT_CONTRACT, KNOWN_CODE_STORAGE_CONTRACT, L2_TO_L1_LOGS_MERKLE_TREE_LEAVES, COMPUTATIONAL_PRICE_FOR_PUBDATA, L2_MESSAGE_ROOT} from "./Constants.sol"; import {ReconstructionMismatch, PubdataField} from "./SystemContractErrors.sol"; +import {IL2DAValidator} from "./interfaces/IL2DAValidator.sol"; /** * @author Matter Labs @@ -182,9 +184,10 @@ contract L1Messenger is IL1Messenger, SystemContractBase { emit BytecodeL1PublicationRequested(_bytecodeHash); } - /// @notice Verifies that the {_totalL2ToL1PubdataAndStateDiffs} reflects what occurred within the L1Batch and that + /// @notice Verifies that the {_operatorInput} reflects what occurred within the L1Batch and that /// the compressed statediffs are equivalent to the full state diffs. - /// @param _totalL2ToL1PubdataAndStateDiffs The total pubdata and uncompressed state diffs of transactions that were + /// @param _l2DAValidator the address of the l2 da validator + /// @param _operatorInput The total pubdata and uncompressed state diffs of transactions that were /// processed in the current L1 Batch. Pubdata consists of L2 to L1 Logs, messages, deployed bytecode, and state diffs. /// @dev Function that should be called exactly once per L1 Batch by the bootloader. /// @dev Checks that totalL2ToL1Pubdata is strictly packed data that should to be published to L1. @@ -193,12 +196,71 @@ contract L1Messenger is IL1Messenger, SystemContractBase { /// @dev Performs calculation of L2ToL1Logs merkle tree root, "sends" such root and keccak256(totalL2ToL1Pubdata) /// to L1 using low-level (VM) L2Log. function publishPubdataAndClearState( - bytes calldata _totalL2ToL1PubdataAndStateDiffs + address _l2DAValidator, + bytes calldata _operatorInput ) external onlyCallFromBootloader { uint256 calldataPtr = 0; + // Check function sig and data in the other hashes + // 4 + 32 + 32 + 32 + 32 + 32 + 32 + // 4 bytes for L2 DA Validator `validatePubdata` function selector + // 32 bytes for rolling hash of user L2 -> L1 logs + // 32 bytes for root hash of user L2 -> L1 logs + // 32 bytes for hash of messages + // 32 bytes for hash of uncompressed bytecodes sent to L1 + // Operator data: 32 bytes for offset + // 32 bytes for length + + bytes4 inputL2DAValidatePubdataFunctionSig = bytes4(_operatorInput[calldataPtr:calldataPtr + 4]); + if (inputL2DAValidatePubdataFunctionSig != IL2DAValidator.validatePubdata.selector) { + revert ReconstructionMismatch( + PubdataField.InputDAFunctionSig, + bytes32(IL2DAValidator.validatePubdata.selector), + bytes32(inputL2DAValidatePubdataFunctionSig) + ); + } + calldataPtr += 4; + + bytes32 inputChainedLogsHash = bytes32(_operatorInput[calldataPtr:calldataPtr + 32]); + if (inputChainedLogsHash != chainedLogsHash) { + revert ReconstructionMismatch(PubdataField.InputLogsHash, chainedLogsHash, inputChainedLogsHash); + } + calldataPtr += 32; + + // Check happens below after we reconstruct the logs root hash + bytes32 inputChainedLogsRootHash = bytes32(_operatorInput[calldataPtr:calldataPtr + 32]); + calldataPtr += 32; + + bytes32 inputChainedMsgsHash = bytes32(_operatorInput[calldataPtr:calldataPtr + 32]); + if (inputChainedMsgsHash != chainedMessagesHash) { + revert ReconstructionMismatch(PubdataField.InputMsgsHash, chainedMessagesHash, inputChainedMsgsHash); + } + calldataPtr += 32; + + bytes32 inputChainedBytecodesHash = bytes32(_operatorInput[calldataPtr:calldataPtr + 32]); + if (inputChainedBytecodesHash != chainedL1BytecodesRevealDataHash) { + revert ReconstructionMismatch( + PubdataField.InputBytecodeHash, + chainedL1BytecodesRevealDataHash, + inputChainedBytecodesHash + ); + } + calldataPtr += 32; + + uint256 offset = uint256(bytes32(_operatorInput[calldataPtr:calldataPtr + 32])); + // The length of the pubdata input should be stored right next to the calldata. + // We need to change offset by 32 - 4 = 28 bytes, since 32 bytes is the length of the offset + // itself and the 4 bytes are the selector which is not included inside the offset. + if (offset != calldataPtr + 28) { + revert ReconstructionMismatch(PubdataField.Offset, bytes32(calldataPtr + 28), bytes32(offset)); + } + uint256 length = uint256(bytes32(_operatorInput[calldataPtr + 32:calldataPtr + 64])); + + // Shift calldata ptr past the pubdata offset and len + calldataPtr += 64; + /// Check logs - uint32 numberOfL2ToL1Logs = uint32(bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4])); + uint32 numberOfL2ToL1Logs = uint32(bytes4(_operatorInput[calldataPtr:calldataPtr + 4])); if (numberOfL2ToL1Logs > L2_TO_L1_LOGS_MERKLE_TREE_LEAVES) { revert ReconstructionMismatch( PubdataField.NumberOfLogs, @@ -208,11 +270,20 @@ contract L1Messenger is IL1Messenger, SystemContractBase { } calldataPtr += 4; + // We need to ensure that length is enough to read all logs + if (length < 4 + numberOfL2ToL1Logs * L2_TO_L1_LOG_SERIALIZE_SIZE) { + revert ReconstructionMismatch( + PubdataField.Length, + bytes32(4 + numberOfL2ToL1Logs * L2_TO_L1_LOG_SERIALIZE_SIZE), + bytes32(length) + ); + } + bytes32[] memory l2ToL1LogsTreeArray = new bytes32[](L2_TO_L1_LOGS_MERKLE_TREE_LEAVES); bytes32 reconstructedChainedLogsHash = bytes32(0); for (uint256 i = 0; i < numberOfL2ToL1Logs; ++i) { bytes32 hashedLog = EfficientCall.keccak( - _totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + L2_TO_L1_LOG_SERIALIZE_SIZE] + _operatorInput[calldataPtr:calldataPtr + L2_TO_L1_LOG_SERIALIZE_SIZE] ); calldataPtr += L2_TO_L1_LOG_SERIALIZE_SIZE; l2ToL1LogsTreeArray[i] = hashedLog; @@ -233,114 +304,40 @@ contract L1Messenger is IL1Messenger, SystemContractBase { ); } } - bytes32 l2ToL1LogsTreeRoot = l2ToL1LogsTreeArray[0]; - - /// Check messages - uint32 numberOfMessages = uint32(bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4])); - calldataPtr += 4; - bytes32 reconstructedChainedMessagesHash = bytes32(0); - for (uint256 i = 0; i < numberOfMessages; ++i) { - uint32 currentMessageLength = uint32(bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4])); - calldataPtr += 4; - bytes32 hashedMessage = EfficientCall.keccak( - _totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + currentMessageLength] - ); - calldataPtr += currentMessageLength; - reconstructedChainedMessagesHash = keccak256(abi.encode(reconstructedChainedMessagesHash, hashedMessage)); - } - if (reconstructedChainedMessagesHash != chainedMessagesHash) { - revert ReconstructionMismatch(PubdataField.MsgHash, chainedMessagesHash, reconstructedChainedMessagesHash); - } + bytes32 localLogsRootHash = l2ToL1LogsTreeArray[0]; - /// Check bytecodes - uint32 numberOfBytecodes = uint32(bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4])); - calldataPtr += 4; - bytes32 reconstructedChainedL1BytecodesRevealDataHash = bytes32(0); - for (uint256 i = 0; i < numberOfBytecodes; ++i) { - uint32 currentBytecodeLength = uint32( - bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4]) - ); - calldataPtr += 4; - reconstructedChainedL1BytecodesRevealDataHash = keccak256( - abi.encode( - reconstructedChainedL1BytecodesRevealDataHash, - Utils.hashL2Bytecode( - _totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + currentBytecodeLength] - ) - ) - ); - calldataPtr += currentBytecodeLength; - } - if (reconstructedChainedL1BytecodesRevealDataHash != chainedL1BytecodesRevealDataHash) { - revert ReconstructionMismatch( - PubdataField.Bytecode, - chainedL1BytecodesRevealDataHash, - reconstructedChainedL1BytecodesRevealDataHash - ); - } + bytes32 aggregatedRootHash = L2_MESSAGE_ROOT.getAggregatedRoot(); + bytes32 fullRootHash = keccak256(bytes.concat(localLogsRootHash, aggregatedRootHash)); - /// Check State Diffs - /// encoding is as follows: - /// header (1 byte version, 3 bytes total len of compressed, 1 byte enumeration index size) - /// body (`compressedStateDiffSize` bytes, 4 bytes number of state diffs, `numberOfStateDiffs` * `STATE_DIFF_ENTRY_SIZE` bytes for the uncompressed state diffs) - /// encoded state diffs: [20bytes address][32bytes key][32bytes derived key][8bytes enum index][32bytes initial value][32bytes final value] - if ( - uint256(uint8(bytes1(_totalL2ToL1PubdataAndStateDiffs[calldataPtr]))) != - STATE_DIFF_COMPRESSION_VERSION_NUMBER - ) { - revert ReconstructionMismatch( - PubdataField.StateDiffCompressionVersion, - bytes32(STATE_DIFF_COMPRESSION_VERSION_NUMBER), - bytes32(uint256(uint8(bytes1(_totalL2ToL1PubdataAndStateDiffs[calldataPtr])))) - ); + if (inputChainedLogsRootHash != localLogsRootHash) { + revert ReconstructionMismatch(PubdataField.InputLogsRootHash, localLogsRootHash, inputChainedLogsRootHash); } - ++calldataPtr; - - uint24 compressedStateDiffSize = uint24(bytes3(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 3])); - calldataPtr += 3; - - uint8 enumerationIndexSize = uint8(bytes1(_totalL2ToL1PubdataAndStateDiffs[calldataPtr])); - ++calldataPtr; - bytes calldata compressedStateDiffs = _totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + - compressedStateDiffSize]; - calldataPtr += compressedStateDiffSize; - - bytes calldata totalL2ToL1Pubdata = _totalL2ToL1PubdataAndStateDiffs[:calldataPtr]; - - uint32 numberOfStateDiffs = uint32(bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4])); - calldataPtr += 4; - - bytes calldata stateDiffs = _totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + - (numberOfStateDiffs * STATE_DIFF_ENTRY_SIZE)]; - calldataPtr += numberOfStateDiffs * STATE_DIFF_ENTRY_SIZE; - - bytes32 stateDiffHash = COMPRESSOR_CONTRACT.verifyCompressedStateDiffs( - numberOfStateDiffs, - enumerationIndexSize, - stateDiffs, - compressedStateDiffs - ); - - /// Check for calldata strict format - if (calldataPtr != _totalL2ToL1PubdataAndStateDiffs.length) { - revert ReconstructionMismatch( - PubdataField.ExtraData, - bytes32(calldataPtr), - bytes32(_totalL2ToL1PubdataAndStateDiffs.length) - ); + bytes32 l2DAValidatorOutputhash = bytes32(0); + if (_l2DAValidator != address(0)) { + bytes memory returnData = EfficientCall.call({ + _gas: gasleft(), + _address: _l2DAValidator, + _value: 0, + _data: _operatorInput, + _isSystem: false + }); + + l2DAValidatorOutputhash = abi.decode(returnData, (bytes32)); } - PUBDATA_CHUNK_PUBLISHER.chunkAndPublishPubdata(totalL2ToL1Pubdata); - /// Native (VM) L2 to L1 log - SystemContractHelper.toL1(true, bytes32(uint256(SystemLogKey.L2_TO_L1_LOGS_TREE_ROOT_KEY)), l2ToL1LogsTreeRoot); + SystemContractHelper.toL1(true, bytes32(uint256(SystemLogKey.L2_TO_L1_LOGS_TREE_ROOT_KEY)), fullRootHash); + SystemContractHelper.toL1( + true, + bytes32(uint256(SystemLogKey.USED_L2_DA_VALIDATOR_ADDRESS_KEY)), + bytes32(uint256(uint160(_l2DAValidator))) + ); SystemContractHelper.toL1( true, - bytes32(uint256(SystemLogKey.TOTAL_L2_TO_L1_PUBDATA_KEY)), - EfficientCall.keccak(totalL2ToL1Pubdata) + bytes32(uint256(SystemLogKey.L2_DA_VALIDATOR_OUTPUT_HASH_KEY)), + l2DAValidatorOutputhash ); - SystemContractHelper.toL1(true, bytes32(uint256(SystemLogKey.STATE_DIFF_HASH_KEY)), stateDiffHash); /// Clear logs state chainedLogsHash = bytes32(0); diff --git a/system-contracts/contracts/L2GatewayUpgrade.sol b/system-contracts/contracts/L2GatewayUpgrade.sol new file mode 100644 index 000000000..e16629e77 --- /dev/null +++ b/system-contracts/contracts/L2GatewayUpgrade.sol @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {SystemContractHelper} from "./libraries/SystemContractHelper.sol"; +import {FixedForceDeploymentsData, ZKChainSpecificForceDeploymentsData} from "./interfaces/IL2GenesisUpgrade.sol"; + +import {L2GatewayUpgradeHelper} from "./L2GatewayUpgradeHelper.sol"; +import {ITransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {IL2SharedBridgeLegacy} from "./interfaces/IL2SharedBridgeLegacy.sol"; +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts-v4/token/ERC20/extensions/IERC20Metadata.sol"; +import {WRAPPED_BASE_TOKEN_IMPL_ADDRESS} from "./Constants.sol"; + +/// @dev Storage slot with the admin of the contract used for EIP1967 proxies (e.g., TUP, BeaconProxy, etc.). +bytes32 constant PROXY_ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + +/// @custom:security-contact security@matterlabs.dev +/// @author Matter Labs +/// @title L2GatewayUpgrade +/// @notice Facilitates the upgrade of the L2 protocol to a version that supports the gateway. +/// @dev This contract is neither predeployed nor a system contract. It resides in this folder due to overlapping functionality with `L2GenesisUpgrade` and to facilitate code reuse. +/// @dev During the upgrade process, this contract will be force-deployed onto the address of the `ComplexUpgrader` system contract, so +/// `this` will take the address of the `ComplexUpgrader`. This approach is used instead of delegate-calling `ComplexUpgrader` +/// to alleviate the need to predeploy the implementation. In the future this limitation will be removed with the new `ComplexUpgrader` +/// functionality. +/// @dev All the logic happens inside the constructor, since once the constructor execution is done, the normal `ComplexUpgrader` +/// bytecode will be deployed back in its place. +contract L2GatewayUpgrade { + /// @notice Initializes the `L2GatewayUpgrade` contract. + /// @dev This constructor is intended to be delegate-called by the `ComplexUpgrader` contract. + /// @param _ctmDeployer Address of the CTM Deployer contract. + /// @param _fixedForceDeploymentsData Encoded data for fixed force deployments. + /// @param _additionalForceDeploymentsData Encoded data for ZK-Chain specific force deployments. + constructor( + address _ctmDeployer, + bytes memory _fixedForceDeploymentsData, + bytes memory _additionalForceDeploymentsData + ) { + L2GatewayUpgradeHelper.performForceDeployedContractsInit( + _ctmDeployer, + _fixedForceDeploymentsData, + _additionalForceDeploymentsData + ); + + ZKChainSpecificForceDeploymentsData memory additionalForceDeploymentsData = abi.decode( + _additionalForceDeploymentsData, + (ZKChainSpecificForceDeploymentsData) + ); + + FixedForceDeploymentsData memory fixedForceDeploymentsData = abi.decode( + _fixedForceDeploymentsData, + (FixedForceDeploymentsData) + ); + + address l2LegacyBridgeAddress = additionalForceDeploymentsData.l2LegacySharedBridge; + if (l2LegacyBridgeAddress != address(0)) { + // Force upgrade the TransparentUpgradeableProxy for the legacy bridge. + forceUpgradeTransparentProxy( + l2LegacyBridgeAddress, + // We are sure that `impl` is deployed, since it is supposed to be included + // as part of the "usual" force deployments array. + fixedForceDeploymentsData.l2SharedBridgeLegacyImpl, + hex"" + ); + + // Force upgrade the UpgradeableBeacon proxy for the bridged standard ERC20. + forceUpgradeBeaconProxy( + address(IL2SharedBridgeLegacy(l2LegacyBridgeAddress).l2TokenBeacon()), + // We are sure that `impl` is deployed, since it is supposed to be included + // as part of the "usual" force deployments array. + fixedForceDeploymentsData.l2BridgedStandardERC20Impl + ); + } + + if (additionalForceDeploymentsData.predeployedL2WethAddress != address(0)) { + // Query the old data to avoid accidentally overwriting it. + string memory name = IERC20Metadata(additionalForceDeploymentsData.predeployedL2WethAddress).name(); + string memory symbol = IERC20Metadata(additionalForceDeploymentsData.predeployedL2WethAddress).symbol(); + + // Force upgrade the TransparentUpgradeableProxy for the predeployed L2 WETH. + forceUpgradeTransparentProxy( + additionalForceDeploymentsData.predeployedL2WethAddress, + WRAPPED_BASE_TOKEN_IMPL_ADDRESS, + L2GatewayUpgradeHelper.getWethInitData( + name, + symbol, + additionalForceDeploymentsData.baseTokenL1Address, + additionalForceDeploymentsData.baseTokenAssetId + ) + ); + } + } + + /// @notice Forces an upgrade of a TransparentUpgradeableProxy contract. + /// @dev Constructs the appropriate calldata for upgrading the proxy and executes the upgrade + /// by mimicCall-ing the admin of the proxy. + /// @param _proxyAddr Address of the TransparentUpgradeableProxy to upgrade. + /// @param _newImpl Address of the new implementation contract. + /// @param _additionalData Additional calldata to pass to the `upgradeToAndCall` function, if any. + function forceUpgradeTransparentProxy(address _proxyAddr, address _newImpl, bytes memory _additionalData) internal { + bytes memory upgradeData; + if (_additionalData.length > 0) { + upgradeData = abi.encodeCall(ITransparentUpgradeableProxy.upgradeToAndCall, (_newImpl, _additionalData)); + } else { + upgradeData = abi.encodeCall(ITransparentUpgradeableProxy.upgradeTo, (_newImpl)); + } + + // Retrieve the proxy admin address from the proxy's storage slot. + address proxyAdmin = address(uint160(uint256(SystemContractHelper.forcedSload(_proxyAddr, PROXY_ADMIN_SLOT)))); + + SystemContractHelper.mimicCallWithPropagatedRevert(_proxyAddr, proxyAdmin, upgradeData); + } + + /// @notice Forces an upgrade of an UpgradeableBeacon proxy contract. + /// @dev Constructs the appropriate calldata for upgrading the proxy and executes the upgrade + /// by mimicCall-ing the admin of the proxy. + /// @param _proxyAddr Address of the UpgradeableBeacon proxy to upgrade. + /// @param _newImpl Address of the new implementation contract. + function forceUpgradeBeaconProxy(address _proxyAddr, address _newImpl) internal { + bytes memory upgradeData = abi.encodeCall(UpgradeableBeacon.upgradeTo, (_newImpl)); + + // Retrieve the owner of the beacon. + address owner = UpgradeableBeacon(_proxyAddr).owner(); + + // Execute the upgrade via a low-level call, propagating any revert. + SystemContractHelper.mimicCallWithPropagatedRevert(_proxyAddr, owner, upgradeData); + } +} diff --git a/system-contracts/contracts/L2GatewayUpgradeHelper.sol b/system-contracts/contracts/L2GatewayUpgradeHelper.sol new file mode 100644 index 000000000..c9ebc2ac7 --- /dev/null +++ b/system-contracts/contracts/L2GatewayUpgradeHelper.sol @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {DEPLOYER_SYSTEM_CONTRACT, L2_BRIDGE_HUB, L2_ASSET_ROUTER, L2_MESSAGE_ROOT, L2_NATIVE_TOKEN_VAULT_ADDR} from "./Constants.sol"; +import {IContractDeployer, ForceDeployment} from "./interfaces/IContractDeployer.sol"; +import {SystemContractHelper} from "./libraries/SystemContractHelper.sol"; +import {FixedForceDeploymentsData, ZKChainSpecificForceDeploymentsData} from "./interfaces/IL2GenesisUpgrade.sol"; +import {IL2SharedBridgeLegacy} from "./interfaces/IL2SharedBridgeLegacy.sol"; +import {WRAPPED_BASE_TOKEN_IMPL_ADDRESS, L2_ASSET_ROUTER} from "./Constants.sol"; +import {IL2WrappedBaseToken} from "./interfaces/IL2WrappedBaseToken.sol"; + +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; + +/// @title L2GatewayUpgradeHelper +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice A helper library for initializing and managing force-deployed contracts during either the L2 gateway upgrade or +/// the genesis after the gateway protocol upgrade. +library L2GatewayUpgradeHelper { + /// @notice Initializes force-deployed contracts required for the L2 genesis upgrade. + /// @param _ctmDeployer Address of the CTM Deployer contract. + /// @param _fixedForceDeploymentsData Encoded data for forced deployment that + /// is the same for all the chains. + /// @param _additionalForceDeploymentsData Encoded data for force deployments that + /// is specific for each ZK Chain. + function performForceDeployedContractsInit( + address _ctmDeployer, + bytes memory _fixedForceDeploymentsData, + bytes memory _additionalForceDeploymentsData + ) internal { + // Decode and retrieve the force deployments data. + ForceDeployment[] memory forceDeployments = _getForceDeploymentsData( + _fixedForceDeploymentsData, + _additionalForceDeploymentsData + ); + + // Force deploy the contracts on specified addresses. + IContractDeployer(DEPLOYER_SYSTEM_CONTRACT).forceDeployOnAddresses{value: msg.value}(forceDeployments); + + // It is expected that either through the force deployments above + // or upon initialization, both the L2 deployment of BridgeHub, AssetRouter, and MessageRoot are deployed. + // However, there is still some follow-up finalization that needs to be done. + + // Retrieve the owner of the BridgeHub contract. + address bridgehubOwner = L2_BRIDGE_HUB.owner(); + + // Prepare calldata to set addresses in BridgeHub. + bytes memory data = abi.encodeCall( + L2_BRIDGE_HUB.setAddresses, + (L2_ASSET_ROUTER, _ctmDeployer, address(L2_MESSAGE_ROOT)) + ); + + // Execute the call to set addresses in BridgeHub. + (bool success, bytes memory returnData) = SystemContractHelper.mimicCall( + address(L2_BRIDGE_HUB), + bridgehubOwner, + data + ); + + // Revert with the original revert reason if the call failed. + if (!success) { + /// @dev Propagate the revert reason from the failed call. + assembly { + revert(add(returnData, 0x20), returndatasize()) + } + } + } + + /// @notice Retrieves and constructs the force deployments array. + /// @dev Decodes the provided force deployments data and organizes them into an array of `ForceDeployment` to + /// to execute. + /// @param _fixedForceDeploymentsData Encoded data for forced deployment that + /// is the same for all the chains. + /// @param _additionalForceDeploymentsData Encoded data for force deployments that + /// is specific for each ZK Chain. + /// @return forceDeployments An array of `ForceDeployment` structs containing deployment details. + function _getForceDeploymentsData( + bytes memory _fixedForceDeploymentsData, + bytes memory _additionalForceDeploymentsData + ) internal returns (ForceDeployment[] memory forceDeployments) { + // Decode the fixed and additional force deployments data. + FixedForceDeploymentsData memory fixedForceDeploymentsData = abi.decode( + _fixedForceDeploymentsData, + (FixedForceDeploymentsData) + ); + ZKChainSpecificForceDeploymentsData memory additionalForceDeploymentsData = abi.decode( + _additionalForceDeploymentsData, + (ZKChainSpecificForceDeploymentsData) + ); + + forceDeployments = new ForceDeployment[](4); + + // Configure the MessageRoot deployment. + forceDeployments[0] = ForceDeployment({ + bytecodeHash: fixedForceDeploymentsData.messageRootBytecodeHash, + newAddress: address(L2_MESSAGE_ROOT), + callConstructor: true, + value: 0, + // solhint-disable-next-line func-named-parameters + input: abi.encode(address(L2_BRIDGE_HUB)) + }); + + // Configure the BridgeHub deployment. + forceDeployments[1] = ForceDeployment({ + bytecodeHash: fixedForceDeploymentsData.bridgehubBytecodeHash, + newAddress: address(L2_BRIDGE_HUB), + callConstructor: true, + value: 0, + input: abi.encode( + fixedForceDeploymentsData.l1ChainId, + fixedForceDeploymentsData.aliasedL1Governance, + fixedForceDeploymentsData.maxNumberOfZKChains + ) + }); + + // Configure the AssetRouter deployment. + forceDeployments[2] = ForceDeployment({ + bytecodeHash: fixedForceDeploymentsData.l2AssetRouterBytecodeHash, + newAddress: address(L2_ASSET_ROUTER), + callConstructor: true, + value: 0, + // solhint-disable-next-line func-named-parameters + input: abi.encode( + fixedForceDeploymentsData.l1ChainId, + fixedForceDeploymentsData.eraChainId, + fixedForceDeploymentsData.l1AssetRouter, + additionalForceDeploymentsData.l2LegacySharedBridge, + additionalForceDeploymentsData.baseTokenAssetId, + fixedForceDeploymentsData.aliasedL1Governance + ) + }); + + // Ensure the WETH token is deployed and retrieve its address. + address wrappedBaseTokenAddress = _ensureWethToken({ + _predeployedWethToken: additionalForceDeploymentsData.predeployedL2WethAddress, + _aliasedL1Governance: fixedForceDeploymentsData.aliasedL1Governance, + _baseTokenL1Address: additionalForceDeploymentsData.baseTokenL1Address, + _baseTokenAssetId: additionalForceDeploymentsData.baseTokenAssetId, + _baseTokenName: additionalForceDeploymentsData.baseTokenName, + _baseTokenSymbol: additionalForceDeploymentsData.baseTokenSymbol + }); + + address deployedTokenBeacon; + bool contractsDeployedAlready; + if (additionalForceDeploymentsData.l2LegacySharedBridge != address(0)) { + // In production, the `fixedForceDeploymentsData.dangerousTestOnlyForcedBeacon` must always + // be equal to 0. It is only for simplifying testing. + if (fixedForceDeploymentsData.dangerousTestOnlyForcedBeacon == address(0)) { + deployedTokenBeacon = address( + IL2SharedBridgeLegacy(additionalForceDeploymentsData.l2LegacySharedBridge).l2TokenBeacon() + ); + } else { + deployedTokenBeacon = fixedForceDeploymentsData.dangerousTestOnlyForcedBeacon; + } + + contractsDeployedAlready = true; + } + + // Configure the Native Token Vault deployment. + forceDeployments[3] = ForceDeployment({ + bytecodeHash: fixedForceDeploymentsData.l2NtvBytecodeHash, + newAddress: L2_NATIVE_TOKEN_VAULT_ADDR, + callConstructor: true, + value: 0, + // solhint-disable-next-line func-named-parameters + input: abi.encode( + fixedForceDeploymentsData.l1ChainId, + fixedForceDeploymentsData.aliasedL1Governance, + fixedForceDeploymentsData.l2TokenProxyBytecodeHash, + additionalForceDeploymentsData.l2LegacySharedBridge, + deployedTokenBeacon, + contractsDeployedAlready, + wrappedBaseTokenAddress, + additionalForceDeploymentsData.baseTokenAssetId + ) + }); + } + + /// @notice Constructs the initialization calldata for the L2WrappedBaseToken. + /// @param _wrappedBaseTokenName The name of the wrapped base token. + /// @param _wrappedBaseTokenSymbol The symbol of the wrapped base token. + /// @param _baseTokenL1Address The L1 address of the base token. + /// @param _baseTokenAssetId The asset ID of the base token. + /// @return initData The encoded initialization calldata. + function getWethInitData( + string memory _wrappedBaseTokenName, + string memory _wrappedBaseTokenSymbol, + address _baseTokenL1Address, + bytes32 _baseTokenAssetId + ) internal pure returns (bytes memory initData) { + initData = abi.encodeCall( + IL2WrappedBaseToken.initializeV3, + (_wrappedBaseTokenName, _wrappedBaseTokenSymbol, L2_ASSET_ROUTER, _baseTokenL1Address, _baseTokenAssetId) + ); + } + + /// @notice Ensures that the WETH token is deployed. If not predeployed, deploys it. + /// @param _predeployedWethToken The potential address of the predeployed WETH token. + /// @param _aliasedL1Governance Address of the aliased L1 governance. + /// @param _baseTokenL1Address L1 address of the base token. + /// @param _baseTokenAssetId Asset ID of the base token. + /// @param _baseTokenName Name of the base token. + /// @param _baseTokenSymbol Symbol of the base token. + /// @return The address of the ensured WETH token. + function _ensureWethToken( + address _predeployedWethToken, + address _aliasedL1Governance, + address _baseTokenL1Address, + bytes32 _baseTokenAssetId, + string memory _baseTokenName, + string memory _baseTokenSymbol + ) private returns (address) { + if (_predeployedWethToken != address(0)) { + return _predeployedWethToken; + } + + string memory wrappedBaseTokenName = string.concat("Wrapped ", _baseTokenName); + string memory wrappedBaseTokenSymbol = string.concat("W", _baseTokenSymbol); + + bytes memory initData = getWethInitData( + wrappedBaseTokenName, + wrappedBaseTokenSymbol, + _baseTokenL1Address, + _baseTokenAssetId + ); + + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy{salt: bytes32(0)}( + WRAPPED_BASE_TOKEN_IMPL_ADDRESS, + _aliasedL1Governance, + initData + ); + + return address(proxy); + } +} diff --git a/system-contracts/contracts/L2GenesisUpgrade.sol b/system-contracts/contracts/L2GenesisUpgrade.sol new file mode 100644 index 000000000..00a22c799 --- /dev/null +++ b/system-contracts/contracts/L2GenesisUpgrade.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {SYSTEM_CONTEXT_CONTRACT} from "./Constants.sol"; +import {ISystemContext} from "./interfaces/ISystemContext.sol"; +import {InvalidChainId} from "contracts/SystemContractErrors.sol"; +import {IL2GenesisUpgrade} from "./interfaces/IL2GenesisUpgrade.sol"; + +import {L2GatewayUpgradeHelper} from "./L2GatewayUpgradeHelper.sol"; + +/// @custom:security-contact security@matterlabs.dev +/// @author Matter Labs +/// @notice The l2 component of the genesis upgrade. +contract L2GenesisUpgrade is IL2GenesisUpgrade { + /// @notice The function that is delegateCalled from the complex upgrader. + /// @dev It is used to set the chainId and to deploy the force deployments. + /// @param _chainId the chain id + /// @param _ctmDeployer the address of the ctm deployer + /// @param _fixedForceDeploymentsData the force deployments data + /// @param _additionalForceDeploymentsData the additional force deployments data + function genesisUpgrade( + uint256 _chainId, + address _ctmDeployer, + bytes calldata _fixedForceDeploymentsData, + bytes calldata _additionalForceDeploymentsData + ) external payable { + if (_chainId == 0) { + revert InvalidChainId(); + } + ISystemContext(SYSTEM_CONTEXT_CONTRACT).setChainId(_chainId); + + L2GatewayUpgradeHelper.performForceDeployedContractsInit( + _ctmDeployer, + _fixedForceDeploymentsData, + _additionalForceDeploymentsData + ); + + emit UpgradeComplete(_chainId); + } +} diff --git a/system-contracts/contracts/MsgValueSimulator.sol b/system-contracts/contracts/MsgValueSimulator.sol index 5fcd0f2d9..fe74f8683 100644 --- a/system-contracts/contracts/MsgValueSimulator.sol +++ b/system-contracts/contracts/MsgValueSimulator.sol @@ -20,9 +20,9 @@ import {InvalidCall} from "./SystemContractErrors.sol"; contract MsgValueSimulator is SystemContractBase { /// @notice Extract value, isSystemCall and to from the extraAbi params. /// @dev The contract accepts value, the callee and whether the call should be a system one via its ABI params. - /// @dev The first ABI param contains the value in the [0..127] bits. The 128th contains - /// the flag whether or not the call should be a system one. - /// The second ABI params contains the callee. + /// @dev The first ABI param contains the value. The second one contains + /// the address to call, while the third one contains whether the `systemCall` flag should + /// be used (this can be helpful when e.g. calling `ContractDeployer`). function _getAbiParams() internal view returns (uint256 value, bool isSystemCall, address to) { value = SystemContractHelper.getExtraAbiData(0); uint256 addressAsUint = SystemContractHelper.getExtraAbiData(1); diff --git a/system-contracts/contracts/PubdataChunkPublisher.sol b/system-contracts/contracts/PubdataChunkPublisher.sol index 9402f05a6..7c2abf2e1 100644 --- a/system-contracts/contracts/PubdataChunkPublisher.sol +++ b/system-contracts/contracts/PubdataChunkPublisher.sol @@ -2,9 +2,7 @@ pragma solidity 0.8.24; import {IPubdataChunkPublisher} from "./interfaces/IPubdataChunkPublisher.sol"; -import {SystemContractBase} from "./abstract/SystemContractBase.sol"; -import {L1_MESSENGER_CONTRACT, BLOB_SIZE_BYTES, MAX_NUMBER_OF_BLOBS, SystemLogKey} from "./Constants.sol"; -import {SystemContractHelper} from "./libraries/SystemContractHelper.sol"; +import {BLOB_SIZE_BYTES, MAX_NUMBER_OF_BLOBS} from "./Constants.sol"; import {TooMuchPubdata} from "./SystemContractErrors.sol"; /** @@ -12,21 +10,23 @@ import {TooMuchPubdata} from "./SystemContractErrors.sol"; * @custom:security-contact security@matterlabs.dev * @notice Smart contract for chunking pubdata into the appropriate size for EIP-4844 blobs. */ -contract PubdataChunkPublisher is IPubdataChunkPublisher, SystemContractBase { +contract PubdataChunkPublisher is IPubdataChunkPublisher { /// @notice Chunks pubdata into pieces that can fit into blobs. /// @param _pubdata The total l2 to l1 pubdata that will be sent via L1 blobs. /// @dev Note: This is an early implementation, in the future we plan to support up to 16 blobs per l1 batch. - /// @dev We always publish 6 system logs even if our pubdata fits into a single blob. This makes processing logs on L1 easier. - function chunkAndPublishPubdata(bytes calldata _pubdata) external onlyCallFrom(address(L1_MESSENGER_CONTRACT)) { + function chunkPubdataToBlobs(bytes calldata _pubdata) external pure returns (bytes32[] memory blobLinearHashes) { if (_pubdata.length > BLOB_SIZE_BYTES * MAX_NUMBER_OF_BLOBS) { revert TooMuchPubdata(BLOB_SIZE_BYTES * MAX_NUMBER_OF_BLOBS, _pubdata.length); } - bytes32[] memory blobHashes = new bytes32[](MAX_NUMBER_OF_BLOBS); + // `+BLOB_SIZE_BYTES-1` is used to round up the division. + uint256 blobCount = (_pubdata.length + BLOB_SIZE_BYTES - 1) / BLOB_SIZE_BYTES; - // We allocate to the full size of MAX_NUMBER_OF_BLOBS * BLOB_SIZE_BYTES because we need to pad + blobLinearHashes = new bytes32[](blobCount); + + // We allocate to the full size of blobCount * BLOB_SIZE_BYTES because we need to pad // the data on the right with 0s if it doesn't take up the full blob - bytes memory totalBlobs = new bytes(BLOB_SIZE_BYTES * MAX_NUMBER_OF_BLOBS); + bytes memory totalBlobs = new bytes(BLOB_SIZE_BYTES * blobCount); assembly { // The pointer to the allocated memory above. We skip 32 bytes to avoid overwriting the length. @@ -34,15 +34,9 @@ contract PubdataChunkPublisher is IPubdataChunkPublisher, SystemContractBase { calldatacopy(ptr, _pubdata.offset, _pubdata.length) } - for (uint256 i = 0; i < MAX_NUMBER_OF_BLOBS; ++i) { + for (uint256 i = 0; i < blobCount; ++i) { uint256 start = BLOB_SIZE_BYTES * i; - // We break if the pubdata isn't enough to cover all 6 blobs. On L1 it is expected that the hash - // will be bytes32(0) if a blob isn't going to be used. - if (start >= _pubdata.length) { - break; - } - bytes32 blobHash; assembly { // The pointer to the allocated memory above skipping the length. @@ -50,15 +44,7 @@ contract PubdataChunkPublisher is IPubdataChunkPublisher, SystemContractBase { blobHash := keccak256(add(ptr, start), BLOB_SIZE_BYTES) } - blobHashes[i] = blobHash; - } - - for (uint8 i = 0; i < MAX_NUMBER_OF_BLOBS; ++i) { - SystemContractHelper.toL1( - true, - bytes32(uint256(SystemLogKey(i + uint256(SystemLogKey.BLOB_ONE_HASH_KEY)))), - blobHashes[i] - ); + blobLinearHashes[i] = blobHash; } } } diff --git a/system-contracts/contracts/SloadContract.sol b/system-contracts/contracts/SloadContract.sol new file mode 100644 index 000000000..23a80ae7d --- /dev/null +++ b/system-contracts/contracts/SloadContract.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @title SloadContract +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice This contract provides a method to read values from arbitrary storage slots +/// @dev It is used by the `SystemContractHelper` library to help system contracts read +/// arbitrary slots of contracts. +contract SloadContract { + /// @notice Reads the value stored at a specific storage slot + /// @param slot The storage slot number to read from + /// @return value The value stored at the specified slot as a bytes32 type + function sload(bytes32 slot) external view returns (bytes32 value) { + assembly { + // sload retrieves the value at the given storage slot + value := sload(slot) + } + } +} diff --git a/system-contracts/contracts/SystemContext.sol b/system-contracts/contracts/SystemContext.sol index 4763b4153..3e989bdda 100644 --- a/system-contracts/contracts/SystemContext.sol +++ b/system-contracts/contracts/SystemContext.sol @@ -1,14 +1,13 @@ // SPDX-License-Identifier: MIT -// solhint-disable reason-string, gas-custom-errors - pragma solidity 0.8.24; import {ISystemContext} from "./interfaces/ISystemContext.sol"; import {SystemContractBase} from "./abstract/SystemContractBase.sol"; import {ISystemContextDeprecated} from "./interfaces/ISystemContextDeprecated.sol"; import {SystemContractHelper} from "./libraries/SystemContractHelper.sol"; -import {BOOTLOADER_FORMAL_ADDRESS, SystemLogKey} from "./Constants.sol"; +import {BOOTLOADER_FORMAL_ADDRESS, SystemLogKey, COMPLEX_UPGRADER_CONTRACT} from "./Constants.sol"; +import {InconsistentNewBatchTimestamp, InvalidNewL2BlockNumber, IncorrectVirtualBlockInsideMiniblock, IncorrectSameL2BlockPrevBlockHash, IncorrectSameL2BlockTimestamp, CannotReuseL2BlockNumberFromPreviousBatch, NoVirtualBlocks, L2BlockAndBatchTimestampMismatch, UpgradeTransactionMustBeFirst, L2BlockNumberZero, PreviousL2BlockHashIsIncorrect, CannotInitializeFirstVirtualBlock, IncorrectL2BlockHash, NonMonotonicL2BlockTimestamp, CurrentBatchNumberMustBeGreaterThanZero, TimestampsShouldBeIncremental, ProvidedBatchNumberIsNotCorrect} from "contracts/SystemContractErrors.sol"; /** * @author Matter Labs @@ -85,7 +84,7 @@ contract SystemContext is ISystemContext, ISystemContextDeprecated, SystemContra /// @notice Set the chainId origin. /// @param _newChainId The chainId - function setChainId(uint256 _newChainId) external onlyCallFromForceDeployer { + function setChainId(uint256 _newChainId) external onlyCallFrom(address(COMPLEX_UPGRADER_CONTRACT)) { chainId = _newChainId; } @@ -245,14 +244,20 @@ contract SystemContext is ISystemContext, ISystemContextDeprecated, SystemContra /// @param _expectedPrevL2BlockHash The expected hash of the previous L2 block. /// @param _isFirstInBatch Whether this method is called for the first time in the batch. function _upgradeL2Blocks(uint128 _l2BlockNumber, bytes32 _expectedPrevL2BlockHash, bool _isFirstInBatch) internal { - require(_isFirstInBatch, "Upgrade transaction must be first"); + if (!_isFirstInBatch) { + revert UpgradeTransactionMustBeFirst(); + } // This is how it will be commonly done in practice, but it will simplify some logic later - require(_l2BlockNumber > 0, "L2 block number is never expected to be zero"); + if (_l2BlockNumber == 0) { + revert L2BlockNumberZero(); + } unchecked { bytes32 correctPrevBlockHash = _calculateLegacyL2BlockHash(_l2BlockNumber - 1); - require(correctPrevBlockHash == _expectedPrevL2BlockHash, "The previous L2 block hash is incorrect"); + if (correctPrevBlockHash != _expectedPrevL2BlockHash) { + revert PreviousL2BlockHashIsIncorrect(correctPrevBlockHash, _expectedPrevL2BlockHash); + } // Whenever we'll be queried about the hashes of the blocks before the upgrade, // we'll use batches' hashes, so we don't need to store 256 previous hashes. @@ -290,7 +295,9 @@ contract SystemContext is ISystemContext, ISystemContextDeprecated, SystemContra // Remembering the batch number on which the upgrade to the virtual blocks has been done. virtualBlockUpgradeInfo.virtualBlockStartBatch = currentBatchNumber; - require(_maxVirtualBlocksToCreate > 0, "Can't initialize the first virtual block"); + if (_maxVirtualBlocksToCreate == 0) { + revert CannotInitializeFirstVirtualBlock(); + } // solhint-disable-next-line gas-increment-by-one _maxVirtualBlocksToCreate -= 1; } else if (_maxVirtualBlocksToCreate == 0) { @@ -355,11 +362,12 @@ contract SystemContext is ISystemContext, ISystemContextDeprecated, SystemContra // We check that the timestamp of the L2 block is consistent with the timestamp of the batch. if (_isFirstInBatch) { uint128 currentBatchTimestamp = currentBatchInfo.timestamp; - require( - _l2BlockTimestamp >= currentBatchTimestamp, - "The timestamp of the L2 block must be greater than or equal to the timestamp of the current batch" - ); - require(_maxVirtualBlocksToCreate > 0, "There must be a virtual block created at the start of the batch"); + if (_l2BlockTimestamp < currentBatchTimestamp) { + revert L2BlockAndBatchTimestampMismatch(_l2BlockTimestamp, currentBatchTimestamp); + } + if (_maxVirtualBlocksToCreate == 0) { + revert NoVirtualBlocks(); + } } (uint128 currentL2BlockNumber, uint128 currentL2BlockTimestamp) = getL2BlockNumberAndTimestamp(); @@ -371,13 +379,21 @@ contract SystemContext is ISystemContext, ISystemContextDeprecated, SystemContra _setNewL2BlockData(_l2BlockNumber, _l2BlockTimestamp, _expectedPrevL2BlockHash); } else if (currentL2BlockNumber == _l2BlockNumber) { - require(!_isFirstInBatch, "Can not reuse L2 block number from the previous batch"); - require(currentL2BlockTimestamp == _l2BlockTimestamp, "The timestamp of the same L2 block must be same"); - require( - _expectedPrevL2BlockHash == _getLatest257L2blockHash(_l2BlockNumber - 1), - "The previous hash of the same L2 block must be same" - ); - require(_maxVirtualBlocksToCreate == 0, "Can not create virtual blocks in the middle of the miniblock"); + if (_isFirstInBatch) { + revert CannotReuseL2BlockNumberFromPreviousBatch(); + } + if (currentL2BlockTimestamp != _l2BlockTimestamp) { + revert IncorrectSameL2BlockTimestamp(_l2BlockTimestamp, currentL2BlockTimestamp); + } + if (_expectedPrevL2BlockHash != _getLatest257L2blockHash(_l2BlockNumber - 1)) { + revert IncorrectSameL2BlockPrevBlockHash( + _expectedPrevL2BlockHash, + _getLatest257L2blockHash(_l2BlockNumber - 1) + ); + } + if (_maxVirtualBlocksToCreate != 0) { + revert IncorrectVirtualBlockInsideMiniblock(); + } } else if (currentL2BlockNumber + 1 == _l2BlockNumber) { // From the checks in _upgradeL2Blocks it is known that currentL2BlockNumber can not be 0 bytes32 prevL2BlockHash = _getLatest257L2blockHash(currentL2BlockNumber - 1); @@ -389,16 +405,17 @@ contract SystemContext is ISystemContext, ISystemContextDeprecated, SystemContra currentL2BlockTxsRollingHash ); - require(_expectedPrevL2BlockHash == pendingL2BlockHash, "The current L2 block hash is incorrect"); - require( - _l2BlockTimestamp > currentL2BlockTimestamp, - "The timestamp of the new L2 block must be greater than the timestamp of the previous L2 block" - ); + if (_expectedPrevL2BlockHash != pendingL2BlockHash) { + revert IncorrectL2BlockHash(_expectedPrevL2BlockHash, pendingL2BlockHash); + } + if (_l2BlockTimestamp <= currentL2BlockTimestamp) { + revert NonMonotonicL2BlockTimestamp(_l2BlockTimestamp, currentL2BlockTimestamp); + } // Since the new block is created, we'll clear out the rolling hash _setNewL2BlockData(_l2BlockNumber, _l2BlockTimestamp, _expectedPrevL2BlockHash); } else { - revert("Invalid new L2 block number"); + revert InvalidNewL2BlockNumber(_l2BlockNumber); } _setVirtualBlock(_l2BlockNumber, _maxVirtualBlocksToCreate, _l2BlockTimestamp); @@ -417,7 +434,9 @@ contract SystemContext is ISystemContext, ISystemContextDeprecated, SystemContra (, uint128 currentL2BlockTimestamp) = getL2BlockNumberAndTimestamp(); // The structure of the "setNewBatch" implies that currentBatchNumber > 0, but we still double check it - require(currentBatchNumber > 0, "The current batch number must be greater than 0"); + if (currentBatchNumber == 0) { + revert CurrentBatchNumberMustBeGreaterThanZero(); + } // In order to spend less pubdata, the packed version is published uint256 packedTimestamps = (uint256(currentBatchTimestamp) << 128) | currentL2BlockTimestamp; @@ -433,10 +452,9 @@ contract SystemContext is ISystemContext, ISystemContextDeprecated, SystemContra /// @param _newTimestamp The timestamp of the new batch. function _ensureBatchConsistentWithL2Block(uint128 _newTimestamp) internal view { uint128 currentBlockTimestamp = currentL2BlockInfo.timestamp; - require( - _newTimestamp > currentBlockTimestamp, - "The timestamp of the batch must be greater than the timestamp of the previous block" - ); + if (_newTimestamp <= currentBlockTimestamp) { + revert InconsistentNewBatchTimestamp(_newTimestamp, currentBlockTimestamp); + } } /// @notice Increments the current batch number and sets the new timestamp @@ -455,17 +473,19 @@ contract SystemContext is ISystemContext, ISystemContextDeprecated, SystemContra uint256 _baseFee ) external onlyCallFromBootloader { (uint128 previousBatchNumber, uint128 previousBatchTimestamp) = getBatchNumberAndTimestamp(); - require(_newTimestamp > previousBatchTimestamp, "Timestamps should be incremental"); - require(previousBatchNumber + 1 == _expectedNewNumber, "The provided batch number is not correct"); + if (_newTimestamp <= previousBatchTimestamp) { + revert TimestampsShouldBeIncremental(_newTimestamp, previousBatchTimestamp); + } + if (previousBatchNumber + 1 != _expectedNewNumber) { + revert ProvidedBatchNumberIsNotCorrect(previousBatchNumber + 1, _expectedNewNumber); + } _ensureBatchConsistentWithL2Block(_newTimestamp); batchHashes[previousBatchNumber] = _prevBatchHash; // Setting new block number and timestamp - BlockInfo memory newBlockInfo = BlockInfo({number: previousBatchNumber + 1, timestamp: _newTimestamp}); - - currentBatchInfo = newBlockInfo; + currentBatchInfo = BlockInfo({number: previousBatchNumber + 1, timestamp: _newTimestamp}); baseFee = _baseFee; @@ -480,8 +500,7 @@ contract SystemContext is ISystemContext, ISystemContextDeprecated, SystemContra uint256 _number, uint256 _baseFee ) external onlyCallFromBootloader { - BlockInfo memory newBlockInfo = BlockInfo({number: uint128(_number), timestamp: uint128(_newTimestamp)}); - currentBatchInfo = newBlockInfo; + currentBatchInfo = BlockInfo({number: uint128(_number), timestamp: uint128(_newTimestamp)}); baseFee = _baseFee; } diff --git a/system-contracts/contracts/SystemContractErrors.sol b/system-contracts/contracts/SystemContractErrors.sol index b5dfc8276..c2104e48f 100644 --- a/system-contracts/contracts/SystemContractErrors.sol +++ b/system-contracts/contracts/SystemContractErrors.sol @@ -6,8 +6,6 @@ pragma solidity ^0.8.20; error AddressHasNoCode(address); // 0xefce78c7 error CallerMustBeBootloader(); -// 0xb7549616 -error CallerMustBeForceDeployer(); // 0x9eedbd2b error CallerMustBeSystemContract(); // 0x4f951510 @@ -26,8 +24,6 @@ error DerivedKeyNotEqualToCompressedValue(bytes32 expected, bytes32 provided); error DictionaryDividedByEightNotGreaterThanEncodedDividedByTwo(); // 0x1c25715b error EmptyBytes32(); -// 0x92bf3cf8 -error EmptyVirtualBlocks(); // 0xc06d5cb2 error EncodedAndRealBytecodeChunkNotEqual(uint64 expected, uint64 provided); // 0x2bfbfc11 @@ -36,8 +32,6 @@ error EncodedLengthNotFourTimesSmallerThanOriginal(); error FailedToChargeGas(); // 0x1f70c58f error FailedToPayOperator(); -// 0x9d5da395 -error FirstL2BlockInitializationError(); // 0x9e4a3c8a error HashIsNonZero(bytes32); // 0x86302004 @@ -48,23 +42,19 @@ error IndexOutOfBounds(); error IndexSizeError(); // 0x03eb8b54 error InsufficientFunds(uint256 required, uint256 actual); -// 0x1c26714c -error InsufficientGas(); // 0xae962d4e error InvalidCall(); -// 0x6a84bc39 +// 0x8cbd7f8b error InvalidCodeHash(CodeHashReason); // 0xb4fa3fb3 error InvalidInput(); // 0x60b85677 error InvalidNonceOrderingChange(); -// 0x90f049c9 +// 0xc6b7f67d error InvalidSig(SigField, uint256); // 0xf4a271b5 error Keccak256InvalidReturnData(); -// 0xd2906dd9 -error L2BlockMustBeGreaterThanZero(); -// 0x43e266b0 +// 0xcea34703 error MalformedBytecode(BytecodeError); // 0xe90aded4 error NonceAlreadyUsed(address account, uint256 nonce); @@ -84,7 +74,7 @@ error NonIncreasingTimestamp(); error NotAllowedToDeployInKernelSpace(); // 0x35278d12 error Overflow(); -// 0x7f7b0cf7 +// 0xe5ec477a error ReconstructionMismatch(PubdataField, bytes32 expected, bytes32 actual); // 0x3adb5f1d error ShaInvalidReturnData(); @@ -104,14 +94,107 @@ error UnsupportedOperation(); error UnsupportedPaymasterFlow(); // 0x17a84415 error UnsupportedTxType(uint256); -// 0x5708aead -error UpgradeMustBeFirstTxn(); // 0x626ade30 error ValueMismatch(uint256 expected, uint256 actual); -// 0x460b9939 -error ValuesNotEqual(uint256 expected, uint256 actual); // 0x6818f3f9 error ZeroNonceError(); +// 0x4f2b5b33 +error SloadContractBytecodeUnknown(); +// 0x43197434 +error PreviousBytecodeUnknown(); + +// 0x7a47c9a2 +error InvalidChainId(); + +// 0xc84a0422 +error UpgradeTransactionMustBeFirst(); + +// 0x543f4c07 +error L2BlockNumberZero(); + +// 0x702a599f +error PreviousL2BlockHashIsIncorrect(bytes32 correctPrevBlockHash, bytes32 expectedPrevL2BlockHash); + +// 0x2692f507 +error CannotInitializeFirstVirtualBlock(); + +// 0x5e9ad9b0 +error L2BlockAndBatchTimestampMismatch(uint128 l2BlockTimestamp, uint128 currentBatchTimestamp); + +// 0x159a6f2e +error InconsistentNewBatchTimestamp(uint128 newBatchTimestamp, uint128 lastL2BlockTimestamp); + +// 0xdcdfb0da +error NoVirtualBlocks(); + +// 0x141d6142 +error CannotReuseL2BlockNumberFromPreviousBatch(); + +// 0xf34da52d +error IncorrectSameL2BlockTimestamp(uint128 l2BlockTimestamp, uint128 currentL2BlockTimestamp); + +// 0x5822b85d +error IncorrectSameL2BlockPrevBlockHash(bytes32 expectedPrevL2BlockHash, bytes32 latestL2blockHash); + +// 0x6d391091 +error IncorrectVirtualBlockInsideMiniblock(); + +// 0xdf841e81 +error IncorrectL2BlockHash(bytes32 expectedPrevL2BlockHash, bytes32 pendingL2BlockHash); + +// 0x35dbda93 +error NonMonotonicL2BlockTimestamp(uint128 l2BlockTimestamp, uint128 currentL2BlockTimestamp); + +// 0x6ad429e8 +error CurrentBatchNumberMustBeGreaterThanZero(); + +// 0x09c63320 +error TimestampsShouldBeIncremental(uint128 newTimestamp, uint128 previousBatchTimestamp); + +// 0x33cb1485 +error ProvidedBatchNumberIsNotCorrect(uint128 previousBatchNumber, uint128 _expectedNewNumber); + +// 0xaa957ece +error CodeOracleCallFailed(); + +// 0x26772295 +error ReturnedBytecodeDoesNotMatchExpectedHash(bytes32 returnedBytecode, bytes32 expectedBytecodeHash); + +// 0x7f08f26b +error SecondCallShouldHaveCostLessGas(uint256 secondCallCost, uint256 firstCallCost); + +// 0xaa016ed2 +error ThirdCallShouldHaveSameGasCostAsSecondCall(uint256 thirdCallCost, uint256 secondCallCost); + +// 0xee455381 +error CallToKeccakShouldHaveSucceeded(); + +// 0x9c9d5e18 +error KeccakReturnDataSizeShouldBe32Bytes(uint256 returnDataSize); + +// 0x0c69f92e +error KeccakResultIsNotCorrect(bytes32 result); + +// 0x262f4984 +error KeccakShouldStartWorkingAgain(); + +// 0x034e49a6 +error KeccakMismatchBetweenNumberOfInputsAndOutputs(uint256 testInputsLength, uint256 expectedOutputsLength); + +// 0x92f5b709 +error KeccakHashWasNotCalculatedCorrectly(bytes32 result, bytes32 expectedOutputs); + +// 0xbf961a28 +error TransactionFailed(); + +// 0xdd629f86 +error NotEnoughGas(); + +// 0xf0b4e88f +error TooMuchGas(); + +// 0x8c13f15d +error InvalidNewL2BlockNumber(uint256 l2BlockNumber); enum CodeHashReason { NotContractOnConstructor, @@ -129,8 +212,13 @@ enum PubdataField { LogsHash, MsgHash, Bytecode, - StateDiffCompressionVersion, - ExtraData + InputDAFunctionSig, + InputLogsHash, + InputLogsRootHash, + InputMsgsHash, + InputBytecodeHash, + Offset, + Length } enum BytecodeError { diff --git a/system-contracts/contracts/abstract/SystemContractBase.sol b/system-contracts/contracts/abstract/SystemContractBase.sol index 89966a576..b0bdc36b5 100644 --- a/system-contracts/contracts/abstract/SystemContractBase.sol +++ b/system-contracts/contracts/abstract/SystemContractBase.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.20; import {SystemContractHelper} from "../libraries/SystemContractHelper.sol"; -import {BOOTLOADER_FORMAL_ADDRESS, FORCE_DEPLOYER} from "../Constants.sol"; -import {SystemCallFlagRequired, Unauthorized, CallerMustBeSystemContract, CallerMustBeBootloader, CallerMustBeForceDeployer} from "../SystemContractErrors.sol"; +import {BOOTLOADER_FORMAL_ADDRESS} from "../Constants.sol"; +import {SystemCallFlagRequired, Unauthorized, CallerMustBeSystemContract, CallerMustBeBootloader} from "../SystemContractErrors.sol"; /** * @author Matter Labs @@ -51,13 +51,4 @@ abstract contract SystemContractBase { } _; } - - /// @notice Modifier that makes sure that the method - /// can only be called from the L1 force deployer. - modifier onlyCallFromForceDeployer() { - if (msg.sender != FORCE_DEPLOYER) { - revert CallerMustBeForceDeployer(); - } - _; - } } diff --git a/system-contracts/contracts/interfaces/IBridgehub.sol b/system-contracts/contracts/interfaces/IBridgehub.sol new file mode 100644 index 000000000..210fc287a --- /dev/null +++ b/system-contracts/contracts/interfaces/IBridgehub.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. +pragma solidity ^0.8.20; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +interface IBridgehub { + function setAddresses(address _assetRouter, address _ctmDeployer, address _messageRoot) external; + + function owner() external view returns (address); +} diff --git a/system-contracts/contracts/interfaces/IComplexUpgrader.sol b/system-contracts/contracts/interfaces/IComplexUpgrader.sol index 3b1468417..d97720c6d 100644 --- a/system-contracts/contracts/interfaces/IComplexUpgrader.sol +++ b/system-contracts/contracts/interfaces/IComplexUpgrader.sol @@ -2,11 +2,19 @@ // We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. pragma solidity ^0.8.20; +import {ForceDeployment} from "./IContractDeployer.sol"; + /** * @author Matter Labs * @custom:security-contact security@matterlabs.dev * @notice The interface for the ComplexUpgrader contract. */ interface IComplexUpgrader { + function forceDeployAndUpgrade( + ForceDeployment[] calldata _forceDeployments, + address _delegateTo, + bytes calldata _calldata + ) external payable; + function upgrade(address _delegateTo, bytes calldata _calldata) external payable; } diff --git a/system-contracts/contracts/interfaces/IContractDeployer.sol b/system-contracts/contracts/interfaces/IContractDeployer.sol index 6e0bac3dc..f72aa19d4 100644 --- a/system-contracts/contracts/interfaces/IContractDeployer.sol +++ b/system-contracts/contracts/interfaces/IContractDeployer.sol @@ -2,6 +2,20 @@ // We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. pragma solidity ^0.8.20; +/// @notice A struct that describes a forced deployment on an address +struct ForceDeployment { + // The bytecode hash to put on an address + bytes32 bytecodeHash; + // The address on which to deploy the bytecodehash to + address newAddress; + // Whether to run the constructor on the force deployment + bool callConstructor; + // The value with which to initialize a contract + uint256 value; + // The constructor calldata + bytes input; +} + interface IContractDeployer { /// @notice Defines the version of the account abstraction protocol /// that a contract claims to follow. @@ -88,4 +102,7 @@ interface IContractDeployer { /// @notice Can be called by an account to update its nonce ordering function updateNonceOrdering(AccountNonceOrdering _nonceOrdering) external; + + /// @notice This method is to be used only during an upgrade to set bytecodes on specific addresses. + function forceDeployOnAddresses(ForceDeployment[] calldata _deployments) external payable; } diff --git a/system-contracts/contracts/interfaces/ICreate2Factory.sol b/system-contracts/contracts/interfaces/ICreate2Factory.sol new file mode 100644 index 000000000..e9a0faf15 --- /dev/null +++ b/system-contracts/contracts/interfaces/ICreate2Factory.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. +pragma solidity ^0.8.20; + +import {IContractDeployer} from "./IContractDeployer.sol"; + +/// @custom:security-contact security@matterlabs.dev +/// @author Matter Labs +/// @notice The contract that can be used for deterministic contract deployment. +interface ICreate2Factory { + /// @notice Function that calls the `create2` method of the `ContractDeployer` contract. + /// @dev This function accepts the same parameters as the `create2` function of the ContractDeployer system contract, + /// so that we could efficiently relay the calldata. + function create2( + bytes32, // _salt + bytes32, // _bytecodeHash + bytes calldata // _input + ) external payable returns (address); + + /// @notice Function that calls the `create2Account` method of the `ContractDeployer` contract. + /// @dev This function accepts the same parameters as the `create2Account` function of the ContractDeployer system contract, + /// so that we could efficiently relay the calldata. + function create2Account( + bytes32, // _salt + bytes32, // _bytecodeHash + bytes calldata, // _input + IContractDeployer.AccountAbstractionVersion // _aaVersion + ) external payable returns (address); +} diff --git a/system-contracts/contracts/interfaces/IL1Messenger.sol b/system-contracts/contracts/interfaces/IL1Messenger.sol index 88e2c81d8..51535d8d8 100644 --- a/system-contracts/contracts/interfaces/IL1Messenger.sol +++ b/system-contracts/contracts/interfaces/IL1Messenger.sol @@ -46,7 +46,7 @@ interface IL1Messenger { event BytecodeL1PublicationRequested(bytes32 _bytecodeHash); - function sendToL1(bytes memory _message) external returns (bytes32); + function sendToL1(bytes calldata _message) external returns (bytes32); function sendL2ToL1Log(bool _isService, bytes32 _key, bytes32 _value) external returns (uint256 logIdInMerkleTree); diff --git a/system-contracts/contracts/interfaces/IL2DAValidator.sol b/system-contracts/contracts/interfaces/IL2DAValidator.sol new file mode 100644 index 000000000..02e5bf953 --- /dev/null +++ b/system-contracts/contracts/interfaces/IL2DAValidator.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. +pragma solidity ^0.8.20; + +interface IL2DAValidator { + function validatePubdata( + // The rolling hash of the user L2->L1 logs. + bytes32 _chainedLogsHash, + // The root hash of the user L2->L1 logs. + bytes32 _logsRootHash, + // The chained hash of the L2->L1 messages + bytes32 _chainedMessagesHash, + // The chained hash of uncompressed bytecodes sent to L1 + bytes32 _chainedBytecodesHash, + // Same operator input + bytes calldata _totalL2ToL1PubdataAndStateDiffs + ) external returns (bytes32 outputHash); +} diff --git a/system-contracts/contracts/interfaces/IL2GenesisUpgrade.sol b/system-contracts/contracts/interfaces/IL2GenesisUpgrade.sol new file mode 100644 index 000000000..d86cb70a6 --- /dev/null +++ b/system-contracts/contracts/interfaces/IL2GenesisUpgrade.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. +pragma solidity ^0.8.20; + +// solhint-disable-next-line gas-struct-packing +struct ZKChainSpecificForceDeploymentsData { + bytes32 baseTokenAssetId; + address l2LegacySharedBridge; + address predeployedL2WethAddress; + address baseTokenL1Address; + /// @dev Some info about the base token, it is + /// needed to deploy weth token in case it is not present + string baseTokenName; + string baseTokenSymbol; +} + +/// @notice THe structure that describes force deployments that are the same for each chain. +/// @dev Note, that for simplicity, the same struct is used both for upgrading to the +/// Gateway version and for the Genesis. Some fields may not be used in either of those. +// solhint-disable-next-line gas-struct-packing +struct FixedForceDeploymentsData { + uint256 l1ChainId; + uint256 eraChainId; + address l1AssetRouter; + bytes32 l2TokenProxyBytecodeHash; + address aliasedL1Governance; + uint256 maxNumberOfZKChains; + bytes32 bridgehubBytecodeHash; + bytes32 l2AssetRouterBytecodeHash; + bytes32 l2NtvBytecodeHash; + bytes32 messageRootBytecodeHash; + address l2SharedBridgeLegacyImpl; + address l2BridgedStandardERC20Impl; + // The forced beacon address. It is needed only for internal testing. + // MUST be equal to 0 in production. + // It will be the job of the governance to ensure that this value is set correctly. + address dangerousTestOnlyForcedBeacon; +} + +interface IL2GenesisUpgrade { + event UpgradeComplete(uint256 _chainId); + + function genesisUpgrade( + uint256 _chainId, + address _ctmDeployer, + bytes calldata _fixedForceDeploymentsData, + bytes calldata _additionalForceDeploymentsData + ) external payable; +} diff --git a/system-contracts/contracts/interfaces/IL2SharedBridgeLegacy.sol b/system-contracts/contracts/interfaces/IL2SharedBridgeLegacy.sol new file mode 100644 index 000000000..77c6d9ec9 --- /dev/null +++ b/system-contracts/contracts/interfaces/IL2SharedBridgeLegacy.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +interface IL2SharedBridgeLegacy { + event FinalizeDeposit( + address indexed l1Sender, + address indexed l2Receiver, + address indexed l2Token, + uint256 amount + ); + + function l2TokenBeacon() external view returns (UpgradeableBeacon); + + function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external; + + function l1TokenAddress(address _l2Token) external view returns (address); + + function l2TokenAddress(address _l1Token) external view returns (address); + + function l1Bridge() external view returns (address); + + function l1SharedBridge() external view returns (address); + + function deployBeaconProxy(bytes32 _salt) external returns (address); + + function sendMessageToL1(bytes calldata _message) external; +} diff --git a/system-contracts/contracts/interfaces/IL2WrappedBaseToken.sol b/system-contracts/contracts/interfaces/IL2WrappedBaseToken.sol new file mode 100644 index 000000000..0bfc9352e --- /dev/null +++ b/system-contracts/contracts/interfaces/IL2WrappedBaseToken.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. +pragma solidity ^0.8.20; + +interface IL2WrappedBaseToken { + /// @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 initializeV3( + string calldata name_, + string calldata symbol_, + address _l2Bridge, + address _l1Address, + bytes32 _baseTokenAssetId + ) external; +} diff --git a/system-contracts/contracts/interfaces/IMessageRoot.sol b/system-contracts/contracts/interfaces/IMessageRoot.sol new file mode 100644 index 000000000..3966caa15 --- /dev/null +++ b/system-contracts/contracts/interfaces/IMessageRoot.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. +pragma solidity ^0.8.20; + +/** + * @author Matter Labs + * @notice MessageRoot contract is responsible for storing and aggregating the roots of the batches from different chains into the MessageRoot. + * @custom:security-contact security@matterlabs.dev + */ +interface IMessageRoot { + /// @notice The aggregated root of the batches from different chains. + /// @return aggregatedRoot of the batches from different chains. + function getAggregatedRoot() external view returns (bytes32 aggregatedRoot); +} diff --git a/system-contracts/contracts/interfaces/IPubdataChunkPublisher.sol b/system-contracts/contracts/interfaces/IPubdataChunkPublisher.sol index 47893abdb..b422bb359 100644 --- a/system-contracts/contracts/interfaces/IPubdataChunkPublisher.sol +++ b/system-contracts/contracts/interfaces/IPubdataChunkPublisher.sol @@ -10,5 +10,6 @@ pragma solidity ^0.8.20; interface IPubdataChunkPublisher { /// @notice Chunks pubdata into pieces that can fit into blobs. /// @param _pubdata The total l2 to l1 pubdata that will be sent via L1 blobs. - function chunkAndPublishPubdata(bytes calldata _pubdata) external; + /// @dev Note: This is an early implementation, in the future we plan to support up to 16 blobs per l1 batch. + function chunkPubdataToBlobs(bytes calldata _pubdata) external pure returns (bytes32[] memory blobLinearHashes); } diff --git a/system-contracts/contracts/interfaces/ISystemContext.sol b/system-contracts/contracts/interfaces/ISystemContext.sol index 6b9a37fce..ff083fd0b 100644 --- a/system-contracts/contracts/interfaces/ISystemContext.sol +++ b/system-contracts/contracts/interfaces/ISystemContext.sol @@ -58,4 +58,6 @@ interface ISystemContext { function gasPerPubdataByte() external view returns (uint256 gasPerPubdataByte); function getCurrentPubdataSpent() external view returns (uint256 currentPubdataSpent); + + function setChainId(uint256 _newChainId) external; } diff --git a/system-contracts/contracts/libraries/SystemContractHelper.sol b/system-contracts/contracts/libraries/SystemContractHelper.sol index e8469e308..e53fe1145 100644 --- a/system-contracts/contracts/libraries/SystemContractHelper.sol +++ b/system-contracts/contracts/libraries/SystemContractHelper.sol @@ -2,10 +2,12 @@ // We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. pragma solidity ^0.8.20; -import {MAX_SYSTEM_CONTRACT_ADDRESS} from "../Constants.sol"; +import {MAX_SYSTEM_CONTRACT_ADDRESS, DEPLOYER_SYSTEM_CONTRACT, FORCE_DEPLOYER, KNOWN_CODE_STORAGE_CONTRACT, SLOAD_CONTRACT_ADDRESS} from "../Constants.sol"; +import {ForceDeployment, IContractDeployer} from "../interfaces/IContractDeployer.sol"; +import {SloadContract} from "../SloadContract.sol"; -import {CALLFLAGS_CALL_ADDRESS, CODE_ADDRESS_CALL_ADDRESS, EVENT_WRITE_ADDRESS, EVENT_INITIALIZE_ADDRESS, GET_EXTRA_ABI_DATA_ADDRESS, LOAD_CALLDATA_INTO_ACTIVE_PTR_CALL_ADDRESS, META_CODE_SHARD_ID_OFFSET, META_CALLER_SHARD_ID_OFFSET, META_SHARD_ID_OFFSET, META_AUX_HEAP_SIZE_OFFSET, META_HEAP_SIZE_OFFSET, META_PUBDATA_PUBLISHED_OFFSET, META_CALL_ADDRESS, PTR_CALLDATA_CALL_ADDRESS, PTR_ADD_INTO_ACTIVE_CALL_ADDRESS, PTR_SHRINK_INTO_ACTIVE_CALL_ADDRESS, PTR_PACK_INTO_ACTIVE_CALL_ADDRESS, PRECOMPILE_CALL_ADDRESS, SET_CONTEXT_VALUE_CALL_ADDRESS, TO_L1_CALL_ADDRESS} from "./SystemContractsCaller.sol"; -import {IndexOutOfBounds, FailedToChargeGas} from "../SystemContractErrors.sol"; +import {CalldataForwardingMode, SystemContractsCaller, MIMIC_CALL_CALL_ADDRESS, CALLFLAGS_CALL_ADDRESS, CODE_ADDRESS_CALL_ADDRESS, EVENT_WRITE_ADDRESS, EVENT_INITIALIZE_ADDRESS, GET_EXTRA_ABI_DATA_ADDRESS, LOAD_CALLDATA_INTO_ACTIVE_PTR_CALL_ADDRESS, META_CODE_SHARD_ID_OFFSET, META_CALLER_SHARD_ID_OFFSET, META_SHARD_ID_OFFSET, META_AUX_HEAP_SIZE_OFFSET, META_HEAP_SIZE_OFFSET, META_PUBDATA_PUBLISHED_OFFSET, META_CALL_ADDRESS, PTR_CALLDATA_CALL_ADDRESS, PTR_ADD_INTO_ACTIVE_CALL_ADDRESS, PTR_SHRINK_INTO_ACTIVE_CALL_ADDRESS, PTR_PACK_INTO_ACTIVE_CALL_ADDRESS, PRECOMPILE_CALL_ADDRESS, SET_CONTEXT_VALUE_CALL_ADDRESS, TO_L1_CALL_ADDRESS} from "./SystemContractsCaller.sol"; +import {IndexOutOfBounds, FailedToChargeGas, SloadContractBytecodeUnknown, PreviousBytecodeUnknown} from "../SystemContractErrors.sol"; uint256 constant UINT32_MASK = type(uint32).max; uint256 constant UINT64_MASK = type(uint64).max; @@ -358,4 +360,125 @@ library SystemContractHelper { revert FailedToChargeGas(); } } + + /// @notice Performs a `mimicCall` to an address. + /// @param _to The address to call. + /// @param _whoToMimic The address to mimic. + /// @param _data The data to pass to the call. + /// @return success Whether the call was successful. + /// @return returndata The return data of the call. + function mimicCall( + address _to, + address _whoToMimic, + bytes memory _data + ) internal returns (bool success, bytes memory returndata) { + // In zkSync, no memory-related values can exceed uint32, so it is safe to convert here + uint32 dataStart; + uint32 dataLength = uint32(_data.length); + assembly { + dataStart := add(_data, 0x20) + } + + uint256 farCallAbi = SystemContractsCaller.getFarCallABI({ + dataOffset: 0, + memoryPage: 0, + dataStart: dataStart, + dataLength: dataLength, + gasPassed: uint32(gasleft()), + shardId: 0, + forwardingMode: CalldataForwardingMode.UseHeap, + isConstructorCall: false, + isSystemCall: false + }); + + address callAddr = MIMIC_CALL_CALL_ADDRESS; + uint256 rtSize; + assembly { + success := call(_to, callAddr, 0, farCallAbi, _whoToMimic, 0, 0) + rtSize := returndatasize() + } + + returndata = new bytes(rtSize); + assembly { + returndatacopy(add(returndata, 0x20), 0, rtSize) + } + } + + /// @notice Force deploys some bytecode hash to an address + /// without invoking the constructor. + /// @param _addr The address to force-deploy the bytecodehash to. + /// @param _bytecodeHash The bytecode hash to force-deploy. + function forceDeployNoConstructor(address _addr, bytes32 _bytecodeHash) internal { + ForceDeployment[] memory deployments = new ForceDeployment[](1); + deployments[0] = ForceDeployment({ + bytecodeHash: _bytecodeHash, + newAddress: _addr, + callConstructor: false, + value: 0, + input: hex"" + }); + mimicCallWithPropagatedRevert( + address(DEPLOYER_SYSTEM_CONTRACT), + FORCE_DEPLOYER, + abi.encodeCall(IContractDeployer.forceDeployOnAddresses, deployments) + ); + } + + /// @notice Reads a certain storage slot from a contract. + /// @param _addr The address to read the slot from. + /// @param _key The key to read. + /// @return result The value stored at slot `_key` under the address `_addr`. + /// @dev zkEVM similarly to EVM only has an opcode to read + /// from the current contract's storage. However, sometimes system contracts + /// may require to read private storage slots of a contract. In order to provide + /// generic functionality to read arbitrary storage slots from other contracts, the following + /// scheme is used: + /// 1. Force-deploy `SloadContract` to the address. + /// 2. Read the required slot. + /// 3. Force-deploy the previous bytecode back. + /// @dev Note, that the function will overwrite the account states of the `_addr`, i.e. + /// this function should NEVER be used against custom accounts. + function forcedSload(address _addr, bytes32 _key) internal returns (bytes32 result) { + bytes32 sloadContractBytecodeHash; + address sloadContractAddress = SLOAD_CONTRACT_ADDRESS; + assembly { + sloadContractBytecodeHash := extcodehash(sloadContractAddress) + } + + // Just in case, that the `sloadContractBytecodeHash` is known + if (KNOWN_CODE_STORAGE_CONTRACT.getMarker(sloadContractBytecodeHash) == 0) { + revert SloadContractBytecodeUnknown(); + } + + bytes32 previoushHash; + assembly { + previoushHash := extcodehash(_addr) + } + + // Just in case, double checking that the previous bytecode is known. + // It may be needed since `previoushHash` could be non-zero and unknown if it is + // equal to keccak(""). It is the case for used default accounts. + if (KNOWN_CODE_STORAGE_CONTRACT.getMarker(previoushHash) == 0) { + revert PreviousBytecodeUnknown(); + } + + forceDeployNoConstructor(_addr, sloadContractBytecodeHash); + result = SloadContract(_addr).sload(_key); + forceDeployNoConstructor(_addr, previoushHash); + } + + /// @notice Performs a `mimicCall` to an address, while ensuring that the call + /// was successful + /// @param _to The address to call. + /// @param _whoToMimic The address to mimic. + /// @param _data The data to pass to the call. + function mimicCallWithPropagatedRevert(address _to, address _whoToMimic, bytes memory _data) internal { + (bool success, bytes memory returnData) = mimicCall(_to, _whoToMimic, _data); + if (!success) { + // Propagate revert reason + assembly { + revert(add(returnData, 0x20), returndatasize()) + } + } + } } diff --git a/system-contracts/contracts/test-contracts/CodeOracleTest.sol b/system-contracts/contracts/test-contracts/CodeOracleTest.sol index 31de9d366..f93a5d54a 100644 --- a/system-contracts/contracts/test-contracts/CodeOracleTest.sol +++ b/system-contracts/contracts/test-contracts/CodeOracleTest.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.20; +import {CodeOracleCallFailed, ReturnedBytecodeDoesNotMatchExpectedHash, SecondCallShouldHaveCostLessGas, ThirdCallShouldHaveSameGasCostAsSecondCall} from "contracts/SystemContractErrors.sol"; + address constant REAL_CODE_ORACLE_ADDR = 0x0000000000000000000000000000000000008011; contract CodeOracleTest { @@ -19,13 +21,14 @@ contract CodeOracleTest { gasCost = gasBefore - gasleft(); // Check the result - require(success, "CodeOracle call failed"); + if (!success) { + revert CodeOracleCallFailed(); + } // Check the returned bytecode - require( - keccak256(returnedBytecode) == _expectedBytecodeHash, - "Returned bytecode does not match the expected hash" - ); + if (keccak256(returnedBytecode) != _expectedBytecodeHash) { + revert ReturnedBytecodeDoesNotMatchExpectedHash(keccak256(returnedBytecode), _expectedBytecodeHash); + } } function codeOracleTest(bytes32 _versionedHash, bytes32 _expectedBytecodeHash) external view { @@ -35,7 +38,11 @@ contract CodeOracleTest { uint256 secondCallCost = this.callCodeOracle(_versionedHash, _expectedBytecodeHash); uint256 thirdCallCost = this.callCodeOracle(_versionedHash, _expectedBytecodeHash); - require(secondCallCost < firstCallCost, "The second call should have cost less gas"); - require(thirdCallCost == secondCallCost, "The third call should have the same gas cost as the second call"); + if (secondCallCost >= firstCallCost) { + revert SecondCallShouldHaveCostLessGas(secondCallCost, firstCallCost); + } + if (thirdCallCost != secondCallCost) { + revert ThirdCallShouldHaveSameGasCostAsSecondCall(thirdCallCost, secondCallCost); + } } } diff --git a/system-contracts/contracts/test-contracts/DummyBridgehub.sol b/system-contracts/contracts/test-contracts/DummyBridgehub.sol new file mode 100644 index 000000000..4beadb4ce --- /dev/null +++ b/system-contracts/contracts/test-contracts/DummyBridgehub.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +contract DummyBridgehub { + address public owner; + + constructor(uint256 _l1ChainId, address _aliasedL1Governance, uint256 _maxNumberOfZKChains) { + owner = _aliasedL1Governance; + } +} diff --git a/system-contracts/contracts/test-contracts/DummyL2AssetRouter.sol b/system-contracts/contracts/test-contracts/DummyL2AssetRouter.sol new file mode 100644 index 000000000..65796aa3f --- /dev/null +++ b/system-contracts/contracts/test-contracts/DummyL2AssetRouter.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +contract DummyL2AssetRouter { + constructor( + uint256 _l1ChainId, + address _l1AssetRouter, + address _aliasedL1Governance, + bytes32 _baseTokenAssetId, + uint256 _maxNumberOfZKChains + ) {} +} diff --git a/system-contracts/contracts/test-contracts/DummyL2NativeTokenVault.sol b/system-contracts/contracts/test-contracts/DummyL2NativeTokenVault.sol new file mode 100644 index 000000000..1832237d2 --- /dev/null +++ b/system-contracts/contracts/test-contracts/DummyL2NativeTokenVault.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +contract DummyL2NativeTokenVault { + constructor( + uint256 _l1ChainId, + address _aliasedL1Governance, + bytes32 _l2TokenProxyBytecodeHash, + address _bridgedTokenBeacon, + bool _contractsDeployedAlready, + address _wethToken, + bytes32 _baseTokenAssetId + ) {} +} diff --git a/system-contracts/contracts/test-contracts/DummyMessageRoot.sol b/system-contracts/contracts/test-contracts/DummyMessageRoot.sol new file mode 100644 index 000000000..d49cdd50f --- /dev/null +++ b/system-contracts/contracts/test-contracts/DummyMessageRoot.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +contract DummyMessageRoot { + constructor(address) {} +} diff --git a/system-contracts/contracts/test-contracts/KeccakTest.sol b/system-contracts/contracts/test-contracts/KeccakTest.sol index 79581afc4..61cbcfea0 100644 --- a/system-contracts/contracts/test-contracts/KeccakTest.sol +++ b/system-contracts/contracts/test-contracts/KeccakTest.sol @@ -5,6 +5,7 @@ pragma abicoder v2; import {LOAD_LATEST_RETURNDATA_INTO_ACTIVE_PTR_CALL_ADDRESS, PTR_PACK_INTO_ACTIVE_CALL_ADDRESS, SystemContractsCaller, CalldataForwardingMode, RAW_FAR_CALL_BY_REF_CALL_ADDRESS} from "../libraries/SystemContractsCaller.sol"; import {EfficientCall, KECCAK256_SYSTEM_CONTRACT} from "../libraries/EfficientCall.sol"; +import {CallToKeccakShouldHaveSucceeded, KeccakReturnDataSizeShouldBe32Bytes, KeccakResultIsNotCorrect, KeccakShouldStartWorkingAgain, KeccakMismatchBetweenNumberOfInputsAndOutputs, KeccakHashWasNotCalculatedCorrectly} from "contracts/SystemContractErrors.sol"; // In this test it is important to actually change the real Keccak256's contract's bytecode, // which requires changes in the real AccountCodeStorage contract @@ -62,13 +63,17 @@ contract KeccakTest { _loadReturnDataIntoActivePtr(); _loadFarCallABIIntoActivePtr(1000000); bool success = rawCallByRef(KECCAK256_SYSTEM_CONTRACT); - require(success, "The call to keccak should have succeeded"); + if (!success) { + revert CallToKeccakShouldHaveSucceeded(); + } uint256 returndataSize = 0; assembly { returndataSize := returndatasize() } - require(returndataSize == 32, "The return data size should be 32 bytes"); + if (returndataSize != 32) { + revert KeccakReturnDataSizeShouldBe32Bytes(returndataSize); + } bytes32 result; assembly { @@ -76,7 +81,9 @@ contract KeccakTest { result := mload(0) } - require(result == EMPTY_STRING_KECCAK, "The result is not correct"); + if (result != EMPTY_STRING_KECCAK) { + revert KeccakResultIsNotCorrect(result); + } } function keccakUpgradeTest( @@ -110,7 +117,9 @@ contract KeccakTest { // Now it should work again hash = this.callKeccak(msg.data[0:0]); - require(hash == EMPTY_STRING_KECCAK, "Keccak should start working again"); + if (hash != EMPTY_STRING_KECCAK) { + revert KeccakShouldStartWorkingAgain(); + } } function keccakPerformUpgrade(bytes calldata upgradeCalldata) external { @@ -134,7 +143,9 @@ contract KeccakTest { bytes[] calldata testInputs, bytes32[] calldata expectedOutputs ) external { - require(testInputs.length == expectedOutputs.length, "mismatch between number of inputs and outputs"); + if (testInputs.length != expectedOutputs.length) { + revert KeccakMismatchBetweenNumberOfInputsAndOutputs(testInputs.length, expectedOutputs.length); + } // Firstly, we upgrade keccak256 bytecode to the correct version. EfficientCall.mimicCall({ @@ -154,7 +165,9 @@ contract KeccakTest { } for (uint256 i = 0; i < result.length; i++) { - require(result[i] == expectedOutputs[i], "hash was not calculated correctly"); + if (result[i] != expectedOutputs[i]) { + revert KeccakHashWasNotCalculatedCorrectly(result[i], expectedOutputs[i]); + } } // Upgrading it back to the original version: diff --git a/system-contracts/contracts/test-contracts/TransferTest.sol b/system-contracts/contracts/test-contracts/TransferTest.sol index ca76a9932..d554183be 100644 --- a/system-contracts/contracts/test-contracts/TransferTest.sol +++ b/system-contracts/contracts/test-contracts/TransferTest.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.20; +import {TransactionFailed, NotEnoughGas, TooMuchGas} from "contracts/SystemContractErrors.sol"; + contract TransferTest { function transfer(address payable to, uint256 amount, bool warmUpRecipient) public payable { if (warmUpRecipient) { @@ -20,7 +22,9 @@ contract TransferTest { bool success = to.send(amount); - require(success, "Transaction failed"); + if (!success) { + revert TransactionFailed(); + } } receive() external payable {} @@ -30,8 +34,12 @@ contract TransferTestRecipient { event Received(address indexed sender, uint256 amount); receive() external payable { - require(gasleft() >= 2100, "Not enough gas"); - require(gasleft() <= 2300, "Too much gas"); + if (gasleft() < 2100) { + revert NotEnoughGas(); + } + if (gasleft() > 2300) { + revert TooMuchGas(); + } emit Received(msg.sender, msg.value); } } diff --git a/system-contracts/foundry.toml b/system-contracts/foundry.toml index 06485883a..2cd03cece 100644 --- a/system-contracts/foundry.toml +++ b/system-contracts/foundry.toml @@ -5,10 +5,10 @@ libs = ["lib"] cache_path = "cache-forge" evm_version = "paris" remappings = [ - "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", - "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/", + "@openzeppelin/contracts-v4/=lib/openzeppelin-contracts-v4/contracts/", + "@openzeppelin/contracts-upgradeable-v4/=lib/openzeppelin-contracts-upgradeable-v4/contracts/", ] -[profile.default.zksync] -zksolc = "1.5.0" +[profile.default.zksync] enable_eravm_extensions = true +zksolc = "1.5.0" diff --git a/system-contracts/lib/openzeppelin-contracts b/system-contracts/lib/openzeppelin-contracts deleted file mode 120000 index 99aa45507..000000000 --- a/system-contracts/lib/openzeppelin-contracts +++ /dev/null @@ -1 +0,0 @@ -../../lib/openzeppelin-contracts \ No newline at end of file diff --git a/system-contracts/lib/openzeppelin-contracts-upgradeable b/system-contracts/lib/openzeppelin-contracts-upgradeable deleted file mode 120000 index f1fc7a76a..000000000 --- a/system-contracts/lib/openzeppelin-contracts-upgradeable +++ /dev/null @@ -1 +0,0 @@ -../../lib/openzeppelin-contracts-upgradeable \ No newline at end of file diff --git a/system-contracts/lib/openzeppelin-contracts-upgradeable-v4 b/system-contracts/lib/openzeppelin-contracts-upgradeable-v4 new file mode 120000 index 000000000..0551b6016 --- /dev/null +++ b/system-contracts/lib/openzeppelin-contracts-upgradeable-v4 @@ -0,0 +1 @@ +../../lib/openzeppelin-contracts-upgradeable-v4 \ No newline at end of file diff --git a/system-contracts/lib/openzeppelin-contracts-v4 b/system-contracts/lib/openzeppelin-contracts-v4 new file mode 120000 index 000000000..693e94537 --- /dev/null +++ b/system-contracts/lib/openzeppelin-contracts-v4 @@ -0,0 +1 @@ +../../lib/openzeppelin-contracts-v4 \ No newline at end of file diff --git a/system-contracts/package.json b/system-contracts/package.json index ef53db110..146fdfcba 100644 --- a/system-contracts/package.json +++ b/system-contracts/package.json @@ -5,7 +5,7 @@ "license": "MIT", "dependencies": { "@matterlabs/hardhat-zksync-deploy": "^0.7.0", - "@matterlabs/hardhat-zksync-solc": "^1.1.4", + "@matterlabs/hardhat-zksync-solc": "=1.1.4", "@matterlabs/hardhat-zksync-verify": "^1.4.3", "commander": "^9.4.1", "eslint": "^8.51.0", @@ -19,8 +19,10 @@ }, "devDependencies": { "@matterlabs/hardhat-zksync-chai-matchers": "^0.2.0", - "@matterlabs/hardhat-zksync-node": "^0.0.1-beta.7", + "@matterlabs/hardhat-zksync-node": "^1.2.0", "@nomicfoundation/hardhat-chai-matchers": "^1.0.3", + "@openzeppelin/contracts-upgradeable-v4": "npm:@openzeppelin/contracts-upgradeable@4.9.5", + "@openzeppelin/contracts-v4": "npm:@openzeppelin/contracts@4.9.5", "@nomiclabs/hardhat-ethers": "^2.0.0", "@typechain/ethers-v5": "^2.0.0", "@types/chai": "^4.2.21", @@ -51,6 +53,7 @@ }, "scripts": { "build": "yarn build:system-contracts && yarn build:bootloader", + "build:foundry": "yarn preprocess:system-contracts && forge build --zksync && yarn preprocess:bootloader && forge build --zksync", "build:bootloader": "yarn preprocess:bootloader && yarn compile-yul compile-bootloader", "build:system-contracts": "yarn preprocess:system-contracts && hardhat compile && yarn compile-yul compile-precompiles", "build:test-system-contracts": "yarn preprocess:system-contracts --test-mode && hardhat compile && yarn compile-yul compile-precompiles && yarn compile-zasm", @@ -62,11 +65,13 @@ "compile-yul": "ts-node scripts/compile-yul.ts", "compile-zasm": "ts-node scripts/compile-zasm.ts", "deploy-preimages": "ts-node scripts/deploy-preimages.ts", + "copy:typechain": "mkdir -p ../l2-contracts/typechain && cp ./typechain/ContractDeployerFactory.ts ../l2-contracts/typechain/", "preprocess:bootloader": "rm -rf ./bootloader/build && yarn ts-node scripts/preprocess-bootloader.ts", "preprocess:system-contracts": "rm -rf ./contracts-preprocessed && ts-node scripts/preprocess-system-contracts.ts", "verify-on-explorer": "hardhat run scripts/verify-on-explorer.ts", "test": "yarn build:test-system-contracts && hardhat test --network zkSyncTestNode", - "test-node": "hardhat node-zksync --tag v0.0.1-vm1.5.0", + "test-no-build": "hardhat test --network zkSyncTestNode", + "test-node": "hardhat node-zksync --tag 0.1.0-alpha.29", "test:bootloader": "cd bootloader/test_infra && cargo run" } } diff --git a/system-contracts/scripts/constants.ts b/system-contracts/scripts/constants.ts index b354c36ab..0827b2df1 100644 --- a/system-contracts/scripts/constants.ts +++ b/system-contracts/scripts/constants.ts @@ -174,6 +174,62 @@ export const SYSTEM_CONTRACTS: ISystemContracts = { codeName: "Create2Factory", lang: Language.Solidity, }, + L2GenesisUpgrade: { + // This is explicitly a non-system-contract address. + // We do not use the same address as create2 factories on EVM, since + // this is a zkEVM create2 factory. + address: "0x0000000000000000000000000000000000010001", + codeName: "L2GenesisUpgrade", + lang: Language.Solidity, + }, + L2BridgeHub: { + // This is explicitly a non-system-contract address. + // We do not use the same address as create2 factories on EVM, since + // this is a zkEVM create2 factory. + address: "0x0000000000000000000000000000000000010002", + codeName: "Bridgehub", + lang: Language.Solidity, + }, + L2AssetRouter: { + // This is explicitly a non-system-contract address. + // We do not use the same address as create2 factories on EVM, since + // this is a zkEVM create2 factory. + address: "0x0000000000000000000000000000000000010003", + codeName: "L2AssetRouter", + lang: Language.Solidity, + }, + L2NativeTokenVault: { + // This is explicitly a non-system-contract address. + // We do not use the same address as create2 factories on EVM, since + // this is a zkEVM create2 factory. + address: "0x0000000000000000000000000000000000010004", + codeName: "L2NativeTokenVault", + lang: Language.Solidity, + }, + L2MessageRoot: { + // This is explicitly a non-system-contract address. + // We do not use the same address as create2 factories on EVM, since + // this is a zkEVM create2 factory. + address: "0x0000000000000000000000000000000000010005", + codeName: "MessageRoot", + lang: Language.Solidity, + }, + SloadContract: { + // This is explicitly a non-system-contract address. + // We do not use the same address as create2 factories on EVM, since + // this is a zkEVM create2 factory. + address: "0x0000000000000000000000000000000000010006", + codeName: "SloadContract", + lang: Language.Solidity, + }, + L2WrappedBaseTokenImplementation: { + // This is explicitly a non-system-contract address. + // We do not use the same address as create2 factories on EVM, since + // this is a zkEVM create2 factory. + address: "0x0000000000000000000000000000000000010007", + codeName: "L2WrappedBaseToken", + lang: Language.Solidity, + }, } as const; export const EIP712_TX_ID = 113; diff --git a/system-contracts/scripts/preprocess-bootloader.ts b/system-contracts/scripts/preprocess-bootloader.ts index e3dc18aaf..29454dcd2 100644 --- a/system-contracts/scripts/preprocess-bootloader.ts +++ b/system-contracts/scripts/preprocess-bootloader.ts @@ -63,7 +63,7 @@ function getSystemContextCodeHash() { // Maybe in the future some of these params will be passed // in a JSON file. For now, a simple object is ok here. const params = { - MARK_BATCH_AS_REPUBLISHED_SELECTOR: getSelector("KnownCodesStorage", "markFactoryDeps"), + MARK_FACTORY_DEPS_SELECTOR: getSelector("KnownCodesStorage", "markFactoryDeps"), VALIDATE_TX_SELECTOR: getSelector("IAccount", "validateTransaction"), EXECUTE_TX_SELECTOR: getSelector("DefaultAccount", "executeTransaction"), RIGHT_PADDED_GET_ACCOUNT_VERSION_SELECTOR: getPaddedSelector("ContractDeployer", "extendedAccountVersion"), diff --git a/system-contracts/test/CodeOracle.spec.ts b/system-contracts/test/CodeOracle.spec.ts index d9b0c3781..e8838ff0b 100644 --- a/system-contracts/test/CodeOracle.spec.ts +++ b/system-contracts/test/CodeOracle.spec.ts @@ -60,7 +60,10 @@ describe("CodeOracle tests", function () { const versionedHash = hashBytecode(unknownLargeBytecode); const keccakHash = ethers.utils.keccak256(unknownLargeBytecode); - await expect(codeOracleTest.codeOracleTest(versionedHash, keccakHash)).to.be.rejectedWith("CodeOracle call failed"); + await expect(codeOracleTest.codeOracleTest(versionedHash, keccakHash)).to.be.revertedWithCustomError( + codeOracleTest, + "CodeOracleCallFailed" + ); }); after(async () => { diff --git a/system-contracts/test/Compressor.spec.ts b/system-contracts/test/Compressor.spec.ts index 615ab9211..184d675c8 100644 --- a/system-contracts/test/Compressor.spec.ts +++ b/system-contracts/test/Compressor.spec.ts @@ -183,13 +183,6 @@ describe("Compressor tests", function () { }); describe("verifyCompressedStateDiffs", function () { - it("non l1 messenger failed to call", async () => { - await expect(compressor.verifyCompressedStateDiffs(0, 8, "0x", "0x0000")).to.be.revertedWithCustomError( - compressor, - "Unauthorized" - ); - }); - it("enumeration index size is too large", async () => { const stateDiffs = [ { diff --git a/system-contracts/test/DefaultAccount.spec.ts b/system-contracts/test/DefaultAccount.spec.ts index 9f3d380d3..6770746a4 100644 --- a/system-contracts/test/DefaultAccount.spec.ts +++ b/system-contracts/test/DefaultAccount.spec.ts @@ -38,7 +38,9 @@ describe("DefaultAccount tests", function () { mockERC20 = (await deployContract("MockContract")) as MockContract; paymasterFlowIface = new ethers.utils.Interface((await loadArtifact("IPaymasterFlow")).abi); - ERC20Iface = new ethers.utils.Interface((await loadArtifact("IERC20")).abi); + ERC20Iface = new ethers.utils.Interface( + (await loadArtifact("@openzeppelin/contracts-v4/token/ERC20/IERC20.sol:IERC20")).abi + ); bootloaderAccount = await ethers.getImpersonatedSigner(TEST_BOOTLOADER_FORMAL_ADDRESS); }); diff --git a/system-contracts/test/L1Messenger.spec.ts b/system-contracts/test/L1Messenger.spec.ts index 678da92a9..342385be0 100644 --- a/system-contracts/test/L1Messenger.spec.ts +++ b/system-contracts/test/L1Messenger.spec.ts @@ -1,31 +1,33 @@ import { ethers, network } from "hardhat"; import type { L1Messenger } from "../typechain"; +import { IL2DAValidatorFactory } from "../typechain/IL2DAValidatorFactory"; import { L1MessengerFactory } from "../typechain"; import { prepareEnvironment, setResult } from "./shared/mocks"; -import type { StateDiff } from "./shared/utils"; -import { compressStateDiffs, deployContractOnAddress, encodeStateDiffs, getCode, getWallets } from "./shared/utils"; -import { utils } from "zksync-ethers"; +import { deployContractOnAddress, getCode, getWallets } from "./shared/utils"; +import { utils, L2VoidSigner } from "zksync-ethers"; import type { Wallet } from "zksync-ethers"; import { TEST_KNOWN_CODE_STORAGE_CONTRACT_ADDRESS, TEST_L1_MESSENGER_SYSTEM_CONTRACT_ADDRESS, TEST_BOOTLOADER_FORMAL_ADDRESS, - TWO_IN_256, } from "./shared/constants"; import { expect } from "chai"; -import { BigNumber } from "ethers"; import { randomBytes } from "crypto"; +const EXPECTED_DA_INPUT_OFFSET = 160; +const L2_TO_L1_LOGS_MERKLE_TREE_LEAVES = 16_384; +const L2_TO_L1_LOG_SERIALIZE_SIZE = 88; +const L2_L1_LOGS_TREE_DEFAULT_LEAF_HASH = "0x72abee45b59e344af8a6e520241c4744aff26ed411f4c4b00f8af09adada43ba"; + describe("L1Messenger tests", () => { let l1Messenger: L1Messenger; let wallet: Wallet; let l1MessengerAccount: ethers.Signer; let knownCodeStorageAccount: ethers.Signer; let bootloaderAccount: ethers.Signer; - let stateDiffsSetupData: StateDiffSetupData; let logData: LogData; - let bytecodeData: ContentLengthPair; let emulator: L1MessengerPubdataEmulator; + let bytecode; before(async () => { await prepareEnvironment(); @@ -36,13 +38,16 @@ describe("L1Messenger tests", () => { knownCodeStorageAccount = await ethers.getImpersonatedSigner(TEST_KNOWN_CODE_STORAGE_CONTRACT_ADDRESS); bootloaderAccount = await ethers.getImpersonatedSigner(TEST_BOOTLOADER_FORMAL_ADDRESS); // setup - stateDiffsSetupData = await setupStateDiffs(); logData = setupLogData(l1MessengerAccount, l1Messenger); - bytecodeData = await setupBytecodeData(ethers.constants.AddressZero); + bytecode = await getCode(TEST_L1_MESSENGER_SYSTEM_CONTRACT_ADDRESS); await setResult("SystemContext", "txNumberInBlock", [], { failure: false, returnData: ethers.utils.defaultAbiCoder.encode(["uint16"], [1]), }); + await setResult("IMessageRoot", "getAggregatedRoot", [], { + failure: false, + returnData: ethers.constants.HashZero, + }); emulator = new L1MessengerPubdataEmulator(); }); @@ -50,7 +55,10 @@ describe("L1Messenger tests", () => { // cleaning the state of l1Messenger await l1Messenger .connect(bootloaderAccount) - .publishPubdataAndClearState(emulator.buildTotalL2ToL1PubdataAndStateDiffs()); + .publishPubdataAndClearState( + ethers.constants.AddressZero, + await emulator.buildTotalL2ToL1PubdataAndStateDiffs(l1Messenger) + ); await network.provider.request({ method: "hardhat_stopImpersonatingAccount", params: [TEST_L1_MESSENGER_SYSTEM_CONTRACT_ADDRESS], @@ -73,23 +81,15 @@ describe("L1Messenger tests", () => { emulator.addLog(logData.logs[0].log); await (await l1Messenger.connect(l1MessengerAccount).sendToL1(logData.messages[0].message)).wait(); emulator.addLog(logData.messages[0].log); - emulator.addMessage({ - lengthBytes: logData.messages[0].currentMessageLengthBytes, - content: logData.messages[0].message, - }); - await ( - await l1Messenger - .connect(knownCodeStorageAccount) - .requestBytecodeL1Publication(await ethers.utils.hexlify(utils.hashBytecode(bytecodeData.content)), { - gasLimit: 130000000, - }) - ).wait(); - emulator.addBytecode(bytecodeData); - emulator.setStateDiffsSetupData(stateDiffsSetupData); + await ( await l1Messenger .connect(bootloaderAccount) - .publishPubdataAndClearState(emulator.buildTotalL2ToL1PubdataAndStateDiffs(), { gasLimit: 1000000000 }) + .publishPubdataAndClearState( + ethers.constants.AddressZero, + await emulator.buildTotalL2ToL1PubdataAndStateDiffs(l1Messenger), + { gasLimit: 1000000000 } + ) ).wait(); }); @@ -98,7 +98,21 @@ describe("L1Messenger tests", () => { await expect( l1Messenger .connect(bootloaderAccount) - .publishPubdataAndClearState(emulator.buildTotalL2ToL1PubdataAndStateDiffs({ numberOfLogs: 0x4002 })) + .publishPubdataAndClearState( + ethers.constants.AddressZero, + await emulator.buildTotalL2ToL1PubdataAndStateDiffs(l1Messenger, { numberOfLogs: 0x4002 }) + ) + ).to.be.revertedWithCustomError(l1Messenger, "ReconstructionMismatch"); + }); + + it("should revert Invalid input DA signature", async () => { + await expect( + l1Messenger + .connect(bootloaderAccount) + .publishPubdataAndClearState( + ethers.constants.AddressZero, + await emulator.buildTotalL2ToL1PubdataAndStateDiffs(l1Messenger, { l2DaValidatorFunctionSig: "0x12121212" }) + ) ).to.be.revertedWithCustomError(l1Messenger, "ReconstructionMismatch"); }); @@ -120,50 +134,71 @@ describe("L1Messenger tests", () => { await expect( l1Messenger .connect(bootloaderAccount) - .publishPubdataAndClearState(emulator.buildTotalL2ToL1PubdataAndStateDiffs(overrideData)) + .publishPubdataAndClearState( + ethers.constants.AddressZero, + await emulator.buildTotalL2ToL1PubdataAndStateDiffs(l1Messenger, overrideData) + ) ).to.be.revertedWithCustomError(l1Messenger, "ReconstructionMismatch"); }); - it("should revert chainedMessageHash mismatch", async () => { - // Buffer.alloc(32, 6), to trigger the revert - const wrongMessage = { lengthBytes: logData.messages[0].currentMessageLengthBytes, content: Buffer.alloc(32, 6) }; - const overrideData = { messages: [...emulator.messages] }; - overrideData.messages[0] = wrongMessage; + it("should revert Invalid input msgs hash", async () => { + const correctChainedMessagesHash = await l1Messenger.provider.getStorageAt(l1Messenger.address, 2); + await expect( - l1Messenger - .connect(bootloaderAccount) - .publishPubdataAndClearState(emulator.buildTotalL2ToL1PubdataAndStateDiffs(overrideData)) + l1Messenger.connect(bootloaderAccount).publishPubdataAndClearState( + ethers.constants.AddressZero, + await emulator.buildTotalL2ToL1PubdataAndStateDiffs(l1Messenger, { + chainedMessagesHash: ethers.utils.keccak256(correctChainedMessagesHash), + }) + ) ).to.be.revertedWithCustomError(l1Messenger, "ReconstructionMismatch"); }); - it("should revert state diff compression version mismatch", async () => { - await ( - await l1Messenger - .connect(knownCodeStorageAccount) - .requestBytecodeL1Publication(await ethers.utils.hexlify(utils.hashBytecode(bytecodeData.content)), { - gasLimit: 130000000, + it("should revert Invalid bytecodes hash", async () => { + const correctChainedBytecodesHash = await l1Messenger.provider.getStorageAt(l1Messenger.address, 3); + + await expect( + l1Messenger.connect(bootloaderAccount).publishPubdataAndClearState( + ethers.constants.AddressZero, + await emulator.buildTotalL2ToL1PubdataAndStateDiffs(l1Messenger, { + chainedBytecodeHash: ethers.utils.keccak256(correctChainedBytecodesHash), }) - ).wait(); - // modify version to trigger the revert + ) + ).to.be.revertedWithCustomError(l1Messenger, "ReconstructionMismatch"); + }); + + it("should revert Invalid offset", async () => { await expect( l1Messenger.connect(bootloaderAccount).publishPubdataAndClearState( - emulator.buildTotalL2ToL1PubdataAndStateDiffs({ - version: ethers.utils.hexZeroPad(ethers.utils.hexlify(66), 1), + ethers.constants.AddressZero, + await emulator.buildTotalL2ToL1PubdataAndStateDiffs(l1Messenger, { + operatorDataOffset: EXPECTED_DA_INPUT_OFFSET + 1, }) ) ).to.be.revertedWithCustomError(l1Messenger, "ReconstructionMismatch"); }); - it("should revert extra data", async () => { - // add extra data to trigger the revert + it("should revert Invalid length", async () => { await expect( l1Messenger .connect(bootloaderAccount) .publishPubdataAndClearState( - ethers.utils.concat([emulator.buildTotalL2ToL1PubdataAndStateDiffs(), Buffer.alloc(1, 64)]) + ethers.constants.AddressZero, + await emulator.buildTotalL2ToL1PubdataAndStateDiffs(l1Messenger, { operatorDataLength: 1 }) ) ).to.be.revertedWithCustomError(l1Messenger, "ReconstructionMismatch"); }); + + it("should revert Invalid root hash", async () => { + await expect( + l1Messenger.connect(bootloaderAccount).publishPubdataAndClearState( + ethers.constants.AddressZero, + await emulator.buildTotalL2ToL1PubdataAndStateDiffs(l1Messenger, { + chainedLogsRootHash: ethers.constants.HashZero, + }) + ) + ).to.be.revertedWithCustomError(l1Messenger, "ReconstructionMismatch"); + }); }); describe("sendL2ToL1Log", async () => { @@ -235,10 +270,6 @@ describe("L1Messenger tests", () => { .and.to.emit(l1Messenger, "L2ToL1LogSent") .withArgs([0, true, 1, l1Messenger.address, expectedKey, ethers.utils.keccak256(logData.messages[0].message)]); emulator.addLog(logData.messages[0].log); - emulator.addMessage({ - lengthBytes: logData.messages[0].currentMessageLengthBytes, - content: logData.messages[0].message, - }); }); }); @@ -255,85 +286,16 @@ describe("L1Messenger tests", () => { await expect( l1Messenger .connect(knownCodeStorageAccount) - .requestBytecodeL1Publication(await ethers.utils.hexlify(utils.hashBytecode(bytecodeData.content)), { - gasLimit: 130000000, + .requestBytecodeL1Publication(ethers.utils.hexlify(utils.hashBytecode(bytecode)), { + gasLimit: 2300000000, }) ) .to.emit(l1Messenger, "BytecodeL1PublicationRequested") - .withArgs(await ethers.utils.hexlify(utils.hashBytecode(bytecodeData.content))); - emulator.addBytecode(bytecodeData); + .withArgs(ethers.utils.hexlify(utils.hashBytecode(bytecode))); }); }); }); -// Interface represents the structure of the data that that is used in totalL2ToL1PubdataAndStateDiffs. -interface StateDiffSetupData { - encodedStateDiffs: string; - compressedStateDiffs: string; - enumerationIndexSizeBytes: string; - numberOfStateDiffsBytes: string; - compressedStateDiffsSizeBytes: string; -} - -async function setupStateDiffs(): Promise { - const stateDiffs: StateDiff[] = [ - { - key: "0x1234567890123456789012345678901234567890123456789012345678901230", - index: 0, - initValue: BigNumber.from("0x1234567890123456789012345678901234567890123456789012345678901231"), - finalValue: BigNumber.from("0x1234567890123456789012345678901234567890123456789012345678901230"), - }, - { - key: "0x1234567890123456789012345678901234567890123456789012345678901232", - index: 1, - initValue: TWO_IN_256.sub(1), - finalValue: BigNumber.from(1), - }, - { - key: "0x1234567890123456789012345678901234567890123456789012345678901234", - index: 0, - initValue: TWO_IN_256.div(2), - finalValue: BigNumber.from(1), - }, - { - key: "0x1234567890123456789012345678901234567890123456789012345678901236", - index: 2323, - initValue: BigNumber.from("0x1234567890123456789012345678901234567890123456789012345678901237"), - finalValue: BigNumber.from("0x0239329298382323782378478237842378478237847237237872373272373272"), - }, - { - key: "0x1234567890123456789012345678901234567890123456789012345678901238", - index: 2, - initValue: BigNumber.from(0), - finalValue: BigNumber.from(1), - }, - ]; - const encodedStateDiffs = encodeStateDiffs(stateDiffs); - const compressedStateDiffs = compressStateDiffs(4, stateDiffs); - const enumerationIndexSizeBytes = ethers.utils.hexZeroPad(ethers.utils.hexlify(4), 1); - await setResult( - "Compressor", - "verifyCompressedStateDiffs", - [stateDiffs.length, 4, encodedStateDiffs, compressedStateDiffs], - { - failure: false, - returnData: ethers.utils.defaultAbiCoder.encode(["bytes32"], [ethers.utils.keccak256(encodedStateDiffs)]), - } - ); - const numberOfStateDiffsBytes = ethers.utils.hexZeroPad(ethers.utils.hexlify(stateDiffs.length), 4); - const compressedStateDiffsSizeBytes = ethers.utils.hexZeroPad( - ethers.utils.hexlify(ethers.utils.arrayify(compressedStateDiffs).length), - 3 - ); - return { - encodedStateDiffs, - compressedStateDiffs, - enumerationIndexSizeBytes, - numberOfStateDiffsBytes, - compressedStateDiffsSizeBytes, - }; -} - // Interface for L2ToL1Log struct. interface L2ToL1Log { l2ShardId: number; @@ -416,47 +378,34 @@ function setupLogData(l1MessengerAccount: ethers.Signer, l1Messenger: L1Messenge }; } -// Represents the structure of the bytecode/message data that is part of the pubdata. -interface ContentLengthPair { - content: string; - lengthBytes: string; -} - -async function setupBytecodeData(l1MessengerAddress: string): Promise { - const content = await getCode(l1MessengerAddress); - const lengthBytes = ethers.utils.hexZeroPad(ethers.utils.hexlify(ethers.utils.arrayify(content).length), 4); - return { - content, - lengthBytes, - }; -} - // Used for emulating the pubdata published by the L1Messenger. class L1MessengerPubdataEmulator implements EmulatorData { numberOfLogs: number; encodedLogs: string[]; - numberOfMessages: number; - messages: ContentLengthPair[]; - numberOfBytecodes: number; - bytecodes: ContentLengthPair[]; - stateDiffsSetupData: StateDiffSetupData; - version: string; + l2DaValidatorFunctionSig: string; + chainedLogsHash: string; + chainedLogsRootHash: string; + operatorDataOffset: number; + operatorDataLength: number; + + // These two fields are always zero, we need + // them just to extend the interface. + chainedMessagesHash: string; + chainedBytecodeHash: string; constructor() { this.numberOfLogs = 0; this.encodedLogs = []; - this.numberOfMessages = 0; - this.messages = []; - this.numberOfBytecodes = 0; - this.bytecodes = []; - this.stateDiffsSetupData = { - compressedStateDiffsSizeBytes: "", - enumerationIndexSizeBytes: "", - compressedStateDiffs: "", - numberOfStateDiffsBytes: "", - encodedStateDiffs: "", - }; - this.version = ethers.utils.hexZeroPad(ethers.utils.hexlify(1), 1); + + const factoryInterface = IL2DAValidatorFactory.connect( + ethers.constants.AddressZero, + new L2VoidSigner(ethers.constants.AddressZero) + ); + this.l2DaValidatorFunctionSig = factoryInterface.interface.getSighash("validatePubdata"); + + this.chainedLogsHash = ethers.constants.HashZero; + this.chainedLogsRootHash = ethers.constants.HashZero; + this.operatorDataOffset = EXPECTED_DA_INPUT_OFFSET; } addLog(log: string): void { @@ -464,70 +413,80 @@ class L1MessengerPubdataEmulator implements EmulatorData { this.numberOfLogs++; } - addMessage(message: ContentLengthPair): void { - this.messages.push(message); - this.numberOfMessages++; - } - - addBytecode(bytecode: ContentLengthPair): void { - this.bytecodes.push(bytecode); - this.numberOfBytecodes++; - } - - setStateDiffsSetupData(data: StateDiffSetupData) { - this.stateDiffsSetupData = data; - } + async buildTotalL2ToL1PubdataAndStateDiffs( + l1Messenger: L1Messenger, + overrideData: EmulatorOverrideData = {} + ): Promise { + const storedChainedMessagesHash = await l1Messenger.provider.getStorageAt(l1Messenger.address, 2); + const storedChainedBytecodesHash = await l1Messenger.provider.getStorageAt(l1Messenger.address, 3); - buildTotalL2ToL1PubdataAndStateDiffs(overrideData: EmulatorOverrideData = {}): string { const { + l2DaValidatorFunctionSig = this.l2DaValidatorFunctionSig, + chainedLogsHash = calculateChainedLogsHash(this.encodedLogs), + chainedLogsRootHash = calculateLogsRootHash(this.encodedLogs), + chainedMessagesHash = storedChainedMessagesHash, + chainedBytecodeHash = storedChainedBytecodesHash, + operatorDataOffset = this.operatorDataOffset, numberOfLogs = this.numberOfLogs, encodedLogs = this.encodedLogs, - numberOfMessages = this.numberOfMessages, - messages = this.messages, - numberOfBytecodes = this.numberOfBytecodes, - bytecodes = this.bytecodes, - stateDiffsSetupData = this.stateDiffsSetupData, - version = this.version, } = overrideData; - - const messagePairs = []; - for (let i = 0; i < numberOfMessages; i++) { - messagePairs.push(messages[i].lengthBytes, messages[i].content); - } - - const bytecodePairs = []; - for (let i = 0; i < numberOfBytecodes; i++) { - bytecodePairs.push(bytecodes[i].lengthBytes, bytecodes[i].content); - } + const operatorDataLength = overrideData.operatorDataLength + ? overrideData.operatorDataLength + : numberOfLogs * L2_TO_L1_LOG_SERIALIZE_SIZE + 4; return ethers.utils.concat([ + l2DaValidatorFunctionSig, + chainedLogsHash, + chainedLogsRootHash, + chainedMessagesHash, + chainedBytecodeHash, + ethers.utils.defaultAbiCoder.encode(["uint256"], [operatorDataOffset]), + ethers.utils.defaultAbiCoder.encode(["uint256"], [operatorDataLength]), ethers.utils.hexZeroPad(ethers.utils.hexlify(numberOfLogs), 4), ...encodedLogs, - ethers.utils.hexZeroPad(ethers.utils.hexlify(numberOfMessages), 4), - ...messagePairs, - ethers.utils.hexZeroPad(ethers.utils.hexlify(numberOfBytecodes), 4), - ...bytecodePairs, - version, - stateDiffsSetupData.compressedStateDiffsSizeBytes, - stateDiffsSetupData.enumerationIndexSizeBytes, - stateDiffsSetupData.compressedStateDiffs, - stateDiffsSetupData.numberOfStateDiffsBytes, - stateDiffsSetupData.encodedStateDiffs, ]); } } // Represents the structure of the data that the emulator uses. interface EmulatorData { + l2DaValidatorFunctionSig: string; + chainedLogsHash: string; + chainedLogsRootHash: string; + chainedMessagesHash: string; + chainedBytecodeHash: string; + operatorDataOffset: number; + operatorDataLength: number; numberOfLogs: number; encodedLogs: string[]; - numberOfMessages: number; - messages: ContentLengthPair[]; - numberOfBytecodes: number; - bytecodes: ContentLengthPair[]; - stateDiffsSetupData: StateDiffSetupData; - version: string; } // Represents a type that allows for overriding specific properties of the EmulatorData. // This is useful when you want to change some properties of the emulator data without affecting the others. type EmulatorOverrideData = Partial; + +function calculateChainedLogsHash(logs: string[]): string { + let hash = ethers.constants.HashZero; + for (const log of logs) { + const logHash = ethers.utils.keccak256(log); + hash = ethers.utils.keccak256(ethers.utils.concat([hash, logHash])); + } + + return hash; +} + +function calculateLogsRootHash(logs: string[]): string { + const logsTreeArray: string[] = new Array(L2_TO_L1_LOGS_MERKLE_TREE_LEAVES).fill(L2_L1_LOGS_TREE_DEFAULT_LEAF_HASH); + for (let i = 0; i < logs.length; i++) { + logsTreeArray[i] = ethers.utils.keccak256(logs[i]); + } + + let length = L2_TO_L1_LOGS_MERKLE_TREE_LEAVES; + + while (length > 1) { + for (let i = 0; i < length; i += 2) { + logsTreeArray[i / 2] = ethers.utils.keccak256(ethers.utils.concat([logsTreeArray[i], logsTreeArray[i + 1]])); + } + length /= 2; + } + return logsTreeArray[0]; +} diff --git a/system-contracts/test/L2GenesisUpgrade.spec.ts b/system-contracts/test/L2GenesisUpgrade.spec.ts new file mode 100644 index 000000000..a76100dc5 --- /dev/null +++ b/system-contracts/test/L2GenesisUpgrade.spec.ts @@ -0,0 +1,146 @@ +import { expect } from "chai"; +import { ethers, network } from "hardhat"; +import * as zksync from "zksync-ethers"; +import type { ComplexUpgrader, L2GenesisUpgrade } from "../typechain"; +import { ComplexUpgraderFactory, L2GenesisUpgradeFactory } from "../typechain"; +import { + TEST_L2_GENESIS_UPGRADE_CONTRACT_ADDRESS, + TEST_FORCE_DEPLOYER_ADDRESS, + REAL_L2_ASSET_ROUTER_ADDRESS, + REAL_L2_MESSAGE_ROOT_ADDRESS, + TEST_COMPLEX_UPGRADER_CONTRACT_ADDRESS, + ADDRESS_ONE, +} from "./shared/constants"; +import { deployContractOnAddress, loadArtifact } from "./shared/utils"; +import { prepareEnvironment, setResult } from "./shared/mocks"; + +describe("L2GenesisUpgrade tests", function () { + let l2GenesisUpgrade: L2GenesisUpgrade; + let complexUpgrader: ComplexUpgrader; + const chainId = 270; + + const ctmDeployerAddress = ethers.utils.hexlify(ethers.utils.randomBytes(20)); + const bridgehubOwnerAddress = ethers.utils.hexlify(ethers.utils.randomBytes(20)); + + const forceDeployments = [ + { + bytecodeHash: "0x0100056f53fd9e940906d998a80ed53392e5c50a8eb198baf9f78fd84ce7ec70", + newAddress: "0x0000000000000000000000000000000000020002", + callConstructor: true, + value: 0, + input: "0x", + }, + ]; + + let fixedForceDeploymentsData: string; + + const additionalForceDeploymentsData = ethers.utils.defaultAbiCoder.encode( + [ + "tuple(bytes32 baseTokenAssetId, address l2LegacySharedBridge, address predeployedL2WethAddress, address baseTokenL1Address, string baseTokenName, string baseTokenSymbol)", + ], + [ + { + baseTokenAssetId: "0x0100056f53fd9e940906d998a80ed53392e5c50a8eb198baf9f78fd84ce7ec70", + l2LegacySharedBridge: ethers.constants.AddressZero, + predeployedL2WethAddress: ADDRESS_ONE, + baseTokenL1Address: ADDRESS_ONE, + baseTokenName: "Ether", + baseTokenSymbol: "ETH", + }, + ] + ); + + before(async () => { + await prepareEnvironment(); + + const wallet = await ethers.getImpersonatedSigner(TEST_FORCE_DEPLOYER_ADDRESS); + await deployContractOnAddress(TEST_COMPLEX_UPGRADER_CONTRACT_ADDRESS, "ComplexUpgrader"); + await deployContractOnAddress(TEST_L2_GENESIS_UPGRADE_CONTRACT_ADDRESS, "L2GenesisUpgrade"); + complexUpgrader = ComplexUpgraderFactory.connect(TEST_COMPLEX_UPGRADER_CONTRACT_ADDRESS, wallet); + l2GenesisUpgrade = L2GenesisUpgradeFactory.connect(TEST_L2_GENESIS_UPGRADE_CONTRACT_ADDRESS, wallet); + + await setResult( + "IBridgehub", + "setAddresses", + [REAL_L2_ASSET_ROUTER_ADDRESS, ctmDeployerAddress, REAL_L2_MESSAGE_ROOT_ADDRESS], + { + failure: false, + returnData: "0x", + } + ); + await setResult("IBridgehub", "owner", [], { + failure: false, + returnData: ethers.utils.defaultAbiCoder.encode(["address"], [bridgehubOwnerAddress]), + }); + + await setResult("SystemContext", "setChainId", [chainId], { + failure: false, + returnData: "0x", + }); + + await setResult("ContractDeployer", "forceDeployOnAddresses", [forceDeployments], { + failure: false, + returnData: "0x", + }); + + const msgRootBytecode = (await loadArtifact("DummyMessageRoot")).bytecode; + const messageRootBytecodeHash = zksync.utils.hashBytecode(msgRootBytecode); + + const ntvBytecode = (await loadArtifact("DummyL2NativeTokenVault")).bytecode; + const ntvBytecodeHash = zksync.utils.hashBytecode(ntvBytecode); + + const l2AssetRouterBytecode = (await loadArtifact("DummyL2AssetRouter")).bytecode; + const l2AssetRouterBytecodeHash = zksync.utils.hashBytecode(l2AssetRouterBytecode); + + const bridgehubBytecode = (await loadArtifact("DummyBridgehub")).bytecode; + const bridgehubBytecodeHash = zksync.utils.hashBytecode(bridgehubBytecode); + + fixedForceDeploymentsData = ethers.utils.defaultAbiCoder.encode( + [ + "tuple(uint256 l1ChainId, uint256 eraChainId, address l1AssetRouter, bytes32 l2TokenProxyBytecodeHash, address aliasedL1Governance, uint256 maxNumberOfZKChains, bytes32 bridgehubBytecodeHash, bytes32 l2AssetRouterBytecodeHash, bytes32 l2NtvBytecodeHash, bytes32 messageRootBytecodeHash, address l2SharedBridgeLegacyImpl, address l2BridgedStandardERC20Impl, address dangerousTestOnlyForcedBeacon)", + ], + [ + { + l1ChainId: 1, + eraChainId: 1, + l1AssetRouter: ADDRESS_ONE, + l2TokenProxyBytecodeHash: "0x0100056f53fd9e940906d998a80ed53392e5c50a8eb198baf9f78fd84ce7ec70", + aliasedL1Governance: ADDRESS_ONE, + maxNumberOfZKChains: 100, + bridgehubBytecodeHash: bridgehubBytecodeHash, + l2AssetRouterBytecodeHash: l2AssetRouterBytecodeHash, + l2NtvBytecodeHash: ntvBytecodeHash, + messageRootBytecodeHash: messageRootBytecodeHash, + // For genesis upgrade these values will always be zero + l2SharedBridgeLegacyImpl: ethers.constants.AddressZero, + l2BridgedStandardERC20Impl: ethers.constants.AddressZero, + dangerousTestOnlyForcedBeacon: ethers.constants.AddressZero, + }, + ] + ); + }); + + describe("upgrade", function () { + it("successfully upgraded", async () => { + const data = l2GenesisUpgrade.interface.encodeFunctionData("genesisUpgrade", [ + chainId, + ctmDeployerAddress, + fixedForceDeploymentsData, + additionalForceDeploymentsData, + ]); + + // Note, that the event is emitted at the complex upgrader, but the event declaration is taken from the l2GenesisUpgrade contract. + await expect(complexUpgrader.upgrade(l2GenesisUpgrade.address, data)) + .to.emit( + new ethers.Contract(complexUpgrader.address, l2GenesisUpgrade.interface, complexUpgrader.signer), + "UpgradeComplete" + ) + .withArgs(chainId); + + await network.provider.request({ + method: "hardhat_stopImpersonatingAccount", + params: [TEST_FORCE_DEPLOYER_ADDRESS], + }); + }); + }); +}); diff --git a/system-contracts/test/PubdataChunkPublisher.spec.ts b/system-contracts/test/PubdataChunkPublisher.spec.ts index 8dfbfebf9..68d4bfa5b 100644 --- a/system-contracts/test/PubdataChunkPublisher.spec.ts +++ b/system-contracts/test/PubdataChunkPublisher.spec.ts @@ -35,29 +35,22 @@ describe("PubdataChunkPublisher tests", () => { }); }); - describe("chunkAndPublishPubdata", () => { - it("non-L1Messenger failed to call", async () => { - await expect(pubdataChunkPublisher.chunkAndPublishPubdata("0x1337")).to.be.revertedWithCustomError( - pubdataChunkPublisher, - "Unauthorized" - ); - }); - + describe("chunkPubdataToBlobs", () => { it("Too Much Pubdata", async () => { const pubdata = genRandHex(blobSizeInBytes * maxNumberBlobs + 1); await expect( - pubdataChunkPublisher.connect(l1MessengerAccount).chunkAndPublishPubdata(pubdata) + pubdataChunkPublisher.connect(l1MessengerAccount).chunkPubdataToBlobs(pubdata) ).to.be.revertedWithCustomError(pubdataChunkPublisher, "TooMuchPubdata"); }); it("Publish 1 Blob", async () => { const pubdata = genRandHex(blobSizeInBytes); - await pubdataChunkPublisher.connect(l1MessengerAccount).chunkAndPublishPubdata(pubdata); + await pubdataChunkPublisher.connect(l1MessengerAccount).chunkPubdataToBlobs(pubdata); }); it("Publish 2 Blobs", async () => { const pubdata = genRandHex(blobSizeInBytes * maxNumberBlobs); - await pubdataChunkPublisher.connect(l1MessengerAccount).chunkAndPublishPubdata(pubdata); + await pubdataChunkPublisher.connect(l1MessengerAccount).chunkPubdataToBlobs(pubdata); }); }); }); diff --git a/system-contracts/test/SystemContext.spec.ts b/system-contracts/test/SystemContext.spec.ts index 2117c59da..1848defe5 100644 --- a/system-contracts/test/SystemContext.spec.ts +++ b/system-contracts/test/SystemContext.spec.ts @@ -108,7 +108,7 @@ describe("SystemContext tests", () => { systemContext .connect(bootloaderAccount) .setNewBatch(batchHash, batchData.batchTimestamp, batchData.batchNumber.add(1), 1) - ).to.be.rejectedWith("Timestamps should be incremental"); + ).to.be.revertedWithCustomError(systemContext, "TimestampsShouldBeIncremental"); }); it("should revert wrong block number", async () => { @@ -118,7 +118,7 @@ describe("SystemContext tests", () => { systemContext .connect(bootloaderAccount) .setNewBatch(batchHash, batchData.batchTimestamp.add(1), batchData.batchNumber, 1) - ).to.be.rejectedWith("The provided batch number is not correct"); + ).to.be.revertedWithCustomError(systemContext, "ProvidedBatchNumberIsNotCorrect"); }); it("should set new batch", async () => { @@ -173,9 +173,7 @@ describe("SystemContext tests", () => { const expectedBlockHash = ethers.utils.keccak256(ethers.utils.solidityPack(["uint32"], [blockData.blockNumber])); await expect( systemContext.connect(bootloaderAccount).setL2Block(blockData.blockNumber.add(1), 0, expectedBlockHash, true, 1) - ).to.be.rejectedWith( - "The timestamp of the L2 block must be greater than or equal to the timestamp of the current batch" - ); + ).to.be.revertedWithCustomError(systemContext, "L2BlockAndBatchTimestampMismatch"); }); it("should revert There must be a virtual block created at the start of the batch", async () => { @@ -185,7 +183,7 @@ describe("SystemContext tests", () => { systemContext .connect(bootloaderAccount) .setL2Block(blockData.blockNumber.add(1), blockData.blockTimestamp.add(42), expectedBlockHash, true, 0) - ).to.be.rejectedWith("There must be a virtual block created at the start of the batch"); + ).to.be.revertedWithCustomError(systemContext, "NoVirtualBlocks"); }); it("should revert Upgrade transaction must be first", async () => { @@ -195,7 +193,7 @@ describe("SystemContext tests", () => { systemContext .connect(bootloaderAccount) .setL2Block(blockData.blockNumber.add(1), blockData.blockTimestamp.add(42), expectedBlockHash, false, 1) - ).to.be.rejectedWith("Upgrade transaction must be first"); + ).to.be.revertedWithCustomError(systemContext, "UpgradeTransactionMustBeFirst"); }); it("should revert L2 block number is never expected to be zero", async () => { @@ -205,7 +203,7 @@ describe("SystemContext tests", () => { systemContext .connect(bootloaderAccount) .setL2Block(0, blockData.blockTimestamp.add(42), expectedBlockHash, true, 1) - ).to.be.rejectedWith("L2 block number is never expected to be zero"); + ).to.be.revertedWithCustomError(systemContext, "L2BlockNumberZero"); }); it("should revert The previous L2 block hash is incorrect", async () => { @@ -215,7 +213,7 @@ describe("SystemContext tests", () => { systemContext .connect(bootloaderAccount) .setL2Block(blockData.blockNumber.add(1), blockData.blockTimestamp.add(42), wrongBlockHash, true, 1) - ).to.be.rejectedWith("The previous L2 block hash is incorrect"); + ).to.be.revertedWithCustomError(systemContext, "PreviousL2BlockHashIsIncorrect"); }); it("should set L2 block, check blockNumber & blockTimestamp change, also check getBlockHashEVM", async () => { @@ -248,7 +246,7 @@ describe("SystemContext tests", () => { systemContext .connect(bootloaderAccount) .setL2Block(blockData.blockNumber, blockData.blockTimestamp.add(42), expectedBlockHash, true, 1) - ).to.be.rejectedWith("Can not reuse L2 block number from the previous batch"); + ).to.be.revertedWithCustomError(systemContext, "CannotReuseL2BlockNumberFromPreviousBatch"); }); it("should revert The timestamp of the same L2 block must be same", async () => { @@ -258,7 +256,7 @@ describe("SystemContext tests", () => { systemContext .connect(bootloaderAccount) .setL2Block(blockData.blockNumber, blockData.blockTimestamp.add(42), expectedBlockHash, false, 1) - ).to.be.rejectedWith("The timestamp of the same L2 block must be same"); + ).to.be.revertedWithCustomError(systemContext, "IncorrectSameL2BlockTimestamp"); }); it("should revert The previous hash of the same L2 block must be same", async () => { @@ -270,7 +268,7 @@ describe("SystemContext tests", () => { systemContext .connect(bootloaderAccount) .setL2Block(blockData.blockNumber, blockData.blockTimestamp, expectedBlockHash, false, 1) - ).to.be.rejectedWith("The previous hash of the same L2 block must be same"); + ).to.be.revertedWithCustomError(systemContext, "IncorrectSameL2BlockPrevBlockHash"); }); it("should revert Can not create virtual blocks in the middle of the miniblock", async () => { @@ -282,7 +280,7 @@ describe("SystemContext tests", () => { systemContext .connect(bootloaderAccount) .setL2Block(blockData.blockNumber, blockData.blockTimestamp, expectedBlockHash, false, 1) - ).to.be.rejectedWith("Can not create virtual blocks in the middle of the miniblock"); + ).to.be.revertedWithCustomError(systemContext, "IncorrectVirtualBlockInsideMiniblock"); }); it("should set block again, no data changed", async () => { @@ -307,7 +305,7 @@ describe("SystemContext tests", () => { systemContext .connect(bootloaderAccount) .setL2Block(blockData.blockNumber.add(1), blockData.blockTimestamp.add(42), invalidBlockHash, false, 0) - ).to.be.rejectedWith("The current L2 block hash is incorrect"); + ).to.be.revertedWithCustomError(systemContext, "IncorrectL2BlockHash"); }); it("should revert The timestamp of the new L2 block must be greater than the timestamp of the previous L2 block", async () => { @@ -326,9 +324,7 @@ describe("SystemContext tests", () => { systemContext .connect(bootloaderAccount) .setL2Block(blockData.blockNumber.add(1), 0, expectedBlockHash, false, 0) - ).to.be.rejectedWith( - "The timestamp of the new L2 block must be greater than the timestamp of the previous L2 block" - ); + ).to.be.revertedWithCustomError(systemContext, "NonMonotonicL2BlockTimestamp"); }); it("should set block again and check blockNumber & blockTimestamp also check getBlockHashEVM", async () => { @@ -363,7 +359,7 @@ describe("SystemContext tests", () => { systemContext .connect(bootloaderAccount) .setL2Block(blockData.blockNumber.add(111), blockData.blockTimestamp.add(42), expectedBlockHash, false, 0) - ).to.be.rejectedWith("Invalid new L2 block number"); + ).to.be.revertedWithCustomError(systemContext, "InvalidNewL2BlockNumber"); }); it("should update currentL2BlockTxsRollingHash", async () => { @@ -384,8 +380,9 @@ describe("SystemContext tests", () => { const batchData = await systemContext.getBatchNumberAndTimestamp(); const baseFee = await systemContext.baseFee(); await systemContext.connect(bootloaderAccount).unsafeOverrideBatch(batchData.batchTimestamp, 0, baseFee); - await expect(systemContext.connect(bootloaderAccount).publishTimestampDataToL1()).to.be.rejectedWith( - "The current batch number must be greater than 0" + await expect(systemContext.connect(bootloaderAccount).publishTimestampDataToL1()).to.be.revertedWithCustomError( + systemContext, + "CurrentBatchNumberMustBeGreaterThanZero" ); await systemContext .connect(bootloaderAccount) diff --git a/system-contracts/test/shared/constants.ts b/system-contracts/test/shared/constants.ts index c85f56415..c3f82c989 100644 --- a/system-contracts/test/shared/constants.ts +++ b/system-contracts/test/shared/constants.ts @@ -15,6 +15,7 @@ export const TEST_BOOTLOADER_UTILITIES_ADDRESS = "0x0000000000000000000000000000 export const TEST_COMPRESSOR_CONTRACT_ADDRESS = "0x000000000000000000000000000000000000900e"; export const TEST_COMPLEX_UPGRADER_CONTRACT_ADDRESS = "0x000000000000000000000000000000000000900f"; export const TEST_PUBDATA_CHUNK_PUBLISHER_ADDRESS = "0x0000000000000000000000000000000000009011"; +export const TEST_L2_GENESIS_UPGRADE_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000010001"; // event writer should be on the original address because event logs are filtered by address export const REAL_EVENT_WRITER_CONTRACT_ADDRESS = "0x000000000000000000000000000000000000800d"; @@ -26,6 +27,12 @@ export const REAL_CODE_ORACLE_CONTRACT_ADDRESS = "0x0000000000000000000000000000 export const REAL_MSG_VALUE_SYSTEM_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000008009"; export const REAL_SYSTEM_CONTEXT_ADDRESS = "0x000000000000000000000000000000000000800b"; +export const REAL_BRIDGEHUB_ADDRESS = "0x0000000000000000000000000000000000010002"; +export const REAL_L2_ASSET_ROUTER_ADDRESS = "0x0000000000000000000000000000000000010003"; +export const REAL_L2_MESSAGE_ROOT_ADDRESS = "0x0000000000000000000000000000000000010005"; + export const EMPTY_STRING_KECCAK = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"; export const TWO_IN_256 = BigNumber.from(2).pow(256); export const ONE_BYTES32_HEX = "0x0000000000000000000000000000000000000000000000000000000000000001"; + +export const ADDRESS_ONE = "0x0000000000000000000000000000000000000001"; diff --git a/system-contracts/test/shared/mocks.ts b/system-contracts/test/shared/mocks.ts index 4dd51671e..8e38ba278 100644 --- a/system-contracts/test/shared/mocks.ts +++ b/system-contracts/test/shared/mocks.ts @@ -14,6 +14,8 @@ import { TEST_SYSTEM_CONTEXT_CONTRACT_ADDRESS, TEST_COMPRESSOR_CONTRACT_ADDRESS, TEST_PUBDATA_CHUNK_PUBLISHER_ADDRESS, + REAL_BRIDGEHUB_ADDRESS, + REAL_L2_MESSAGE_ROOT_ADDRESS, } from "./constants"; import { deployContractOnAddress, getWallets, loadArtifact } from "./utils"; @@ -37,6 +39,13 @@ const TEST_SYSTEM_CONTRACTS_MOCKS = { MsgValueSimulator: TEST_MSG_VALUE_SYSTEM_CONTRACT_ADDRESS, Bootloader: TEST_BOOTLOADER_FORMAL_ADDRESS, PubdataChunkPublisher: TEST_PUBDATA_CHUNK_PUBLISHER_ADDRESS, + // We use `IBridgehub` name, since this is the name of the file in the system-contracts folder. + // The contract itself is present in a different one. + // For bridgehub we mock the real address for simplicity. + // In case of need, it can be ported to use the test address. + IBridgehub: REAL_BRIDGEHUB_ADDRESS, + // For similar reasons we mock the L2 message real root only for simplicity + IMessageRoot: REAL_L2_MESSAGE_ROOT_ADDRESS, }; // Deploys mocks, and cleans previous call results during deployments. diff --git a/tools/README.md b/tools/README.md index 081ab8d70..a49cf4c73 100644 --- a/tools/README.md +++ b/tools/README.md @@ -7,3 +7,11 @@ 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/state-transition/Verifier.sol ``` + +## L2 mode + +At the time of this writing, `modexp` precompile is not present on zkSync Era. In order to deploy the verifier on top of a ZK Chain, a different version has to be used with custom implementation of modular exponentiation. + +```shell +cargo run --bin zksync_verifier_contract_generator --release -- --input_path data/scheduler_key.json --output_path ../l2-contracts/contracts/verifier/Verifier.sol --l2_mode +``` diff --git a/tools/data/verifier_contract_template.txt b/tools/data/verifier_contract_template.txt index 5ef32b2c5..23249c9ab 100644 --- a/tools/data/verifier_contract_template.txt +++ b/tools/data/verifier_contract_template.txt @@ -8,7 +8,7 @@ import {IVerifier} from "./chain-interfaces/IVerifier.sol"; /// @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 hyperchain circuits. +/// Modifications have been made to optimize the proof system for ZK chain circuits. /// @dev Contract was generated from a verification key with a hash of 0x{{vk_hash}} /// @dev It uses a custom memory layout inside the inline assembly block. Each reserved memory cell is declared in the /// constants below. @@ -278,8 +278,7 @@ contract Verifier is IVerifier { /// @inheritdoc IVerifier function verify( uint256[] calldata, // _publicInputs - uint256[] calldata, // _proof - uint256[] calldata // _recursiveAggregationInput + uint256[] calldata // _proof ) public view virtual returns (bool) { // No memory was accessed yet, so keys can be loaded into the right place and not corrupt any other memory. _loadVerificationKey(); @@ -309,18 +308,7 @@ contract Verifier is IVerifier { } /// @dev Performs modular exponentiation using the formula (value ^ power) mod R_MOD. - function modexp(value, power) -> res { - mstore(0x00, 0x20) - mstore(0x20, 0x20) - mstore(0x40, 0x20) - mstore(0x60, value) - mstore(0x80, power) - mstore(0xa0, R_MOD) - if iszero(staticcall(gas(), 5, 0, 0xc0, 0x00, 0x20)) { - revertWithMessage(24, "modexp precompile failed") - } - res := mload(0x00) - } + {{modexp_function}} /// @dev Performs a point multiplication operation and stores the result in a given memory destination. function pointMulIntoDest(point, s, dest) { @@ -458,7 +446,17 @@ contract Verifier is IVerifier { // 2. Load the proof (except for the recursive part) offset := calldataload(0x24) let proofLengthInWords := calldataload(add(offset, 0x04)) - isValid := and(eq(proofLengthInWords, 44), isValid) + + // Check the proof length depending on whether the recursive part is present + let expectedProofLength + switch mload(VK_RECURSIVE_FLAG_SLOT) + case 0 { + expectedProofLength := 44 + } + default { + expectedProofLength := 48 + } + isValid := and(eq(proofLengthInWords, expectedProofLength), isValid) // PROOF_STATE_POLYS_0 { @@ -605,21 +603,13 @@ contract Verifier is IVerifier { } // 3. Load the recursive part of the proof - offset := calldataload(0x44) - let recursiveProofLengthInWords := calldataload(add(offset, 0x04)) - - switch mload(VK_RECURSIVE_FLAG_SLOT) - case 0 { - // recursive part should be empty - isValid := and(iszero(recursiveProofLengthInWords), isValid) - } - default { + if mload(VK_RECURSIVE_FLAG_SLOT) { // recursive part should be consist of 2 points - isValid := and(eq(recursiveProofLengthInWords, 4), isValid) + // PROOF_RECURSIVE_PART_P1 { - let x := mod(calldataload(add(offset, 0x024)), Q_MOD) - let y := mod(calldataload(add(offset, 0x044)), Q_MOD) + let x := mod(calldataload(add(offset, 0x5a4)), Q_MOD) + let y := mod(calldataload(add(offset, 0x5c4)), Q_MOD) let xx := mulmod(x, x, Q_MOD) isValid := and(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD)), isValid) mstore(PROOF_RECURSIVE_PART_P1_X_SLOT, x) @@ -627,8 +617,8 @@ contract Verifier is IVerifier { } // PROOF_RECURSIVE_PART_P2 { - let x := mod(calldataload(add(offset, 0x064)), Q_MOD) - let y := mod(calldataload(add(offset, 0x084)), Q_MOD) + let x := mod(calldataload(add(offset, 0x5e4)), Q_MOD) + let y := mod(calldataload(add(offset, 0x604)), Q_MOD) let xx := mulmod(x, x, Q_MOD) isValid := and(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD)), isValid) mstore(PROOF_RECURSIVE_PART_P2_X_SLOT, x) diff --git a/tools/src/main.rs b/tools/src/main.rs index 746373fe4..4da69d921 100644 --- a/tools/src/main.rs +++ b/tools/src/main.rs @@ -115,6 +115,10 @@ struct Opt { /// Output path to verifier contract file. #[structopt(short = "o", long = "output_path", default_value = "data/Verifier.sol")] output_path: String, + + /// The Verifier is to be compiled for an L2 network, where modexp precompile is not available. + #[structopt(short = "l2", long = "l2_mode")] + l2_mode: bool, } fn main() -> Result<(), Box> { @@ -135,7 +139,7 @@ fn main() -> Result<(), Box> { let vk_hash = hex::encode(calculate_verification_key_hash(verification_key).to_fixed_bytes()); let verifier_contract_template = - insert_residue_elements_and_commitments(&verifier_contract_template, &vk, &vk_hash)?; + insert_residue_elements_and_commitments(&verifier_contract_template, &vk, &vk_hash, opt.l2_mode)?; let mut file = File::create(opt.output_path)?; @@ -147,6 +151,7 @@ fn insert_residue_elements_and_commitments( template: &str, vk: &HashMap, vk_hash: &str, + l2_mode: bool, ) -> Result> { let reg = Handlebars::new(); let residue_g2_elements = generate_residue_g2_elements(vk); @@ -155,11 +160,16 @@ fn insert_residue_elements_and_commitments( let verifier_contract_template = template.replace("{{residue_g2_elements}}", &residue_g2_elements); + let modexp_function = get_modexp_function(l2_mode); + let verifier_contract_template = verifier_contract_template.replace("{{modexp_function}}", &modexp_function); + + Ok(reg.render_template( &verifier_contract_template, &json!({"residue_g2_elements": residue_g2_elements, "commitments": commitments, - "vk_hash": vk_hash}), + "vk_hash": vk_hash, + "modexp_function": modexp_function}), )?) } @@ -334,3 +344,37 @@ fn generate_residue_g2_elements(vk: &HashMap) -> String { residue_g2_elements } + + +fn get_modexp_function(l2_mode: bool) -> String { + if l2_mode { + r#"function modexp(value, power) -> res { + res := 1 + for { + + } gt(power, 0) { + + } { + if mod(power, 2) { + res := mulmod(res, value, R_MOD) + } + value := mulmod(value, value, R_MOD) + power := shr(1, power) + } + }"#.to_string() + } else { + r#"function modexp(value, power) -> res { + mstore(0x00, 0x20) + mstore(0x20, 0x20) + mstore(0x40, 0x20) + mstore(0x60, value) + mstore(0x80, power) + mstore(0xa0, R_MOD) + if iszero(staticcall(gas(), 5, 0, 0xc0, 0x00, 0x20)) { + revertWithMessage(24, "modexp precompile failed") + } + res := mload(0x00) + }"#.to_string() + } +} + diff --git a/yarn.lock b/yarn.lock index 66cd67b1a..96046f0a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -722,6 +722,23 @@ chalk "4.1.2" fs-extra "^11.1.1" +"@matterlabs/hardhat-zksync-node@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@matterlabs/hardhat-zksync-node/-/hardhat-zksync-node-1.2.0.tgz#800b24d33ca3a35c92afdc75d1ab05a6a957b15f" + integrity sha512-cTL8FrsolQEJMn2K25Nj/78rydRs/YiQyUu3Q1Rn5axrtVWXWATUP4z7hE5qH2lWk3VZcC9GYFrewP5c1Q+A9Q== + dependencies: + "@matterlabs/hardhat-zksync-solc" "^1.2.5" + axios "^1.7.2" + chai "^4.3.4" + chalk "^4.1.2" + debug "^4.3.5" + fs-extra "^11.2.0" + proxyquire "^2.1.3" + sinon "^18.0.0" + sinon-chai "^3.7.0" + source-map-support "^0.5.21" + undici "^6.18.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" @@ -746,16 +763,7 @@ proper-lockfile "^4.1.2" semver "^7.5.1" -"@matterlabs/hardhat-zksync-solc@^0.3.15": - version "0.3.17" - resolved "https://registry.yarnpkg.com/@matterlabs/hardhat-zksync-solc/-/hardhat-zksync-solc-0.3.17.tgz#72f199544dc89b268d7bfc06d022a311042752fd" - integrity sha512-aZgQ0yfXW5xPkfuEH1d44ncWV4T2LzKZd0VVPo4PL5cUrYs2/II1FaEDp5zsf3FxOR1xT3mBsjuSrtJkk4AL8Q== - dependencies: - "@nomiclabs/hardhat-docker" "^2.0.0" - chalk "4.1.2" - dockerode "^3.3.4" - -"@matterlabs/hardhat-zksync-solc@^1.0.5", "@matterlabs/hardhat-zksync-solc@^1.1.4": +"@matterlabs/hardhat-zksync-solc@=1.1.4", "@matterlabs/hardhat-zksync-solc@^1.0.5", "@matterlabs/hardhat-zksync-solc@^1.1.4": version "1.1.4" resolved "https://registry.yarnpkg.com/@matterlabs/hardhat-zksync-solc/-/hardhat-zksync-solc-1.1.4.tgz#04a2fad6fb6b6944c64ad969080ee65b9af3f617" integrity sha512-4/usbogh9neewR2/v8Dn2OzqVblZMUuT/iH2MyPZgPRZYQlL4SlZtMvokU9UQjZT6iSoaKCbbdWESHDHSzfUjA== @@ -772,6 +780,32 @@ sinon-chai "^3.7.0" undici "^5.14.0" +"@matterlabs/hardhat-zksync-solc@^0.3.15": + version "0.3.17" + resolved "https://registry.yarnpkg.com/@matterlabs/hardhat-zksync-solc/-/hardhat-zksync-solc-0.3.17.tgz#72f199544dc89b268d7bfc06d022a311042752fd" + integrity sha512-aZgQ0yfXW5xPkfuEH1d44ncWV4T2LzKZd0VVPo4PL5cUrYs2/II1FaEDp5zsf3FxOR1xT3mBsjuSrtJkk4AL8Q== + dependencies: + "@nomiclabs/hardhat-docker" "^2.0.0" + chalk "4.1.2" + dockerode "^3.3.4" + +"@matterlabs/hardhat-zksync-solc@^1.2.5": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@matterlabs/hardhat-zksync-solc/-/hardhat-zksync-solc-1.2.5.tgz#fbeeabc3fea0dd232fa3c8cb31bd93c103eba11a" + integrity sha512-iZyznWl1Hoe/Z46hnUe1s2drBZBjJOS/eN+Ql2lIBX9B6NevBl9DYzkKzH5HEIMCLGnX9sWpRAJqUQJWy9UB6w== + dependencies: + "@nomiclabs/hardhat-docker" "^2.0.2" + chai "^4.3.4" + chalk "^4.1.2" + debug "^4.3.5" + dockerode "^4.0.2" + fs-extra "^11.2.0" + proper-lockfile "^4.1.2" + semver "^7.6.2" + sinon "^18.0.0" + sinon-chai "^3.7.0" + undici "^6.18.2" + "@matterlabs/hardhat-zksync-verify@0.6.1": version "0.6.1" resolved "https://registry.yarnpkg.com/@matterlabs/hardhat-zksync-verify/-/hardhat-zksync-verify-0.6.1.tgz#3fd83f4177ac0b138656ed93d4756ec27f1d329d" @@ -1101,9 +1135,9 @@ undici "^5.14.0" "@nomicfoundation/hardhat-verify@^2.0.0": - version "2.0.8" - resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-verify/-/hardhat-verify-2.0.8.tgz#6a77dc03de990a1a3aa8e6dc073c393263dbf258" - integrity sha512-x/OYya7A2Kcz+3W/J78dyDHxr0ezU23DKTrRKfy5wDPCnePqnr79vm8EXqX3gYps6IjPBYyGPZ9K6E5BnrWx5Q== + version "2.0.6" + resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-verify/-/hardhat-verify-2.0.6.tgz#02623c431244c92a852c524008239fc616e1c658" + integrity sha512-oKUI5fl8QC8jysE2LUBHE6rObzEmccJcc4b43Ov7LFMlCBZJE27qoqGIsg/++wX7L8Jdga+bkejPxl8NvsecpQ== dependencies: "@ethersproject/abi" "^5.1.2" "@ethersproject/address" "^5.0.2" @@ -1181,7 +1215,7 @@ "@nomicfoundation/solidity-analyzer-win32-ia32-msvc" "0.1.1" "@nomicfoundation/solidity-analyzer-win32-x64-msvc" "0.1.1" -"@nomiclabs/hardhat-docker@^2.0.0": +"@nomiclabs/hardhat-docker@^2.0.0", "@nomiclabs/hardhat-docker@^2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-docker/-/hardhat-docker-2.0.2.tgz#ae964be17951275a55859ff7358e9e7c77448846" integrity sha512-XgGEpRT3wlA1VslyB57zyAHV+oll8KnV1TjwnxxC1tpAL04/lbdwpdO5KxInVN8irMSepqFpsiSkqlcnvbE7Ng== @@ -1216,12 +1250,14 @@ resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-waffle/-/hardhat-waffle-2.0.6.tgz#d11cb063a5f61a77806053e54009c40ddee49a54" integrity sha512-+Wz0hwmJGSI17B+BhU/qFRZ1l6/xMW82QGXE/Gi+WTmwgJrQefuBs1lIf7hzQ1hLk6hpkvb/zwcNkpVKRYTQYg== -"@openzeppelin/contracts-upgradeable-v4@npm:@openzeppelin/contracts-upgradeable@4.9.5": +"@openzeppelin/contracts-upgradeable-v4@npm:@openzeppelin/contracts-upgradeable@4.9.5", "@openzeppelin/contracts-upgradeable@4.9.5": + name "@openzeppelin/contracts-upgradeable-v4" version "4.9.5" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.5.tgz#572b5da102fc9be1d73f34968e0ca56765969812" integrity sha512-f7L1//4sLlflAN7fVzJLoRedrf5Na3Oal5PZfIq55NFcVZ90EpV1q5xOvL4lFvg3MNICSDr2hH0JUBxwlxcoPg== -"@openzeppelin/contracts-v4@npm:@openzeppelin/contracts@4.9.5": +"@openzeppelin/contracts-v4@npm:@openzeppelin/contracts@4.9.5", "@openzeppelin/contracts@4.9.5": + name "@openzeppelin/contracts-v4" version "4.9.5" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.5.tgz#1eed23d4844c861a1835b5d33507c1017fa98de8" integrity sha512-ZK+W5mVhRppff9BE6YdR8CC52C8zAvsVAiWhEtQ5+oNxFE6h1WdeWo+FJSF8KKvtxxVYZ7MTP/5KoVpAU3aSWg== @@ -1413,20 +1449,27 @@ dependencies: type-detect "4.0.8" -"@sinonjs/commons@^3.0.0": +"@sinonjs/commons@^3.0.0", "@sinonjs/commons@^3.0.1": 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@^11.2.2": +"@sinonjs/fake-timers@11.2.2", "@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/fake-timers@^13.0.1": + version "13.0.5" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz#36b9dbc21ad5546486ea9173d6bea063eb1717d5" + integrity sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw== + dependencies: + "@sinonjs/commons" "^3.0.1" + "@sinonjs/samsam@^8.0.0": version "8.0.0" resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-8.0.0.tgz#0d488c91efb3fa1442e26abea81759dfc8b5ac60" @@ -1441,6 +1484,11 @@ resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz#5981a8db18b56ba38ef0efb7d995b12aa7b51918" integrity sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ== +"@sinonjs/text-encoding@^0.7.3": + version "0.7.3" + resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz#282046f03e886e352b2d5f5da5eb755e01457f3f" + integrity sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA== + "@solidity-parser/parser@^0.14.0": version "0.14.5" resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.14.5.tgz#87bc3cc7b068e08195c219c91cd8ddff5ef1a804" @@ -2227,6 +2275,15 @@ axios@^1.6.2: form-data "^4.0.0" proxy-from-env "^1.1.0" +axios@^1.7.2: + version "1.7.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" + integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" @@ -2549,6 +2606,19 @@ chai@^4.3.10, chai@^4.3.6, chai@^4.3.7: pathval "^1.1.1" type-detect "^4.0.8" +chai@^4.3.4: + version "4.5.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.5.0.tgz#707e49923afdd9b13a8b0b47d33d732d13812fd8" + integrity sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw== + 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.1.0" + 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" @@ -2961,6 +3031,13 @@ debug@^3.1.0, debug@^3.2.6, debug@^3.2.7: dependencies: ms "^2.1.1" +debug@^4.3.5: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + decamelize@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" @@ -3041,7 +3118,7 @@ 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: +diff@^5.1.0, diff@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== @@ -3873,6 +3950,14 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" +fill-keys@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/fill-keys/-/fill-keys-1.0.2.tgz#9a8fa36f4e8ad634e3bf6b4f3c8882551452eb20" + integrity sha512-tcgI872xXjwFF4xgQmLxi76GnwJG3g/3isB1l4/G5Z4zrbddGpBjqZCO9oEAcB5wX0Hj/5iQB3toxfO7in1hHA== + dependencies: + is-object "~1.0.1" + merge-descriptors "~1.0.0" + fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -4004,7 +4089,7 @@ fs-extra@^0.30.0: path-is-absolute "^1.0.0" rimraf "^2.2.8" -fs-extra@^11.1.1: +fs-extra@^11.1.1, fs-extra@^11.2.0: version "11.2.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b" integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== @@ -4908,6 +4993,11 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-object@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf" + integrity sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA== + is-path-inside@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" @@ -5551,6 +5641,11 @@ memorystream@^0.3.1: resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" integrity sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw== +merge-descriptors@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" + integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== + merge2@^1.2.3, merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -5764,6 +5859,11 @@ mocha@^9.0.2: yargs-parser "20.2.4" yargs-unparser "2.0.0" +module-not-found-error@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/module-not-found-error/-/module-not-found-error-1.0.1.tgz#cf8b4ff4f29640674d6cdd02b0e3bc523c2bbdc0" + integrity sha512-pEk4ECWQXV6z2zjhRZUongnLJNUeGQJ3w6OQ5ctGwD+i5o93qjRQUk2Rt6VdNeu3sEP0AB4LcfvdebpxBRVr4g== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -5774,7 +5874,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.1.1: +ms@2.1.3, ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -5815,6 +5915,17 @@ nise@^5.1.5: just-extend "^6.2.0" path-to-regexp "^6.2.1" +nise@^6.0.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/nise/-/nise-6.1.1.tgz#78ea93cc49be122e44cb7c8fdf597b0e8778b64a" + integrity sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g== + dependencies: + "@sinonjs/commons" "^3.0.1" + "@sinonjs/fake-timers" "^13.0.1" + "@sinonjs/text-encoding" "^0.7.3" + just-extend "^6.2.0" + path-to-regexp "^8.1.0" + 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" @@ -6102,6 +6213,11 @@ path-to-regexp@^6.2.1: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.2.tgz#324377a83e5049cbecadc5554d6a63a9a4866b36" integrity sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw== +path-to-regexp@^8.1.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4" + integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ== + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -6253,6 +6369,15 @@ proxy-from-env@^1.1.0: resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== +proxyquire@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/proxyquire/-/proxyquire-2.1.3.tgz#2049a7eefa10a9a953346a18e54aab2b4268df39" + integrity sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg== + dependencies: + fill-keys "^1.0.2" + module-not-found-error "^1.0.1" + resolve "^1.11.1" + prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -6512,7 +6637,7 @@ resolve@1.17.0: dependencies: path-parse "^1.0.6" -resolve@^1.1.6, resolve@^1.22.4, resolve@^1.8.1: +resolve@^1.1.6, resolve@^1.11.1, resolve@^1.22.4, resolve@^1.8.1: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -6696,6 +6821,11 @@ semver@^7.3.7: resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.1.tgz#60bfe090bf907a25aa8119a72b9f90ef7ca281b2" integrity sha512-f/vbBsu+fOiYt+lmwZV0rVwJScl46HppnOA1ZvIuBWKOTlllpyJ3bfVax76/OrhCH38dyxoDIA8K7uB963IYgA== +semver@^7.6.2: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + serialize-javascript@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" @@ -6809,6 +6939,18 @@ sinon@^17.0.1: nise "^5.1.5" supports-color "^7.2.0" +sinon@^18.0.0: + version "18.0.1" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-18.0.1.tgz#464334cdfea2cddc5eda9a4ea7e2e3f0c7a91c5e" + integrity sha512-a2N2TDY1uGviajJ6r4D1CyRAkzE9NNVlYOV1wX5xQDuAk0ONgzgRl0EjCQuRCPxOwp13ghsMwt9Gdldujs39qw== + dependencies: + "@sinonjs/commons" "^3.0.1" + "@sinonjs/fake-timers" "11.2.2" + "@sinonjs/samsam" "^8.0.0" + diff "^5.2.0" + nise "^6.0.0" + supports-color "^7" + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -6965,7 +7107,7 @@ solidity-coverage@^0.8.5: shelljs "^0.8.3" web3-utils "^1.3.6" -source-map-support@^0.5.13: +source-map-support@^0.5.13, source-map-support@^0.5.21: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== @@ -7159,7 +7301,7 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^7.1.0, supports-color@^7.2.0: +supports-color@^7, 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== @@ -7485,6 +7627,11 @@ type-detect@4.0.8, type-detect@^4.0.0, type-detect@^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== +type-detect@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.1.0.tgz#deb2453e8f08dcae7ae98c626b13dddb0155906c" + integrity sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw== + type-fest@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" @@ -7635,6 +7782,11 @@ undici@^5.14.0: dependencies: "@fastify/busboy" "^2.0.0" +undici@^6.18.2: + version "6.20.1" + resolved "https://registry.yarnpkg.com/undici/-/undici-6.20.1.tgz#fbb87b1e2b69d963ff2d5410a40ffb4c9e81b621" + integrity sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA== + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -7918,6 +8070,13 @@ 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.8.0-beta.5: + version "5.8.0-beta.5" + resolved "https://registry.yarnpkg.com/zksync-ethers/-/zksync-ethers-5.8.0-beta.5.tgz#4f70193a86bd1e41b25b0aa5aa32f6d41d52f7c6" + integrity sha512-saT/3OwLgifqzrBG7OujvUMapzXnshAaLzAZMycUtdV20eLSSVkyLIARVwh1M6hMQIUvX2htV0JN82QRMyM3Ig== + dependencies: + ethers "~5.7.0" + zksync-ethers@^5.0.0: version "5.8.0" resolved "https://registry.yarnpkg.com/zksync-ethers/-/zksync-ethers-5.8.0.tgz#ff054345048f851c33cb6efcf2094f40d4da6063" @@ -7931,10 +8090,3 @@ zksync-ethers@^5.9.0: integrity sha512-Y2Mx6ovvxO6UdC2dePLguVzvNToOY8iLWeq5ne+jgGSJxAi/f4He/NF6FNsf6x1aWX0o8dy4Df8RcOQXAkj5qw== dependencies: ethers "~5.7.0" - -zksync-web3@^0.15.4: - version "0.15.5" - resolved "https://registry.yarnpkg.com/zksync-web3/-/zksync-web3-0.15.5.tgz#aabe379464963ab573e15948660a709f409b5316" - integrity sha512-97gB7OKJL4spegl8fGO54g6cvTd/75G6yFWZWEa2J09zhjTrfqabbwE/GwiUJkFQ5BbzoH4JaTlVz1hoYZI+DQ== - dependencies: - ethers "~5.7.0"