diff --git a/.github/workflows/contracts.yml b/.github/workflows/contracts.yml index 0ef0037..2e6b02f 100644 --- a/.github/workflows/contracts.yml +++ b/.github/workflows/contracts.yml @@ -76,7 +76,7 @@ jobs: run: forge test --evm-version cancun -vvv - name: Run foundry coverage - run : forge coverage --evm-version cancun --report lcov + run : forge coverage --evm-version cancun --skip "DeployScroll.t.sol" --report lcov - name : Prune coverage run : lcov --rc branch_coverage=1 --remove ./lcov.info -o ./lcov.info.pruned 'src/mocks/*' 'src/test/*' 'scripts/*' 'node_modules/*' 'lib/*' diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml new file mode 100644 index 0000000..7dccd11 --- /dev/null +++ b/.github/workflows/docker-release.yml @@ -0,0 +1,78 @@ +name: Build and publish scroll-alpine image +# This workflow aims to build images from the feat-deterministic-deployment branch + +on: + workflow_dispatch: + +jobs: + build: + name: Clone, Build, Publish + runs-on: ubuntu-latest + steps: + + - name: Check out repository + uses: actions/checkout@v3 + with: + submodules: true + + - name: Update submodules recursively + run: git submodule update --init --recursive + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Setup Node.js environment + uses: actions/setup-node@v3 + with: + node-version: '21' + + - name: Install dependencies + run: npm install + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to Dockerhub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build deploy image + id: build_deploy_image + env: + REPOSITORY: scrolltech/scroll-stack-contracts + uses: docker/build-push-action@v3 + with: + platforms: linux/amd64,linux/arm64 + push: true + context: . + file: docker/Dockerfile.deploy + tags: | + ${{ env.REPOSITORY }}:deploy-${{ github.sha }} + + - name: Build gen image + id: build_gen_image + env: + REPOSITORY: scrolltech/scroll-stack-contracts + uses: docker/build-push-action@v3 + with: + platforms: linux/amd64,linux/arm64 + push: true + context: . + file: docker/Dockerfile.gen-configs + tags: | + ${{ env.REPOSITORY }}:gen-configs-${{ github.sha }} + + - name: Build verify image + id: build_verify_image + env: + REPOSITORY: scrolltech/scroll-stack-contracts + uses: docker/build-push-action@v3 + with: + platforms: linux/amd64,linux/arm64 + push: true + context: . + file: docker/Dockerfile.verify + tags: | + ${{ env.REPOSITORY }}:verify-${{ github.sha }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 88e59a6..185194c 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ broadcast # Visual Studio Code .vscode + +volume +.DS_Store diff --git a/docker/Dockerfile.deploy b/docker/Dockerfile.deploy new file mode 100644 index 0000000..af7b007 --- /dev/null +++ b/docker/Dockerfile.deploy @@ -0,0 +1,48 @@ +# Use the latest node Debian slim base image +# This makes installing yarn dep much easier +FROM node:20-bookworm-slim + +# Switch to bash shell +SHELL ["/bin/bash", "-c"] + +WORKDIR /root + +# Install dependencies +RUN apt update +RUN apt install --yes curl bash coreutils git jq ca-certificates + +# Download and run the Foundry installation script +RUN curl -L https://foundry.paradigm.xyz | bash + +# Set the environment variables to ensure Foundry tools are in the PATH +ENV PATH="/root/.foundry/bin:${PATH}" + +# Run foundryup to update Foundry +RUN foundryup -v nightly-fdd321bac95f0935529164a88faf99d4d5cfa321 + +# copy dependencies +COPY ./lib /contracts/lib +COPY ./node_modules/@openzeppelin /contracts/node_modules/@openzeppelin +COPY ./node_modules/hardhat /contracts/node_modules/hardhat + +# copy configurations +COPY foundry.toml /contracts/foundry.toml +COPY remappings.txt /contracts/remappings.txt + +# copy source code +COPY ./src /contracts/src +COPY ./scripts /contracts/scripts + +# compile contracts +ENV FOUNDRY_EVM_VERSION="cancun" +ENV FOUNDRY_BYTECODE_HASH="none" + +WORKDIR /contracts +RUN forge build + +# copy script configs +COPY ./docker/templates/config-contracts.toml /contracts/docker/templates/config-contracts.toml + +COPY ./docker/scripts/deploy.sh /contracts/docker/scripts/deploy.sh + +ENTRYPOINT ["/bin/bash", "/contracts/docker/scripts/deploy.sh"] diff --git a/docker/Dockerfile.gen-configs b/docker/Dockerfile.gen-configs new file mode 100644 index 0000000..df71058 --- /dev/null +++ b/docker/Dockerfile.gen-configs @@ -0,0 +1,56 @@ +# Use the latest node Debian slim base image +# This makes installing yarn dep much easier +FROM node:20-bookworm-slim + +# Switch to bash shell +SHELL ["/bin/bash", "-c"] + +WORKDIR /root + +# Install dependencies +RUN apt update +RUN apt install --yes curl bash coreutils git jq ca-certificates + +# Download and run the Foundry installation script +RUN curl -L https://foundry.paradigm.xyz | bash + +# Set the environment variables to ensure Foundry tools are in the PATH +ENV PATH="/root/.foundry/bin:${PATH}" + +# Run foundryup to update Foundry +RUN foundryup -v nightly-fdd321bac95f0935529164a88faf99d4d5cfa321 + +# copy dependencies +COPY ./lib /contracts/lib +COPY ./node_modules/@openzeppelin /contracts/node_modules/@openzeppelin +COPY ./node_modules/hardhat /contracts/node_modules/hardhat + +# copy configurations +COPY foundry.toml /contracts/foundry.toml +COPY remappings.txt /contracts/remappings.txt + +# copy source code +COPY ./src /contracts/src +COPY ./scripts /contracts/scripts + +# compile contracts +ENV FOUNDRY_EVM_VERSION="cancun" +ENV FOUNDRY_BYTECODE_HASH="none" + +WORKDIR /contracts +RUN forge build + +# copy script configs +COPY ./docker/templates/balance-checker-config.json /contracts/docker/templates/balance-checker-config.json +COPY ./docker/templates/bridge-history-config.json /contracts/docker/templates/bridge-history-config.json +COPY ./docker/templates/chain-monitor-config.json /contracts/docker/templates/chain-monitor-config.json +COPY ./docker/templates/config-contracts.toml /contracts/docker/templates/config-contracts.toml +COPY ./docker/templates/coordinator-config.json /contracts/docker/templates/coordinator-config.json +COPY ./docker/templates/genesis.json /contracts/docker/templates/genesis.json +COPY ./docker/templates/rollup-config.json /contracts/docker/templates/rollup-config.json +COPY ./docker/templates/rollup-explorer-backend-config.json /contracts/docker/templates/rollup-explorer-backend-config.json +COPY ./docker/templates/admin-system-backend-config.json /contracts/docker/templates/admin-system-backend-config.json + +COPY ./docker/scripts/gen-configs.sh /contracts/docker/scripts/gen-configs.sh + +ENTRYPOINT ["/bin/bash", "/contracts/docker/scripts/gen-configs.sh"] diff --git a/docker/Dockerfile.verify b/docker/Dockerfile.verify new file mode 100644 index 0000000..7df5a64 --- /dev/null +++ b/docker/Dockerfile.verify @@ -0,0 +1,45 @@ +# Use the latest node Debian slim base image +# This makes installing yarn dep much easier +FROM node:20-bookworm-slim + +# Switch to bash shell +SHELL ["/bin/bash", "-c"] + +WORKDIR /root + +# Install dependencies +RUN apt update +RUN apt install --yes curl bash coreutils git jq ca-certificates + +# Download and run the Foundry installation script +RUN curl -L https://foundry.paradigm.xyz | bash + +# Set the environment variables to ensure Foundry tools are in the PATH +ENV PATH="/root/.foundry/bin:${PATH}" + +# Run foundryup to update Foundry +RUN foundryup -v nightly-fdd321bac95f0935529164a88faf99d4d5cfa321 + +# copy dependencies +COPY ./lib /contracts/lib +COPY ./node_modules/@openzeppelin /contracts/node_modules/@openzeppelin +COPY ./node_modules/hardhat /contracts/node_modules/hardhat + +# copy configurations +COPY foundry.toml /contracts/foundry.toml +COPY remappings.txt /contracts/remappings.txt + +# copy source code +COPY ./src /contracts/src +COPY ./scripts /contracts/scripts + +# compile contracts +ENV FOUNDRY_EVM_VERSION="cancun" +ENV FOUNDRY_BYTECODE_HASH="none" + +WORKDIR /contracts +RUN forge build + +COPY ./docker/scripts/verify.sh /contracts/docker/scripts/verify.sh + +ENTRYPOINT ["/bin/bash", "/contracts/docker/scripts/verify.sh"] diff --git a/docker/config-example.toml b/docker/config-example.toml new file mode 100644 index 0000000..77e5207 --- /dev/null +++ b/docker/config-example.toml @@ -0,0 +1,134 @@ +[general] + +L1_RPC_ENDPOINT = "http://l1-devnet:8545" +L1_RPC_ENDPOINT_WEBSOCKET = "ws://l1-devnet:8546" +L2_RPC_ENDPOINT = "http://l2-sequencer:8545" + +CHAIN_NAME_L1="Ethereum" +CHAIN_NAME_L2="Scroll SDK" +CHAIN_ID_L1 = 111111 +CHAIN_ID_L2 = 221122 + +L1_CONTRACT_DEPLOYMENT_BLOCK = 0 + +[accounts] + +# note: for now we simply use Anvil's dev accounts + +DEPLOYER_PRIVATE_KEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + +L1_COMMIT_SENDER_PRIVATE_KEY = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" +L1_FINALIZE_SENDER_PRIVATE_KEY = "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a" +L1_GAS_ORACLE_SENDER_PRIVATE_KEY = "0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6" +L2_GAS_ORACLE_SENDER_PRIVATE_KEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + +DEPLOYER_ADDR = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" +OWNER_ADDR = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + +L1_COMMIT_SENDER_ADDR = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" +L1_FINALIZE_SENDER_ADDR = "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" +L1_GAS_ORACLE_SENDER_ADDR = "0x90F79bf6EB2c4f870365E785982E1f101E93b906" +L2_GAS_ORACLE_SENDER_ADDR = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + +[db] + +ADMIN_SYSTEM_BACKEND_DB_CONNECTION_STRING = "postgres://postgres:qwerty12345@postgresql:5432/scroll?sslmode=disable" +BLOCKSCOUT_DB_CONNECTION_STRING = "postgres://postgres:qwerty12345@postgresql:5432/blockscout" +BRIDGE_HISTORY_DB_CONNECTION_STRING = "postgres://postgres:qwerty12345@postgresql:5432/scroll?sslmode=disable" +CHAIN_MONITOR_DB_CONNECTION_STRING = "postgres://postgres:qwerty12345@postgresql:5432/scroll?sslmode=disable" +GAS_ORACLE_DB_CONNECTION_STRING = "postgres://postgres:qwerty12345@postgresql:5432/scroll?sslmode=disable" +COORDINATOR_DB_CONNECTION_STRING = "postgres://postgres:qwerty12345@postgresql:5432/scroll?sslmode=disable" +L1_EXPLORER_DB_CONNECTION_STRING = "postgres://postgres:qwerty12345@postgresql:5432/l1-explorer" +ROLLUP_NODE_DB_CONNECTION_STRING = "postgres://postgres:qwerty12345@postgresql:5432/scroll?sslmode=disable" +ROLLUP_EXPLORER_DB_CONNECTION_STRING = "postgres://postgres:qwerty12345@postgresql:5432/scroll?sslmode=disable" + +[gas-token] + +ALTERNATIVE_GAS_TOKEN_ENABLED = false +# EXAMPLE_GAS_TOKEN_DECIMAL = 6 +# L1_GAS_TOKEN = "0x68a041e7c20Afa4784b5d9C63246c89545Ac0E66" +GAS_ORACLE_INCORPORATE_TOKEN_EXCHANGE_RATE_ENANBLED = false +EXCHANGE_RATE_UPDATE_MODE = "Fixed" +FIXED_EXCHANGE_RATE = "0.01" +TOKEN_SYMBOL_PAIR = "UNIETH" + +[sequencer] + +L2GETH_SIGNER_ADDRESS = "0x756EA06BDEe36de11F22DCca45a31d8a178eF3c6" +L2GETH_KEYSTORE = '{"address":"756ea06bdee36de11f22dcca45a31d8a178ef3c6","crypto":{"cipher":"aes-128-ctr","ciphertext":"8bfb4e48c6b172f1f5794d2874476ca62f8184507c0916dbd45fe77a0056114c","cipherparams":{"iv":"509eb70e7379a776e0779634b6668277"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"ef0f0334e5db7c12d76993e1e9627593fd0d7cd44444b689e792c86bc8a5d75f"},"mac":"d766cac11d0bd563316f5655ebf07550b6fce98ba0ca3c13acdc1b65f5f185ca"},"id":"4459ad67-f2ce-48b2-8940-c0582106a4c6","version":3}' +L2GETH_PASSWORD = "scroll2022" +L2GETH_NODEKEY = "2f59ff2fdee1c42a3d8c8980c313bc8d6c2557463f7bb92b0f0bc89a6d3cbf01" +L2_GETH_STATIC_PEERS = '["enode://848a7d59dd8f60dd1a51160e6bc15c194937855443de9be4b2abd83e11a5c4ac21d61d065448c5c520826fe83f1f29eb5a452daccca27b8113aa897074132507@l2-sequencer:30303"]' +L2GETH_CCC_NUMWORKERS = 5 + +[rollup] + +MAX_TX_IN_CHUNK = 100 +MAX_BLOCK_IN_CHUNK = 100 +MAX_BATCH_IN_BUNDLE = 30 +MAX_L1_MESSAGE_GAS_LIMIT = 10_000_000 +TEST_ENV_MOCK_FINALIZE_ENABLED = true +TEST_ENV_MOCK_FINALIZE_TIMEOUT_SEC = 300 + +[frontend] + +EXTERNAL_RPC_URI_L1 = "http://l1-devnet.scrollsdk" +EXTERNAL_RPC_URI_L2 = "http://l2-rpc.scrollsdk" +BRIDGE_API_URI = "http://bridge-history-api.scrollsdk/api" +ROLLUPSCAN_API_URI = "http://rollup-explorer-backend.scrollsdk/api" +EXTERNAL_EXPLORER_URI_L1 = "http://l1-explorer.scrollsdk" +EXTERNAL_EXPLORER_URI_L2 = "http://blockscout.scrollsdk" +ADMIN_SYSTEM_DASHBOARD_URI = "http://admin-system-dashboard.scrollsdk" +GRAFANA_URI = "http://grafana.scrollsdk" + +[genesis] + +L2_MAX_ETH_SUPPLY = "226156424291633194186662080095093570025917938800079226639565593765455331328" +L2_DEPLOYER_INITIAL_BALANCE = 1000000000000000000 + +[contracts] + +DEPLOYMENT_SALT = "devnetSalt-000" + +# contracts deployed outside this script +L1_FEE_VAULT_ADDR = "0x0000000000000000000000000000000000000001" + +[contracts.overrides] + +# L1_WETH = "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14" +# L1_PLONK_VERIFIER = "0x0000000000000000000000000000000000000001" + +L2_MESSAGE_QUEUE = "0x5300000000000000000000000000000000000000" +L1_GAS_PRICE_ORACLE = "0x5300000000000000000000000000000000000002" +L2_WHITELIST = "0x5300000000000000000000000000000000000003" +L2_WETH = "0x5300000000000000000000000000000000000004" +L2_TX_FEE_VAULT = "0x5300000000000000000000000000000000000005" + +[contracts.verify] + +VERIFIER_TYPE_L1 = "blockscout" +VERIFIER_TYPE_L2 = "blockscout" +EXPLORER_URI_L1 = "http://l1-explorer.scrollsdk" +EXPLORER_URI_L2 = "http://blockscout.scrollsdk" +RPC_URI_L1 = "http://l1-devnet.scrollsdk" +RPC_URI_L2 = "http://l2-rpc.scrollsdk" +EXPLORER_API_KEY_L1 = "" +EXPLORER_API_KEY_L2 = "" + +[coordinator] + +CHUNK_COLLECTION_TIME_SEC = 3600 +BATCH_COLLECTION_TIME_SEC = 1800 +BUNDLE_COLLECTION_TIME_SEC = 600 +COORDINATOR_JWT_SECRET_KEY = "e788b62d39254928a821ac1c76b274a8c835aa1e20ecfb6f50eb10e87847de44" + +[ingress] + +FRONTEND_HOST = "frontends.scrollsdk" +BRIDGE_HISTORY_API_HOST = "bridge-history-api.scrollsdk" +ROLLUP_EXPLORER_API_HOST = "rollup-explorer-backend.scrollsdk" +COORDINATOR_API_HOST = "coordinator-api.scrollsdk" +RPC_GATEWAY_HOST = "l2-rpc.scrollsdk" +BLOCKSCOUT_HOST = "blockscout.scrollsdk" +ADMIN_SYSTEM_DASHBOARD_HOST= "admin-system-dashboard.scrollsdk" +GRAFANA_HOST = "http://grafana.scrollsdk" diff --git a/docker/scripts/build.sh b/docker/scripts/build.sh new file mode 100755 index 0000000..93b15de --- /dev/null +++ b/docker/scripts/build.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +latest_commit=$(git log -1 --pretty=format:%h) +tag=${latest_commit:0:8} +echo "Using Docker image tag: $tag" +echo "" + +docker build -f docker/Dockerfile.gen-configs -t scrolltech/scroll-stack-contracts:gen-configs-$tag-amd64 --platform linux/amd64 . +echo +echo "built scrolltech/scroll-stack-contracts:gen-configs-$tag-amd64" +echo + +docker build -f docker/Dockerfile.gen-configs -t scrolltech/scroll-stack-contracts:gen-configs-$tag-arm64 --platform linux/arm64 . +echo +echo "built scrolltech/scroll-stack-contracts:gen-configs-$tag-arm64" +echo + +docker build -f docker/Dockerfile.deploy -t scrolltech/scroll-stack-contracts:deploy-$tag-amd64 --platform linux/amd64 . +echo +echo "built scrolltech/scroll-stack-contracts:deploy-$tag-amd64" +echo + +docker build -f docker/Dockerfile.deploy -t scrolltech/scroll-stack-contracts:deploy-$tag-arm64 --platform linux/arm64 . +echo +echo "built scrolltech/scroll-stack-contracts:deploy-$tag-arm64" +echo diff --git a/docker/scripts/deploy.sh b/docker/scripts/deploy.sh new file mode 100755 index 0000000..286dcd1 --- /dev/null +++ b/docker/scripts/deploy.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +export FOUNDRY_EVM_VERSION="cancun" +export FOUNDRY_BYTECODE_HASH="none" + +if [ "${L1_RPC_ENDPOINT}" = "" ]; then + echo "L1_RPC_ENDPOINT is not set" + L1_RPC_ENDPOINT="http://host.docker.internal:8543" +fi + +if [ "$L2_RPC_ENDPOINT" = "" ]; then + echo "L2_RPC_ENDPOINT is not set" + L2_RPC_ENDPOINT="http://host.docker.internal:8545" +fi + +if [ "${BATCH_SIZE}" = "" ]; then + BATCH_SIZE="100" +fi + +echo "using L1_RPC_ENDPOINT = $L1_RPC_ENDPOINT" +echo "using L2_RPC_ENDPOINT = $L2_RPC_ENDPOINT" + +# simulate L1 +echo "" +echo "simulating on L1" +forge script scripts/deterministic/DeployScroll.s.sol:DeployScroll --rpc-url "$L1_RPC_ENDPOINT" --sig "run(string,string)" "L1" "verify-config" || exit 1 + +# simulate L2 +echo "" +echo "simulating on L2" +forge script scripts/deterministic/DeployScroll.s.sol:DeployScroll --rpc-url "$L2_RPC_ENDPOINT" --sig "run(string,string)" "L2" "verify-config" --legacy || exit 1 + +# deploy L1 +echo "" +echo "deploying on L1" +forge script scripts/deterministic/DeployScroll.s.sol:DeployScroll --rpc-url "$L1_RPC_ENDPOINT" --batch-size "$BATCH_SIZE" --sig "run(string,string)" "L1" "verify-config" --broadcast || exit 1 + +# deploy L2 +echo "" +echo "deploying on L2" +forge script scripts/deterministic/DeployScroll.s.sol:DeployScroll --rpc-url "$L2_RPC_ENDPOINT" --batch-size "$BATCH_SIZE" --sig "run(string,string)" "L2" "verify-config" --broadcast --legacy || exit 1 + +# log broadcast files +echo "" +echo "Broadcast files:" +for file in broadcast/DeployScroll.s.sol/*/*; do + if [ -f "$file" ]; then + echo "$file:" + cat "$file" + echo "" + fi +done \ No newline at end of file diff --git a/docker/scripts/gen-configs.sh b/docker/scripts/gen-configs.sh new file mode 100755 index 0000000..fd0e495 --- /dev/null +++ b/docker/scripts/gen-configs.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +# the deployment of the L1GasTokenGateway implementation necessitates fetching the gas token decimal +# in this case it requires the context of layer 1 +gen_config_contracts_toml() { + config_file="./volume/config.toml" + gas_token_addr=$(grep -E "^L1_GAS_TOKEN =" "$config_file" | sed 's/ *= */=/' | cut -d'=' -f2-) + gas_token_enabled=$(grep -E "^ALTERNATIVE_GAS_TOKEN_ENABLED =" "$config_file" | sed 's/ *= */=/' | cut -d'=' -f2-) + l1_rpc_url=$(grep -E "^L1_RPC_ENDPOINT =" "$config_file" | sed 's/ *= */=/' | cut -d'=' -f2- | sed 's/"//g') + + if [[ "$gas_token_enabled" == "true" && "$gas_token_addr" != "" && "$gas_token_addr" != "0x0000000000000000000000000000000000000000" ]]; then + echo "gas token enabled and address provided" + forge script scripts/deterministic/DeployScroll.s.sol:DeployScroll --rpc-url "$l1_rpc_url" --sig "run(string,string)" "none" "write-config" || exit 1 + else + echo "gas token disabled or address not provided" + forge script scripts/deterministic/DeployScroll.s.sol:DeployScroll --sig "run(string,string)" "none" "write-config" || exit 1 + fi +} + +# format_config_file will add "scrollConfig: |" to the first line and indent the rest +format_config_file() { + local file="$1" + local config_scroll_key="scrollConfig: |" + temp_file=$(mktemp) + + { + echo $config_scroll_key + while IFS= read -r line; do + echo " $line" + done < <(grep "" "$file") + } > "$temp_file" + + mv "$temp_file" "$file" +} + +echo "" +echo "generating config-contracts.toml" +gen_config_contracts_toml + +echo "" +echo "generating genesis.yaml" +forge script scripts/deterministic/GenerateGenesis.s.sol:GenerateGenesis || exit 1 +format_config_file "./volume/genesis.yaml" + +echo "" +echo "generating rollup-config.yaml" +forge script scripts/deterministic/GenerateConfigs.s.sol:GenerateRollupConfig || exit 1 +format_config_file "./volume/rollup-config.yaml" + +echo "" +echo "generating coordinator-config.yaml" +forge script scripts/deterministic/GenerateConfigs.s.sol:GenerateCoordinatorConfig || exit 1 +format_config_file "./volume/coordinator-config.yaml" + +echo "" +echo "generating chain-monitor-config.yaml" +forge script scripts/deterministic/GenerateConfigs.s.sol:GenerateChainMonitorConfig || exit 1 +format_config_file "./volume/chain-monitor-config.yaml" + +echo "" +echo "generating bridge-history-config.yaml" +forge script scripts/deterministic/GenerateConfigs.s.sol:GenerateBridgeHistoryConfig || exit 1 +format_config_file "./volume/bridge-history-config.yaml" + +echo "" +echo "generating balance-checker-config.yaml" +forge script scripts/deterministic/GenerateConfigs.s.sol:GenerateBalanceCheckerConfig || exit 1 +format_config_file "./volume/balance-checker-config.yaml" + +echo "" +echo "generating frontend-config.yaml" +forge script scripts/deterministic/GenerateConfigs.s.sol:GenerateFrontendConfig || exit 1 +format_config_file "./volume/frontend-config.yaml" + +echo "" +echo "generating rollup-explorer-backend-config.yaml" +forge script scripts/deterministic/GenerateConfigs.s.sol:GenerateRollupExplorerBackendConfig || exit 1 +format_config_file "./volume/rollup-explorer-backend-config.yaml" + +echo "" +echo "generating admin-system-backend-config.yaml" +forge script scripts/deterministic/GenerateConfigs.s.sol:GenerateAdminSystemBackendConfig || exit 1 +format_config_file "./volume/admin-system-backend-config.yaml" \ No newline at end of file diff --git a/docker/scripts/push.sh b/docker/scripts/push.sh new file mode 100755 index 0000000..da28510 --- /dev/null +++ b/docker/scripts/push.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +latest_commit=$(git log -1 --pretty=format:%h) +tag=${latest_commit:0:8} +echo "Using Docker image tag: $tag" +echo "" + +docker push scrolltech/scroll-stack-contracts:gen-configs-$tag-amd64 +docker push scrolltech/scroll-stack-contracts:gen-configs-$tag-arm64 + +docker manifest create scrolltech/scroll-stack-contracts:gen-configs-$tag \ + --amend scrolltech/scroll-stack-contracts:gen-configs-$tag-amd64 \ + --amend scrolltech/scroll-stack-contracts:gen-configs-$tag-arm64 + +docker manifest push scrolltech/scroll-stack-contracts:gen-configs-$tag + +docker push scrolltech/scroll-stack-contracts:deploy-$tag-amd64 +docker push scrolltech/scroll-stack-contracts:deploy-$tag-arm64 + +docker manifest create scrolltech/scroll-stack-contracts:deploy-$tag \ + --amend scrolltech/scroll-stack-contracts:deploy-$tag-amd64 \ + --amend scrolltech/scroll-stack-contracts:deploy-$tag-arm64 + +docker manifest push scrolltech/scroll-stack-contracts:deploy-$tag diff --git a/docker/scripts/verify.sh b/docker/scripts/verify.sh new file mode 100755 index 0000000..13e3323 --- /dev/null +++ b/docker/scripts/verify.sh @@ -0,0 +1,163 @@ +#!/bin/sh + +# extract values from config file +config_file="./volume/config.toml" +CHAIN_ID_L1=$(grep -E "^CHAIN_ID_L1 =" "$config_file" | sed 's/ *= */=/' | cut -d'=' -f2-) +CHAIN_ID_L2=$(grep -E "^CHAIN_ID_L2 =" "$config_file" | sed 's/ *= */=/' | cut -d'=' -f2-) +RPC_URI_L1=$(grep -E "^RPC_URI_L1 =" "$config_file" | sed 's/ *= */=/' | cut -d'=' -f2- | tr -d '"') +RPC_URI_L2=$(grep -E "^RPC_URI_L2 =" "$config_file" | sed 's/ *= */=/' | cut -d'=' -f2- | tr -d '"') +VERIFIER_TYPE_L1=$(grep -E "^VERIFIER_TYPE_L1 =" "$config_file" | sed 's/ *= */=/' | cut -d'=' -f2- | tr -d '"') +VERIFIER_TYPE_L2=$(grep -E "^VERIFIER_TYPE_L2 =" "$config_file" | sed 's/ *= */=/' | cut -d'=' -f2- | tr -d '"') +EXPLORER_URI_L1=$(grep -E "^EXPLORER_URI_L1 =" "$config_file" | sed 's/ *= */=/' | cut -d'=' -f2- | tr -d '"') +EXPLORER_URI_L2=$(grep -E "^EXPLORER_URI_L2 =" "$config_file" | sed 's/ *= */=/' | cut -d'=' -f2- | tr -d '"') +EXPLORER_API_KEY_L1=$(grep -E "^EXPLORER_API_KEY_L1 =" "$config_file" | sed 's/ *= */=/' | cut -d'=' -f2- | tr -d '"') +EXPLORER_API_KEY_L2=$(grep -E "^EXPLORER_API_KEY_L2 =" "$config_file" | sed 's/ *= */=/' | cut -d'=' -f2- | tr -d '"') +ALTERNATIVE_GAS_TOKEN_ENABLED=$(grep -E "^ALTERNATIVE_GAS_TOKEN_ENABLED =" "$config_file" | sed 's/ *= */=/' | cut -d'=' -f2-) +TEST_ENV_MOCK_FINALIZE_ENABLED=$(grep -E "^TEST_ENV_MOCK_FINALIZE_ENABLED =" "$config_file" | sed 's/ *= */=/' | cut -d'=' -f2-) + +# extract contract name and address +extract_contract_info() { + contract_name=$(cut -d "=" -f 1 <<< "$line" | tr -d '"') + contract_addr=$(cut -d "=" -f 2 <<< "$line" | tr -d '"' | tr -d ' ') +} + +get_source_code_name() { + # specially handle the case where alternative gas token is enabled + if [[ "$ALTERNATIVE_GAS_TOKEN_ENABLED" == "true" && "$1" =~ ^(L1_SCROLL_MESSENGER_IMPLEMENTATION_ADDR|L2_TX_FEE_VAULT_ADDR)$ ]]; then + case "$1" in + L1_SCROLL_MESSENGER_IMPLEMENTATION_ADDR) echo L1ScrollMessengerNonETH ;; + L2_TX_FEE_VAULT_ADDR) echo L2TxFeeVaultWithGasToken ;; + *) + esac + # specially handle the case where mock finalize is enabled + elif [[ "$TEST_ENV_MOCK_FINALIZE_ENABLED" == "true" && "$1" =~ ^(L1_SCROLL_CHAIN_IMPLEMENTATION_ADDR)$ ]]; then + case "$1" in + L1_SCROLL_CHAIN_IMPLEMENTATION_ADDR) echo ScrollChainMockFinalize ;; + *) + esac + else + case "$1" in + L1_WETH_ADDR) echo WrappedEther ;; + L1_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR) echo EmptyContract ;; + L1_PROXY_ADMIN_ADDR) echo ProxyAdminSetOwner ;; + L1_WHITELIST_ADDR) echo Whitelist ;; + L1_SCROLL_CHAIN_PROXY_ADDR) echo TransparentUpgradeableProxy ;; + L1_SCROLL_MESSENGER_PROXY_ADDR) echo TransparentUpgradeableProxy ;; + L1_ENFORCED_TX_GATEWAY_IMPLEMENTATION_ADDR) echo EnforcedTxGateway ;; + L1_ENFORCED_TX_GATEWAY_PROXY_ADDR) echo TransparentUpgradeableProxy ;; + L1_ZKEVM_VERIFIER_V2_ADDR) echo ZkEvmVerifierV2 ;; + L1_MULTIPLE_VERSION_ROLLUP_VERIFIER_ADDR ) echo MultipleVersionRollupVerifierSetOwner ;; + L1_MESSAGE_QUEUE_IMPLEMENTATION_ADDR) echo L1MessageQueueWithGasPriceOracle ;; + L1_MESSAGE_QUEUE_PROXY_ADDR) echo TransparentUpgradeableProxy ;; + L1_SCROLL_CHAIN_IMPLEMENTATION_ADDR) echo ScrollChain ;; + L1_GATEWAY_ROUTER_IMPLEMENTATION_ADDR) echo L1GatewayRouter ;; + L1_GATEWAY_ROUTER_PROXY_ADDR) echo TransparentUpgradeableProxy ;; + L1_ETH_GATEWAY_PROXY_ADDR) echo TransparentUpgradeableProxy ;; + L1_WETH_GATEWAY_PROXY_ADDR) echo TransparentUpgradeableProxy ;; + L1_STANDARD_ERC20_GATEWAY_PROXY_ADDR) echo TransparentUpgradeableProxy ;; + L1_CUSTOM_ERC20_GATEWAY_PROXY_ADDR) echo TransparentUpgradeableProxy ;; + L1_ERC721_GATEWAY_PROXY_ADDR) echo TransparentUpgradeableProxy ;; + L1_ERC1155_GATEWAY_PROXY_ADDR) echo TransparentUpgradeableProxy ;; + L2_MESSAGE_QUEUE_ADDR) echo L2MessageQueue ;; + L1_GAS_PRICE_ORACLE_ADDR) echo L1GasPriceOracle ;; + L1_GAS_TOKEN_GATEWAY_IMPLEMENTATION_ADDR) echo L1GasTokenGateway ;; + L1_GAS_TOKEN_GATEWAY_PROXY_ADDR) echo TransparentUpgradeableProxy ;; + L1_WRAPPED_TOKEN_GATEWAY_ADDR) echo L1WrappedTokenGateway ;; + L2_WHITELIST_ADDR) echo Whitelist ;; + L2_WETH_ADDR) echo WrappedEther ;; + L2_TX_FEE_VAULT_ADDR) echo L2TxFeeVault ;; + L2_PROXY_ADMIN_ADDR) echo ProxyAdminSetOwner ;; + L2_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR) echo EmptyContract ;; + L2_SCROLL_MESSENGER_PROXY_ADDR) echo TransparentUpgradeableProxy ;; + L2_ETH_GATEWAY_PROXY_ADDR) echo TransparentUpgradeableProxy ;; + L2_WETH_GATEWAY_PROXY_ADDR) echo TransparentUpgradeableProxy ;; + L2_STANDARD_ERC20_GATEWAY_PROXY_ADDR) echo TransparentUpgradeableProxy ;; + L2_CUSTOM_ERC20_GATEWAY_PROXY_ADDR) echo TransparentUpgradeableProxy ;; + L2_ERC721_GATEWAY_PROXY_ADDR) echo TransparentUpgradeableProxy ;; + L2_ERC1155_GATEWAY_PROXY_ADDR) echo TransparentUpgradeableProxy ;; + L2_SCROLL_STANDARD_ERC20_ADDR) echo ScrollStandardERC20 ;; + L2_SCROLL_STANDARD_ERC20_FACTORY_ADDR) echo ScrollStandardERC20FactorySetOwner ;; + L1_SCROLL_MESSENGER_IMPLEMENTATION_ADDR) echo L1ScrollMessenger ;; + L1_STANDARD_ERC20_GATEWAY_IMPLEMENTATION_ADDR) echo L1StandardERC20Gateway ;; + L1_ETH_GATEWAY_IMPLEMENTATION_ADDR) echo L1ETHGateway ;; + L1_WETH_GATEWAY_IMPLEMENTATION_ADDR) echo L1WETHGateway ;; + L1_CUSTOM_ERC20_GATEWAY_IMPLEMENTATION_ADDR) echo L1CustomERC20Gateway ;; + L1_ERC721_GATEWAY_IMPLEMENTATION_ADDR) echo L1ERC721Gateway ;; + L1_ERC1155_GATEWAY_IMPLEMENTATION_ADDR ) echo L1ERC1155Gateway ;; + L2_SCROLL_MESSENGER_IMPLEMENTATION_ADDR) echo L2ScrollMessenger ;; + L2_GATEWAY_ROUTER_IMPLEMENTATION_ADDR) echo L2GatewayRouter ;; + L2_GATEWAY_ROUTER_PROXY_ADDR) echo TransparentUpgradeableProxy ;; + L2_STANDARD_ERC20_GATEWAY_IMPLEMENTATION_ADDR) echo L2StandardERC20Gateway ;; + L2_ETH_GATEWAY_IMPLEMENTATION_ADDR) echo L2ETHGateway ;; + L2_WETH_GATEWAY_IMPLEMENTATION_ADDR) echo L2WETHGateway ;; + L2_CUSTOM_ERC20_GATEWAY_IMPLEMENTATION_ADDR) echo L2CustomERC20Gateway ;; + L2_ERC721_GATEWAY_IMPLEMENTATION_ADDR) echo L2ERC721Gateway ;; + L2_ERC1155_GATEWAY_IMPLEMENTATION_ADDR) echo L2ERC1155Gateway ;; + *) echo "" ;; # default: return void string + esac + fi +} + +function is_predeploy_contract() { + local contract_name="$1" + + if [[ "$contract_name" == "L2MessageQueue" || "$contract_name" == "L1GasPriceOracle" || "$contract_name" == "Whitelist" || "$contract_name" == "WrappedEther" || "$contract_name" == "L2TxFeeVault" ]]; then + return 0 # True + else + return 1 # False + fi +} + +# read the file line by line +while IFS= read -r line; do + extract_contract_info "$line" + + # get contracts deployment layer + if [[ "$contract_name" =~ ^L1 ]]; then + layer="L1" + # specially handle contract_name L1_GAS_PRICE_ORACLE_ADDR + if [[ "$contract_name" == "L1_GAS_PRICE_ORACLE_ADDR" ]]; then + layer="L2" + fi + elif [[ "$contract_name" =~ ^L2 ]]; then + layer="L2" + else + echo "wrong contract name, not starts with L1 or L2, contract_name: $contract_name" + continue + fi + + source_code_name=$(get_source_code_name $contract_name) + + # skip if source_code_name or contract_addr is empty + if [[ -z $source_code_name || -z $contract_addr ]]; then + echo "empty source_code_name $source_code_name or contract_addr $contract_addr" + continue + fi + + # verify contract + echo "" + echo "verifing contract $contract_name with address $contract_addr on $layer" + EXTRA_PARAMS="" + if [[ "$layer" == "L1" ]]; then + if [[ "$VERIFIER_TYPE_L1" == "etherscan" ]]; then + EXTRA_PARAMS="--api-key $EXPLORER_API_KEY_L1" + elif [[ "$VERIFIER_TYPE_L1" == "blockscout" ]]; then + EXTRA_PARAMS="--verifier-url ${EXPLORER_URI_L1}/api/ --verifier $VERIFIER_TYPE_L1" + elif [[ "$VERIFIER_TYPE_L1" == "sourcify" ]]; then + EXTRA_PARAMS="--api-key $EXPLORER_API_KEY_L1 --verifier-url $EXPLORER_URI_L1 --verifier $VERIFIER_TYPE_L1" + fi + forge verify-contract $contract_addr $source_code_name --rpc-url $RPC_URI_L1 --chain-id $CHAIN_ID_L1 --watch --guess-constructor-args --skip-is-verified-check $EXTRA_PARAMS + elif [[ "$layer" == "L2" ]]; then + if [[ "$VERIFIER_TYPE_L2" == "etherscan" ]]; then + EXTRA_PARAMS="--api-key $EXPLORER_API_KEY_L2" + elif [[ "$VERIFIER_TYPE_L2" == "blockscout" ]]; then + EXTRA_PARAMS="--verifier-url ${EXPLORER_URI_L2}/api/ --verifier $VERIFIER_TYPE_L2" + elif [[ "$VERIFIER_TYPE_L2" == "sourcify" ]]; then + EXTRA_PARAMS="--api-key $EXPLORER_API_KEY_L2 --verifier-url $EXPLORER_URI_L2 --verifier $VERIFIER_TYPE_L2" + fi + if ! is_predeploy_contract "$source_code_name"; then + string="$EXTRA_PARAMS\" --guess-constructor-args\"" + fi + forge verify-contract $contract_addr $source_code_name --rpc-url $RPC_URI_L2 --chain-id $CHAIN_ID_L2 --watch --skip-is-verified-check $EXTRA_PARAMS + fi +done < ./volume/config-contracts.toml \ No newline at end of file diff --git a/docker/templates/admin-system-backend-config.json b/docker/templates/admin-system-backend-config.json new file mode 100644 index 0000000..2687e0e --- /dev/null +++ b/docker/templates/admin-system-backend-config.json @@ -0,0 +1,59 @@ +{ + "db_config": { + "driver_name": "postgres", + "dsn": "", + "max_open_connections": 200, + "max_idel_connections": 20 + }, + "read_only_db_config": { + "driver_name": "postgres", + "dsn": "", + "max_open_connections": 200, + "max_idel_connections": 20 + }, + "db_mappings": { + "default": "read_only", + "batch_chunk": "read_only", + "prover_block_list": "read_write" + }, + "auth_db_config": { + "driver_name": "postgres", + "dsn": "", + "max_open_connections": 200, + "max_idel_connections": 20 + }, + "authentication": { + "mode": "skip", + "jwt": { + "secret": "scroll admin system secret key", + "token_expire_seconds": 3600 + }, + "ldap": { + "endpoint": "ldap://xxx.xxx.com:389", + "bind_dn": "", + "bind_password": "", + "search_base_dn_list": [""], + "search_filter": "(mail=%s)" + }, + "otp": { + "issuer": "ScrollAdmin(Dev)", + "enabled": true, + "admin_only": true + } + }, + "authorization": { + "casbin": { + "model_path": "conf/model.conf", + "policy_path": "conf/policy.csv" + } + }, + "prometheus": { + "endpoint": "https://xxx.xxx.com/prometheus", + "user": "", + "password": "" + }, + "admin": { + "prover_becomes_offline_since_last_get_task_seconds": 3600, + "prover_becomes_idle_since_last_task_assigned_seconds": 1800 + } +} \ No newline at end of file diff --git a/docker/templates/balance-checker-config.json b/docker/templates/balance-checker-config.json new file mode 100644 index 0000000..fdd59f3 --- /dev/null +++ b/docker/templates/balance-checker-config.json @@ -0,0 +1,48 @@ +{ + "addresses": [ + { + "rpc_url": "${SCROLL_L1_RPC}", + "min_balance_ether": "10", + "address": "${L1_COMMIT_SENDER_ADDRESS}", + "name": "L1_COMMIT_SENDER", + "confirmation_number": 1 + }, + { + "rpc_url": "${SCROLL_L1_RPC}", + "min_balance_ether": "10", + "address": "${L1_FINALIZE_SENDER_ADDRESS}", + "name": "L1_FINALIZE_SENDER", + "confirmation_number": 1 + }, + { + "rpc_url": "${SCROLL_L1_RPC}", + "min_balance_ether": "1.1", + "address": "${L1_GAS_ORACLE_SENDER_ADDRESS}", + "name": "L1_GAS_ORACLE_SENDER", + "confirmation_number": 1 + }, + { + "rpc_url": "${SCROLL_L1_RPC}", + "min_balance_ether": "0", + "address": "${L1_SCROLL_FEE_VAULT_ADDRESS}", + "name": "L1_SCROLL_FEE_VAULT", + "confirmation_number": 1 + }, + { + "rpc_url": "${SCROLL_L2_RPC}", + "min_balance_ether": "1.1", + "address": "${L2_GAS_ORACLE_SENDER_ADDRESS}", + "name": "L2_GAS_ORACLE_SENDER", + "confirmation_number": 1 + }, + { + "rpc_url": "${SCROLL_L2_RPC}", + "min_balance_ether": "0", + "address": "${L2_TX_FEE_VAULT_ADDR}", + "name": "L2_TX_FEE_VAULT", + "confirmation_number": 1 + } + ], + "JOB_INTERVAL_SECS": 60, + "BIND_PORT": 8080 +} \ No newline at end of file diff --git a/docker/templates/bridge-history-config.json b/docker/templates/bridge-history-config.json new file mode 100644 index 0000000..bbf8780 --- /dev/null +++ b/docker/templates/bridge-history-config.json @@ -0,0 +1,58 @@ +{ + "L1": { + "confirmation": 0, + "endpoint": null, + "startHeight": null, + "blockTime": 12, + "fetchLimit": 16, + "MessageQueueAddr": null, + "MessengerAddr": null, + "ScrollChainAddr": null, + "GatewayRouterAddr": null, + "ETHGatewayAddr": null, + "WETHGatewayAddr": null, + "StandardERC20GatewayAddr": null, + "CustomERC20GatewayAddr": null, + "ERC721GatewayAddr": null, + "ERC1155GatewayAddr": null, + "USDCGatewayAddr": "0x0000000000000000000000000000000000000000", + "LIDOGatewayAddr": "0x0000000000000000000000000000000000000000", + "DAIGatewayAddr": "0x0000000000000000000000000000000000000000", + "PufferGatewayAddr": "0x0000000000000000000000000000000000000000", + "GasTokenGatewayAddr": null, + "WrappedTokenGatewayAddr": null + }, + "L2": { + "confirmation": 0, + "endpoint": null, + "blockTime": 3, + "fetchLimit": 64, + "MessageQueueAddr": null, + "MessengerAddr": null, + "GatewayRouterAddr": null, + "ETHGatewayAddr": null, + "WETHGatewayAddr": null, + "StandardERC20GatewayAddr": null, + "CustomERC20GatewayAddr": null, + "ERC721GatewayAddr": null, + "ERC1155GatewayAddr": null, + "USDCGatewayAddr": "0x0000000000000000000000000000000000000000", + "LIDOGatewayAddr": "0x0000000000000000000000000000000000000000", + "DAIGatewayAddr": "0x0000000000000000000000000000000000000000", + "PufferGatewayAddr": "0x0000000000000000000000000000000000000000" + }, + "db": { + "dsn": "", + "driverName": "postgres", + "maxOpenNum": 200, + "maxIdleNum": 20 + }, + "redis": { + "address": "localhost:6379", + "username": "default", + "password": "", + "local": true, + "minIdleConns": 10, + "readTimeoutMs": 500 + } +} diff --git a/docker/templates/chain-monitor-config.json b/docker/templates/chain-monitor-config.json new file mode 100644 index 0000000..e1b57c0 --- /dev/null +++ b/docker/templates/chain-monitor-config.json @@ -0,0 +1,58 @@ +{ + "l1_config": { + "l1_url": null, + "confirm": "0x20", + "start_number": null, + "l1_contracts": { + "l1_gateways": { + "eth_gateway": null, + "weth_gateway": null, + "standard_erc20_gateway": null, + "custom_erc20_gateway": null, + "erc721_gateway": null, + "erc1155_gateway": null, + "dai_gateway": "0x0000000000000000000000000000000000000000", + "usdc_gateway": "0x0000000000000000000000000000000000000000", + "lido_gateway": "0x0000000000000000000000000000000000000000", + "puffer_gateway": "0x0000000000000000000000000000000000000000", + "gas_token_gateway": null + }, + "scroll_messenger": null, + "message_queue": null, + "scroll_chain": null, + "gas_token": null + }, + "start_messenger_balance": 0 + }, + "l2_config": { + "l2_url": null, + "confirm": "0x80", + "l2_contracts": { + "l2_gateways": { + "eth_gateway": null, + "weth_gateway": null, + "standard_erc20_gateway": null, + "custom_erc20_gateway": null, + "erc721_gateway": null, + "erc1155_gateway": null, + "dai_gateway": "0x0000000000000000000000000000000000000000", + "usdc_gateway": "0x0000000000000000000000000000000000000000", + "lido_gateway": "0x0000000000000000000000000000000000000000", + "puffer_gateway": "0x0000000000000000000000000000000000000000" + }, + "scroll_messenger": null, + "message_queue": null + } + }, + "slack_webhook_config": { + "webhook_url": "http://localhost:1234", + "worker_count": 5, + "worker_buffer_size": 1000 + }, + "db_config": { + "driver_name": "postgres", + "dsn": "", + "maxOpenNum": 100, + "maxIdleNum": 20 + } +} diff --git a/docker/templates/config-contracts.toml b/docker/templates/config-contracts.toml new file mode 100644 index 0000000..dae22e7 --- /dev/null +++ b/docker/templates/config-contracts.toml @@ -0,0 +1,58 @@ +L1_WETH_ADDR = "" +L1_PROXY_ADMIN_ADDR = "" +L1_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR = "" +L1_WHITELIST_ADDR = "" +L1_SCROLL_CHAIN_PROXY_ADDR = "" +L1_SCROLL_MESSENGER_PROXY_ADDR = "" +L1_ENFORCED_TX_GATEWAY_IMPLEMENTATION_ADDR = "" +L1_ENFORCED_TX_GATEWAY_PROXY_ADDR = "" +L1_PLONK_VERIFIER_ADDR = "" +L1_ZKEVM_VERIFIER_V2_ADDR = "" +L1_MULTIPLE_VERSION_ROLLUP_VERIFIER_ADDR = "" +L1_MESSAGE_QUEUE_IMPLEMENTATION_ADDR = "" +L1_MESSAGE_QUEUE_PROXY_ADDR = "" +L1_SCROLL_CHAIN_IMPLEMENTATION_ADDR = "" +L1_GATEWAY_ROUTER_IMPLEMENTATION_ADDR = "" +L1_GATEWAY_ROUTER_PROXY_ADDR = "" +L1_ETH_GATEWAY_PROXY_ADDR = "" +L1_WETH_GATEWAY_PROXY_ADDR = "" +L1_STANDARD_ERC20_GATEWAY_PROXY_ADDR = "" +L1_CUSTOM_ERC20_GATEWAY_PROXY_ADDR = "" +L1_ERC721_GATEWAY_PROXY_ADDR = "" +L1_ERC1155_GATEWAY_PROXY_ADDR = "" +L2_MESSAGE_QUEUE_ADDR = "" +L1_GAS_PRICE_ORACLE_ADDR = "" +L1_GAS_TOKEN_ADDR = "" +L1_GAS_TOKEN_GATEWAY_IMPLEMENTATION_ADDR = "" +L1_GAS_TOKEN_GATEWAY_PROXY_ADDR = "" +L1_WRAPPED_TOKEN_GATEWAY_ADDR = "" +L2_WHITELIST_ADDR = "" +L2_WETH_ADDR = "" +L2_TX_FEE_VAULT_ADDR = "" +L2_PROXY_ADMIN_ADDR = "" +L2_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR = "" +L2_SCROLL_MESSENGER_PROXY_ADDR = "" +L2_ETH_GATEWAY_PROXY_ADDR = "" +L2_WETH_GATEWAY_PROXY_ADDR = "" +L2_STANDARD_ERC20_GATEWAY_PROXY_ADDR = "" +L2_CUSTOM_ERC20_GATEWAY_PROXY_ADDR = "" +L2_ERC721_GATEWAY_PROXY_ADDR = "" +L2_ERC1155_GATEWAY_PROXY_ADDR = "" +L2_SCROLL_STANDARD_ERC20_ADDR = "" +L2_SCROLL_STANDARD_ERC20_FACTORY_ADDR = "" +L1_SCROLL_MESSENGER_IMPLEMENTATION_ADDR = "" +L1_STANDARD_ERC20_GATEWAY_IMPLEMENTATION_ADDR = "" +L1_ETH_GATEWAY_IMPLEMENTATION_ADDR = "" +L1_WETH_GATEWAY_IMPLEMENTATION_ADDR = "" +L1_CUSTOM_ERC20_GATEWAY_IMPLEMENTATION_ADDR = "" +L1_ERC721_GATEWAY_IMPLEMENTATION_ADDR = "" +L1_ERC1155_GATEWAY_IMPLEMENTATION_ADDR = "" +L2_SCROLL_MESSENGER_IMPLEMENTATION_ADDR = "" +L2_GATEWAY_ROUTER_IMPLEMENTATION_ADDR = "" +L2_GATEWAY_ROUTER_PROXY_ADDR = "" +L2_STANDARD_ERC20_GATEWAY_IMPLEMENTATION_ADDR = "" +L2_ETH_GATEWAY_IMPLEMENTATION_ADDR = "" +L2_WETH_GATEWAY_IMPLEMENTATION_ADDR = "" +L2_CUSTOM_ERC20_GATEWAY_IMPLEMENTATION_ADDR = "" +L2_ERC721_GATEWAY_IMPLEMENTATION_ADDR = "" +L2_ERC1155_GATEWAY_IMPLEMENTATION_ADDR = "" diff --git a/docker/templates/coordinator-config.json b/docker/templates/coordinator-config.json new file mode 100644 index 0000000..50d3e7e --- /dev/null +++ b/docker/templates/coordinator-config.json @@ -0,0 +1,40 @@ +{ + "prover_manager": { + "provers_per_session": 1, + "session_attempts": 100, + "chunk_collection_time_sec": 3600, + "batch_collection_time_sec": 1800, + "bundle_collection_time_sec": 600, + "verifier": { + "mock_mode": false, + "low_version_circuit": { + "params_path": "/verifier/params", + "assets_path": "/verifier/assets/lo", + "fork_name": "darwin", + "min_prover_version": "v4.4.43" + }, + "high_version_circuit": { + "params_path": "/verifier/params", + "assets_path": "/verifier/assets/hi", + "fork_name": "darwinV2", + "min_prover_version": "v4.4.56" + } + }, + "max_verifier_workers": 4, + "min_prover_version": "v4.3.41" + }, + "db": { + "driver_name": "postgres", + "dsn": "", + "maxOpenNum": 200, + "maxIdleNum": 20 + }, + "l2": { + "chain_id": null + }, + "auth": { + "secret": null, + "challenge_expire_duration_sec": 10, + "login_expire_duration_sec": 3600 + } +} \ No newline at end of file diff --git a/docker/templates/genesis.json b/docker/templates/genesis.json new file mode 100644 index 0000000..ff8769b --- /dev/null +++ b/docker/templates/genesis.json @@ -0,0 +1,51 @@ +{ + "config": { + "chainId": null, + "homesteadBlock": 0, + "eip150Block": 0, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "archimedesBlock": 0, + "shanghaiBlock": 0, + "bernoulliBlock": 0, + "curieBlock": 0, + "darwinTime": 0, + "darwinV2Time": 0, + "clique": { + "period": 3, + "epoch": 30000, + "relaxed_period": true + }, + "scroll": { + "useZktrie": true, + "maxTxPerBlock": null, + "maxTxPayloadBytesPerBlock": 122880, + "feeVaultAddress": null, + "l1Config": { + "l1ChainId": null, + "l1MessageQueueAddress": null, + "scrollChainAddress": null, + "numL1MessagesPerBlock": "10" + } + } + }, + "nonce": "0x0", + "timestamp": null, + "extraData": null, + "gasLimit": "10000000", + "difficulty": "0x1", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": {}, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "baseFeePerGas": null +} diff --git a/docker/templates/rollup-config.json b/docker/templates/rollup-config.json new file mode 100644 index 0000000..909181b --- /dev/null +++ b/docker/templates/rollup-config.json @@ -0,0 +1,130 @@ +{ + "l1_config": { + "endpoint": null, + "start_height": 0, + "relayer_config": { + "gas_price_oracle_contract_address": null, + "sender_config": { + "endpoint": null, + "escalate_blocks": 100, + "escalate_multiple_num": 11, + "escalate_multiple_den": 10, + "min_gas_tip": 1, + "max_gas_price": 10000000000000, + "tx_type": "DynamicFeeTx", + "check_pending_time": 3, + "confirmations": "0x0" + }, + "gas_oracle_config": { + "min_gas_price": 0, + "gas_price_diff": 50000, + "l1_base_fee_weight": 0.086, + "l1_blob_base_fee_weight": 0.030, + "check_committed_batches_window_minutes": 5, + "l1_base_fee_default": 15000000000, + "l1_blob_base_fee_default": 1, + "alternative_gas_token_config": { + "enabled": false, + "mode": "Fixed", + "fixed_exchange_rate": 0.001, + "token_symbol_pair": "" + } + }, + "gas_oracle_sender_signer_config": { + "signer_type": "PrivateKey", + "private_key_signer_config": { + "private_key": "1313131313131313131313131313131313131313131313131313131313131313" + } + } + } + }, + "l2_config": { + "confirmations": "0x10", + "endpoint": null, + "l2_message_queue_address": null, + "relayer_config": { + "rollup_contract_address": null, + "gas_price_oracle_contract_address": null, + "sender_config": { + "endpoint": null, + "escalate_blocks": 4, + "escalate_multiple_num": 12, + "escalate_multiple_den": 10, + "min_gas_tip": 100000000, + "max_gas_price": 200000000000, + "max_blob_gas_price": 200000000000, + "tx_type": "DynamicFeeTx", + "check_pending_time": 10, + "confirmations": "0x0", + "max_pending_blob_txs": 3 + }, + "gas_oracle_config": { + "min_gas_price": 0, + "gas_price_diff": 50000, + "alternative_gas_token_config": { + "enabled": false, + "mode": "Fixed", + "fixed_exchange_rate": 0.001, + "token_symbol_pair": "" + } + }, + "chain_monitor": { + "enabled": true, + "timeout": 3, + "try_times": 5, + "base_url": "http://chain-monitor:8080" + }, + "enable_test_env_bypass_features": null, + "finalize_batch_without_proof_timeout_sec": null, + "finalize_bundle_without_proof_timeout_sec": null, + "gas_oracle_sender_signer_config": { + "signer_type": "PrivateKey", + "private_key_signer_config": { + "private_key": "1313131313131313131313131313131313131313131313131313131313131313" + } + }, + "commit_sender_signer_config": { + "signer_type": "PrivateKey", + "private_key_signer_config": { + "private_key": "1414141414141414141414141414141414141414141414141414141414141414" + } + }, + "finalize_sender_signer_config": { + "signer_type": "PrivateKey", + "private_key_signer_config": { + "private_key": "1515151515151515151515151515151515151515151515151515151515151515" + } + }, + "l1_commit_gas_limit_multiplier": 1.2 + }, + "chunk_proposer_config": { + "propose_interval_milliseconds": 100, + "max_block_num_per_chunk": null, + "max_tx_num_per_chunk": null, + "max_l1_commit_gas_per_chunk": 5000000, + "max_l1_commit_calldata_size_per_chunk": 110000, + "chunk_timeout_sec": 2700, + "max_row_consumption_per_chunk": 1000000, + "gas_cost_increase_multiplier": 1.2, + "max_uncompressed_batch_bytes_size": 634880 + }, + "batch_proposer_config": { + "propose_interval_milliseconds": 1000, + "max_l1_commit_gas_per_batch": 5000000, + "max_l1_commit_calldata_size_per_batch": 110000, + "batch_timeout_sec": 2700, + "gas_cost_increase_multiplier": 1.2, + "max_uncompressed_batch_bytes_size": 634880 + }, + "bundle_proposer_config": { + "max_batch_num_per_bundle": 20, + "bundle_timeout_sec": 36000 + } + }, + "db_config": { + "driver_name": "postgres", + "dsn": "", + "maxOpenNum": 50, + "maxIdleNum": 20 + } +} \ No newline at end of file diff --git a/docker/templates/rollup-explorer-backend-config.json b/docker/templates/rollup-explorer-backend-config.json new file mode 100644 index 0000000..a8ec249 --- /dev/null +++ b/docker/templates/rollup-explorer-backend-config.json @@ -0,0 +1,5 @@ +{ + "db_url": null, + "open_api_addr": "/rollupscan", + "max_per_page": 500 +} \ No newline at end of file diff --git a/docs/manual-deployment.md b/docs/manual-deployment.md new file mode 100644 index 0000000..86363e0 --- /dev/null +++ b/docs/manual-deployment.md @@ -0,0 +1,86 @@ +# Manual Deployment @scroll-tech/scroll-contracts + +## Overview + +This document will guide you through manually deploying Scroll contracts to both layer 1 and layer 2 networks. + +### Requirements + +This repository requires `node` version>=20.12.2, `yarn` and `foundry` to be previously installed. + +- **Node.js:** https://nodejs.org/en/download/package-manager +- **Yarn:** https://www.npmjs.com/package/yarn +- **Foundry:** https://book.getfoundry.sh/getting-started/installation + +### Config + +1. Create directory `volume` on the root directory of the repo (all config files will be put or generated under this directory) + +```bash +mkdir volume +``` + +2. Create config file, and copy config variables from example file `./docker/config-example.toml` + +```bash +cp ./docker/config-example.toml ./volume/config.toml +``` + +If you've previously launched Scroll chain components using Scroll-SDK, you may already have a `config.toml` file. If so directly copy it to `./volume/config.toml`. +**Important Note: If you are launching a scroll chain through scroll-sdk, make sure this config.toml file is the same as the one used in scroll-sdk.** + +Details about the some important variables you may want to change: + +| Configuration Variable | Description | +| -------------------------------- | ---------------------------------------------------------------------------------------- | +| L1_RPC_ENDPOINT | The RPC endpoint for the layer 1 network | +| L2_RPC_ENDPOINT | The RPC endpoint for the layer 2 network | +| CHAIN_ID_L1 | The chain ID of the layer 1 network | +| CHAIN_ID_L2 | The chain ID of the layer 2 network | +| DEPLOYER_PRIVATE_KEY | The private key of the deployer on both layer 1 and layer 2 | +| OWNER_PRIVATE_KEY | The private key of the owner of Scroll contracts on both layer 1 and layer 2 | +| L1_COMMIT_SENDER_PRIVATE_KEY | The private key of the commit sender (sequencer) on layer 1 | +| L1_FINALIZE_SENDER_PRIVATE_KEY | The private key of the finalize sender (prover) on layer 1 | +| L1_GAS_ORACLE_SENDER_PRIVATE_KEY | The private key of the gas oracle sender on layer 1 | +| L2_GAS_ORACLE_SENDER_PRIVATE_KEY | The private key of the gas oracle sender on layer 2 | +| DEPLOYER_ADDR | The address of the deployer on both layer 1 and layer 2 | +| OWNER_ADDR | The address of the owner of Scroll contracts on both layer 1 and layer 2 | +| L1_COMMIT_SENDER_ADDR | The address of the commit sender (sequencer) on layer 1 | +| L1_FINALIZE_SENDER_ADDR | The address of the finalize sender (prover) on layer 1 | +| L1_GAS_ORACLE_SENDER_ADDR | The address of the gas oracle sender on layer 1 | +| L2_GAS_ORACLE_SENDER_ADDR | The address of the gas oracle sender on layer 2 | +| DEPLOYMENT_SALT | The salt used to deploy contracts, make it unique to prevent contract address collisions | +| L1_CONTRACT_DEPLOYMENT_BLOCK | The block that l2-sequencer and bridge-history-fetcher start to sync contracts event | + +### Deploy + +1. Install packages + +```bash +yarn install +``` + +2. Initialize git submodules. + +```bash +git submodule update --init --recursive +``` + +3. Set and export environment variables (Change the RPCs to the one you are using) + +```bash +export L1_RPC_ENDPOINT=http://l1-devnet.scrollsdk +export L2_RPC_ENDPOINT=http://l2-rpc.scrollsdk +``` + +1. Generate predicted contract addresses (This step is required mainly because we check if each contract is deployed as we expected) + +```bash +forge script scripts/deterministic/DeployScroll.s.sol:DeployScroll --sig "run(string,string)" "none" "write-config" +``` + +5. Deploy contracts on both layer1 and layer2 (Deployment may be interrupted by errors. Rerun the command to resume in such cases.) + +```bash +./docker/scripts/deploy.sh +``` diff --git a/foundry.toml b/foundry.toml index 741b5bb..cc2cbc0 100644 --- a/foundry.toml +++ b/foundry.toml @@ -7,15 +7,14 @@ libs = ["lib"] remappings = [] # a list of remappings libraries = [] # a list of deployed libraries to link against cache = true # whether to cache builds or not -force = true # whether to ignore the cache (clean build) -# evm_version = 'london' # the evm version (by hardfork name) +force = false # whether to ignore the cache (clean build) +evm_version = 'cancun' # the evm version (by hardfork name) solc_version = '0.8.24' # override for the solc version (setting this ignores `auto_detect_solc`) optimizer = true # enable or disable the solc optimizer optimizer_runs = 200 # the number of optimizer runs verbosity = 2 # the verbosity of tests ignored_error_codes = [] # a list of ignored solc error codes fuzz_runs = 256 # the number of fuzz runs for tests -ffi = false # whether to enable ffi or not sender = '0x00a329c0648769a73afac7f9381e08fb43dbea72' # the address of `msg.sender` in tests tx_origin = '0x00a329c0648769a73afac7f9381e08fb43dbea72' # the address of `tx.origin` in tests initial_balance = '0xffffffffffffffffffffffff' # the initial balance of the test contract @@ -28,3 +27,14 @@ block_timestamp = 0 # the value of `bl block_difficulty = 0 # the value of `block.difficulty` in tests gas_reports = ["L2GasPriceOracle"] + +# remove bytecode hash for reliable deterministic addresses +bytecode_hash = 'none' + +# file system permissions +ffi = true + +fs_permissions = [ + { access='read', path='./docker' }, + { access='read-write', path='./volume' }, +] diff --git a/package.json b/package.json index 3113d74..7a53592 100644 --- a/package.json +++ b/package.json @@ -68,5 +68,6 @@ }, "engines": { "node": ">=10.4.0" - } + }, + "packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72" } diff --git a/scripts/deterministic/Configuration.sol b/scripts/deterministic/Configuration.sol new file mode 100644 index 0000000..59708c9 --- /dev/null +++ b/scripts/deterministic/Configuration.sol @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.24; + +import {Script} from "forge-std/Script.sol"; +import {VmSafe} from "forge-std/Vm.sol"; +import {stdToml} from "forge-std/StdToml.sol"; + +import {CONFIG_PATH, CONFIG_CONTRACTS_PATH, CONFIG_CONTRACTS_TEMPLATE_PATH} from "./Constants.sol"; + +/// @notice Configuration allows inheriting contracts to read the TOML configuration file. +abstract contract Configuration is Script { + using stdToml for string; + + /******************* + * State variables * + *******************/ + + string internal cfg; + string internal contractsCfg; + + /**************************** + * Configuration parameters * + ****************************/ + + // general + string internal L1_RPC_ENDPOINT; + string internal L2_RPC_ENDPOINT; + + string internal CHAIN_NAME_L1; + string internal CHAIN_NAME_L2; + uint64 internal CHAIN_ID_L1; + uint64 internal CHAIN_ID_L2; + + uint256 internal MAX_TX_IN_CHUNK; + uint256 internal MAX_BLOCK_IN_CHUNK; + uint256 internal MAX_BATCH_IN_BUNDLE; + uint256 internal MAX_L1_MESSAGE_GAS_LIMIT; + + uint256 internal L1_CONTRACT_DEPLOYMENT_BLOCK; + + bool internal ALTERNATIVE_GAS_TOKEN_ENABLED; + + bool internal TEST_ENV_MOCK_FINALIZE_ENABLED; + uint256 internal TEST_ENV_MOCK_FINALIZE_TIMEOUT_SEC; + + // accounts + uint256 internal DEPLOYER_PRIVATE_KEY; + uint256 internal L1_COMMIT_SENDER_PRIVATE_KEY; + uint256 internal L1_FINALIZE_SENDER_PRIVATE_KEY; + uint256 internal L1_GAS_ORACLE_SENDER_PRIVATE_KEY; + uint256 internal L2_GAS_ORACLE_SENDER_PRIVATE_KEY; + + address internal DEPLOYER_ADDR; + address internal L1_COMMIT_SENDER_ADDR; + address internal L1_FINALIZE_SENDER_ADDR; + address internal L1_GAS_ORACLE_SENDER_ADDR; + address internal L2_GAS_ORACLE_SENDER_ADDR; + + address internal OWNER_ADDR; + + address internal L2GETH_SIGNER_ADDRESS; + + // db + string internal ROLLUP_EXPLORER_BACKEND_DB_CONNECTION_STRING; + + // genesis + uint256 internal L2_MAX_ETH_SUPPLY; + uint256 internal L2_DEPLOYER_INITIAL_BALANCE; + uint256 internal L2_SCROLL_MESSENGER_INITIAL_BALANCE; + + // contracts + string internal DEPLOYMENT_SALT; + + address internal L1_FEE_VAULT_ADDR; + + // coordinator + string internal CHUNK_COLLECTION_TIME_SEC; + string internal BATCH_COLLECTION_TIME_SEC; + string internal BUNDLE_COLLECTION_TIME_SEC; + string internal COORDINATOR_JWT_SECRET_KEY; + + // frontend + string internal EXTERNAL_RPC_URI_L1; + string internal EXTERNAL_RPC_URI_L2; + string internal BRIDGE_API_URI; + string internal ROLLUPSCAN_API_URI; + string internal EXTERNAL_EXPLORER_URI_L1; + string internal EXTERNAL_EXPLORER_URI_L2; + string internal ADMIN_SYSTEM_DASHBOARD_URI; + string internal GRAFANA_URI; + + /********************** + * Internal interface * + **********************/ + + function readConfig() internal { + if (!vm.exists(CONFIG_CONTRACTS_PATH)) { + string memory template = vm.readFile(CONFIG_CONTRACTS_TEMPLATE_PATH); + vm.writeFile(CONFIG_CONTRACTS_PATH, template); + } + + cfg = vm.readFile(CONFIG_PATH); + contractsCfg = vm.readFile(CONFIG_CONTRACTS_PATH); + + L1_RPC_ENDPOINT = cfg.readString(".general.L1_RPC_ENDPOINT"); + L2_RPC_ENDPOINT = cfg.readString(".general.L2_RPC_ENDPOINT"); + + CHAIN_NAME_L1 = cfg.readString(".general.CHAIN_NAME_L1"); + CHAIN_NAME_L2 = cfg.readString(".general.CHAIN_NAME_L2"); + CHAIN_ID_L1 = uint64(cfg.readUint(".general.CHAIN_ID_L1")); + CHAIN_ID_L2 = uint64(cfg.readUint(".general.CHAIN_ID_L2")); + + MAX_TX_IN_CHUNK = cfg.readUint(".rollup.MAX_TX_IN_CHUNK"); + MAX_BLOCK_IN_CHUNK = cfg.readUint(".rollup.MAX_BLOCK_IN_CHUNK"); + MAX_BATCH_IN_BUNDLE = cfg.readUint(".rollup.MAX_BATCH_IN_BUNDLE"); + MAX_L1_MESSAGE_GAS_LIMIT = cfg.readUint(".rollup.MAX_L1_MESSAGE_GAS_LIMIT"); + + L1_CONTRACT_DEPLOYMENT_BLOCK = cfg.readUint(".general.L1_CONTRACT_DEPLOYMENT_BLOCK"); + + ALTERNATIVE_GAS_TOKEN_ENABLED = cfg.readBool(".gas-token.ALTERNATIVE_GAS_TOKEN_ENABLED"); + + TEST_ENV_MOCK_FINALIZE_ENABLED = cfg.readBool(".rollup.TEST_ENV_MOCK_FINALIZE_ENABLED"); + TEST_ENV_MOCK_FINALIZE_TIMEOUT_SEC = cfg.readUint(".rollup.TEST_ENV_MOCK_FINALIZE_TIMEOUT_SEC"); + + DEPLOYER_PRIVATE_KEY = cfg.readUint(".accounts.DEPLOYER_PRIVATE_KEY"); + L1_COMMIT_SENDER_PRIVATE_KEY = cfg.readUint(".accounts.L1_COMMIT_SENDER_PRIVATE_KEY"); + L1_FINALIZE_SENDER_PRIVATE_KEY = cfg.readUint(".accounts.L1_FINALIZE_SENDER_PRIVATE_KEY"); + L1_GAS_ORACLE_SENDER_PRIVATE_KEY = cfg.readUint(".accounts.L1_GAS_ORACLE_SENDER_PRIVATE_KEY"); + L2_GAS_ORACLE_SENDER_PRIVATE_KEY = cfg.readUint(".accounts.L2_GAS_ORACLE_SENDER_PRIVATE_KEY"); + + DEPLOYER_ADDR = cfg.readAddress(".accounts.DEPLOYER_ADDR"); + L1_COMMIT_SENDER_ADDR = cfg.readAddress(".accounts.L1_COMMIT_SENDER_ADDR"); + L1_FINALIZE_SENDER_ADDR = cfg.readAddress(".accounts.L1_FINALIZE_SENDER_ADDR"); + L1_GAS_ORACLE_SENDER_ADDR = cfg.readAddress(".accounts.L1_GAS_ORACLE_SENDER_ADDR"); + L2_GAS_ORACLE_SENDER_ADDR = cfg.readAddress(".accounts.L2_GAS_ORACLE_SENDER_ADDR"); + + OWNER_ADDR = cfg.readAddress(".accounts.OWNER_ADDR"); + + L2GETH_SIGNER_ADDRESS = cfg.readAddress(".sequencer.L2GETH_SIGNER_ADDRESS"); + + ROLLUP_EXPLORER_BACKEND_DB_CONNECTION_STRING = cfg.readString(".db.ROLLUP_EXPLORER_DB_CONNECTION_STRING"); + + L2_MAX_ETH_SUPPLY = cfg.readUint(".genesis.L2_MAX_ETH_SUPPLY"); + L2_DEPLOYER_INITIAL_BALANCE = cfg.readUint(".genesis.L2_DEPLOYER_INITIAL_BALANCE"); + L2_SCROLL_MESSENGER_INITIAL_BALANCE = L2_MAX_ETH_SUPPLY - L2_DEPLOYER_INITIAL_BALANCE; + + DEPLOYMENT_SALT = cfg.readString(".contracts.DEPLOYMENT_SALT"); + + L1_FEE_VAULT_ADDR = cfg.readAddress(".contracts.L1_FEE_VAULT_ADDR"); + + CHUNK_COLLECTION_TIME_SEC = cfg.readString(".coordinator.CHUNK_COLLECTION_TIME_SEC"); + BATCH_COLLECTION_TIME_SEC = cfg.readString(".coordinator.BATCH_COLLECTION_TIME_SEC"); + BUNDLE_COLLECTION_TIME_SEC = cfg.readString(".coordinator.BUNDLE_COLLECTION_TIME_SEC"); + COORDINATOR_JWT_SECRET_KEY = cfg.readString(".coordinator.COORDINATOR_JWT_SECRET_KEY"); + + EXTERNAL_RPC_URI_L1 = cfg.readString(".frontend.EXTERNAL_RPC_URI_L1"); + EXTERNAL_RPC_URI_L2 = cfg.readString(".frontend.EXTERNAL_RPC_URI_L2"); + BRIDGE_API_URI = cfg.readString(".frontend.BRIDGE_API_URI"); + ROLLUPSCAN_API_URI = cfg.readString(".frontend.ROLLUPSCAN_API_URI"); + EXTERNAL_EXPLORER_URI_L1 = cfg.readString(".frontend.EXTERNAL_EXPLORER_URI_L1"); + EXTERNAL_EXPLORER_URI_L2 = cfg.readString(".frontend.EXTERNAL_EXPLORER_URI_L2"); + ADMIN_SYSTEM_DASHBOARD_URI = cfg.readString(".frontend.ADMIN_SYSTEM_DASHBOARD_URI"); + GRAFANA_URI = cfg.readString(".frontend.GRAFANA_URI"); + + runSanityCheck(); + } + + /// @dev Ensure that `addr` is not the zero address. + /// This helps catch bugs arising from incorrect deployment order. + function notnull(address addr) internal pure returns (address) { + require(addr != address(0), "null address"); + return addr; + } + + function tryGetOverride(string memory name) internal returns (address) { + address addr; + string memory key; + if (keccak256(abi.encodePacked(name)) == keccak256(abi.encodePacked("L1_GAS_TOKEN"))) { + key = string(abi.encodePacked(".gas-token.", name)); + } else { + key = string(abi.encodePacked(".contracts.overrides.", name)); + } + + if (!vm.keyExistsToml(cfg, key)) { + return address(0); + } + + addr = cfg.readAddress(key); + + if (addr.code.length == 0) { + (VmSafe.CallerMode callerMode, , ) = vm.readCallers(); + + // if we're ready to start broadcasting transactions, then we + // must ensure that the override contract has been deployed. + if (callerMode == VmSafe.CallerMode.Broadcast || callerMode == VmSafe.CallerMode.RecurrentBroadcast) { + revert( + string( + abi.encodePacked( + "[ERROR] override ", + name, + " = ", + vm.toString(addr), + " not deployed in broadcast mode" + ) + ) + ); + } + } + + return addr; + } + + /********************* + * Private functions * + *********************/ + + function runSanityCheck() private view { + verifyAccount("DEPLOYER", DEPLOYER_PRIVATE_KEY, DEPLOYER_ADDR); + verifyAccount("L1_COMMIT_SENDER", L1_COMMIT_SENDER_PRIVATE_KEY, L1_COMMIT_SENDER_ADDR); + verifyAccount("L1_FINALIZE_SENDER", L1_FINALIZE_SENDER_PRIVATE_KEY, L1_FINALIZE_SENDER_ADDR); + verifyAccount("L1_GAS_ORACLE_SENDER", L1_GAS_ORACLE_SENDER_PRIVATE_KEY, L1_GAS_ORACLE_SENDER_ADDR); + verifyAccount("L2_GAS_ORACLE_SENDER", L2_GAS_ORACLE_SENDER_PRIVATE_KEY, L2_GAS_ORACLE_SENDER_ADDR); + } + + function verifyAccount( + string memory name, + uint256 privateKey, + address addr + ) private pure { + if (vm.addr(privateKey) != addr) { + revert( + string( + abi.encodePacked( + "[ERROR] ", + name, + "_ADDR (", + vm.toString(addr), + ") does not match ", + name, + "_PRIVATE_KEY" + ) + ) + ); + } + } +} diff --git a/scripts/deterministic/Constants.sol b/scripts/deterministic/Constants.sol new file mode 100644 index 0000000..3238bef --- /dev/null +++ b/scripts/deterministic/Constants.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.24; + +/// @dev The default deterministic deployment salt prefix. +string constant DEFAULT_DEPLOYMENT_SALT = "ScrollStack"; + +/// @dev The address of DeterministicDeploymentProxy. +/// See https://github.com/Arachnid/deterministic-deployment-proxy. +address constant DETERMINISTIC_DEPLOYMENT_PROXY_ADDR = 0x4e59b44847b379578588920cA78FbF26c0B4956C; + +/// @dev The default minimum withdraw amount configured on L2TxFeeVault. +uint256 constant FEE_VAULT_MIN_WITHDRAW_AMOUNT = 1 ether; + +// template files +string constant CONFIG_CONTRACTS_TEMPLATE_PATH = "./docker/templates/config-contracts.toml"; +string constant GENESIS_JSON_TEMPLATE_PATH = "./docker/templates/genesis.json"; +string constant ROLLUP_CONFIG_TEMPLATE_PATH = "./docker/templates/rollup-config.json"; +string constant COORDINATOR_CONFIG_TEMPLATE_PATH = "./docker/templates/coordinator-config.json"; +string constant CHAIN_MONITOR_CONFIG_TEMPLATE_PATH = "./docker/templates/chain-monitor-config.json"; +string constant BRIDGE_HISTORY_CONFIG_TEMPLATE_PATH = "./docker/templates/bridge-history-config.json"; +string constant BALANCE_CHECKER_CONFIG_TEMPLATE_PATH = "./docker/templates/balance-checker-config.json"; +string constant ROLLUP_EXPLORER_BACKEND_CONFIG_TEMPLATE_PATH = "./docker/templates/rollup-explorer-backend-config.json"; +string constant ADMIN_SYSTEM_BACKEND_CONFIG_TEMPLATE_PATH = "./docker/templates/admin-system-backend-config.json"; + +// input files +string constant CONFIG_PATH = "./volume/config.toml"; + +// output files +string constant CONFIG_CONTRACTS_PATH = "./volume/config-contracts.toml"; +string constant GENESIS_ALLOC_JSON_PATH = "./volume/__genesis-alloc.json"; +string constant GENESIS_JSON_PATH = "./volume/genesis.yaml"; +string constant ROLLUP_CONFIG_PATH = "./volume/rollup-config.yaml"; +string constant COORDINATOR_CONFIG_PATH = "./volume/coordinator-config.yaml"; +string constant CHAIN_MONITOR_CONFIG_PATH = "./volume/chain-monitor-config.yaml"; +string constant BRIDGE_HISTORY_CONFIG_PATH = "./volume/bridge-history-config.yaml"; +string constant BALANCE_CHECKER_CONFIG_PATH = "./volume/balance-checker-config.yaml"; +string constant FRONTEND_ENV_PATH = "./volume/frontend-config.yaml"; +string constant ROLLUP_EXPLORER_BACKEND_CONFIG_PATH = "./volume/rollup-explorer-backend-config.yaml"; +string constant ADMIN_SYSTEM_BACKEND_CONFIG_PATH = "./volume/admin-system-backend-config.yaml"; + +// plonk verifier configs +bytes32 constant V4_VERIFIER_DIGEST = 0x0a1904dbfff4614fb090b4b3864af4874f12680c32f07889e9ede8665097e5ec; + +// plonk verifier v0.13.1 creation code +bytes constant PLONK_VERIFIER_CREATION_CODE = hex"62000025565b60006040519050600081036200001a57606090505b818101604052919050565b6136a3620000338162000005565b816200003f82398181f3fe60017f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd477f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001610151565b60007f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4782107f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47841080821692505050600082146000841480821780158481169450505050507f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd478384097f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd478384097f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd478482097f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47600382088381148581169550505050505092915050565b8060003506602052806020350660405280604035066060528060603506608052806080350660a0528060a0350660c0528060c0350660e0528060e0350661010052806101003506610120528061012035066101405280610140350661016052806101603506610180528061018035066101a052806101a035066101c052806101c035066101e052806101e0350661020052806102003506610220528061022035066102405280610240350661026052806102603506610280528061028035066102a052806102a035066102c052806102c035066102e052806102e0350661030052806103003506610320527f083d7f8552bdf1dd98458c19d469c458809424d40c483abc2e528b75a6f0f1466000526103203580610340526103403580610360528461027d8284610048565b169450505061038060002061038052610380518181066103a052806103c0525061036035806103e052610380358061040052846102ba8284610048565b169450505060606103c02061042052610420518181066104405280610460525060016104805360216104602061048052610480518181066104a052806104c052506103a035806104e0526103c0358061050052846103188284610048565b16945050506103e03580610520526104003580610540528461033a8284610048565b16945050506104203580610560526104403580610580528461035c8284610048565b169450505060e06104c0206105a0526105a0518181066105c052806105e05250610460358061060052610480358061062052846103998284610048565b16945050506104a03580610640526104c0358061066052846103bb8284610048565b16945050506104e035806106805261050035806106a052846103dd8284610048565b169450505061052035806106c05261054035806106e052846103ff8284610048565b16945050506101206105e0206107005261070051818106610720528061074052508061056035066107605280610580350661078052806105a035066107a052806105c035066107c052806105e035066107e0528061060035066108005280610620350661082052806106403506610840528061066035066108605280610680350661088052806106a035066108a052806106c035066108c052806106e035066108e052806107003506610900528061072035066109205280610740350661094052806107603506610960526102406107402061098052610980518181066109a052806109c0525060016109e05360216109c0206109e0526109e051818106610a005280610a2052506107803580610a40526107a03580610a6052846105248284610048565b16945050506060610a2020610a8052610a8051818106610aa05280610ac052506107c03580610ae0526107e03580610b0052846105618284610048565b169450505060205160405160581b8101905060605160b01b8101905080610b205260805160a05160581b8101905060c05160b01b8101905080610b4052846105a98284610048565b169450505060e0516101005160581b810190506101205160b01b8101905080610b6052610140516101605160581b810190506101805160b01b8101905080610b8052846105f68284610048565b169450505080610720516107205109610ba05280610ba051610ba05109610bc05280610bc051610bc05109610be05280610be051610be05109610c005280610c0051610c005109610c205280610c2051610c205109610c405280610c4051610c405109610c605280610c6051610c605109610c805280610c8051610c805109610ca05280610ca051610ca05109610cc05280610cc051610cc05109610ce05280610ce051610ce05109610d005280610d0051610d005109610d205280610d2051610d205109610d405280610d4051610d405109610d605280610d6051610d605109610d805280610d8051610d805109610da05280610da051610da05109610dc05280610dc051610dc05109610de05280610de051610de05109610e005280610e0051610e005109610e205280610e2051610e205109610e405280610e4051610e405109610e605280610e6051610e605109610e805280610e8051610e805109610ea05280610ea051610ea05109610ec052807f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000610ec05108610ee052807f30644e66c81e03716be83b486d6feabcc7ddd0fe6cbf5e72d585d142f7829b05610ee05109610f0052807f2d19f86a2342079b8c1a6417471d461040256eaa689be51f08e6a92e1243ce65610f005109610f2052807f034a5608bdef988e2c35e19f3a64124ce80e799e111d8b723afb4c65ddbc319c6107205108610f4052807f24a1fcd63e9f03b27281db85fe631ec8e5c466f8178a4ee94d4942b7ccd90e1c610f005109610f6052807f0bc2519ca2929c7745ce6a30831e3994426f8150622f21a7f698b2dc2326f1e56107205108610f8052807f0d5eb4c216db2c3262de3f6a2ef71a9be95ff21a7a1a50ed069d6131e7d54e5f610f005109610fa052807f230599b0ca5673f75572064c528a3dc13ed3f62dff9f1fa43d449462082ab1a26107205108610fc052807f26501ebfe559ea5826f023d3e76e4b66f170cd940408eb5590a4075c80b498d6610f005109610fe052807f0a142fb2fbd7b5d1916021e29a130cf636c31ab475b0853bb33dee376f4b672b610720510861100052807f082a7bd4c0a7e4352229d332c27a160da18f0d7c651f3047df41b80345532f6e610f00510961102052807f2839d29e2089bbf496267283bf07424f86a4dacc149a404964a03d90aaacd093610720510861104052807f19277f31ecb5bfe8604677099c09556812b0b5c50cceb2b584098183a5a6c5c8610f00510961106052807f173ccf40f47be0415809ceace57802f5158332836ceabddbbfd874104a593a39610720510861108052807f20bab6e5f766b4edf82399e9c5ff0e40d4b6875321a3d8020e18521d8f5c7241610f0051096110a052807f0fa9978ce9caeb3bc02cabccbb824a1c537d60f55815988f35c9a37660a38dc061072051086110c052806001610f0051096110e052807f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000610720510861110052807f1dba8b5bdd64ef6ce29a9039aca3c0e524395c43b9227b96c75090cc6cc7ec97610f00510961112052807f12a9c31703ccb0bcd5b5b57cd4dd977803fa8c04c096f4fa7c9164c78338136a610720510861114052807f0d94d63997367c97a8ed16c17adaae39262b9af83acb9e003f94c217303dd160610f00510961116052807f22cf783949fb23920f632ef506a6aa2402084d503eedd291044d337cbfc22ea1610720510861118052807f303a348fae5a4f041e5c056919bc140f68267e2fb55a522282b02d6a100e01a0610f0051096111a052807e2a19e332d7512599f4404d67c5444dc00d6a18c45f1e6ec131c829dff1fe6161072051086111c052807f1951441010b2b95a6e47a6075066a50a036f5ba978c050f2821df86636c0facb610f0051096111e052807f17130a62d07ee6cf4a089faf311ab35324c48c9f00f91f9ec1c3fd2db93f0536610720510861120052807f04fe6e3fa02c3830525c10d7bbf567639bfc836de8fe4e471c889a638d381c71610f00510961122052807f2b65e033410567f965f434dec58bf0f98c3764da90bb224a27595b3062c7e390610720510861124052807f24db2e49a2c215211bae763372d0d8b05d0140adbc6d9d63f2a226fb711fd873610f00510961126052807f0b8920293e6f8b089ca1cf830eb07faccb32a79abd4bd32d513fce987ee0278e610720510861128052807f0f6afbf59e2fef78443acf353ca6d17cfc07ebc6343141cd56a9102bd4864004610f0051096112a052807f20f9527d4301b0b17415768144da86e02c2bfc8245882ec3ed38e5681b79bffd61072051086112c052807f1283ba6f4b7b1a76ba2008fe823128bea4adb9269cbfd7c41c223be65bc60863610f0051096112e052807f1de0940395b685b2fe303cb7ff502f9e83862f21dcf998cd27bfb9ad9439f79e610720510861130052807f10e6c9ec7941500e1b1095e82fb0034bd9e95777ca9ae5ce296eadc089110518610f00510961132052807f1f7d848667f0501b9d3fafce51d155114e4a90d0af1e8ac31a7347d366eefae9610720510861134052807f25ad5db2a6bf79a14fd0d2ffd3d0927af7fdd30bce40f88fb46774fd262d7673610f00510961136052807f0ab6f0c03a722688687f72b6adb0c5e23036153cab7878018f7a8096c9d2898e610720510861138052807f2cd3ee06866876806a4a382f7a95576f34fd9ce6aad8bf350670ed33fe3259ac610f0051096113a052807f0390606c5ac929a94e060d8706ec00edf3364b61cee0b15c3d71085ff1cda65561072051086113c052807f2f835d9f4207df4efa4ffa0b2bbf9a4f54221c57cc506b7a5f8dae90bd2e3d0a610f0051096113e052807ee0f0d39f29c0dabe004bab55c1be0dd411cbf0ad690516e454470332d1c2f7610720510861140052807f30526acf1fa80f36309d80530d854ae7d52fc97bcb8d0a6a2a7f852e7b6e9d79610f00510961142052807e11e3a3c18990f387b2c56373fc0d7553041eccae2c66271962706574916288610720510861144052807f0af04af9c243a8b4b8767330b8dae01f562d7641cdf0a0c4d288e395c0aebb72610f00510961146052807f257403791eedf774ffd9d285c8a6783dd2067206abc8cfcc715911fe2f51448f610720510861148052807f2387d5be5175ba27fd2f8779b460ffdd830e06cd2f6bcf30c3715fbed25b1b67610f0051096114a052807f0cdc78b48fbbe601bb20be3ccd20587fa525e17b4a4da160807095d51da4e49a61072051086114c052807f18c95f1ae6514e11a1b30fd7923947c5ffcec5347f16e91b4dd654168326bede610f0051096114e052807f179aef57fae05218169d35deef48109728652313faa28775f60ba17d6cd94123610720510861150052807f1ed7bccd53b52d451436ae36d9f0225657083d7e909edb5560d7ea488aebe0d1610f00510961152052807f118c91a58d7c72e4a419977fa7913606d12baac9e91a953be30a0b4b65141f30610720510861154052807f0ba3551f265c0941ccb3766d47b26720ad620a597137ff3c84310bd3e28e26c9610f00510961156052807f24c0f953bad596e7eb9ccf4939cef13c7ad1ddef08817154bfb0e9c00d71d938610720510861158052807f0803f4ae22d04b4c9c282c70c843e12bfb2d84b89bd7ef84dc3b90d60053b1c8610f0051096115a052807f286059c4be6154dd1c281945b93d77312d06638fdde1810c67a664bdefac4e3961072051086115c052807f29aa84e8187de51daa6de67f44fe365fbb789d1e5f97bc95844628487ebd3a0d610f0051096115e052807f06b9c98ac8b3bb0c0de25f373c8321fd6cbb4b2a1a21b3fbbf9bcd4b7142c5f4610720510861160052807f0e4fc6c7e1947e44222db52506e305b8a9ad70f6a834db5a4778d4a30f1c6f92610f00510961162052807f221487aaff9d21e5962290917a9e52a47e867751d1849536fc6920f0e0e3906f610720510861164052807f20816bee57855658ecd8a204faf6bced826a725b63c6a1a388b4609a2478809f610f00510961166052807f0fe2e28489ac49d0cb77a3b1868a9b6fa5c975ed15f2ceedbb2d94f9cb877f62610720510861168052807f0d44b8855e09d5ae332469459793c1444814f6d137f9e93e780bd41c97e37caa610f0051096116a052807f231f95ed8327ca7b852bdc70e9ed9718e01ef17741bf8752cbd62177581c835761072051086116c052807f07fe49da5568a43070d955e0d212d956e1ff8abd41a763c737e292bee0476699610f0051096116e052807f286604988bc8fbf94776efd5af6e7f0646345d8b38120cca0bff62d50fb89968610720510861170052610f40518181610f805109905080611720528181610fc051099050806117405281816110005109905080611760528181611040510990508061178052818161108051099050806117a05281816110c051099050806117c052818161110051099050806117e0528181611140510990508061180052818161118051099050806118205281816111c051099050806118405281816112005109905080611860528181611240510990508061188052818161128051099050806118a05281816112c051099050806118c052818161130051099050806118e0528181611340510990508061190052818161138051099050806119205281816113c051099050806119405281816114005109905080611960528181611440510990508061198052818161148051099050806119a05281816114c051099050806119c052818161150051099050806119e05281816115405109905080611a005281816115805109905080611a205281816115c05109905080611a405281816116005109905080611a605281816116405109905080611a805281816116805109905080611aa05281816116c05109905080611ac05281816117005109905080611ae0528181610ee05109905080611b0052506020611b40526020611b60526020611b8052611b0051611ba0527f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffffff611bc0527f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001611be0528260016020611b2060c0611b4060055afa14169250611b20516000610ee05190508282611ae05109610ee0528282820991506117005190508282611ac05109611700528282820991506116c05190508282611aa051096116c0528282820991506116805190508282611a805109611680528282820991506116405190508282611a605109611640528282820991506116005190508282611a405109611600528282820991506115c05190508282611a2051096115c0528282820991506115805190508282611a0051096115805282828209915061154051905082826119e051096115405282828209915061150051905082826119c05109611500528282820991506114c051905082826119a051096114c0528282820991506114805190508282611980510961148052828282099150611440519050828261196051096114405282828209915061140051905082826119405109611400528282820991506113c0519050828261192051096113c052828282099150611380519050828261190051096113805282828209915061134051905082826118e051096113405282828209915061130051905082826118c05109611300528282820991506112c051905082826118a051096112c0528282820991506112805190508282611880510961128052828282099150611240519050828261186051096112405282828209915061120051905082826118405109611200528282820991506111c0519050828261182051096111c052828282099150611180519050828261180051096111805282828209915061114051905082826117e051096111405282828209915061110051905082826117c05109611100528282820991506110c051905082826117a051096110c052828282099150611080519050828261178051096110805282828209915061104051905082826117605109611040528282820991506110005190508282611740510961100052828282099150610fc051905082826117205109610fc052828282099150610f805190508282610f405109610f805282828209915081610f4052505080610f4051610f205109611c005280610f8051610f605109611c205280610fc051610fa05109611c40528061100051610fe05109611c605280611040516110205109611c805280611080516110605109611ca052806110c0516110a05109611cc05280611100516110e05109611ce05280611140516111205109611d005280611180516111605109611d2052806111c0516111a05109611d405280611200516111e05109611d605280611240516112205109611d805280611280516112605109611da052806112c0516112a05109611dc05280611300516112e05109611de05280611340516113205109611e005280611380516113605109611e2052806113c0516113a05109611e405280611400516113e05109611e605280611440516114205109611e805280611480516114605109611ea052806114c0516114a05109611ec05280611500516114e05109611ee05280611540516115205109611f005280611580516115605109611f2052806115c0516115a05109611f405280611600516115e05109611f605280611640516116205109611f805280611680516116605109611fa052806116c0516116a05109611fc05280611700516116e05109611fe05280602051611ce05109818183604051611d005109089050818183606051611d205109089050818183608051611d40510908905081818360a051611d60510908905081818360c051611d80510908905081818360e051611da0510908905081818361010051611dc0510908905081818361012051611de0510908905081818361014051611e00510908905081818361016051611e20510908905081818361018051611e4051090890508181836101a051611e6051090890508181836101c051611e8051090890508181836101e051611ea0510908905081818361020051611ec0510908905081818361022051611ee0510908905081818361024051611f00510908905081818361026051611f20510908905081818361028051611f4051090890508181836102a051611f6051090890508181836102c051611f8051090890508181836102e051611fa0510908905081818361030051611fc0510908905081818361032051611fe0510908905080612000525080610780516107a05109612020528061202051610760510861204052806107c0518203612040510861206052806108205161206051096120805280612080516105c051096120a052806108e05182036001086120c05280611ce0516120c051096120e052806120e0516120a051086121005280612100516105c0510961212052806108e0516108e0510961214052806108e051820361214051086121605280611c0051612160510961218052806121805161212051086121a052806121a0516105c051096121c05280611c005182036001086121e05280611c4051611c2051086122005280611c605161220051086122205280611c805161222051086122405280611ca05161224051086122605280611cc051612260510861228052806122805182036121e051086122a052806104405161088051096122c052806122c0516107e051086122e052806104a0516122e051086123005280610440516108a05109612320528061232051610760510861234052806104a051612340510861236052806123005161236051096123805280610440516108c051096123a052806123a05161200051086123c052806104a0516123c051086123e05280612380516123e05109612400528061090051612400510961242052806104405160010961244052806124405161072051096124605280612460516107e0510861248052806104a05161248051086124a05280610440517f09226b6e22c6f0ca64ec26aad4c86e715b5f898e5e963f25870e56bbe533e9a2096124c052806124c05161072051096124e052806124e051610760510861250052806104a051612500510861252052806124a05161252051096125405280610440517f13b360d4e82fe915fed16081038f98c211427b87a281bd733c277dbadf10372b09612560528061256051610720510961258052806125805161200051086125a052806104a0516125a051086125c05280612540516125c051096125e052806108e0516125e051096126005280612600518203612420510861262052806122a05161262051096126405280612640516121c051086126605280612660516105c051096126805280611ce05161092051096126a052806126a05161268051086126c052806126c0516105c051096126e05280611c005161092051096127005280612700516126e051086127205280612720516105c051096127405280610440516108005108612760528061084051610760510961278052806104405161278051086127a05280612760516127a051096127c0528061092051820361094051086127e052806127c0516127e051096128005280610960516127a05109612820528061282051820361276051086128405280612840518203612800510861286052806122a051612860510961288052806128805161274051086128a05280610ec051610ec051096128c05280610ec0516128c051096128e05280610ec0516128e051096129005280610ec05160010961292052806128c05160010961294052806128e0516001096129605280610ee0516128a05109612980528061072051610ba051096129a05280600161072051096129c052806129c0518203610aa051086129e052807f0d94d63997367c97a8ed16c17adaae39262b9af83acb9e003f94c217303dd1606107205109612a005280612a00518203610aa05108612a2052807f1dba8b5bdd64ef6ce29a9039aca3c0e524395c43b9227b96c75090cc6cc7ec976107205109612a405280612a40518203610aa05108612a6052807f303a348fae5a4f041e5c056919bc140f68267e2fb55a522282b02d6a100e01a06107205109612a805280612a80518203610aa05108612aa052807f2eedbf565be4b0b88a0e251d750f7559486d77bea4d5c9612ce3041295d380d5610aa051098181837f01768f1c854cef712e4220990c71e303dfc67089d4e3a73016fef1815a2c7f2c610720510908905080612ac05250807f0cf547ca658485dd9a20bd27b080d3147ec1caa826e725f7f7989fc9668dd16f610aa051098181837f077ebe9a531776ef7c9e90d5a0685181f70e74298eca38a6c92d84d4c0457ceb610720510908905080612ae05250807f077ebe9a531776ef7c9e90d5a0685181f70e74298eca38a6c92d84d4c0457ceb610aa051098181837e32f4eea6716d08d38119c3d378a36e865ffacfa73eb2357603f3f3368c88c6610720510908905080612b005250807f0d71dfba734c88cf774db4e0e4f959cb98acfbbb1fc789e5167bbca1fcafd24e610aa051098181837f25ea65fea4371c04dd1eaf23d69f2e0f9524ef5b9845d01cdb218e89a8052321610720510908905080612b205250806129e051600109612b405280612a6051612b405109612b605280612a2051612b605109612b805280612aa051612b805109612ba052807f12a9c31703ccb0bcd5b5b57cd4dd977803fa8c04c096f4fa7c9164c78338136b610aa051098181837f1dba8b5bdd64ef6ce29a9039aca3c0e524395c43b9227b96c75090cc6cc7ec96610720510908905080612bc05250807f1dba8b5bdd64ef6ce29a9039aca3c0e524395c43b9227b96c75090cc6cc7ec96610aa051098181837f1025b522462e72d539ad797831c912abfe0dc14b7e56dd9687bbceb53c8a1b37610720510908905080612be05250806001610aa051098181837f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000610720510908905080612c005250612ac0518181612ae05109905080612c20528181612b005109905080612c40528181612b205109905080612c60528181612bc05109905080612c80528181612be05109905080612ca0528181612b605109905080612cc0528181612c005109905080612ce0528181612b405109905080612d0052506020612d40526020612d60526020612d8052612d0051612da0527f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffffff612dc0527f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001612de0528260016020612d2060c0612d4060055afa14169250612d20516000612b405190508282612ce05109612b4052828282099150612c005190508282612cc05109612c0052828282099150612b605190508282612ca05109612b6052828282099150612be05190508282612c805109612be052828282099150612bc05190508282612c605109612bc052828282099150612b205190508282612c405109612b2052828282099150612b005190508282612c205109612b0052828282099150612ae05190508282612ac05109612ae05282828209915081612ac0525050612ac0518181612ae0510890508181612b00510890508181612b205108905080612e00525080612b6051612ba05109612e2052612bc0518181612be05108905080612e40525080612b4051612ba05109612e6052612c005180612e805250612e00518181612e405109905080612ea0528181612e805109905080612ec052506020612f00526020612f20526020612f4052612ec051612f60527f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffffff612f80527f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001612fa0528260016020612ee060c0612f0060055afa14169250612ee0516000612e805190508282612ea05109612e8052828282099150612e405190508282612e005109612e405282828209915081612e0052505080612e4051612e205109612fc05280612e8051612e605109612fe052806109a0516109a0510961300052806109a051613000510961302052806109a051613020510961304052806109a051613040510961306052806109a051613060510961308052806109a05161308051096130a052806109a0516130a051096130c052806109a0516130c051096130e052806109a0516130e051096131005280610a0051610a0051096131205280610a005161312051096131405280612ac0516107605109818183612ae0516107805109089050818183612b00516107a05109089050818183612b20516107c0510908905080613160525080612e0051613160510961318052806001613180518303096131a0528060016131a051096131c05280612e20516001096131e05280612bc0516108e05109818183612be051610900510908905080613200525080612fc05161320051096132205280600161322051830309613240528060016131e051096132605280612bc0516109205109818183612be051610940510908905080613280525080612fc05161328051096132a052806109a0516132a0518303096132c052806109a0516131e051096132e052806132c05161324051086133005280610a005161330051096133205280610a005161326051096133405280610a00516132e051096133605280613320516131c051086133805280612e60516001096133a05280612c00516109605109806133c0525080612fe0516133c051096133e0528060016133e051830309613400528060016133a051096134205280612c00516107e0510980613440525080612fe051613440510961346052806109a0516134605183030961348052806109a0516133a051096134a052806134805161340051086134c05280612c00516108005109806134e0525080612fe0516134e05109613500528061300051613500518303096135205280613000516133a051096135405280613520516134c051086135605280612c0051610820510980613580525080612fe05161358051096135a05280613020516135a0518303096135c05280613020516133a051096135e052806135c05161356051086136005280612c0051610840510980613620525080612fe0516136205109613640528061304051613640518303096136605280613040516133a0510961368052806136605161360051086136a05280612c00516108805109806136c0525080612fe0516136c051096136e05280613060516136e0518303096137005280613060516133a051096137205280613700516136a051086137405280612c00516108a0510980613760525080612fe0516137605109613780528061308051613780518303096137a05280613080516133a051096137c052806137a05161374051086137e05280612c00516108c0510980613800525080612fe051613800510961382052806130a0516138205183030961384052806130a0516133a051096138605280613840516137e051086138805280612e605161292051096138a05280612e605161294051096138c05280612e605161296051096138e05280612c0051612980510980613900525080612fe051613900510961392052806130c0516139205183030961394052806130c0516133a0510961396052806130c0516138a0510961398052806130c0516138c051096139a052806130c0516138e051096139c052806139405161388051086139e05280612c0051610860510980613a00525080612fe051613a005109613a2052806130e051613a2051830309613a4052806130e0516133a05109613a605280613a40516139e05108613a80528061312051613a805109613aa05280613120516134205109613ac05280613120516134a05109613ae05280613120516135405109613b005280613120516135e05109613b205280613120516136805109613b405280613120516137205109613b605280613120516137c05109613b805280613120516138605109613ba05280613120516139605109613bc05280613120516139805109613be05280613120516139a05109613c005280613120516139c05109613c20528061312051613a605109613c405280613aa0516133805108613c605280612ba051600109613c805280610aa051600109613ca0526001613cc0526002613ce052613c6051613d00528260016040613cc06060613cc060075afa14169250613cc051613d2052613ce051613d405261034051613d605261036051613d80528260016040613d206080613d2060065afa141692506104e051613da05261050051613dc05261334051613de0528260016040613da06060613da060075afa14169250613d2051613e0052613d4051613e2052613da051613e4052613dc051613e60528260016040613e006080613e0060065afa1416925061052051613e805261054051613ea05261336051613ec0528260016040613e806060613e8060075afa14169250613e0051613ee052613e2051613f0052613e8051613f2052613ea051613f40528260016040613ee06080613ee060065afa141692506103e051613f605261040051613f8052613ac051613fa0528260016040613f606060613f6060075afa14169250613ee051613fc052613f0051613fe052613f605161400052613f8051614020528260016040613fc06080613fc060065afa141692507f2ad47afc517e78898ecd3a5c20c46975e6c50bf030951071878c7470e2f3f9f1614040527f1ceaca4426cdc11c593eee4db1e3981bfd8bd0eba770b93aa85f5f15dc16096061406052613ae051614080528260016040614040606061404060075afa14169250613fc0516140a052613fe0516140c052614040516140e052614060516141005282600160406140a060806140a060065afa141692507f0fb05ccb81603592ce60bd6199890470ef6f9caca3c17570df7701d70a2dd957614120527f1cc1301d6d462edf2e79083532a337ef3f087fbf0b9516d08074784017e10c7e61414052613b0051614160528260016040614120606061412060075afa141692506140a051614180526140c0516141a052614120516141c052614140516141e0528260016040614180608061418060065afa141692507f1e69edc3b54a25a5be1efb7515b5eaa259569e44cf6e7b99887bd2015311104f614200527f293e037f34e8ad87a6ebea272575c751f96a1e0acbf05bf5774e0581c3cc382361422052613b2051614240528260016040614200606061420060075afa1416925061418051614260526141a05161428052614200516142a052614220516142c0528260016040614260608061426060065afa141692507f0cf666852fd76b36f03572da1111ea68014217801887369fd35f9460f6bb13246142e0527f230bb407465f3dcda6711ec136903711485b30046005c0dd769bd85d1cc8a56761430052613b40516143205282600160406142e060606142e060075afa14169250614260516143405261428051614360526142e05161438052614300516143a0528260016040614340608061434060065afa141692507f2c2ef5dd4ea527c7b6a1adb979cf19e316cf50515b2225ecd040f40f46ad8fc66143c0527f2cd240820bffbbfa44c0832dfe16e8a40c4e75d800f58f7576733ca37a9c73696143e052613b60516144005282600160406143c060606143c060075afa14169250614340516144205261436051614440526143c051614460526143e051614480528260016040614420608061442060065afa141692507f1e2415bae32b721ff7b95b1766a3d0a4ea86d62fc8b4ed864092e8be29b375b46144a0527f0193d85fa914927d0567822f343ead8682e19e1948bdfe0d70e5ce64eaddf26a6144c052613b80516144e05282600160406144a060606144a060075afa14169250614420516145005261444051614520526144a051614540526144c051614560528260016040614500608061450060065afa141692507f02b01e53fe9c70fbc4e13606ffb809f58993a5b31d9e9a69e5c86b2833b52ac8614580527f21fe9d89f5e7d29aa77fb345a045fabbcc5f2be8bbd2f571306475118175ae306145a052613ba0516145c0528260016040614580606061458060075afa14169250614500516145e052614520516146005261458051614620526145a0516146405282600160406145e060806145e060065afa1416925061060051614660526106205161468052613bc0516146a0528260016040614660606061466060075afa141692506145e0516146c052614600516146e0526146605161470052614680516147205282600160406146c060806146c060065afa1416925061064051614740526106605161476052613be051614780528260016040614740606061474060075afa141692506146c0516147a0526146e0516147c052614740516147e052614760516148005282600160406147a060806147a060065afa1416925061068051614820526106a05161484052613c0051614860528260016040614820606061482060075afa141692506147a051614880526147c0516148a052614820516148c052614840516148e0528260016040614880608061488060065afa141692506106c051614900526106e05161492052613c2051614940528260016040614900606061490060075afa1416925061488051614960526148a05161498052614900516149a052614920516149c0528260016040614960608061496060065afa14169250610560516149e05261058051614a0052613c4051614a205282600160406149e060606149e060075afa1416925061496051614a405261498051614a60526149e051614a8052614a0051614aa0528260016040614a406080614a4060065afa14169250610a4051614ac052610a6051614ae052613c80518103614b00528260016040614ac06060614ac060075afa14169250614a4051614b2052614a6051614b4052614ac051614b6052614ae051614b80528260016040614b206080614b2060065afa14169250610ae051614ba052610b0051614bc052613ca051614be0528260016040614ba06060614ba060075afa14169250614b2051614c0052614b4051614c2052614ba051614c4052614bc051614c60528260016040614c006080614c0060065afa14169250614c0051614c8052614c2051614ca052610ae051614cc052610b0051614ce052610b2051614d0052610b4051614d2052610b6051614d4052610b8051614d6052610100614c8020614d805280614d805106614da05280614da051614da05109614dc05280614da051600109614de052614d0051614e0052614d2051614e2052614de051614e40528260016040614e006060614e0060075afa14169250614c8051614e6052614ca051614e8052614e0051614ea052614e2051614ec0528260016040614e606080614e6060065afa14169250614d4051614ee052614d6051614f0052614de051614f20528260016040614ee06060614ee060075afa14169250614cc051614f4052614ce051614f6052614ee051614f8052614f0051614fa0528260016040614f406080614f4060065afa14169250614e6051614fc052614e8051614fe0527f198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2615000527f1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed615020527f090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b615040527f12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa61506052614f405161508052614f60516150a0527f186282957db913abd99f91db59fe69922e95040603ef44c0bd7aa3adeef8f5ac6150c0527f17944351223333f260ddc3b4af45191b856689eda9eab5cbcddbbe570ce860d26150e0527f06d971ff4a7467c3ec596ed6efc674572e32fd6f52b721f97e35b0b3d3546753615100527f06ecdb9f9567f59ed2eee36e1e1d58797fd13cc97fafc2910f5e8a12f202fa9a615120528260016020614fc0610180614fc060085afa14169250826001614fc051141692508261369e57600080fd5b600080f3"; diff --git a/scripts/deterministic/DeployScroll.s.sol b/scripts/deterministic/DeployScroll.s.sol new file mode 100644 index 0000000..d02e011 --- /dev/null +++ b/scripts/deterministic/DeployScroll.s.sol @@ -0,0 +1,1565 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.24; + +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {ProxyAdminSetOwner} from "./contracts/ProxyAdminSetOwner.sol"; +import {TransparentUpgradeableProxy, ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +import {EmptyContract} from "../../src/misc/EmptyContract.sol"; + +import {EnforcedTxGateway} from "../../src/L1/gateways/EnforcedTxGateway.sol"; +import {L1CustomERC20Gateway} from "../../src/L1/gateways/L1CustomERC20Gateway.sol"; +import {L1ERC1155Gateway} from "../../src/L1/gateways/L1ERC1155Gateway.sol"; +import {L1ERC721Gateway} from "../../src/L1/gateways/L1ERC721Gateway.sol"; +import {L1ETHGateway} from "../../src/L1/gateways/L1ETHGateway.sol"; +import {L1GatewayRouter} from "../../src/L1/gateways/L1GatewayRouter.sol"; +import {L1MessageQueueWithGasPriceOracle} from "../../src/L1/rollup/L1MessageQueueWithGasPriceOracle.sol"; +import {L1ScrollMessenger} from "../../src/L1/L1ScrollMessenger.sol"; +import {L1StandardERC20Gateway} from "../../src/L1/gateways/L1StandardERC20Gateway.sol"; +import {L1WETHGateway} from "../../src/L1/gateways/L1WETHGateway.sol"; +import {L2GasPriceOracle} from "../../src/L1/rollup/L2GasPriceOracle.sol"; +import {MultipleVersionRollupVerifierSetOwner} from "./contracts/MultipleVersionRollupVerifierSetOwner.sol"; +import {ScrollChain} from "../../src/L1/rollup/ScrollChain.sol"; +import {ZkEvmVerifierV2} from "../../src/libraries/verifier/ZkEvmVerifierV2.sol"; +import {GasTokenExample} from "../../src/alternative-gas-token/GasTokenExample.sol"; +import {L1ScrollMessengerNonETH} from "../../src/alternative-gas-token/L1ScrollMessengerNonETH.sol"; +import {L1GasTokenGateway} from "../../src/alternative-gas-token/L1GasTokenGateway.sol"; +import {L1WrappedTokenGateway} from "../../src/alternative-gas-token/L1WrappedTokenGateway.sol"; + +import {L2CustomERC20Gateway} from "../../src/L2/gateways/L2CustomERC20Gateway.sol"; +import {L2ERC1155Gateway} from "../../src/L2/gateways/L2ERC1155Gateway.sol"; +import {L2ERC721Gateway} from "../../src/L2/gateways/L2ERC721Gateway.sol"; +import {L2ETHGateway} from "../../src/L2/gateways/L2ETHGateway.sol"; +import {L2GatewayRouter} from "../../src/L2/gateways/L2GatewayRouter.sol"; +import {L2ScrollMessenger} from "../../src/L2/L2ScrollMessenger.sol"; +import {L2StandardERC20Gateway} from "../../src/L2/gateways/L2StandardERC20Gateway.sol"; +import {L2WETHGateway} from "../../src/L2/gateways/L2WETHGateway.sol"; +import {L1GasPriceOracle} from "../../src/L2/predeploys/L1GasPriceOracle.sol"; +import {L2MessageQueue} from "../../src/L2/predeploys/L2MessageQueue.sol"; +import {L2TxFeeVault} from "../../src/L2/predeploys/L2TxFeeVault.sol"; +import {L2TxFeeVaultWithGasToken} from "../../src/alternative-gas-token/L2TxFeeVaultWithGasToken.sol"; +import {Whitelist} from "../../src/L2/predeploys/Whitelist.sol"; +import {WrappedEther} from "../../src/L2/predeploys/WrappedEther.sol"; +import {ScrollStandardERC20} from "../../src/libraries/token/ScrollStandardERC20.sol"; +import {ScrollStandardERC20FactorySetOwner} from "./contracts/ScrollStandardERC20FactorySetOwner.sol"; + +import {ScrollChainMockFinalize} from "../../src/mocks/ScrollChainMockFinalize.sol"; + +import "./Constants.sol"; +import "./Configuration.sol"; +import "./DeterministicDeployment.sol"; + +/// @dev The minimum deployer account balance. +uint256 constant MINIMUM_DEPLOYER_BALANCE = 0.1 ether; + +contract DeployScroll is DeterministicDeployment { + using stdToml for string; + + /********* + * Types * + *********/ + + enum Layer { + None, + L1, + L2 + } + + /******************* + * State variables * + *******************/ + + // general configurations + Layer private broadcastLayer = Layer.None; + + /*********************** + * Contracts to deploy * + ***********************/ + + // L1 addresses + address internal L1_CUSTOM_ERC20_GATEWAY_IMPLEMENTATION_ADDR; + address internal L1_CUSTOM_ERC20_GATEWAY_PROXY_ADDR; + address internal L1_ENFORCED_TX_GATEWAY_IMPLEMENTATION_ADDR; + address internal L1_ENFORCED_TX_GATEWAY_PROXY_ADDR; + address internal L1_ERC1155_GATEWAY_IMPLEMENTATION_ADDR; + address internal L1_ERC1155_GATEWAY_PROXY_ADDR; + address internal L1_ERC721_GATEWAY_IMPLEMENTATION_ADDR; + address internal L1_ERC721_GATEWAY_PROXY_ADDR; + address internal L1_ETH_GATEWAY_IMPLEMENTATION_ADDR; + address internal L1_ETH_GATEWAY_PROXY_ADDR; + address internal L1_GATEWAY_ROUTER_IMPLEMENTATION_ADDR; + address internal L1_GATEWAY_ROUTER_PROXY_ADDR; + address internal L1_MESSAGE_QUEUE_IMPLEMENTATION_ADDR; + address internal L1_MESSAGE_QUEUE_PROXY_ADDR; + address internal L1_MULTIPLE_VERSION_ROLLUP_VERIFIER_ADDR; + address internal L1_PROXY_ADMIN_ADDR; + address internal L1_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR; + address internal L1_SCROLL_CHAIN_IMPLEMENTATION_ADDR; + address internal L1_SCROLL_CHAIN_PROXY_ADDR; + address internal L1_SCROLL_MESSENGER_IMPLEMENTATION_ADDR; + address internal L1_SCROLL_MESSENGER_PROXY_ADDR; + address internal L1_STANDARD_ERC20_GATEWAY_IMPLEMENTATION_ADDR; + address internal L1_STANDARD_ERC20_GATEWAY_PROXY_ADDR; + address internal L1_WETH_ADDR; + address internal L1_WETH_GATEWAY_IMPLEMENTATION_ADDR; + address internal L1_WETH_GATEWAY_PROXY_ADDR; + address internal L1_WHITELIST_ADDR; + address internal L1_PLONK_VERIFIER_ADDR; + address internal L1_ZKEVM_VERIFIER_V2_ADDR; + address internal L1_GAS_TOKEN_ADDR; + address internal L1_GAS_TOKEN_GATEWAY_IMPLEMENTATION_ADDR; + address internal L1_GAS_TOKEN_GATEWAY_PROXY_ADDR; + address internal L1_WRAPPED_TOKEN_GATEWAY_ADDR; + + // L2 addresses + address internal L1_GAS_PRICE_ORACLE_ADDR; + address internal L2_CUSTOM_ERC20_GATEWAY_IMPLEMENTATION_ADDR; + address internal L2_CUSTOM_ERC20_GATEWAY_PROXY_ADDR; + address internal L2_ERC1155_GATEWAY_IMPLEMENTATION_ADDR; + address internal L2_ERC1155_GATEWAY_PROXY_ADDR; + address internal L2_ERC721_GATEWAY_IMPLEMENTATION_ADDR; + address internal L2_ERC721_GATEWAY_PROXY_ADDR; + address internal L2_ETH_GATEWAY_IMPLEMENTATION_ADDR; + address internal L2_ETH_GATEWAY_PROXY_ADDR; + address internal L2_GATEWAY_ROUTER_IMPLEMENTATION_ADDR; + address internal L2_GATEWAY_ROUTER_PROXY_ADDR; + address internal L2_MESSAGE_QUEUE_ADDR; + address internal L2_PROXY_ADMIN_ADDR; + address internal L2_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR; + address internal L2_SCROLL_MESSENGER_IMPLEMENTATION_ADDR; + address internal L2_SCROLL_MESSENGER_PROXY_ADDR; + address internal L2_SCROLL_STANDARD_ERC20_ADDR; + address internal L2_SCROLL_STANDARD_ERC20_FACTORY_ADDR; + address internal L2_STANDARD_ERC20_GATEWAY_IMPLEMENTATION_ADDR; + address internal L2_STANDARD_ERC20_GATEWAY_PROXY_ADDR; + address internal L2_TX_FEE_VAULT_ADDR; + address internal L2_WETH_ADDR; + address internal L2_WETH_GATEWAY_IMPLEMENTATION_ADDR; + address internal L2_WETH_GATEWAY_PROXY_ADDR; + address internal L2_WHITELIST_ADDR; + + /************* + * Utilities * + *************/ + + /// @dev Only broadcast code block if we run the script on the specified layer. + modifier broadcast(Layer layer) { + if (broadcastLayer == layer) { + vm.startBroadcast(DEPLOYER_PRIVATE_KEY); + } else { + // make sure we use the correct sender in simulation + vm.startPrank(DEPLOYER_ADDR); + } + + _; + + if (broadcastLayer == layer) { + vm.stopBroadcast(); + } else { + vm.stopPrank(); + } + } + + /// @dev Only execute block if we run the script on the specified layer. + modifier only(Layer layer) { + if (broadcastLayer != layer) { + return; + } + _; + } + + /// @dev Do not execute block if we run the script on the specified layer. + modifier skip(Layer layer) { + if (broadcastLayer == layer) { + return; + } + _; + } + + /// @dev Only execute block if it's requied by alternative gas token mode. + modifier gasToken(bool gasTokenRequire) { + if (ALTERNATIVE_GAS_TOKEN_ENABLED != gasTokenRequire) { + return; + } + _; + } + + /*************** + * Entry point * + ***************/ + + function run(string memory layer, string memory scriptMode) public { + broadcastLayer = parseLayer(layer); + ScriptMode mode = parseScriptMode(scriptMode); + + DeterministicDeployment.initialize(mode); + + checkDeployerBalance(); + deployAllContracts(); + initializeL1Contracts(); + initializeL2Contracts(); + } + + /********************** + * Internal interface * + **********************/ + + function predictAllContracts() internal { + skipDeployment(); + deployAllContracts(); + } + + /********************* + * Private functions * + *********************/ + + function parseLayer(string memory raw) private pure returns (Layer) { + if (keccak256(bytes(raw)) == keccak256(bytes("L1"))) { + return Layer.L1; + } else if (keccak256(bytes(raw)) == keccak256(bytes("L2"))) { + return Layer.L2; + } else { + return Layer.None; + } + } + + function checkDeployerBalance() private { + // ignore balance during simulation + if (broadcastLayer == Layer.None) { + return; + } + + // check funds for deployment (L1 & L2) + if (DEPLOYER_ADDR.balance < MINIMUM_DEPLOYER_BALANCE) { + revert( + string( + abi.encodePacked( + "[ERROR] insufficient funds on deployer account for contract deployment (", + vm.toString(DEPLOYER_ADDR), + ") minimum ETH balance (in wei): ", + vm.toString(MINIMUM_DEPLOYER_BALANCE) + ) + ) + ); + } + + // check funds for initial deposit (L1, ETH as gas token) + if (broadcastLayer == Layer.L1 && !ALTERNATIVE_GAS_TOKEN_ENABLED) { + // note: L1_SCROLL_MESSENGER_PROXY_ADDR is not known at this point, + // so we read it directly from the generated configuration file. + address l1MessengerProxyAddr = notnull(contractsCfg.readAddress(".L1_SCROLL_MESSENGER_PROXY_ADDR")); + + uint256 l1MessengerBalance = l1MessengerProxyAddr.balance; + uint256 amountToLock = L2_DEPLOYER_INITIAL_BALANCE; + + uint256 amountToSend = 0; + if (l1MessengerBalance < amountToLock) { + amountToSend = amountToLock - l1MessengerBalance; + } + + uint256 minBalance = MINIMUM_DEPLOYER_BALANCE + amountToSend; + + if (DEPLOYER_ADDR.balance < minBalance) { + revert( + string( + abi.encodePacked( + "[ERROR] insufficient funds on deployer account for initial deposit (", + vm.toString(DEPLOYER_ADDR), + ") minimum ETH balance (in wei): ", + vm.toString(minBalance) + ) + ) + ); + } + } + + // check funds for initial deposit (L1, alternative gas token) + // skip it if L1_GAS_TOKEN is not configured in the config file + address gasTokenAddr = tryGetOverride("L1_GAS_TOKEN"); + + if (broadcastLayer == Layer.L1 && ALTERNATIVE_GAS_TOKEN_ENABLED && gasTokenAddr != address(0)) { + // note: L1_GAS_TOKEN_GATEWAY_PROXY_ADDR is not known at this point, + // so we read it directly from the generated configuration file. + address l1GasTokenGatewayAddr = notnull(contractsCfg.readAddress(".L1_GAS_TOKEN_GATEWAY_PROXY_ADDR")); + + uint256 l1GasTokenGatewayBalance = IERC20Metadata(gasTokenAddr).balanceOf(l1GasTokenGatewayAddr); + + uint256 scale = 10**(18 - IERC20Metadata(gasTokenAddr).decimals()); + uint256 amountToLock = L2_DEPLOYER_INITIAL_BALANCE / scale; + if (L2_DEPLOYER_INITIAL_BALANCE % scale != 0) { + amountToLock += 1; + } + + uint256 amountToSend = 0; + if (l1GasTokenGatewayBalance < amountToLock) { + amountToSend = amountToLock - l1GasTokenGatewayBalance; + } + + uint256 minBalance = amountToSend; + + if (IERC20Metadata(gasTokenAddr).balanceOf(DEPLOYER_ADDR) < minBalance) { + revert( + string( + abi.encodePacked( + "[ERROR] insufficient funds on deployer account for initial deposit (", + vm.toString(DEPLOYER_ADDR), + ") minimum ", + IERC20Metadata(gasTokenAddr).symbol(), + " balance (in min token unit): ", + vm.toString(minBalance) + ) + ) + ); + } + } + } + + function transferOwnership(address addr, address newOwner) private { + if (Ownable(addr).owner() != newOwner) { + Ownable(addr).transferOwnership(newOwner); + } + } + + function deployAllContracts() private { + deployL1Contracts1stPass(); + deployL2Contracts1stPass(); + deployL1Contracts2ndPass(); + deployL2Contracts2ndPass(); + } + + // @notice deployL1Contracts1stPass deploys L1 contracts whose initialization does not depend on any L2 addresses. + function deployL1Contracts1stPass() private broadcast(Layer.L1) { + deployL1Weth(); + deployL1ProxyAdmin(); + deployL1PlaceHolder(); + deployL1Whitelist(); + deployL1ScrollChainProxy(); + deployL1ScrollMessengerProxy(); + deployL1EnforcedTxGateway(); + deployL1PlonkVerifier(); + deployL1ZkEvmVerifier(); + deployL1MultipleVersionRollupVerifier(); + deployL1MessageQueue(); + deployL1ScrollChain(); + deployL1GatewayRouter(); + deployL1ETHGatewayProxy(); + deployL1WETHGatewayProxy(); + deployL1StandardERC20GatewayProxy(); + deployL1CustomERC20GatewayProxy(); + deployL1ERC721GatewayProxy(); + deployL1ERC1155GatewayProxy(); + + // alternative gas token contracts + deployGasToken(); + deployL1GasTokenGatewayProxy(); + } + + // @notice deployL2Contracts1stPass deploys L2 contracts whose initialization does not depend on any L1 addresses. + function deployL2Contracts1stPass() private broadcast(Layer.L2) { + deployL2MessageQueue(); + deployL1GasPriceOracle(); + deployL2Whitelist(); + deployL2Weth(); + deployTxFeeVault(); + deployL2ProxyAdmin(); + deployL2PlaceHolder(); + deployL2ScrollMessengerProxy(); + deployL2ETHGatewayProxy(); + deployL2WETHGatewayProxy(); + deployL2StandardERC20GatewayProxy(); + deployL2CustomERC20GatewayProxy(); + deployL2ERC721GatewayProxy(); + deployL2ERC1155GatewayProxy(); + deployScrollStandardERC20Factory(); + } + + // @notice deployL1Contracts2ndPass deploys L1 contracts whose initialization depends on some L2 addresses. + function deployL1Contracts2ndPass() private broadcast(Layer.L1) { + deployL1ScrollMessenger(); + deployL1StandardERC20Gateway(); + deployL1ETHGateway(); + deployL1WETHGateway(); + deployL1CustomERC20Gateway(); + deployL1ERC721Gateway(); + deployL1ERC1155Gateway(); + + // alternative gas token contracts + deployL1GasTokenGateway(); + deployL1WrappedTokenGateway(); + } + + // @notice deployL2Contracts2ndPass deploys L2 contracts whose initialization depends on some L1 addresses. + function deployL2Contracts2ndPass() private broadcast(Layer.L2) { + // upgradable + deployL2ScrollMessenger(); + deployL2GatewayRouter(); + deployL2StandardERC20Gateway(); + deployL2ETHGateway(); + deployL2WETHGateway(); + deployL2CustomERC20Gateway(); + deployL2ERC721Gateway(); + deployL2ERC1155Gateway(); + } + + // @notice initializeL1Contracts initializes contracts deployed on L1. + function initializeL1Contracts() private broadcast(Layer.L1) only(Layer.L1) { + initializeScrollChain(); + initializeL1MessageQueue(); + initializeL1ScrollMessenger(); + initializeEnforcedTxGateway(); + initializeL1GatewayRouter(); + initializeL1CustomERC20Gateway(); + initializeL1ERC1155Gateway(); + initializeL1ERC721Gateway(); + initializeL1ETHGateway(); + initializeL1StandardERC20Gateway(); + initializeL1WETHGateway(); + initializeL1Whitelist(); + + // alternative gas token contracts + initializeL1GasTokenGateway(); + + // lock tokens on L1 to ensure bridge parity, + // we lock ETH in L1ScrollMessenger or GAS_TOKEN in L1GasTokenGateway + // note: this can only be done before transferring ownership + lockTokensOnL1(); + + transferL1ContractOwnership(); + } + + // @notice initializeL2Contracts initializes contracts deployed on L2. + function initializeL2Contracts() private broadcast(Layer.L2) only(Layer.L2) { + initializeL2MessageQueue(); + initializeL2TxFeeVault(); + initializeL1GasPriceOracle(); + initializeL2ScrollMessenger(); + initializeL2GatewayRouter(); + initializeL2CustomERC20Gateway(); + initializeL2ERC1155Gateway(); + initializeL2ERC721Gateway(); + initializeL2ETHGateway(); + initializeL2StandardERC20Gateway(); + initializeL2WETHGateway(); + initializeScrollStandardERC20Factory(); + initializeL2Whitelist(); + + transferL2ContractOwnership(); + } + + /*************************** + * L1: 1st pass deployment * + ***************************/ + + function deployL1Weth() private { + L1_WETH_ADDR = deploy("L1_WETH", type(WrappedEther).creationCode); + } + + function deployL1ProxyAdmin() private { + bytes memory args = abi.encode(DEPLOYER_ADDR); + L1_PROXY_ADMIN_ADDR = deploy("L1_PROXY_ADMIN", type(ProxyAdminSetOwner).creationCode, args); + } + + function deployL1PlaceHolder() private { + L1_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR = deploy( + "L1_PROXY_IMPLEMENTATION_PLACEHOLDER", + type(EmptyContract).creationCode + ); + } + + function deployL1Whitelist() private { + bytes memory args = abi.encode(DEPLOYER_ADDR); + L1_WHITELIST_ADDR = deploy("L1_WHITELIST", type(Whitelist).creationCode, args); + } + + function deployL1ScrollChainProxy() private { + bytes memory args = abi.encode( + notnull(L1_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR), + notnull(L1_PROXY_ADMIN_ADDR), + new bytes(0) + ); + + L1_SCROLL_CHAIN_PROXY_ADDR = deploy( + "L1_SCROLL_CHAIN_PROXY", + type(TransparentUpgradeableProxy).creationCode, + args + ); + } + + function deployL1ScrollMessengerProxy() private { + bytes memory args = abi.encode( + notnull(L1_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR), + notnull(L1_PROXY_ADMIN_ADDR), + new bytes(0) + ); + + L1_SCROLL_MESSENGER_PROXY_ADDR = deploy( + "L1_SCROLL_MESSENGER_PROXY", + type(TransparentUpgradeableProxy).creationCode, + args + ); + } + + function deployL1EnforcedTxGateway() private { + L1_ENFORCED_TX_GATEWAY_IMPLEMENTATION_ADDR = deploy( + "L1_ENFORCED_TX_GATEWAY_IMPLEMENTATION", + type(EnforcedTxGateway).creationCode + ); + + bytes memory args = abi.encode( + notnull(L1_ENFORCED_TX_GATEWAY_IMPLEMENTATION_ADDR), + notnull(L1_PROXY_ADMIN_ADDR), + new bytes(0) + ); + + L1_ENFORCED_TX_GATEWAY_PROXY_ADDR = deploy( + "L1_ENFORCED_TX_GATEWAY_PROXY", + type(TransparentUpgradeableProxy).creationCode, + args + ); + } + + function deployL1PlonkVerifier() private { + L1_PLONK_VERIFIER_ADDR = deploy("L1_PLONK_VERIFIER", PLONK_VERIFIER_CREATION_CODE); + } + + function deployL1ZkEvmVerifier() private { + bytes memory args = abi.encode(notnull(L1_PLONK_VERIFIER_ADDR), V4_VERIFIER_DIGEST); + L1_ZKEVM_VERIFIER_V2_ADDR = deploy("L1_ZKEVM_VERIFIER_V2", type(ZkEvmVerifierV2).creationCode, args); + } + + function deployL1MultipleVersionRollupVerifier() private { + uint256[] memory _versions = new uint256[](1); + address[] memory _verifiers = new address[](1); + + // register V4 verifier: DarwinV2 upgrade, plonk verifier v0.13.1 + _versions[0] = 4; + _verifiers[0] = notnull(L1_ZKEVM_VERIFIER_V2_ADDR); + + bytes memory args = abi.encode(DEPLOYER_ADDR, _versions, _verifiers); + + L1_MULTIPLE_VERSION_ROLLUP_VERIFIER_ADDR = deploy( + "L1_MULTIPLE_VERSION_ROLLUP_VERIFIER", + type(MultipleVersionRollupVerifierSetOwner).creationCode, + args + ); + } + + function deployL1MessageQueue() private { + bytes memory args = abi.encode( + notnull(L1_SCROLL_MESSENGER_PROXY_ADDR), + notnull(L1_SCROLL_CHAIN_PROXY_ADDR), + notnull(L1_ENFORCED_TX_GATEWAY_PROXY_ADDR) + ); + + L1_MESSAGE_QUEUE_IMPLEMENTATION_ADDR = deploy( + "L1_MESSAGE_QUEUE_IMPLEMENTATION", + type(L1MessageQueueWithGasPriceOracle).creationCode, + args + ); + + bytes memory args2 = abi.encode( + notnull(L1_MESSAGE_QUEUE_IMPLEMENTATION_ADDR), + notnull(L1_PROXY_ADMIN_ADDR), + new bytes(0) + ); + + L1_MESSAGE_QUEUE_PROXY_ADDR = deploy( + "L1_MESSAGE_QUEUE_PROXY", + type(TransparentUpgradeableProxy).creationCode, + args2 + ); + } + + function deployL1ScrollChain() private { + bytes memory args = abi.encode( + CHAIN_ID_L2, + notnull(L1_MESSAGE_QUEUE_PROXY_ADDR), + notnull(L1_MULTIPLE_VERSION_ROLLUP_VERIFIER_ADDR) + ); + + bytes memory creationCode = type(ScrollChain).creationCode; + + if (TEST_ENV_MOCK_FINALIZE_ENABLED) { + creationCode = type(ScrollChainMockFinalize).creationCode; + } + + L1_SCROLL_CHAIN_IMPLEMENTATION_ADDR = deploy("L1_SCROLL_CHAIN_IMPLEMENTATION", creationCode, args); + + upgrade(L1_PROXY_ADMIN_ADDR, L1_SCROLL_CHAIN_PROXY_ADDR, L1_SCROLL_CHAIN_IMPLEMENTATION_ADDR); + } + + function deployL1GatewayRouter() private { + L1_GATEWAY_ROUTER_IMPLEMENTATION_ADDR = deploy( + "L1_GATEWAY_ROUTER_IMPLEMENTATION", + type(L1GatewayRouter).creationCode + ); + + bytes memory args = abi.encode( + notnull(L1_GATEWAY_ROUTER_IMPLEMENTATION_ADDR), + notnull(L1_PROXY_ADMIN_ADDR), + new bytes(0) + ); + + L1_GATEWAY_ROUTER_PROXY_ADDR = deploy( + "L1_GATEWAY_ROUTER_PROXY", + type(TransparentUpgradeableProxy).creationCode, + args + ); + } + + function deployL1ETHGatewayProxy() private gasToken(false) { + bytes memory args = abi.encode( + notnull(L1_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR), + notnull(L1_PROXY_ADMIN_ADDR), + new bytes(0) + ); + + L1_ETH_GATEWAY_PROXY_ADDR = deploy( + "L1_ETH_GATEWAY_PROXY", + type(TransparentUpgradeableProxy).creationCode, + args + ); + } + + function deployL1WETHGatewayProxy() private gasToken(false) { + bytes memory args = abi.encode( + notnull(L1_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR), + notnull(L1_PROXY_ADMIN_ADDR), + new bytes(0) + ); + + L1_WETH_GATEWAY_PROXY_ADDR = deploy( + "L1_WETH_GATEWAY_PROXY", + type(TransparentUpgradeableProxy).creationCode, + args + ); + } + + function deployL1StandardERC20GatewayProxy() private { + bytes memory args = abi.encode( + notnull(L1_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR), + notnull(L1_PROXY_ADMIN_ADDR), + new bytes(0) + ); + + L1_STANDARD_ERC20_GATEWAY_PROXY_ADDR = deploy( + "L1_STANDARD_ERC20_GATEWAY_PROXY", + type(TransparentUpgradeableProxy).creationCode, + args + ); + } + + function deployL1CustomERC20GatewayProxy() private { + bytes memory args = abi.encode( + notnull(L1_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR), + notnull(L1_PROXY_ADMIN_ADDR), + new bytes(0) + ); + + L1_CUSTOM_ERC20_GATEWAY_PROXY_ADDR = deploy( + "L1_CUSTOM_ERC20_GATEWAY_PROXY", + type(TransparentUpgradeableProxy).creationCode, + args + ); + } + + function deployL1ERC721GatewayProxy() private { + bytes memory args = abi.encode( + notnull(L1_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR), + notnull(L1_PROXY_ADMIN_ADDR), + new bytes(0) + ); + + L1_ERC721_GATEWAY_PROXY_ADDR = deploy( + "L1_ERC721_GATEWAY_PROXY", + type(TransparentUpgradeableProxy).creationCode, + args + ); + } + + function deployL1ERC1155GatewayProxy() private { + bytes memory args = abi.encode( + notnull(L1_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR), + notnull(L1_PROXY_ADMIN_ADDR), + new bytes(0) + ); + + L1_ERC1155_GATEWAY_PROXY_ADDR = deploy( + "L1_ERC1155_GATEWAY_PROXY", + type(TransparentUpgradeableProxy).creationCode, + args + ); + } + + function deployGasToken() private gasToken(true) { + uint8 decimal = 18; + string memory key = ".gas-token.EXAMPLE_GAS_TOKEN_DECIMAL"; + if (vm.keyExistsToml(cfg, key)) { + decimal = uint8(cfg.readUint(key)); + } + + bytes memory args = abi.encode( + "ScrollGasToken", // _name + "GasToken", // _symbol + decimal, // _decimals + DEPLOYER_ADDR, // _recipient + 10**28 // _amount + ); + + // deploy gas token contract on L1, + // note: if an override address is configured, then we will use that instead + L1_GAS_TOKEN_ADDR = deploy("L1_GAS_TOKEN", type(GasTokenExample).creationCode, args); + } + + function deployL1GasTokenGatewayProxy() private gasToken(true) { + bytes memory args = abi.encode( + notnull(L1_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR), + notnull(L1_PROXY_ADMIN_ADDR), + new bytes(0) + ); + + L1_GAS_TOKEN_GATEWAY_PROXY_ADDR = deploy( + "L1_GAS_TOKEN_GATEWAY_PROXY", + type(TransparentUpgradeableProxy).creationCode, + args + ); + } + + /*************************** + * L2: 1st pass deployment * + ***************************/ + + function deployL2MessageQueue() private { + bytes memory args = abi.encode(DEPLOYER_ADDR); + L2_MESSAGE_QUEUE_ADDR = deploy("L2_MESSAGE_QUEUE", type(L2MessageQueue).creationCode, args); + } + + function deployL1GasPriceOracle() private { + bytes memory args = abi.encode(DEPLOYER_ADDR, true); + L1_GAS_PRICE_ORACLE_ADDR = deploy("L1_GAS_PRICE_ORACLE", type(L1GasPriceOracle).creationCode, args); + } + + function deployL2Whitelist() private { + bytes memory args = abi.encode(DEPLOYER_ADDR); + L2_WHITELIST_ADDR = deploy("L2_WHITELIST", type(Whitelist).creationCode, args); + } + + function deployL2Weth() private { + L2_WETH_ADDR = deploy("L2_WETH", type(WrappedEther).creationCode); + } + + function deployTxFeeVault() private { + if (!ALTERNATIVE_GAS_TOKEN_ENABLED) { + bytes memory args = abi.encode(DEPLOYER_ADDR, L1_FEE_VAULT_ADDR, FEE_VAULT_MIN_WITHDRAW_AMOUNT); + L2_TX_FEE_VAULT_ADDR = deploy("L2_TX_FEE_VAULT", type(L2TxFeeVault).creationCode, args); + } else { + bytes memory args = abi.encode( + L2_ETH_GATEWAY_PROXY_ADDR, + DEPLOYER_ADDR, + L1_FEE_VAULT_ADDR, + FEE_VAULT_MIN_WITHDRAW_AMOUNT + ); + L2_TX_FEE_VAULT_ADDR = deploy("L2_TX_FEE_VAULT", type(L2TxFeeVaultWithGasToken).creationCode, args); + } + } + + function deployL2ProxyAdmin() private { + bytes memory args = abi.encode(DEPLOYER_ADDR); + L2_PROXY_ADMIN_ADDR = deploy("L2_PROXY_ADMIN", type(ProxyAdminSetOwner).creationCode, args); + } + + function deployL2PlaceHolder() private { + L2_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR = deploy( + "L2_PROXY_IMPLEMENTATION_PLACEHOLDER", + type(EmptyContract).creationCode + ); + } + + function deployL2ScrollMessengerProxy() private { + bytes memory args = abi.encode( + notnull(L2_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR), + notnull(L2_PROXY_ADMIN_ADDR), + new bytes(0) + ); + + L2_SCROLL_MESSENGER_PROXY_ADDR = deploy( + "L2_SCROLL_MESSENGER_PROXY", + type(TransparentUpgradeableProxy).creationCode, + args + ); + } + + function deployL2StandardERC20GatewayProxy() private { + bytes memory args = abi.encode( + notnull(L2_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR), + notnull(L2_PROXY_ADMIN_ADDR), + new bytes(0) + ); + + L2_STANDARD_ERC20_GATEWAY_PROXY_ADDR = deploy( + "L2_STANDARD_ERC20_GATEWAY_PROXY", + type(TransparentUpgradeableProxy).creationCode, + args + ); + } + + function deployL2ETHGatewayProxy() private { + bytes memory args = abi.encode( + notnull(L2_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR), + notnull(L2_PROXY_ADMIN_ADDR), + new bytes(0) + ); + + L2_ETH_GATEWAY_PROXY_ADDR = deploy( + "L2_ETH_GATEWAY_PROXY", + type(TransparentUpgradeableProxy).creationCode, + args + ); + } + + function deployL2WETHGatewayProxy() private gasToken(false) { + bytes memory args = abi.encode( + notnull(L2_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR), + notnull(L2_PROXY_ADMIN_ADDR), + new bytes(0) + ); + + L2_WETH_GATEWAY_PROXY_ADDR = deploy( + "L2_WETH_GATEWAY_PROXY", + type(TransparentUpgradeableProxy).creationCode, + args + ); + } + + function deployL2CustomERC20GatewayProxy() private { + bytes memory args = abi.encode( + notnull(L2_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR), + notnull(L2_PROXY_ADMIN_ADDR), + new bytes(0) + ); + + L2_CUSTOM_ERC20_GATEWAY_PROXY_ADDR = deploy( + "L2_CUSTOM_ERC20_GATEWAY_PROXY", + type(TransparentUpgradeableProxy).creationCode, + args + ); + } + + function deployL2ERC721GatewayProxy() private { + bytes memory args = abi.encode( + notnull(L2_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR), + notnull(L2_PROXY_ADMIN_ADDR), + new bytes(0) + ); + + L2_ERC721_GATEWAY_PROXY_ADDR = deploy( + "L2_ERC721_GATEWAY_PROXY", + type(TransparentUpgradeableProxy).creationCode, + args + ); + } + + function deployL2ERC1155GatewayProxy() private { + bytes memory args = abi.encode( + notnull(L2_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR), + notnull(L2_PROXY_ADMIN_ADDR), + new bytes(0) + ); + + L2_ERC1155_GATEWAY_PROXY_ADDR = deploy( + "L2_ERC1155_GATEWAY_PROXY", + type(TransparentUpgradeableProxy).creationCode, + args + ); + } + + function deployScrollStandardERC20Factory() private { + L2_SCROLL_STANDARD_ERC20_ADDR = deploy("L2_SCROLL_STANDARD_ERC20", type(ScrollStandardERC20).creationCode); + bytes memory args = abi.encode(DEPLOYER_ADDR, notnull(L2_SCROLL_STANDARD_ERC20_ADDR)); + + L2_SCROLL_STANDARD_ERC20_FACTORY_ADDR = deploy( + "L2_SCROLL_STANDARD_ERC20_FACTORY", + type(ScrollStandardERC20FactorySetOwner).creationCode, + args + ); + } + + /*************************** + * L1: 2nd pass deployment * + ***************************/ + + function deployL1ScrollMessenger() private { + if (ALTERNATIVE_GAS_TOKEN_ENABLED) { + bytes memory args = abi.encode( + notnull(L1_GAS_TOKEN_GATEWAY_PROXY_ADDR), + notnull(L2_SCROLL_MESSENGER_PROXY_ADDR), + notnull(L1_SCROLL_CHAIN_PROXY_ADDR), + notnull(L1_MESSAGE_QUEUE_PROXY_ADDR) + ); + + L1_SCROLL_MESSENGER_IMPLEMENTATION_ADDR = deploy( + "L1_SCROLL_MESSENGER_IMPLEMENTATION", + type(L1ScrollMessengerNonETH).creationCode, + args + ); + } else { + bytes memory args = abi.encode( + notnull(L2_SCROLL_MESSENGER_PROXY_ADDR), + notnull(L1_SCROLL_CHAIN_PROXY_ADDR), + notnull(L1_MESSAGE_QUEUE_PROXY_ADDR) + ); + + L1_SCROLL_MESSENGER_IMPLEMENTATION_ADDR = deploy( + "L1_SCROLL_MESSENGER_IMPLEMENTATION", + type(L1ScrollMessenger).creationCode, + args + ); + } + + upgrade(L1_PROXY_ADMIN_ADDR, L1_SCROLL_MESSENGER_PROXY_ADDR, L1_SCROLL_MESSENGER_IMPLEMENTATION_ADDR); + } + + function deployL1ETHGateway() private gasToken(false) { + bytes memory args = abi.encode( + notnull(L2_ETH_GATEWAY_PROXY_ADDR), + notnull(L1_GATEWAY_ROUTER_PROXY_ADDR), + notnull(L1_SCROLL_MESSENGER_PROXY_ADDR) + ); + + L1_ETH_GATEWAY_IMPLEMENTATION_ADDR = deploy( + "L1_ETH_GATEWAY_IMPLEMENTATION", + type(L1ETHGateway).creationCode, + args + ); + + upgrade(L1_PROXY_ADMIN_ADDR, L1_ETH_GATEWAY_PROXY_ADDR, L1_ETH_GATEWAY_IMPLEMENTATION_ADDR); + } + + function deployL1WETHGateway() private gasToken(false) { + bytes memory args = abi.encode( + notnull(L1_WETH_ADDR), + notnull(L2_WETH_ADDR), + notnull(L2_WETH_GATEWAY_PROXY_ADDR), + notnull(L1_GATEWAY_ROUTER_PROXY_ADDR), + notnull(L1_SCROLL_MESSENGER_PROXY_ADDR) + ); + + L1_WETH_GATEWAY_IMPLEMENTATION_ADDR = deploy( + "L1_WETH_GATEWAY_IMPLEMENTATION", + type(L1WETHGateway).creationCode, + args + ); + + upgrade(L1_PROXY_ADMIN_ADDR, L1_WETH_GATEWAY_PROXY_ADDR, L1_WETH_GATEWAY_IMPLEMENTATION_ADDR); + } + + function deployL1StandardERC20Gateway() private { + bytes memory args = abi.encode( + notnull(L2_STANDARD_ERC20_GATEWAY_PROXY_ADDR), + notnull(L1_GATEWAY_ROUTER_PROXY_ADDR), + notnull(L1_SCROLL_MESSENGER_PROXY_ADDR), + notnull(L2_SCROLL_STANDARD_ERC20_ADDR), + notnull(L2_SCROLL_STANDARD_ERC20_FACTORY_ADDR) + ); + + L1_STANDARD_ERC20_GATEWAY_IMPLEMENTATION_ADDR = deploy( + "L1_STANDARD_ERC20_GATEWAY_IMPLEMENTATION", + type(L1StandardERC20Gateway).creationCode, + args + ); + + upgrade( + L1_PROXY_ADMIN_ADDR, + L1_STANDARD_ERC20_GATEWAY_PROXY_ADDR, + L1_STANDARD_ERC20_GATEWAY_IMPLEMENTATION_ADDR + ); + } + + function deployL1CustomERC20Gateway() private { + bytes memory args = abi.encode( + notnull(L2_CUSTOM_ERC20_GATEWAY_PROXY_ADDR), + notnull(L1_GATEWAY_ROUTER_PROXY_ADDR), + notnull(L1_SCROLL_MESSENGER_PROXY_ADDR) + ); + + L1_CUSTOM_ERC20_GATEWAY_IMPLEMENTATION_ADDR = deploy( + "L1_CUSTOM_ERC20_GATEWAY_IMPLEMENTATION", + type(L1CustomERC20Gateway).creationCode, + args + ); + + upgrade(L1_PROXY_ADMIN_ADDR, L1_CUSTOM_ERC20_GATEWAY_PROXY_ADDR, L1_CUSTOM_ERC20_GATEWAY_IMPLEMENTATION_ADDR); + } + + function deployL1ERC721Gateway() private { + bytes memory args = abi.encode(notnull(L2_ERC721_GATEWAY_PROXY_ADDR), notnull(L1_SCROLL_MESSENGER_PROXY_ADDR)); + + L1_ERC721_GATEWAY_IMPLEMENTATION_ADDR = deploy( + "L1_ERC721_GATEWAY_IMPLEMENTATION", + type(L1ERC721Gateway).creationCode, + args + ); + + upgrade(L1_PROXY_ADMIN_ADDR, L1_ERC721_GATEWAY_PROXY_ADDR, L1_ERC721_GATEWAY_IMPLEMENTATION_ADDR); + } + + function deployL1ERC1155Gateway() private { + bytes memory args = abi.encode(notnull(L2_ERC1155_GATEWAY_PROXY_ADDR), notnull(L1_SCROLL_MESSENGER_PROXY_ADDR)); + + L1_ERC1155_GATEWAY_IMPLEMENTATION_ADDR = deploy( + "L1_ERC1155_GATEWAY_IMPLEMENTATION", + type(L1ERC1155Gateway).creationCode, + args + ); + + upgrade(L1_PROXY_ADMIN_ADDR, L1_ERC1155_GATEWAY_PROXY_ADDR, L1_ERC1155_GATEWAY_IMPLEMENTATION_ADDR); + } + + // Only run this block during simulation (for predicting the contract address) + // and during deployment on L1. Running it on L2 would fail, as this contract + // calls `gasToken.decimals()` in its constructor. + function deployL1GasTokenGateway() private gasToken(true) skip(Layer.L2) { + bytes memory args = abi.encode( + notnull(L1_GAS_TOKEN_ADDR), + notnull(L2_ETH_GATEWAY_PROXY_ADDR), + notnull(L1_GATEWAY_ROUTER_PROXY_ADDR), + notnull(L1_SCROLL_MESSENGER_PROXY_ADDR) + ); + + L1_GAS_TOKEN_GATEWAY_IMPLEMENTATION_ADDR = deploy( + "L1_GAS_TOKEN_GATEWAY_IMPLEMENTATION", + type(L1GasTokenGateway).creationCode, + args + ); + + upgrade(L1_PROXY_ADMIN_ADDR, L1_GAS_TOKEN_GATEWAY_PROXY_ADDR, L1_GAS_TOKEN_GATEWAY_IMPLEMENTATION_ADDR); + } + + function deployL1WrappedTokenGateway() private gasToken(true) { + bytes memory args = abi.encode(notnull(L1_WETH_ADDR), notnull(L1_STANDARD_ERC20_GATEWAY_PROXY_ADDR)); + + L1_WRAPPED_TOKEN_GATEWAY_ADDR = deploy( + "L1_WRAPPED_TOKEN_GATEWAY", + type(L1WrappedTokenGateway).creationCode, + args + ); + } + + /*************************** + * L2: 2nd pass deployment * + ***************************/ + + function deployL2ScrollMessenger() private { + bytes memory args = abi.encode(notnull(L1_SCROLL_MESSENGER_PROXY_ADDR), notnull(L2_MESSAGE_QUEUE_ADDR)); + + L2_SCROLL_MESSENGER_IMPLEMENTATION_ADDR = deploy( + "L2_SCROLL_MESSENGER_IMPLEMENTATION", + type(L2ScrollMessenger).creationCode, + args + ); + + upgrade(L2_PROXY_ADMIN_ADDR, L2_SCROLL_MESSENGER_PROXY_ADDR, L2_SCROLL_MESSENGER_IMPLEMENTATION_ADDR); + } + + function deployL2GatewayRouter() private { + L2_GATEWAY_ROUTER_IMPLEMENTATION_ADDR = deploy( + "L2_GATEWAY_ROUTER_IMPLEMENTATION", + type(L2GatewayRouter).creationCode + ); + + bytes memory args = abi.encode( + notnull(L2_GATEWAY_ROUTER_IMPLEMENTATION_ADDR), + notnull(L2_PROXY_ADMIN_ADDR), + new bytes(0) + ); + + L2_GATEWAY_ROUTER_PROXY_ADDR = deploy( + "L2_GATEWAY_ROUTER_PROXY", + type(TransparentUpgradeableProxy).creationCode, + args + ); + } + + function deployL2StandardERC20Gateway() private { + bytes memory args = abi.encode( + notnull(L1_STANDARD_ERC20_GATEWAY_PROXY_ADDR), + notnull(L2_GATEWAY_ROUTER_PROXY_ADDR), + notnull(L2_SCROLL_MESSENGER_PROXY_ADDR), + notnull(L2_SCROLL_STANDARD_ERC20_FACTORY_ADDR) + ); + + L2_STANDARD_ERC20_GATEWAY_IMPLEMENTATION_ADDR = deploy( + "L2_STANDARD_ERC20_GATEWAY_IMPLEMENTATION", + type(L2StandardERC20Gateway).creationCode, + args + ); + + upgrade( + L2_PROXY_ADMIN_ADDR, + L2_STANDARD_ERC20_GATEWAY_PROXY_ADDR, + L2_STANDARD_ERC20_GATEWAY_IMPLEMENTATION_ADDR + ); + } + + function deployL2ETHGateway() private { + address COUNTERPART; + if (ALTERNATIVE_GAS_TOKEN_ENABLED) { + COUNTERPART = L1_GAS_TOKEN_GATEWAY_PROXY_ADDR; + } else { + COUNTERPART = L1_ETH_GATEWAY_PROXY_ADDR; + } + bytes memory args = abi.encode( + notnull(COUNTERPART), + notnull(L2_GATEWAY_ROUTER_PROXY_ADDR), + notnull(L2_SCROLL_MESSENGER_PROXY_ADDR) + ); + + L2_ETH_GATEWAY_IMPLEMENTATION_ADDR = deploy( + "L2_ETH_GATEWAY_IMPLEMENTATION", + type(L2ETHGateway).creationCode, + args + ); + + upgrade(L2_PROXY_ADMIN_ADDR, L2_ETH_GATEWAY_PROXY_ADDR, L2_ETH_GATEWAY_IMPLEMENTATION_ADDR); + } + + function deployL2WETHGateway() private gasToken(false) { + bytes memory args = abi.encode( + notnull(L2_WETH_ADDR), + notnull(L1_WETH_ADDR), + notnull(L1_WETH_GATEWAY_PROXY_ADDR), + notnull(L2_GATEWAY_ROUTER_PROXY_ADDR), + notnull(L2_SCROLL_MESSENGER_PROXY_ADDR) + ); + + L2_WETH_GATEWAY_IMPLEMENTATION_ADDR = deploy( + "L2_WETH_GATEWAY_IMPLEMENTATION", + type(L2WETHGateway).creationCode, + args + ); + + upgrade(L2_PROXY_ADMIN_ADDR, L2_WETH_GATEWAY_PROXY_ADDR, L2_WETH_GATEWAY_IMPLEMENTATION_ADDR); + } + + function deployL2CustomERC20Gateway() private { + bytes memory args = abi.encode( + notnull(L1_CUSTOM_ERC20_GATEWAY_PROXY_ADDR), + notnull(L2_GATEWAY_ROUTER_PROXY_ADDR), + notnull(L2_SCROLL_MESSENGER_PROXY_ADDR) + ); + + L2_CUSTOM_ERC20_GATEWAY_IMPLEMENTATION_ADDR = deploy( + "L2_CUSTOM_ERC20_GATEWAY_IMPLEMENTATION", + type(L2CustomERC20Gateway).creationCode, + args + ); + + upgrade(L2_PROXY_ADMIN_ADDR, L2_CUSTOM_ERC20_GATEWAY_PROXY_ADDR, L2_CUSTOM_ERC20_GATEWAY_IMPLEMENTATION_ADDR); + } + + function deployL2ERC721Gateway() private { + bytes memory args = abi.encode(notnull(L1_ERC721_GATEWAY_PROXY_ADDR), notnull(L2_SCROLL_MESSENGER_PROXY_ADDR)); + + L2_ERC721_GATEWAY_IMPLEMENTATION_ADDR = deploy( + "L2_ERC721_GATEWAY_IMPLEMENTATION", + type(L2ERC721Gateway).creationCode, + args + ); + + upgrade(L2_PROXY_ADMIN_ADDR, L2_ERC721_GATEWAY_PROXY_ADDR, L2_ERC721_GATEWAY_IMPLEMENTATION_ADDR); + } + + function deployL2ERC1155Gateway() private { + bytes memory args = abi.encode(notnull(L1_ERC1155_GATEWAY_PROXY_ADDR), notnull(L2_SCROLL_MESSENGER_PROXY_ADDR)); + + L2_ERC1155_GATEWAY_IMPLEMENTATION_ADDR = deploy( + "L2_ERC1155_GATEWAY_IMPLEMENTATION", + type(L2ERC1155Gateway).creationCode, + args + ); + + upgrade(L2_PROXY_ADMIN_ADDR, L2_ERC1155_GATEWAY_PROXY_ADDR, L2_ERC1155_GATEWAY_IMPLEMENTATION_ADDR); + } + + /********************** + * L1: initialization * + **********************/ + + function initializeScrollChain() private { + if (getInitializeCount(L1_SCROLL_CHAIN_PROXY_ADDR) == 0) { + ScrollChain(L1_SCROLL_CHAIN_PROXY_ADDR).initialize( + notnull(L1_MESSAGE_QUEUE_PROXY_ADDR), + notnull(L1_MULTIPLE_VERSION_ROLLUP_VERIFIER_ADDR), + MAX_TX_IN_CHUNK + ); + } + + if (!ScrollChain(L1_SCROLL_CHAIN_PROXY_ADDR).isSequencer(L1_COMMIT_SENDER_ADDR)) { + ScrollChain(L1_SCROLL_CHAIN_PROXY_ADDR).addSequencer(L1_COMMIT_SENDER_ADDR); + } + + if (!ScrollChain(L1_SCROLL_CHAIN_PROXY_ADDR).isProver(L1_FINALIZE_SENDER_ADDR)) { + ScrollChain(L1_SCROLL_CHAIN_PROXY_ADDR).addProver(L1_FINALIZE_SENDER_ADDR); + } + } + + function initializeL1MessageQueue() private { + if (getInitializeCount(L1_MESSAGE_QUEUE_PROXY_ADDR) == 0) { + L1MessageQueueWithGasPriceOracle(L1_MESSAGE_QUEUE_PROXY_ADDR).initialize( + notnull(L1_SCROLL_MESSENGER_PROXY_ADDR), + notnull(L1_SCROLL_CHAIN_PROXY_ADDR), + notnull(L1_ENFORCED_TX_GATEWAY_PROXY_ADDR), + // note: this should be the address of L2GasPriceOracle, + // but since we are using L1MessageQueueWithGasPriceOracle, so we set an all zero address here + address(0), + MAX_L1_MESSAGE_GAS_LIMIT + ); + } + + // note: since we are using L1MessageQueueWithGasPriceOracle, + // and we don't have a L2GasPriceOracle deploy, so we skip the initializeV2. + // instead, we updateWhitelistChecker + // if (getInitializeCount(L1_MESSAGE_QUEUE_PROXY_ADDR) < 2) { + // L1MessageQueueWithGasPriceOracle(L1_MESSAGE_QUEUE_PROXY_ADDR).initializeV2(); + // } + if (L1MessageQueueWithGasPriceOracle(L1_MESSAGE_QUEUE_PROXY_ADDR).whitelistChecker() != L1_WHITELIST_ADDR) { + L1MessageQueueWithGasPriceOracle(L1_MESSAGE_QUEUE_PROXY_ADDR).updateWhitelistChecker( + notnull(L1_WHITELIST_ADDR) + ); + } + + if (getInitializeCount(L1_MESSAGE_QUEUE_PROXY_ADDR) < 3) { + L1MessageQueueWithGasPriceOracle(L1_MESSAGE_QUEUE_PROXY_ADDR).initializeV3(); + } + } + + function initializeL1ScrollMessenger() private { + if (getInitializeCount(L1_SCROLL_MESSENGER_PROXY_ADDR) == 0) { + L1ScrollMessenger(payable(L1_SCROLL_MESSENGER_PROXY_ADDR)).initialize( + notnull(L2_SCROLL_MESSENGER_PROXY_ADDR), + notnull(L1_FEE_VAULT_ADDR), + notnull(L1_SCROLL_CHAIN_PROXY_ADDR), + notnull(L1_MESSAGE_QUEUE_PROXY_ADDR) + ); + } + } + + function initializeEnforcedTxGateway() private { + if (getInitializeCount(L1_ENFORCED_TX_GATEWAY_PROXY_ADDR) == 0) { + EnforcedTxGateway(payable(L1_ENFORCED_TX_GATEWAY_PROXY_ADDR)).initialize( + notnull(L1_MESSAGE_QUEUE_PROXY_ADDR), + notnull(L1_FEE_VAULT_ADDR) + ); + } + + // disable gateway + if (!EnforcedTxGateway(payable(L1_ENFORCED_TX_GATEWAY_PROXY_ADDR)).paused()) { + EnforcedTxGateway(payable(L1_ENFORCED_TX_GATEWAY_PROXY_ADDR)).setPause(true); + } + } + + function initializeL1GatewayRouter() private { + address L2_ETH_GATEWAY_COUNTERPART; + + if (ALTERNATIVE_GAS_TOKEN_ENABLED) { + L2_ETH_GATEWAY_COUNTERPART = L1_GAS_TOKEN_GATEWAY_PROXY_ADDR; + } else { + L2_ETH_GATEWAY_COUNTERPART = L1_ETH_GATEWAY_PROXY_ADDR; + } + + if (getInitializeCount(L1_GATEWAY_ROUTER_PROXY_ADDR) == 0) { + L1GatewayRouter(L1_GATEWAY_ROUTER_PROXY_ADDR).initialize( + notnull(L2_ETH_GATEWAY_COUNTERPART), + notnull(L1_STANDARD_ERC20_GATEWAY_PROXY_ADDR) + ); + } + } + + function initializeL1CustomERC20Gateway() private { + if (getInitializeCount(L1_CUSTOM_ERC20_GATEWAY_PROXY_ADDR) == 0) { + L1CustomERC20Gateway(L1_CUSTOM_ERC20_GATEWAY_PROXY_ADDR).initialize( + notnull(L2_CUSTOM_ERC20_GATEWAY_PROXY_ADDR), + notnull(L1_GATEWAY_ROUTER_PROXY_ADDR), + notnull(L1_SCROLL_MESSENGER_PROXY_ADDR) + ); + } + } + + function initializeL1ERC1155Gateway() private { + if (getInitializeCount(L1_ERC1155_GATEWAY_PROXY_ADDR) == 0) { + L1ERC1155Gateway(L1_ERC1155_GATEWAY_PROXY_ADDR).initialize( + notnull(L2_ERC1155_GATEWAY_PROXY_ADDR), + notnull(L1_SCROLL_MESSENGER_PROXY_ADDR) + ); + } + } + + function initializeL1ERC721Gateway() private { + if (getInitializeCount(L1_ERC721_GATEWAY_PROXY_ADDR) == 0) { + L1ERC721Gateway(L1_ERC721_GATEWAY_PROXY_ADDR).initialize( + notnull(L2_ERC721_GATEWAY_PROXY_ADDR), + notnull(L1_SCROLL_MESSENGER_PROXY_ADDR) + ); + } + } + + function initializeL1ETHGateway() private gasToken(false) { + if (getInitializeCount(L1_ETH_GATEWAY_PROXY_ADDR) == 0) { + L1ETHGateway(L1_ETH_GATEWAY_PROXY_ADDR).initialize( + notnull(L2_ETH_GATEWAY_PROXY_ADDR), + notnull(L1_GATEWAY_ROUTER_PROXY_ADDR), + notnull(L1_SCROLL_MESSENGER_PROXY_ADDR) + ); + } + } + + function initializeL1StandardERC20Gateway() private { + if (getInitializeCount(L1_STANDARD_ERC20_GATEWAY_PROXY_ADDR) == 0) { + L1StandardERC20Gateway(L1_STANDARD_ERC20_GATEWAY_PROXY_ADDR).initialize( + notnull(L2_STANDARD_ERC20_GATEWAY_PROXY_ADDR), + notnull(L1_GATEWAY_ROUTER_PROXY_ADDR), + notnull(L1_SCROLL_MESSENGER_PROXY_ADDR), + notnull(L2_SCROLL_STANDARD_ERC20_ADDR), + notnull(L2_SCROLL_STANDARD_ERC20_FACTORY_ADDR) + ); + } + } + + function initializeL1WETHGateway() private gasToken(false) { + if (getInitializeCount(L1_WETH_GATEWAY_PROXY_ADDR) == 0) { + L1WETHGateway(payable(L1_WETH_GATEWAY_PROXY_ADDR)).initialize( + notnull(L2_WETH_GATEWAY_PROXY_ADDR), + notnull(L1_GATEWAY_ROUTER_PROXY_ADDR), + notnull(L1_SCROLL_MESSENGER_PROXY_ADDR) + ); + } + + // set WETH gateway in router + address[] memory _tokens = new address[](1); + _tokens[0] = notnull(L1_WETH_ADDR); + address[] memory _gateways = new address[](1); + _gateways[0] = notnull(L1_WETH_GATEWAY_PROXY_ADDR); + + if (L1GatewayRouter(L1_GATEWAY_ROUTER_PROXY_ADDR).ERC20Gateway(_tokens[0]) != _gateways[0]) { + L1GatewayRouter(L1_GATEWAY_ROUTER_PROXY_ADDR).setERC20Gateway(_tokens, _gateways); + } + } + + function initializeL1Whitelist() private { + address[] memory accounts = new address[](1); + accounts[0] = L1_GAS_ORACLE_SENDER_ADDR; + + if (!Whitelist(L1_WHITELIST_ADDR).isSenderAllowed(accounts[0])) { + Whitelist(L1_WHITELIST_ADDR).updateWhitelistStatus(accounts, true); + } + } + + function initializeL1GasTokenGateway() private gasToken(true) { + if (getInitializeCount(L1_GAS_TOKEN_GATEWAY_PROXY_ADDR) == 0) { + L1GasTokenGateway(payable(L1_GAS_TOKEN_GATEWAY_PROXY_ADDR)).initialize(); + } + } + + function lockTokensOnL1() private { + if (!ALTERNATIVE_GAS_TOKEN_ENABLED) { + uint256 l1MessengerBalance = address(L1_SCROLL_MESSENGER_PROXY_ADDR).balance; + uint256 amountToLock = L2_DEPLOYER_INITIAL_BALANCE; + + if (l1MessengerBalance < amountToLock) { + uint256 amountToSend = amountToLock - l1MessengerBalance; + (bool sent, bytes memory data) = payable(L1_SCROLL_MESSENGER_PROXY_ADDR).call{value: amountToSend}(""); + require(sent, "[ERROR] failed to lock tokens on layer 1"); + } + } else { + uint256 l1GasTokenGatewayBalance = IERC20Metadata(L1_GAS_TOKEN_ADDR).balanceOf( + L1_GAS_TOKEN_GATEWAY_PROXY_ADDR + ); + + uint256 scale = 10**(18 - IERC20Metadata(L1_GAS_TOKEN_ADDR).decimals()); + uint256 amountToLock = L2_DEPLOYER_INITIAL_BALANCE / scale; + if (L2_DEPLOYER_INITIAL_BALANCE % scale != 0) { + amountToLock += 1; + } + + if (l1GasTokenGatewayBalance < amountToLock) { + uint256 amountTosend = amountToLock - l1GasTokenGatewayBalance; + IERC20Metadata(L1_GAS_TOKEN_ADDR).transfer(L1_GAS_TOKEN_GATEWAY_PROXY_ADDR, amountTosend); + } + } + } + + function transferL1ContractOwnership() private { + transferOwnership(L1_ENFORCED_TX_GATEWAY_PROXY_ADDR, OWNER_ADDR); + transferOwnership(L1_CUSTOM_ERC20_GATEWAY_PROXY_ADDR, OWNER_ADDR); + transferOwnership(L1_ERC1155_GATEWAY_PROXY_ADDR, OWNER_ADDR); + transferOwnership(L1_ERC721_GATEWAY_PROXY_ADDR, OWNER_ADDR); + transferOwnership(L1_GATEWAY_ROUTER_PROXY_ADDR, OWNER_ADDR); + transferOwnership(L1_MESSAGE_QUEUE_PROXY_ADDR, OWNER_ADDR); + transferOwnership(L1_SCROLL_MESSENGER_PROXY_ADDR, OWNER_ADDR); + transferOwnership(L1_STANDARD_ERC20_GATEWAY_PROXY_ADDR, OWNER_ADDR); + transferOwnership(L1_MULTIPLE_VERSION_ROLLUP_VERIFIER_ADDR, OWNER_ADDR); + transferOwnership(L1_PROXY_ADMIN_ADDR, OWNER_ADDR); + transferOwnership(L1_SCROLL_CHAIN_PROXY_ADDR, OWNER_ADDR); + transferOwnership(L1_WHITELIST_ADDR, OWNER_ADDR); + + if (!ALTERNATIVE_GAS_TOKEN_ENABLED) { + transferOwnership(L1_ETH_GATEWAY_PROXY_ADDR, OWNER_ADDR); + transferOwnership(L1_WETH_GATEWAY_PROXY_ADDR, OWNER_ADDR); + } else { + transferOwnership(L1_GAS_TOKEN_GATEWAY_PROXY_ADDR, OWNER_ADDR); + } + } + + /********************** + * L2: initialization * + **********************/ + + function initializeL2MessageQueue() private { + if (L2MessageQueue(L2_MESSAGE_QUEUE_ADDR).messenger() != notnull(L2_SCROLL_MESSENGER_PROXY_ADDR)) { + L2MessageQueue(L2_MESSAGE_QUEUE_ADDR).initialize(L2_SCROLL_MESSENGER_PROXY_ADDR); + } + } + + function initializeL2TxFeeVault() private { + if ( + !ALTERNATIVE_GAS_TOKEN_ENABLED && + L2TxFeeVault(payable(L2_TX_FEE_VAULT_ADDR)).messenger() != notnull(L2_SCROLL_MESSENGER_PROXY_ADDR) + ) { + L2TxFeeVault(payable(L2_TX_FEE_VAULT_ADDR)).updateMessenger(L2_SCROLL_MESSENGER_PROXY_ADDR); + } + } + + function initializeL1GasPriceOracle() private { + if (address(L1GasPriceOracle(L1_GAS_PRICE_ORACLE_ADDR).whitelist()) != notnull(L2_WHITELIST_ADDR)) { + L1GasPriceOracle(L1_GAS_PRICE_ORACLE_ADDR).updateWhitelist(L2_WHITELIST_ADDR); + } + } + + function initializeL2ScrollMessenger() private { + if (getInitializeCount(L2_SCROLL_MESSENGER_PROXY_ADDR) == 0) { + L2ScrollMessenger(payable(L2_SCROLL_MESSENGER_PROXY_ADDR)).initialize( + notnull(L1_SCROLL_MESSENGER_PROXY_ADDR) + ); + } + } + + function initializeL2GatewayRouter() private { + if (getInitializeCount(L2_GATEWAY_ROUTER_PROXY_ADDR) == 0) { + L2GatewayRouter(L2_GATEWAY_ROUTER_PROXY_ADDR).initialize( + notnull(L2_ETH_GATEWAY_PROXY_ADDR), + notnull(L2_STANDARD_ERC20_GATEWAY_PROXY_ADDR) + ); + } + } + + function initializeL2CustomERC20Gateway() private { + if (getInitializeCount(L2_CUSTOM_ERC20_GATEWAY_PROXY_ADDR) == 0) { + L2CustomERC20Gateway(L2_CUSTOM_ERC20_GATEWAY_PROXY_ADDR).initialize( + notnull(L1_CUSTOM_ERC20_GATEWAY_PROXY_ADDR), + notnull(L2_GATEWAY_ROUTER_PROXY_ADDR), + notnull(L2_SCROLL_MESSENGER_PROXY_ADDR) + ); + } + } + + function initializeL2ERC1155Gateway() private { + if (getInitializeCount(L2_ERC1155_GATEWAY_PROXY_ADDR) == 0) { + L2ERC1155Gateway(L2_ERC1155_GATEWAY_PROXY_ADDR).initialize( + notnull(L1_ERC1155_GATEWAY_PROXY_ADDR), + notnull(L2_SCROLL_MESSENGER_PROXY_ADDR) + ); + } + } + + function initializeL2ERC721Gateway() private { + if (getInitializeCount(L2_ERC721_GATEWAY_PROXY_ADDR) == 0) { + L2ERC721Gateway(L2_ERC721_GATEWAY_PROXY_ADDR).initialize( + notnull(L1_ERC721_GATEWAY_PROXY_ADDR), + notnull(L2_SCROLL_MESSENGER_PROXY_ADDR) + ); + } + } + + function initializeL2ETHGateway() private { + address COUNTERPART; + if (ALTERNATIVE_GAS_TOKEN_ENABLED) { + COUNTERPART = L1_GAS_TOKEN_GATEWAY_PROXY_ADDR; + } else { + COUNTERPART = L1_ETH_GATEWAY_PROXY_ADDR; + } + if (getInitializeCount(L2_ETH_GATEWAY_PROXY_ADDR) == 0) { + L2ETHGateway(L2_ETH_GATEWAY_PROXY_ADDR).initialize( + notnull(COUNTERPART), + notnull(L2_GATEWAY_ROUTER_PROXY_ADDR), + notnull(L2_SCROLL_MESSENGER_PROXY_ADDR) + ); + } + } + + function initializeL2StandardERC20Gateway() private { + if (getInitializeCount(L2_STANDARD_ERC20_GATEWAY_PROXY_ADDR) == 0) { + L2StandardERC20Gateway(L2_STANDARD_ERC20_GATEWAY_PROXY_ADDR).initialize( + notnull(L1_STANDARD_ERC20_GATEWAY_PROXY_ADDR), + notnull(L2_GATEWAY_ROUTER_PROXY_ADDR), + notnull(L2_SCROLL_MESSENGER_PROXY_ADDR), + notnull(L2_SCROLL_STANDARD_ERC20_FACTORY_ADDR) + ); + } + } + + function initializeL2WETHGateway() private gasToken(false) { + if (getInitializeCount(L2_WETH_GATEWAY_PROXY_ADDR) == 0) { + L2WETHGateway(payable(L2_WETH_GATEWAY_PROXY_ADDR)).initialize( + notnull(L1_WETH_GATEWAY_PROXY_ADDR), + notnull(L2_GATEWAY_ROUTER_PROXY_ADDR), + notnull(L2_SCROLL_MESSENGER_PROXY_ADDR) + ); + } + + // set WETH gateway in router + address[] memory _tokens = new address[](1); + _tokens[0] = notnull(L2_WETH_ADDR); + address[] memory _gateways = new address[](1); + _gateways[0] = notnull(L2_WETH_GATEWAY_PROXY_ADDR); + + if (L2GatewayRouter(L2_GATEWAY_ROUTER_PROXY_ADDR).ERC20Gateway(_tokens[0]) != _gateways[0]) { + L2GatewayRouter(L2_GATEWAY_ROUTER_PROXY_ADDR).setERC20Gateway(_tokens, _gateways); + } + } + + function initializeScrollStandardERC20Factory() private { + transferOwnership( + notnull(L2_SCROLL_STANDARD_ERC20_FACTORY_ADDR), + notnull(L2_STANDARD_ERC20_GATEWAY_PROXY_ADDR) + ); + } + + function initializeL2Whitelist() private { + address[] memory accounts = new address[](1); + accounts[0] = L2_GAS_ORACLE_SENDER_ADDR; + + if (!Whitelist(L2_WHITELIST_ADDR).isSenderAllowed(accounts[0])) { + Whitelist(L2_WHITELIST_ADDR).updateWhitelistStatus(accounts, true); + } + } + + function transferL2ContractOwnership() private { + transferOwnership(L1_GAS_PRICE_ORACLE_ADDR, OWNER_ADDR); + transferOwnership(L2_CUSTOM_ERC20_GATEWAY_PROXY_ADDR, OWNER_ADDR); + transferOwnership(L2_ERC1155_GATEWAY_PROXY_ADDR, OWNER_ADDR); + transferOwnership(L2_ERC721_GATEWAY_PROXY_ADDR, OWNER_ADDR); + transferOwnership(L2_ETH_GATEWAY_PROXY_ADDR, OWNER_ADDR); + transferOwnership(L2_GATEWAY_ROUTER_PROXY_ADDR, OWNER_ADDR); + transferOwnership(L2_MESSAGE_QUEUE_ADDR, OWNER_ADDR); + transferOwnership(L2_SCROLL_MESSENGER_PROXY_ADDR, OWNER_ADDR); + transferOwnership(L2_STANDARD_ERC20_GATEWAY_PROXY_ADDR, OWNER_ADDR); + transferOwnership(L2_TX_FEE_VAULT_ADDR, OWNER_ADDR); + transferOwnership(L2_PROXY_ADMIN_ADDR, OWNER_ADDR); + transferOwnership(L2_WHITELIST_ADDR, OWNER_ADDR); + + if (!ALTERNATIVE_GAS_TOKEN_ENABLED) { + transferOwnership(L2_WETH_GATEWAY_PROXY_ADDR, OWNER_ADDR); + } + } +} diff --git a/scripts/deterministic/DeterministicDeployment.sol b/scripts/deterministic/DeterministicDeployment.sol new file mode 100644 index 0000000..9839050 --- /dev/null +++ b/scripts/deterministic/DeterministicDeployment.sol @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.24; + +import {console} from "forge-std/Script.sol"; +import {stdToml} from "forge-std/StdToml.sol"; + +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ERC1967Upgrade} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol"; + +import {CONFIG_CONTRACTS_PATH, DEFAULT_DEPLOYMENT_SALT, DETERMINISTIC_DEPLOYMENT_PROXY_ADDR} from "./Constants.sol"; +import {Configuration} from "./Configuration.sol"; + +/// @notice DeterministicDeployment provides utilities for deterministic contract deployments. +abstract contract DeterministicDeployment is Configuration { + using stdToml for string; + + /********* + * Types * + *********/ + + enum ScriptMode { + None, + LogAddresses, + WriteConfig, + VerifyConfig, + EmptyConfig + } + + /******************* + * State variables * + *******************/ + + ScriptMode private mode; + string private saltPrefix; + bool private skipDeploy; + + /********************** + * Internal interface * + **********************/ + + function initialize(ScriptMode _mode) internal { + mode = _mode; + skipDeploy = false; + + if (mode != ScriptMode.EmptyConfig) { + readConfig(); + } + + // salt prefix used for deterministic deployments + if (bytes(DEPLOYMENT_SALT).length != 0) { + saltPrefix = DEPLOYMENT_SALT; + } else { + saltPrefix = DEFAULT_DEPLOYMENT_SALT; + } + + // sanity check: make sure DeterministicDeploymentProxy exists + if (DETERMINISTIC_DEPLOYMENT_PROXY_ADDR.code.length == 0) { + revert( + string( + abi.encodePacked( + "[ERROR] DeterministicDeploymentProxy (", + vm.toString(DETERMINISTIC_DEPLOYMENT_PROXY_ADDR), + ") is not available" + ) + ) + ); + } + } + + function parseScriptMode(string memory scriptMode) internal pure returns (ScriptMode) { + if (keccak256(bytes(scriptMode)) == keccak256(bytes("log-addresses"))) { + return ScriptMode.LogAddresses; + } else if (keccak256(bytes(scriptMode)) == keccak256(bytes("write-config"))) { + return ScriptMode.WriteConfig; + } else if (keccak256(bytes(scriptMode)) == keccak256(bytes("verify-config"))) { + return ScriptMode.VerifyConfig; + } else { + return ScriptMode.None; + } + } + + function skipDeployment() internal { + skipDeploy = true; + } + + function deploy(string memory name, bytes memory codeWithArgs) internal returns (address) { + return _deploy(name, codeWithArgs); + } + + function deploy( + string memory name, + bytes memory code, + bytes memory args + ) internal returns (address) { + return _deploy(name, abi.encodePacked(code, args)); + } + + function predict(string memory name, bytes memory codeWithArgs) internal view returns (address) { + return _predict(name, codeWithArgs); + } + + function predict( + string memory name, + bytes memory code, + bytes memory args + ) internal view returns (address) { + return _predict(name, abi.encodePacked(code, args)); + } + + function upgrade( + address proxyAdminAddr, + address proxyAddr, + address implAddr + ) internal { + address addr = _getImplementation(proxyAddr); + + if (!skipDeploy && addr != implAddr) { + ProxyAdmin(notnull(proxyAdminAddr)).upgrade( + ITransparentUpgradeableProxy(notnull(proxyAddr)), + notnull(implAddr) + ); + } + } + + function getInitializeCount(address contractAddr) internal view returns (uint8) { + bytes32 slotValue = vm.load(address(contractAddr), bytes32(uint256(0))); + return uint8(uint256(slotValue)); + } + + /********************* + * Private functions * + *********************/ + + function _getSalt(string memory name) private view returns (bytes32) { + return keccak256(abi.encodePacked(saltPrefix, name)); + } + + function _deploy(string memory name, bytes memory codeWithArgs) private returns (address) { + // check override (mainly used with predeploys) + address addr = tryGetOverride(name); + + if (addr != address(0)) { + _label(name, addr); + return addr; + } + + // predict determinstic deployment address + addr = _predict(name, codeWithArgs); + _label(name, addr); + + if (skipDeploy) { + return addr; + } + + // skip if the contract is already deployed + if (addr.code.length > 0) { + return addr; + } + + // deploy contract + bytes32 salt = _getSalt(name); + bytes memory data = abi.encodePacked(salt, codeWithArgs); + (bool success, ) = DETERMINISTIC_DEPLOYMENT_PROXY_ADDR.call(data); + require(success, "call failed"); + require(addr.code.length != 0, "deployment address mismatch"); + + return addr; + } + + function _predict(string memory name, bytes memory codeWithArgs) private view returns (address) { + bytes32 salt = _getSalt(name); + + return + address( + uint160( + uint256( + keccak256( + abi.encodePacked( + bytes1(0xff), + DETERMINISTIC_DEPLOYMENT_PROXY_ADDR, + salt, + keccak256(codeWithArgs) + ) + ) + ) + ) + ); + } + + function _label(string memory name, address addr) internal { + vm.label(addr, name); + + if (mode == ScriptMode.None) { + return; + } + + if (mode == ScriptMode.LogAddresses) { + console.log(string(abi.encodePacked(name, "_ADDR=", vm.toString(address(addr))))); + return; + } + + string memory tomlPath = string(abi.encodePacked(".", name, "_ADDR")); + + if (mode == ScriptMode.WriteConfig) { + vm.writeToml(vm.toString(addr), CONFIG_CONTRACTS_PATH, tomlPath); + return; + } + + if (mode == ScriptMode.VerifyConfig) { + address expectedAddr = contractsCfg.readAddress(tomlPath); + + if (addr != expectedAddr) { + revert( + string( + abi.encodePacked( + "[ERROR] unexpected address for ", + name, + ", expected = ", + vm.toString(expectedAddr), + " (from toml config), got = ", + vm.toString(addr) + ) + ) + ); + } + } + } + + function _getImplementation(address proxyAddr) private view returns (address) { + // ERC1967Upgrade implementation slot + bytes32 _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + // get implementation address + return address(uint160(uint256(vm.load(address(proxyAddr), _IMPLEMENTATION_SLOT)))); + } +} diff --git a/scripts/deterministic/GenerateConfigs.s.sol b/scripts/deterministic/GenerateConfigs.s.sol new file mode 100644 index 0000000..456a2e7 --- /dev/null +++ b/scripts/deterministic/GenerateConfigs.s.sol @@ -0,0 +1,426 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.24; + +import {stdToml} from "forge-std/StdToml.sol"; + +import {ADMIN_SYSTEM_BACKEND_CONFIG_PATH, ADMIN_SYSTEM_BACKEND_CONFIG_TEMPLATE_PATH, BALANCE_CHECKER_CONFIG_PATH, BALANCE_CHECKER_CONFIG_TEMPLATE_PATH, BRIDGE_HISTORY_CONFIG_PATH, BRIDGE_HISTORY_CONFIG_TEMPLATE_PATH, CHAIN_MONITOR_CONFIG_PATH, CHAIN_MONITOR_CONFIG_TEMPLATE_PATH, CONFIG_PATH, COORDINATOR_CONFIG_PATH, COORDINATOR_CONFIG_TEMPLATE_PATH, FRONTEND_ENV_PATH, ROLLUP_CONFIG_PATH, ROLLUP_CONFIG_TEMPLATE_PATH, ROLLUP_EXPLORER_BACKEND_CONFIG_PATH, ROLLUP_EXPLORER_BACKEND_CONFIG_TEMPLATE_PATH} from "./Constants.sol"; +import {DeployScroll} from "./DeployScroll.s.sol"; +import {DeterministicDeployment} from "./DeterministicDeployment.sol"; + +contract GenerateRollupConfig is DeployScroll { + using stdToml for string; + + /*************** + * Entry point * + ***************/ + + function run() public { + DeterministicDeployment.initialize(ScriptMode.VerifyConfig); + predictAllContracts(); + + generateRollupConfig(); + } + + /********************* + * Private functions * + *********************/ + + // prettier-ignore + function generateRollupConfig() private { + // initialize template file + if (vm.exists(ROLLUP_CONFIG_PATH)) { + vm.removeFile(ROLLUP_CONFIG_PATH); + } + + string memory template = vm.readFile(ROLLUP_CONFIG_TEMPLATE_PATH); + vm.writeFile(ROLLUP_CONFIG_PATH, template); + + // endpoints + vm.writeJson(L1_RPC_ENDPOINT, ROLLUP_CONFIG_PATH, ".l1_config.endpoint"); + vm.writeJson(L2_RPC_ENDPOINT, ROLLUP_CONFIG_PATH, ".l1_config.relayer_config.sender_config.endpoint"); + vm.writeJson(L2_RPC_ENDPOINT, ROLLUP_CONFIG_PATH, ".l2_config.endpoint"); + vm.writeJson(L1_RPC_ENDPOINT, ROLLUP_CONFIG_PATH, ".l2_config.relayer_config.sender_config.endpoint"); + + // contracts + vm.writeJson(vm.toString(L1_GAS_PRICE_ORACLE_ADDR), ROLLUP_CONFIG_PATH, ".l1_config.relayer_config.gas_price_oracle_contract_address"); + vm.writeJson(vm.toString(L2_MESSAGE_QUEUE_ADDR), ROLLUP_CONFIG_PATH, ".l2_config.l2_message_queue_address"); + vm.writeJson(vm.toString(L1_SCROLL_CHAIN_PROXY_ADDR), ROLLUP_CONFIG_PATH, ".l2_config.relayer_config.rollup_contract_address"); + vm.writeJson(vm.toString(L1_MESSAGE_QUEUE_PROXY_ADDR), ROLLUP_CONFIG_PATH, ".l2_config.relayer_config.gas_price_oracle_contract_address"); + + // other + vm.writeJson(vm.toString(TEST_ENV_MOCK_FINALIZE_ENABLED), ROLLUP_CONFIG_PATH, ".l2_config.relayer_config.enable_test_env_bypass_features"); + vm.writeJson(vm.toString(TEST_ENV_MOCK_FINALIZE_TIMEOUT_SEC), ROLLUP_CONFIG_PATH, ".l2_config.relayer_config.finalize_batch_without_proof_timeout_sec"); + vm.writeJson(vm.toString(TEST_ENV_MOCK_FINALIZE_TIMEOUT_SEC), ROLLUP_CONFIG_PATH, ".l2_config.relayer_config.finalize_bundle_without_proof_timeout_sec"); + + vm.writeJson(vm.toString(MAX_BLOCK_IN_CHUNK), ROLLUP_CONFIG_PATH, ".l2_config.chunk_proposer_config.max_block_num_per_chunk"); + vm.writeJson(vm.toString(MAX_TX_IN_CHUNK), ROLLUP_CONFIG_PATH, ".l2_config.chunk_proposer_config.max_tx_num_per_chunk"); + vm.writeJson(vm.toString(MAX_BATCH_IN_BUNDLE), ROLLUP_CONFIG_PATH, ".l2_config.bundle_proposer_config.max_batch_num_per_bundle"); + + // alternative gas token configuration for gas oracle + if (ALTERNATIVE_GAS_TOKEN_ENABLED) { + bool GAS_ORACLE_INCORPORATE_TOKEN_EXCHANGE_RATE_ENANBLED; + if (vm.keyExistsToml(cfg, ".gas-token.GAS_ORACLE_INCORPORATE_TOKEN_EXCHANGE_RATE_ENANBLED")) { + GAS_ORACLE_INCORPORATE_TOKEN_EXCHANGE_RATE_ENANBLED = cfg.readBool(".gas-token.GAS_ORACLE_INCORPORATE_TOKEN_EXCHANGE_RATE_ENANBLED"); + vm.writeJson(vm.toString(GAS_ORACLE_INCORPORATE_TOKEN_EXCHANGE_RATE_ENANBLED), ROLLUP_CONFIG_PATH, ".l1_config.relayer_config.gas_oracle_config.alternative_gas_token_config.enabled"); + vm.writeJson(vm.toString(GAS_ORACLE_INCORPORATE_TOKEN_EXCHANGE_RATE_ENANBLED), ROLLUP_CONFIG_PATH, ".l2_config.relayer_config.gas_oracle_config.alternative_gas_token_config.enabled"); + } + if (GAS_ORACLE_INCORPORATE_TOKEN_EXCHANGE_RATE_ENANBLED) { + string memory EXCHANGE_RATE_UPDATE_MODE = cfg.readString(".gas-token.EXCHANGE_RATE_UPDATE_MODE"); + vm.writeJson(EXCHANGE_RATE_UPDATE_MODE, ROLLUP_CONFIG_PATH, ".l1_config.relayer_config.gas_oracle_config.alternative_gas_token_config.mode"); + vm.writeJson(EXCHANGE_RATE_UPDATE_MODE, ROLLUP_CONFIG_PATH, ".l2_config.relayer_config.gas_oracle_config.alternative_gas_token_config.mode"); + if (keccak256(abi.encodePacked(EXCHANGE_RATE_UPDATE_MODE)) == keccak256("Fixed")) { + string memory FIXED_EXCHANGE_RATE = cfg.readString(".gas-token.FIXED_EXCHANGE_RATE"); + vm.writeJson(FIXED_EXCHANGE_RATE, ROLLUP_CONFIG_PATH, ".l1_config.relayer_config.gas_oracle_config.alternative_gas_token_config.fixed_exchange_rate"); + vm.writeJson(FIXED_EXCHANGE_RATE, ROLLUP_CONFIG_PATH, ".l2_config.relayer_config.gas_oracle_config.alternative_gas_token_config.fixed_exchange_rate"); + } else if (keccak256(abi.encodePacked(EXCHANGE_RATE_UPDATE_MODE)) == keccak256("BinanceApi")) { + string memory TOKEN_SYMBOL_PAIR = cfg.readString(".gas-token.TOKEN_SYMBOL_PAIR"); + vm.writeJson(TOKEN_SYMBOL_PAIR, ROLLUP_CONFIG_PATH, ".l1_config.relayer_config.gas_oracle_config.alternative_gas_token_config.token_symbol_pair"); + vm.writeJson(TOKEN_SYMBOL_PAIR, ROLLUP_CONFIG_PATH, ".l2_config.relayer_config.gas_oracle_config.alternative_gas_token_config.token_symbol_pair"); + } else { + revert( + string( + abi.encodePacked( + "[ERROR] unsupported exchange rate update mode for gas oracle, mode: ", + EXCHANGE_RATE_UPDATE_MODE + ) + ) + ); + } + } + } + } +} + +contract GenerateCoordinatorConfig is DeployScroll { + /*************** + * Entry point * + ***************/ + + function run() public { + DeterministicDeployment.initialize(ScriptMode.VerifyConfig); + predictAllContracts(); + + generateCoordinatorConfig(); + } + + /********************* + * Private functions * + *********************/ + + function generateCoordinatorConfig() private { + // initialize template file + if (vm.exists(COORDINATOR_CONFIG_PATH)) { + vm.removeFile(COORDINATOR_CONFIG_PATH); + } + + string memory template = vm.readFile(COORDINATOR_CONFIG_TEMPLATE_PATH); + vm.writeFile(COORDINATOR_CONFIG_PATH, template); + + vm.writeJson(CHUNK_COLLECTION_TIME_SEC, COORDINATOR_CONFIG_PATH, ".prover_manager.chunk_collection_time_sec"); + vm.writeJson(BATCH_COLLECTION_TIME_SEC, COORDINATOR_CONFIG_PATH, ".prover_manager.batch_collection_time_sec"); + vm.writeJson(BUNDLE_COLLECTION_TIME_SEC, COORDINATOR_CONFIG_PATH, ".prover_manager.bundle_collection_time_sec"); + + vm.writeJson(vm.toString(CHAIN_ID_L2), COORDINATOR_CONFIG_PATH, ".l2.chain_id"); + vm.writeJson(COORDINATOR_JWT_SECRET_KEY, COORDINATOR_CONFIG_PATH, ".auth.secret"); + } +} + +contract GenerateChainMonitorConfig is DeployScroll { + /*************** + * Entry point * + ***************/ + + function run() public { + DeterministicDeployment.initialize(ScriptMode.VerifyConfig); + predictAllContracts(); + + generateChainMonitorConfig(); + } + + /********************* + * Private functions * + *********************/ + + // prettier-ignore + function generateChainMonitorConfig() private { + // initialize template file + if (vm.exists(CHAIN_MONITOR_CONFIG_PATH)) { + vm.removeFile(CHAIN_MONITOR_CONFIG_PATH); + } + + string memory template = vm.readFile(CHAIN_MONITOR_CONFIG_TEMPLATE_PATH); + vm.writeFile(CHAIN_MONITOR_CONFIG_PATH, template); + + // L1 + vm.writeJson(L1_RPC_ENDPOINT, CHAIN_MONITOR_CONFIG_PATH, ".l1_config.l1_url"); + vm.writeJson(vm.toString(L1_CONTRACT_DEPLOYMENT_BLOCK), CHAIN_MONITOR_CONFIG_PATH, ".l1_config.start_number"); + vm.writeJson(vm.toString(L1_ETH_GATEWAY_PROXY_ADDR), CHAIN_MONITOR_CONFIG_PATH, ".l1_config.l1_contracts.l1_gateways.eth_gateway"); + vm.writeJson(vm.toString(L1_WETH_GATEWAY_PROXY_ADDR), CHAIN_MONITOR_CONFIG_PATH, ".l1_config.l1_contracts.l1_gateways.weth_gateway"); + vm.writeJson(vm.toString(L1_STANDARD_ERC20_GATEWAY_PROXY_ADDR), CHAIN_MONITOR_CONFIG_PATH, ".l1_config.l1_contracts.l1_gateways.standard_erc20_gateway"); + vm.writeJson(vm.toString(L1_CUSTOM_ERC20_GATEWAY_PROXY_ADDR), CHAIN_MONITOR_CONFIG_PATH, ".l1_config.l1_contracts.l1_gateways.custom_erc20_gateway"); + vm.writeJson(vm.toString(L1_ERC721_GATEWAY_PROXY_ADDR), CHAIN_MONITOR_CONFIG_PATH, ".l1_config.l1_contracts.l1_gateways.erc721_gateway"); + vm.writeJson(vm.toString(L1_ERC1155_GATEWAY_PROXY_ADDR), CHAIN_MONITOR_CONFIG_PATH, ".l1_config.l1_contracts.l1_gateways.erc1155_gateway"); + vm.writeJson(vm.toString(L1_GAS_TOKEN_GATEWAY_PROXY_ADDR), CHAIN_MONITOR_CONFIG_PATH, ".l1_config.l1_contracts.l1_gateways.gas_token_gateway"); + vm.writeJson(vm.toString(L1_SCROLL_MESSENGER_PROXY_ADDR), CHAIN_MONITOR_CONFIG_PATH, ".l1_config.l1_contracts.scroll_messenger"); + vm.writeJson(vm.toString(L1_MESSAGE_QUEUE_PROXY_ADDR), CHAIN_MONITOR_CONFIG_PATH, ".l1_config.l1_contracts.message_queue"); + vm.writeJson(vm.toString(L1_SCROLL_CHAIN_PROXY_ADDR), CHAIN_MONITOR_CONFIG_PATH, ".l1_config.l1_contracts.scroll_chain"); + vm.writeJson(vm.toString(L1_GAS_TOKEN_ADDR), CHAIN_MONITOR_CONFIG_PATH, ".l1_config.l1_contracts.gas_token"); + vm.writeJson(vm.toString(L2_DEPLOYER_INITIAL_BALANCE), CHAIN_MONITOR_CONFIG_PATH, ".l1_config.start_messenger_balance"); + + // L2 + vm.writeJson(L2_RPC_ENDPOINT, CHAIN_MONITOR_CONFIG_PATH, ".l2_config.l2_url"); + vm.writeJson(vm.toString(L2_ETH_GATEWAY_PROXY_ADDR), CHAIN_MONITOR_CONFIG_PATH, ".l2_config.l2_contracts.l2_gateways.eth_gateway"); + vm.writeJson(vm.toString(L2_WETH_GATEWAY_PROXY_ADDR), CHAIN_MONITOR_CONFIG_PATH, ".l2_config.l2_contracts.l2_gateways.weth_gateway"); + vm.writeJson(vm.toString(L2_STANDARD_ERC20_GATEWAY_PROXY_ADDR), CHAIN_MONITOR_CONFIG_PATH, ".l2_config.l2_contracts.l2_gateways.standard_erc20_gateway"); + vm.writeJson(vm.toString(L2_CUSTOM_ERC20_GATEWAY_PROXY_ADDR), CHAIN_MONITOR_CONFIG_PATH, ".l2_config.l2_contracts.l2_gateways.custom_erc20_gateway"); + vm.writeJson(vm.toString(L2_ERC721_GATEWAY_PROXY_ADDR), CHAIN_MONITOR_CONFIG_PATH, ".l2_config.l2_contracts.l2_gateways.erc721_gateway"); + vm.writeJson(vm.toString(L2_ERC1155_GATEWAY_PROXY_ADDR), CHAIN_MONITOR_CONFIG_PATH, ".l2_config.l2_contracts.l2_gateways.erc1155_gateway"); + vm.writeJson(vm.toString(L2_SCROLL_MESSENGER_PROXY_ADDR), CHAIN_MONITOR_CONFIG_PATH, ".l2_config.l2_contracts.scroll_messenger"); + vm.writeJson(vm.toString(L2_MESSAGE_QUEUE_ADDR), CHAIN_MONITOR_CONFIG_PATH, ".l2_config.l2_contracts.message_queue"); + } +} + +contract GenerateBridgeHistoryConfig is DeployScroll { + /*************** + * Entry point * + ***************/ + + function run() public { + DeterministicDeployment.initialize(ScriptMode.VerifyConfig); + predictAllContracts(); + + generateBridgeHistoryConfig(); + } + + /********************* + * Private functions * + *********************/ + + // prettier-ignore + function generateBridgeHistoryConfig() private { + // initialize template file + if (vm.exists(BRIDGE_HISTORY_CONFIG_PATH)) { + vm.removeFile(BRIDGE_HISTORY_CONFIG_PATH); + } + + string memory template = vm.readFile(BRIDGE_HISTORY_CONFIG_TEMPLATE_PATH); + vm.writeFile(BRIDGE_HISTORY_CONFIG_PATH, template); + + // L1 contracts + vm.writeJson(vm.toString(L1_MESSAGE_QUEUE_PROXY_ADDR), BRIDGE_HISTORY_CONFIG_PATH, ".L1.MessageQueueAddr"); + vm.writeJson(vm.toString(L1_SCROLL_MESSENGER_PROXY_ADDR), BRIDGE_HISTORY_CONFIG_PATH, ".L1.MessengerAddr"); + vm.writeJson(vm.toString(L1_SCROLL_CHAIN_PROXY_ADDR), BRIDGE_HISTORY_CONFIG_PATH, ".L1.ScrollChainAddr"); + vm.writeJson(vm.toString(L1_GATEWAY_ROUTER_PROXY_ADDR), BRIDGE_HISTORY_CONFIG_PATH, ".L1.GatewayRouterAddr"); + vm.writeJson(vm.toString(L1_ETH_GATEWAY_PROXY_ADDR), BRIDGE_HISTORY_CONFIG_PATH, ".L1.ETHGatewayAddr"); + vm.writeJson(vm.toString(L1_WETH_GATEWAY_PROXY_ADDR), BRIDGE_HISTORY_CONFIG_PATH, ".L1.WETHGatewayAddr"); + vm.writeJson(vm.toString(L1_STANDARD_ERC20_GATEWAY_PROXY_ADDR), BRIDGE_HISTORY_CONFIG_PATH, ".L1.StandardERC20GatewayAddr"); + vm.writeJson(vm.toString(L1_CUSTOM_ERC20_GATEWAY_PROXY_ADDR), BRIDGE_HISTORY_CONFIG_PATH, ".L1.CustomERC20GatewayAddr"); + vm.writeJson(vm.toString(L1_ERC721_GATEWAY_PROXY_ADDR), BRIDGE_HISTORY_CONFIG_PATH, ".L1.ERC721GatewayAddr"); + vm.writeJson(vm.toString(L1_ERC1155_GATEWAY_PROXY_ADDR), BRIDGE_HISTORY_CONFIG_PATH, ".L1.ERC1155GatewayAddr"); + vm.writeJson(vm.toString(L1_GAS_TOKEN_GATEWAY_PROXY_ADDR), BRIDGE_HISTORY_CONFIG_PATH, ".L1.GasTokenGatewayAddr"); + vm.writeJson(vm.toString(L1_WRAPPED_TOKEN_GATEWAY_ADDR), BRIDGE_HISTORY_CONFIG_PATH, ".L1.WrappedTokenGatewayAddr"); + + // L2 contracts + vm.writeJson(vm.toString(L2_MESSAGE_QUEUE_ADDR), BRIDGE_HISTORY_CONFIG_PATH, ".L2.MessageQueueAddr"); + vm.writeJson(vm.toString(L2_SCROLL_MESSENGER_PROXY_ADDR), BRIDGE_HISTORY_CONFIG_PATH, ".L2.MessengerAddr"); + vm.writeJson(vm.toString(L2_GATEWAY_ROUTER_PROXY_ADDR), BRIDGE_HISTORY_CONFIG_PATH, ".L2.GatewayRouterAddr"); + vm.writeJson(vm.toString(L2_ETH_GATEWAY_PROXY_ADDR), BRIDGE_HISTORY_CONFIG_PATH, ".L2.ETHGatewayAddr"); + vm.writeJson(vm.toString(L2_WETH_GATEWAY_PROXY_ADDR), BRIDGE_HISTORY_CONFIG_PATH, ".L2.WETHGatewayAddr"); + vm.writeJson(vm.toString(L2_STANDARD_ERC20_GATEWAY_PROXY_ADDR), BRIDGE_HISTORY_CONFIG_PATH, ".L2.StandardERC20GatewayAddr"); + vm.writeJson(vm.toString(L2_CUSTOM_ERC20_GATEWAY_PROXY_ADDR), BRIDGE_HISTORY_CONFIG_PATH, ".L2.CustomERC20GatewayAddr"); + vm.writeJson(vm.toString(L2_ERC721_GATEWAY_PROXY_ADDR), BRIDGE_HISTORY_CONFIG_PATH, ".L2.ERC721GatewayAddr"); + vm.writeJson(vm.toString(L2_ERC1155_GATEWAY_PROXY_ADDR), BRIDGE_HISTORY_CONFIG_PATH, ".L2.ERC1155GatewayAddr"); + + // endpoints + vm.writeJson(L1_RPC_ENDPOINT, BRIDGE_HISTORY_CONFIG_PATH, ".L1.endpoint"); + vm.writeJson(L2_RPC_ENDPOINT, BRIDGE_HISTORY_CONFIG_PATH, ".L2.endpoint"); + + // others + vm.writeJson(vm.toString(L1_CONTRACT_DEPLOYMENT_BLOCK), BRIDGE_HISTORY_CONFIG_PATH, ".L1.startHeight"); + } +} + +contract GenerateBalanceCheckerConfig is DeployScroll { + /*************** + * Entry point * + ***************/ + + function run() public { + DeterministicDeployment.initialize(ScriptMode.VerifyConfig); + predictAllContracts(); + + generateBalanceCheckerConfig(); + } + + /********************* + * Private functions * + *********************/ + + function generateBalanceCheckerConfig() private { + // initialize template file + if (vm.exists(BALANCE_CHECKER_CONFIG_PATH)) { + vm.removeFile(BALANCE_CHECKER_CONFIG_PATH); + } + + string memory template = vm.readFile(BALANCE_CHECKER_CONFIG_TEMPLATE_PATH); + vm.writeFile(BALANCE_CHECKER_CONFIG_PATH, template); + + vm.writeJson(L1_RPC_ENDPOINT, BALANCE_CHECKER_CONFIG_PATH, ".addresses[0].rpc_url"); + vm.writeJson(vm.toString(L1_COMMIT_SENDER_ADDR), BALANCE_CHECKER_CONFIG_PATH, ".addresses[0].address"); + + vm.writeJson(L1_RPC_ENDPOINT, BALANCE_CHECKER_CONFIG_PATH, ".addresses[1].rpc_url"); + vm.writeJson(vm.toString(L1_FINALIZE_SENDER_ADDR), BALANCE_CHECKER_CONFIG_PATH, ".addresses[1].address"); + + vm.writeJson(L1_RPC_ENDPOINT, BALANCE_CHECKER_CONFIG_PATH, ".addresses[2].rpc_url"); + vm.writeJson(vm.toString(L1_GAS_ORACLE_SENDER_ADDR), BALANCE_CHECKER_CONFIG_PATH, ".addresses[2].address"); + + vm.writeJson(L1_RPC_ENDPOINT, BALANCE_CHECKER_CONFIG_PATH, ".addresses[3].rpc_url"); + vm.writeJson(vm.toString(L1_FEE_VAULT_ADDR), BALANCE_CHECKER_CONFIG_PATH, ".addresses[3].address"); + + vm.writeJson(L2_RPC_ENDPOINT, BALANCE_CHECKER_CONFIG_PATH, ".addresses[4].rpc_url"); + vm.writeJson(vm.toString(L2_GAS_ORACLE_SENDER_ADDR), BALANCE_CHECKER_CONFIG_PATH, ".addresses[4].address"); + + vm.writeJson(L2_RPC_ENDPOINT, BALANCE_CHECKER_CONFIG_PATH, ".addresses[5].rpc_url"); + vm.writeJson(vm.toString(L2_TX_FEE_VAULT_ADDR), BALANCE_CHECKER_CONFIG_PATH, ".addresses[5].address"); + } +} + +contract GenerateFrontendConfig is DeployScroll { + /*************** + * Entry point * + ***************/ + + function run() public { + DeterministicDeployment.initialize(ScriptMode.VerifyConfig); + predictAllContracts(); + + generateFrontendConfig(); + } + + /********************* + * Private functions * + *********************/ + + // prettier-ignore + function generateFrontendConfig() private { + // use writeFile to start a new file + vm.writeFile(FRONTEND_ENV_PATH, "REACT_APP_ETH_SYMBOL = \"ETH\"\n"); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_BASE_CHAIN = \"", CHAIN_NAME_L1, "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_ROLLUP = \"", CHAIN_NAME_L2, "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_CHAIN_ID_L1 = \"", vm.toString(CHAIN_ID_L1), "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_CHAIN_ID_L2 = \"", vm.toString(CHAIN_ID_L2), "\"")); + vm.writeLine(FRONTEND_ENV_PATH, "REACT_APP_CONNECT_WALLET_PROJECT_ID = \"14efbaafcf5232a47d93a68229b71028\""); + + // API endpoints + vm.writeLine(FRONTEND_ENV_PATH, ""); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_EXTERNAL_RPC_URI_L1 = \"", EXTERNAL_RPC_URI_L1, "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_EXTERNAL_RPC_URI_L2 = \"", EXTERNAL_RPC_URI_L2, "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_BRIDGE_API_URI = \"", BRIDGE_API_URI, "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_ROLLUPSCAN_API_URI = \"", ROLLUPSCAN_API_URI, "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_EXTERNAL_EXPLORER_URI_L1 = \"", EXTERNAL_EXPLORER_URI_L1, "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_EXTERNAL_EXPLORER_URI_L2 = \"", EXTERNAL_EXPLORER_URI_L2, "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("ADMIN_SYSTEM_DASHBOARD_URI = \"", ADMIN_SYSTEM_DASHBOARD_URI, "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("GRAFANA_URI = \"", GRAFANA_URI, "\"")); + + // L1 contracts + vm.writeLine(FRONTEND_ENV_PATH, ""); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_L1_CUSTOM_ERC20_GATEWAY_PROXY_ADDR = \"", vm.toString(L1_CUSTOM_ERC20_GATEWAY_PROXY_ADDR), "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_L1_ETH_GATEWAY_PROXY_ADDR = \"", vm.toString(L1_ETH_GATEWAY_PROXY_ADDR), "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_L1_GAS_PRICE_ORACLE = \"", vm.toString(L1_GAS_PRICE_ORACLE_ADDR), "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_L1_GATEWAY_ROUTER_PROXY_ADDR = \"", vm.toString(L1_GATEWAY_ROUTER_PROXY_ADDR), "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_L1_MESSAGE_QUEUE = \"", vm.toString(L1_MESSAGE_QUEUE_PROXY_ADDR), "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_L1_MESSAGE_QUEUE_WITH_GAS_PRICE_ORACLE = \"", vm.toString(L1_MESSAGE_QUEUE_PROXY_ADDR), "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_L1_SCROLL_MESSENGER = \"", vm.toString(L1_SCROLL_MESSENGER_PROXY_ADDR), "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_L1_STANDARD_ERC20_GATEWAY_PROXY_ADDR = \"", vm.toString(L1_STANDARD_ERC20_GATEWAY_PROXY_ADDR), "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_L1_WETH_GATEWAY_PROXY_ADDR = \"", vm.toString(L1_WETH_GATEWAY_PROXY_ADDR), "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_SCROLL_CHAIN = \"", vm.toString(L1_SCROLL_CHAIN_PROXY_ADDR), "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_L1_GAS_TOKEN_GATEWAY = \"", vm.toString(L1_GAS_TOKEN_GATEWAY_PROXY_ADDR), "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_L1_WRAPPED_TOKEN_GATEWAY = \"", vm.toString(L1_WRAPPED_TOKEN_GATEWAY_ADDR), "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_L1_GAS_TOKEN_ADDR = \"", vm.toString(L1_GAS_TOKEN_ADDR), "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_L1_WETH_ADDR = \"", vm.toString(L1_WETH_ADDR), "\"")); + + // L2 contracts + vm.writeLine(FRONTEND_ENV_PATH, ""); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_L2_CUSTOM_ERC20_GATEWAY_PROXY_ADDR = \"", vm.toString(L2_CUSTOM_ERC20_GATEWAY_PROXY_ADDR), "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_L2_ETH_GATEWAY_PROXY_ADDR = \"", vm.toString(L2_ETH_GATEWAY_PROXY_ADDR), "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_L2_GATEWAY_ROUTER_PROXY_ADDR = \"", vm.toString(L2_GATEWAY_ROUTER_PROXY_ADDR), "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_L2_SCROLL_MESSENGER = \"", vm.toString(L2_SCROLL_MESSENGER_PROXY_ADDR), "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_L2_STANDARD_ERC20_GATEWAY_PROXY_ADDR = \"", vm.toString(L2_STANDARD_ERC20_GATEWAY_PROXY_ADDR), "\"")); + vm.writeLine(FRONTEND_ENV_PATH, string.concat("REACT_APP_L2_WETH_GATEWAY_PROXY_ADDR = \"", vm.toString(L2_WETH_GATEWAY_PROXY_ADDR), "\"")); + + // custom token gateways (currently not set) + vm.writeLine(FRONTEND_ENV_PATH, ""); + vm.writeLine(FRONTEND_ENV_PATH, "REACT_APP_L1_USDC_GATEWAY_PROXY_ADDR = \"\""); + vm.writeLine(FRONTEND_ENV_PATH, "REACT_APP_L2_USDC_GATEWAY_PROXY_ADDR = \"\""); + vm.writeLine(FRONTEND_ENV_PATH, "REACT_APP_L1_DAI_GATEWAY_PROXY_ADDR = \"\""); + vm.writeLine(FRONTEND_ENV_PATH, "REACT_APP_L2_DAI_GATEWAY_PROXY_ADDR = \"\""); + vm.writeLine(FRONTEND_ENV_PATH, "REACT_APP_L1_LIDO_GATEWAY_PROXY_ADDR = \"\""); + vm.writeLine(FRONTEND_ENV_PATH, "REACT_APP_L2_LIDO_GATEWAY_PROXY_ADDR = \"\""); + vm.writeLine(FRONTEND_ENV_PATH, "REACT_APP_L1_PUFFER_GATEWAY_PROXY_ADDR = \"\""); + vm.writeLine(FRONTEND_ENV_PATH, "REACT_APP_L2_PUFFER_GATEWAY_PROXY_ADDR = \"\""); + + // misc + vm.writeLine(FRONTEND_ENV_PATH, ""); + vm.writeLine(FRONTEND_ENV_PATH, "REACT_APP_SCROLL_ORIGINS_NFT = \"\""); + vm.writeLine(FRONTEND_ENV_PATH, "REACT_APP_SCROLL_ORIGINS_NFT_V2 = \"\""); + vm.writeLine(FRONTEND_ENV_PATH, "REACT_APP_L1_BATCH_BRIDGE_GATEWAY_PROXY_ADDR = \"\""); + } +} + +contract GenerateRollupExplorerBackendConfig is DeployScroll { + /*************** + * Entry point * + ***************/ + + function run() public { + DeterministicDeployment.initialize(ScriptMode.VerifyConfig); + predictAllContracts(); + + generateRollupExplorerBackendConfig(); + } + + /********************* + * Private functions * + *********************/ + + // prettier-ignore + function generateRollupExplorerBackendConfig() private { + // initialize template file + if (vm.exists(ROLLUP_EXPLORER_BACKEND_CONFIG_PATH)) { + vm.removeFile(ROLLUP_EXPLORER_BACKEND_CONFIG_PATH); + } + + string memory template = vm.readFile(ROLLUP_EXPLORER_BACKEND_CONFIG_TEMPLATE_PATH); + vm.writeFile(ROLLUP_EXPLORER_BACKEND_CONFIG_PATH, template); + + vm.writeJson(ROLLUP_EXPLORER_BACKEND_DB_CONNECTION_STRING, ROLLUP_EXPLORER_BACKEND_CONFIG_PATH, ".db_url"); + } +} + +contract GenerateAdminSystemBackendConfig is DeployScroll { + /*************** + * Entry point * + ***************/ + + function run() public { + DeterministicDeployment.initialize(ScriptMode.VerifyConfig); + predictAllContracts(); + + generateAdminSystemBackendConfig(); + } + + /********************* + * Private functions * + *********************/ + + // prettier-ignore + function generateAdminSystemBackendConfig() private { + // initialize template file + if (vm.exists(ADMIN_SYSTEM_BACKEND_CONFIG_PATH)) { + vm.removeFile(ADMIN_SYSTEM_BACKEND_CONFIG_PATH); + } + + string memory template = vm.readFile(ADMIN_SYSTEM_BACKEND_CONFIG_TEMPLATE_PATH); + vm.writeFile(ADMIN_SYSTEM_BACKEND_CONFIG_PATH, template); + } +} diff --git a/scripts/deterministic/GenerateGenesis.s.sol b/scripts/deterministic/GenerateGenesis.s.sol new file mode 100644 index 0000000..4fa0917 --- /dev/null +++ b/scripts/deterministic/GenerateGenesis.s.sol @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.24; + +import {L1GasPriceOracle} from "../../src/L2/predeploys/L1GasPriceOracle.sol"; +import {L2MessageQueue} from "../../src/L2/predeploys/L2MessageQueue.sol"; +import {L2TxFeeVault} from "../../src/L2/predeploys/L2TxFeeVault.sol"; +import {L2TxFeeVaultWithGasToken} from "../../src/alternative-gas-token/L2TxFeeVaultWithGasToken.sol"; +import {Whitelist} from "../../src/L2/predeploys/Whitelist.sol"; +import {WrappedEther} from "../../src/L2/predeploys/WrappedEther.sol"; + +import {DETERMINISTIC_DEPLOYMENT_PROXY_ADDR, FEE_VAULT_MIN_WITHDRAW_AMOUNT, GENESIS_ALLOC_JSON_PATH, GENESIS_JSON_PATH, GENESIS_JSON_TEMPLATE_PATH} from "./Constants.sol"; +import {DeployScroll} from "./DeployScroll.s.sol"; +import {DeterministicDeployment} from "./DeterministicDeployment.sol"; + +contract GenerateGenesis is DeployScroll { + /*************** + * Entry point * + ***************/ + + function run() public { + DeterministicDeployment.initialize(ScriptMode.VerifyConfig); + predictAllContracts(); + + generateGenesisAlloc(); + generateGenesisJson(); + + // clean up temporary files + vm.removeFile(GENESIS_ALLOC_JSON_PATH); + } + + /********************* + * Private functions * + *********************/ + + function generateGenesisAlloc() private { + if (vm.exists(GENESIS_ALLOC_JSON_PATH)) { + vm.removeFile(GENESIS_ALLOC_JSON_PATH); + } + + // Scroll predeploys + setL2MessageQueue(); + setL1GasPriceOracle(); + setL2Whitelist(); + setL2Weth(); + setL2FeeVault(); + + // other predeploys + setDeterministicDeploymentProxy(); + + // reset sender + vm.resetNonce(msg.sender); + + // prefunded accounts + setL2ScrollMessenger(); + setL2Deployer(); + + // write to file + vm.dumpState(GENESIS_ALLOC_JSON_PATH); + sortJsonByKeys(GENESIS_ALLOC_JSON_PATH); + } + + function setL2MessageQueue() internal { + address predeployAddr = tryGetOverride("L2_MESSAGE_QUEUE"); + + if (predeployAddr == address(0)) { + return; + } + + // set code + L2MessageQueue _queue = new L2MessageQueue(DEPLOYER_ADDR); + vm.etch(predeployAddr, address(_queue).code); + + // set storage + bytes32 _ownerSlot = hex"0000000000000000000000000000000000000000000000000000000000000052"; + vm.store(predeployAddr, _ownerSlot, vm.load(address(_queue), _ownerSlot)); + + // reset so its not included state dump + vm.etch(address(_queue), ""); + vm.resetNonce(address(_queue)); + } + + function setL1GasPriceOracle() internal { + address predeployAddr = tryGetOverride("L1_GAS_PRICE_ORACLE"); + + if (predeployAddr == address(0)) { + return; + } + + // set code + L1GasPriceOracle _oracle = new L1GasPriceOracle(DEPLOYER_ADDR, true); + vm.etch(predeployAddr, address(_oracle).code); + + // set storage + bytes32 _ownerSlot = hex"0000000000000000000000000000000000000000000000000000000000000000"; + vm.store(predeployAddr, _ownerSlot, vm.load(address(_oracle), _ownerSlot)); + + bytes32 _isCurieSlot = hex"0000000000000000000000000000000000000000000000000000000000000008"; + vm.store(predeployAddr, _isCurieSlot, bytes32(uint256(1))); + + // reset so its not included state dump + vm.etch(address(_oracle), ""); + vm.resetNonce(address(_oracle)); + } + + function setL2Whitelist() internal { + address predeployAddr = tryGetOverride("L2_WHITELIST"); + + if (predeployAddr == address(0)) { + return; + } + + // set code + Whitelist _whitelist = new Whitelist(DEPLOYER_ADDR); + vm.etch(predeployAddr, address(_whitelist).code); + + // set storage + bytes32 _ownerSlot = hex"0000000000000000000000000000000000000000000000000000000000000000"; + vm.store(predeployAddr, _ownerSlot, vm.load(address(_whitelist), _ownerSlot)); + + // reset so its not included state dump + vm.etch(address(_whitelist), ""); + vm.resetNonce(address(_whitelist)); + } + + function setL2Weth() internal { + address predeployAddr = tryGetOverride("L2_WETH"); + + if (predeployAddr == address(0)) { + return; + } + + // set code + WrappedEther _weth = new WrappedEther(); + vm.etch(predeployAddr, address(_weth).code); + + // set storage + bytes32 _nameSlot = hex"0000000000000000000000000000000000000000000000000000000000000003"; + vm.store(predeployAddr, _nameSlot, vm.load(address(_weth), _nameSlot)); + + bytes32 _symbolSlot = hex"0000000000000000000000000000000000000000000000000000000000000004"; + vm.store(predeployAddr, _symbolSlot, vm.load(address(_weth), _symbolSlot)); + + // reset so its not included state dump + vm.etch(address(_weth), ""); + vm.resetNonce(address(_weth)); + } + + function setL2FeeVault() internal { + address predeployAddr = tryGetOverride("L2_TX_FEE_VAULT"); + + if (predeployAddr == address(0)) { + return; + } + + // set code + address _vaultAddr; + vm.prank(DEPLOYER_ADDR); + if (!ALTERNATIVE_GAS_TOKEN_ENABLED) { + L2TxFeeVault _vault = new L2TxFeeVault(DEPLOYER_ADDR, L1_FEE_VAULT_ADDR, FEE_VAULT_MIN_WITHDRAW_AMOUNT); + vm.prank(DEPLOYER_ADDR); + _vault.updateMessenger(L2_SCROLL_MESSENGER_PROXY_ADDR); + _vaultAddr = address(_vault); + } else { + L2TxFeeVaultWithGasToken _vault = new L2TxFeeVaultWithGasToken( + L2_ETH_GATEWAY_PROXY_ADDR, + DEPLOYER_ADDR, + L1_FEE_VAULT_ADDR, + FEE_VAULT_MIN_WITHDRAW_AMOUNT + ); + _vaultAddr = address(_vault); + } + + vm.etch(predeployAddr, _vaultAddr.code); + + // set storage + bytes32 _ownerSlot = hex"0000000000000000000000000000000000000000000000000000000000000000"; + vm.store(predeployAddr, _ownerSlot, vm.load(_vaultAddr, _ownerSlot)); + + bytes32 _minWithdrawAmountSlot = hex"0000000000000000000000000000000000000000000000000000000000000001"; + vm.store(predeployAddr, _minWithdrawAmountSlot, vm.load(_vaultAddr, _minWithdrawAmountSlot)); + + bytes32 _messengerSlot = hex"0000000000000000000000000000000000000000000000000000000000000002"; + vm.store(predeployAddr, _messengerSlot, vm.load(_vaultAddr, _messengerSlot)); + + bytes32 _recipientSlot = hex"0000000000000000000000000000000000000000000000000000000000000003"; + vm.store(predeployAddr, _recipientSlot, vm.load(_vaultAddr, _recipientSlot)); + + bytes32 _ETHGatewaySlot = hex"0000000000000000000000000000000000000000000000000000000000000005"; + vm.store(predeployAddr, _ETHGatewaySlot, vm.load(_vaultAddr, _ETHGatewaySlot)); + + // reset so its not included state dump + vm.etch(_vaultAddr, ""); + vm.resetNonce(_vaultAddr); + } + + function setDeterministicDeploymentProxy() internal { + bytes + memory code = hex"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"; + vm.etch(DETERMINISTIC_DEPLOYMENT_PROXY_ADDR, code); + } + + function setL2ScrollMessenger() internal { + vm.deal(L2_SCROLL_MESSENGER_PROXY_ADDR, L2_SCROLL_MESSENGER_INITIAL_BALANCE); + } + + function setL2Deployer() internal { + vm.deal(DEPLOYER_ADDR, L2_DEPLOYER_INITIAL_BALANCE); + } + + function generateGenesisJson() private { + // initialize template file + if (vm.exists(GENESIS_JSON_PATH)) { + vm.removeFile(GENESIS_JSON_PATH); + } + + string memory template = vm.readFile(GENESIS_JSON_TEMPLATE_PATH); + vm.writeFile(GENESIS_JSON_PATH, template); + + // general config + vm.writeJson(vm.toString(CHAIN_ID_L2), GENESIS_JSON_PATH, ".config.chainId"); + + uint256 timestamp = vm.unixTime() / 1000; + vm.writeJson(vm.toString(bytes32(timestamp)), GENESIS_JSON_PATH, ".timestamp"); + + string memory extraData = string( + abi.encodePacked( + "0x0000000000000000000000000000000000000000000000000000000000000000", + vm.replace(vm.toString(L2GETH_SIGNER_ADDRESS), "0x", ""), + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ) + ); + + vm.writeJson(extraData, GENESIS_JSON_PATH, ".extraData"); + + // scroll-specific config + vm.writeJson(vm.toString(MAX_TX_IN_CHUNK), GENESIS_JSON_PATH, ".config.scroll.maxTxPerBlock"); + vm.writeJson(vm.toString(L2_TX_FEE_VAULT_ADDR), GENESIS_JSON_PATH, ".config.scroll.feeVaultAddress"); + + // serialize explicitly as string, otherwise foundry will serialize it as number + string memory l1ChainId = string(abi.encodePacked('"', vm.toString(CHAIN_ID_L1), '"')); + vm.writeJson(l1ChainId, GENESIS_JSON_PATH, ".config.scroll.l1Config.l1ChainId"); + + vm.writeJson( + vm.toString(L1_MESSAGE_QUEUE_PROXY_ADDR), + GENESIS_JSON_PATH, + ".config.scroll.l1Config.l1MessageQueueAddress" + ); + + vm.writeJson( + vm.toString(L1_SCROLL_CHAIN_PROXY_ADDR), + GENESIS_JSON_PATH, + ".config.scroll.l1Config.scrollChainAddress" + ); + + // predeploys and prefunded accounts + string memory alloc = vm.readFile(GENESIS_ALLOC_JSON_PATH); + vm.writeJson(alloc, GENESIS_JSON_PATH, ".alloc"); + } + + /// @notice Sorts the allocs by address + // source: https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/scripts/L2Genesis.s.sol + function sortJsonByKeys(string memory _path) private { + string[] memory commands = new string[](3); + commands[0] = "/bin/bash"; + commands[1] = "-c"; + commands[2] = string.concat("cat <<< $(jq -S '.' ", _path, ") > ", _path); + vm.ffi(commands); + } +} diff --git a/scripts/deterministic/contracts/MultipleVersionRollupVerifierSetOwner.sol b/scripts/deterministic/contracts/MultipleVersionRollupVerifierSetOwner.sol new file mode 100644 index 0000000..21eecac --- /dev/null +++ b/scripts/deterministic/contracts/MultipleVersionRollupVerifierSetOwner.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.24; + +import {MultipleVersionRollupVerifier} from "../../../src/L1/rollup/MultipleVersionRollupVerifier.sol"; + +contract MultipleVersionRollupVerifierSetOwner is MultipleVersionRollupVerifier { + /// @dev allow setting the owner in the constructor, otherwise + /// DeterministicDeploymentProxy would become the owner. + constructor( + address owner, + uint256[] memory _versions, + address[] memory _verifiers + ) MultipleVersionRollupVerifier(_versions, _verifiers) { + _transferOwnership(owner); + } +} diff --git a/scripts/deterministic/contracts/ProxyAdminSetOwner.sol b/scripts/deterministic/contracts/ProxyAdminSetOwner.sol new file mode 100644 index 0000000..a328334 --- /dev/null +++ b/scripts/deterministic/contracts/ProxyAdminSetOwner.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.24; + +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; + +contract ProxyAdminSetOwner is ProxyAdmin { + /// @dev allow setting the owner in the constructor, otherwise + /// DeterministicDeploymentProxy would become the owner. + constructor(address owner) { + _transferOwnership(owner); + } +} diff --git a/scripts/deterministic/contracts/ScrollStandardERC20FactorySetOwner.sol b/scripts/deterministic/contracts/ScrollStandardERC20FactorySetOwner.sol new file mode 100644 index 0000000..7b18d8c --- /dev/null +++ b/scripts/deterministic/contracts/ScrollStandardERC20FactorySetOwner.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.24; + +import {ScrollStandardERC20Factory} from "../../../src/libraries/token/ScrollStandardERC20Factory.sol"; + +contract ScrollStandardERC20FactorySetOwner is ScrollStandardERC20Factory { + /// @dev allow setting the owner in the constructor, otherwise + /// DeterministicDeploymentProxy would become the owner. + constructor(address owner, address _implementation) ScrollStandardERC20Factory(_implementation) { + _transferOwnership(owner); + } +} diff --git a/scripts/foundry/DeployL2BridgeContracts.s.sol b/scripts/foundry/DeployL2BridgeContracts.s.sol index 5ea9077..eba7980 100644 --- a/scripts/foundry/DeployL2BridgeContracts.s.sol +++ b/scripts/foundry/DeployL2BridgeContracts.s.sol @@ -92,7 +92,7 @@ contract DeployL2BridgeContracts is Script { } address owner = vm.addr(L2_DEPLOYER_PRIVATE_KEY); - oracle = new L1GasPriceOracle(owner); + oracle = new L1GasPriceOracle(owner, true); logAddress("L1_GAS_PRICE_ORACLE_ADDR", address(oracle)); } diff --git a/src/L1/L1ScrollMessenger.sol b/src/L1/L1ScrollMessenger.sol index c70719f..47824ca 100644 --- a/src/L1/L1ScrollMessenger.sol +++ b/src/L1/L1ScrollMessenger.sol @@ -125,7 +125,7 @@ contract L1ScrollMessenger is ScrollMessengerBase, IL1ScrollMessenger { address _feeVault, address _rollup, address _messageQueue - ) public initializer { + ) external initializer { ScrollMessengerBase.__ScrollMessengerBase_init(_counterpart, _feeVault); __rollup = _rollup; @@ -169,36 +169,7 @@ contract L1ScrollMessenger is ScrollMessengerBase, IL1ScrollMessenger { bytes memory _message, L2MessageProof memory _proof ) external override whenNotPaused notInExecution { - bytes32 _xDomainCalldataHash = keccak256(_encodeXDomainCalldata(_from, _to, _value, _nonce, _message)); - require(!isL2MessageExecuted[_xDomainCalldataHash], "Message was already successfully executed"); - - { - require(IScrollChain(rollup).isBatchFinalized(_proof.batchIndex), "Batch is not finalized"); - bytes32 _messageRoot = IScrollChain(rollup).withdrawRoots(_proof.batchIndex); - require( - WithdrawTrieVerifier.verifyMerkleProof(_messageRoot, _xDomainCalldataHash, _nonce, _proof.merkleProof), - "Invalid proof" - ); - } - - // @note check more `_to` address to avoid attack in the future when we add more gateways. - require(_to != messageQueue, "Forbid to call message queue"); - _validateTargetAddress(_to); - - // @note This usually will never happen, just in case. - require(_from != xDomainMessageSender, "Invalid message sender"); - - xDomainMessageSender = _from; - (bool success, ) = _to.call{value: _value}(_message); - // reset value to refund gas. - xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER; - - if (success) { - isL2MessageExecuted[_xDomainCalldataHash] = true; - emit RelayedMessage(_xDomainCalldataHash); - } else { - emit FailedRelayedMessage(_xDomainCalldataHash); - } + _relayMessageWithProof(_from, _to, _value, _nonce, _message, _proof); } /// @inheritdoc IL1ScrollMessenger @@ -273,48 +244,7 @@ contract L1ScrollMessenger is ScrollMessengerBase, IL1ScrollMessenger { uint256 _messageNonce, bytes memory _message ) external override whenNotPaused notInExecution { - // The criteria for dropping a message: - // 1. The message is a L1 message. - // 2. The message has not been dropped before. - // 3. the message and all of its replacement are finalized in L1. - // 4. the message and all of its replacement are skipped. - // - // Possible denial of service attack: - // + replayMessage is called every time someone want to drop the message. - // + replayMessage is called so many times for a skipped message, thus results a long list. - // - // We limit the number of `replayMessage` calls of each message, which may solve the above problem. - - // check message exists - bytes memory _xDomainCalldata = _encodeXDomainCalldata(_from, _to, _value, _messageNonce, _message); - bytes32 _xDomainCalldataHash = keccak256(_xDomainCalldata); - require(messageSendTimestamp[_xDomainCalldataHash] > 0, "Provided message has not been enqueued"); - - // check message not dropped - require(!isL1MessageDropped[_xDomainCalldataHash], "Message already dropped"); - - // check message is finalized - uint256 _lastIndex = replayStates[_xDomainCalldataHash].lastIndex; - if (_lastIndex == 0) _lastIndex = _messageNonce; - - // check message is skipped and drop it. - // @note If the list is very long, the message may never be dropped. - while (true) { - IL1MessageQueue(messageQueue).dropCrossDomainMessage(_lastIndex); - _lastIndex = prevReplayIndex[_lastIndex]; - if (_lastIndex == 0) break; - unchecked { - _lastIndex = _lastIndex - 1; - } - } - - isL1MessageDropped[_xDomainCalldataHash] = true; - - // set execution context - xDomainMessageSender = ScrollConstants.DROP_XDOMAIN_MESSAGE_SENDER; - IMessageDropCallback(_from).onDropMessage{value: _value}(_message); - // clear execution context - xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER; + _dropMessage(_from, _to, _value, _messageNonce, _message); } /************************ @@ -335,13 +265,14 @@ contract L1ScrollMessenger is ScrollMessengerBase, IL1ScrollMessenger { * Internal Functions * **********************/ + /// @dev Internal function to do `sendMessage` function call. function _sendMessage( address _to, uint256 _value, bytes memory _message, uint256 _gasLimit, address _refundAddress - ) internal nonReentrant { + ) internal virtual nonReentrant { // compute the actual cross domain message calldata. uint256 _messageNonce = IL1MessageQueue(messageQueue).nextCrossDomainMessageIndex(); bytes memory _xDomainCalldata = _encodeXDomainCalldata(_msgSender(), _to, _value, _messageNonce, _message); @@ -375,4 +306,97 @@ contract L1ScrollMessenger is ScrollMessengerBase, IL1ScrollMessenger { } } } + + /// @dev Internal function to do `relayMessageWithProof` function call. + function _relayMessageWithProof( + address _from, + address _to, + uint256 _value, + uint256 _nonce, + bytes memory _message, + L2MessageProof memory _proof + ) internal virtual { + bytes32 _xDomainCalldataHash = keccak256(_encodeXDomainCalldata(_from, _to, _value, _nonce, _message)); + require(!isL2MessageExecuted[_xDomainCalldataHash], "Message was already successfully executed"); + + { + require(IScrollChain(rollup).isBatchFinalized(_proof.batchIndex), "Batch is not finalized"); + bytes32 _messageRoot = IScrollChain(rollup).withdrawRoots(_proof.batchIndex); + require( + WithdrawTrieVerifier.verifyMerkleProof(_messageRoot, _xDomainCalldataHash, _nonce, _proof.merkleProof), + "Invalid proof" + ); + } + + // @note check more `_to` address to avoid attack in the future when we add more gateways. + require(_to != messageQueue, "Forbid to call message queue"); + _validateTargetAddress(_to); + + // @note This usually will never happen, just in case. + require(_from != xDomainMessageSender, "Invalid message sender"); + + xDomainMessageSender = _from; + (bool success, ) = _to.call{value: _value}(_message); + // reset value to refund gas. + xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER; + + if (success) { + isL2MessageExecuted[_xDomainCalldataHash] = true; + emit RelayedMessage(_xDomainCalldataHash); + } else { + emit FailedRelayedMessage(_xDomainCalldataHash); + } + } + + /// @dev Internal function to do `dropMessage` function call. + function _dropMessage( + address _from, + address _to, + uint256 _value, + uint256 _messageNonce, + bytes memory _message + ) internal virtual { + // The criteria for dropping a message: + // 1. The message is a L1 message. + // 2. The message has not been dropped before. + // 3. the message and all of its replacement are finalized in L1. + // 4. the message and all of its replacement are skipped. + // + // Possible denial of service attack: + // + replayMessage is called every time someone want to drop the message. + // + replayMessage is called so many times for a skipped message, thus results a long list. + // + // We limit the number of `replayMessage` calls of each message, which may solve the above problem. + + // check message exists + bytes memory _xDomainCalldata = _encodeXDomainCalldata(_from, _to, _value, _messageNonce, _message); + bytes32 _xDomainCalldataHash = keccak256(_xDomainCalldata); + require(messageSendTimestamp[_xDomainCalldataHash] > 0, "Provided message has not been enqueued"); + + // check message not dropped + require(!isL1MessageDropped[_xDomainCalldataHash], "Message already dropped"); + + // check message is finalized + uint256 _lastIndex = replayStates[_xDomainCalldataHash].lastIndex; + if (_lastIndex == 0) _lastIndex = _messageNonce; + + // check message is skipped and drop it. + // @note If the list is very long, the message may never be dropped. + while (true) { + IL1MessageQueue(messageQueue).dropCrossDomainMessage(_lastIndex); + _lastIndex = prevReplayIndex[_lastIndex]; + if (_lastIndex == 0) break; + unchecked { + _lastIndex = _lastIndex - 1; + } + } + + isL1MessageDropped[_xDomainCalldataHash] = true; + + // set execution context + xDomainMessageSender = ScrollConstants.DROP_XDOMAIN_MESSAGE_SENDER; + IMessageDropCallback(_from).onDropMessage{value: _value}(_message); + // clear execution context + xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER; + } } diff --git a/src/L1/gateways/L1ETHGateway.sol b/src/L1/gateways/L1ETHGateway.sol index c268a1b..a522f2b 100644 --- a/src/L1/gateways/L1ETHGateway.sol +++ b/src/L1/gateways/L1ETHGateway.sol @@ -14,7 +14,7 @@ import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol"; /// @title L1ETHGateway /// @notice The `L1ETHGateway` is used to deposit ETH on layer 1 and /// finalize withdraw ETH from layer 2. -/// @dev The deposited ETH tokens are held in this gateway. On finalizing withdraw, the corresponding +/// @dev The deposited ETH tokens are held in `L1ScrollMessenger`. On finalizing withdraw, the corresponding /// ETH will be transfer to the recipient directly. contract L1ETHGateway is ScrollGatewayBase, IL1ETHGateway, IMessageDropCallback { /*************** diff --git a/src/L2/predeploys/L1GasPriceOracle.sol b/src/L2/predeploys/L1GasPriceOracle.sol index 6d53532..4d19e5e 100644 --- a/src/L2/predeploys/L1GasPriceOracle.sol +++ b/src/L2/predeploys/L1GasPriceOracle.sol @@ -111,8 +111,10 @@ contract L1GasPriceOracle is OwnableBase, IL1GasPriceOracle { * Constructor * ***************/ - constructor(address _owner) { + constructor(address _owner, bool _isCurie) { _transferOwnership(_owner); + + isCurie = _isCurie; } /************************* diff --git a/src/L2/predeploys/L2TxFeeVault.sol b/src/L2/predeploys/L2TxFeeVault.sol index 20248eb..fb42480 100644 --- a/src/L2/predeploys/L2TxFeeVault.sol +++ b/src/L2/predeploys/L2TxFeeVault.sol @@ -119,13 +119,7 @@ contract L2TxFeeVault is OwnableBase { emit Withdrawal(_value, recipient, msg.sender); - // no fee provided - IL2ScrollMessenger(messenger).sendMessage{value: _value}( - recipient, - _value, - bytes(""), // no message (simple eth transfer) - 0 // _gasLimit can be zero for fee vault. - ); + sendWithdrawMessage(recipient, _value); } /// @notice Triggers a withdrawal of all available funds to the L1 fee wallet. @@ -164,4 +158,19 @@ contract L2TxFeeVault is OwnableBase { emit UpdateMinWithdrawAmount(_oldMinWithdrawAmount, _newMinWithdrawAmount); } + + /********************** + * Internal Functions * + **********************/ + + /// @dev Internal function to do `sendWithdrawMessage` function call. + function sendWithdrawMessage(address _recipient, uint256 _value) internal virtual { + // no fee provided + IL2ScrollMessenger(messenger).sendMessage{value: _value}( + _recipient, + _value, + bytes(""), // no message (simple eth transfer) + 0 // _gasLimit can be zero for fee vault. + ); + } } diff --git a/src/alternative-gas-token/GasTokenExample.sol b/src/alternative-gas-token/GasTokenExample.sol new file mode 100644 index 0000000..dff43af --- /dev/null +++ b/src/alternative-gas-token/GasTokenExample.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT + +pragma solidity =0.8.24; + +import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol"; + +contract GasTokenExample is ERC20 { + uint8 private decimals_; + + constructor( + string memory _name, + string memory _symbol, + uint8 _decimals, + address _recipient, + uint256 _amount + ) ERC20(_name, _symbol) { + decimals_ = _decimals; + _mint(_recipient, _amount); + } + + function decimals() public view virtual override returns (uint8) { + return decimals_; + } +} diff --git a/src/alternative-gas-token/L1GasTokenGateway.sol b/src/alternative-gas-token/L1GasTokenGateway.sol new file mode 100644 index 0000000..c571ee3 --- /dev/null +++ b/src/alternative-gas-token/L1GasTokenGateway.sol @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: MIT + +pragma solidity =0.8.24; + +import {IERC20MetadataUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; +import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; + +import {IL1ETHGateway} from "../L1/gateways/IL1ETHGateway.sol"; +import {IL1ScrollMessenger} from "../L1/IL1ScrollMessenger.sol"; +import {IL2ETHGateway} from "../L2/gateways/IL2ETHGateway.sol"; + +import {IMessageDropCallback} from "../libraries/callbacks/IMessageDropCallback.sol"; +import {ScrollGatewayBase} from "../libraries/gateway/ScrollGatewayBase.sol"; + +// solhint-disable avoid-low-level-calls + +/// @title L1GasTokenGateway +/// @notice The `L1GasTokenGateway` is used to deposit gas token on layer 1 and +/// finalize withdraw gas token from layer 2. +/// @dev The deposited gas tokens are held in this gateway. On finalizing withdraw, the corresponding +/// gas token will be transfer to the recipient directly. +contract L1GasTokenGateway is ScrollGatewayBase, IL1ETHGateway, IMessageDropCallback { + using SafeERC20Upgradeable for IERC20Upgradeable; + + /********** + * Errors * + **********/ + + /// @dev Thrown when `msg.value` is not zero. + error ErrorNonZeroMsgValue(); + + /// @dev Thrown when the selector is invalid during `onDropMessage`. + error ErrorInvalidSelector(); + + /// @dev Thrown when the deposit amount is zero. + error ErrorDepositZeroGasToken(); + + /************* + * Constants * + *************/ + + /// @dev The address of gas token. + address public immutable gasToken; + + /// @dev The scalar to scale the gas token decimals to 18. + uint256 public immutable scale; + + /*************** + * Constructor * + ***************/ + + /// @notice Constructor for `L1GasTokenGateway` implementation contract. + /// + /// @param _gasToken The address of gas token in L1. + /// @param _counterpart The address of `L2ETHGateway` contract in L2. + /// @param _router The address of `L1GatewayRouter` contract in L1. + /// @param _messenger The address of `L1ScrollMessenger` contract in L1. + constructor( + address _gasToken, + address _counterpart, + address _router, + address _messenger + ) ScrollGatewayBase(_counterpart, _router, _messenger) { + if (_gasToken == address(0) || _router == address(0)) revert ErrorZeroAddress(); + + _disableInitializers(); + + gasToken = _gasToken; + scale = 10**(18 - IERC20MetadataUpgradeable(_gasToken).decimals()); + } + + /// @notice Initialize the storage of L1GasTokenGateway. + function initialize() external initializer { + ScrollGatewayBase._initialize(address(0), address(0), address(0)); + } + + /***************************** + * Public Mutating Functions * + *****************************/ + + /// @inheritdoc IL1ETHGateway + function depositETH(uint256 _amount, uint256 _gasLimit) external payable override { + _deposit(_msgSender(), _amount, new bytes(0), _gasLimit); + } + + /// @inheritdoc IL1ETHGateway + function depositETH( + address _to, + uint256 _amount, + uint256 _gasLimit + ) external payable override { + _deposit(_to, _amount, new bytes(0), _gasLimit); + } + + /// @inheritdoc IL1ETHGateway + function depositETHAndCall( + address _to, + uint256 _amount, + bytes calldata _data, + uint256 _gasLimit + ) external payable override { + _deposit(_to, _amount, _data, _gasLimit); + } + + /// @inheritdoc IL1ETHGateway + function finalizeWithdrawETH( + address _from, + address _to, + uint256 _amount, + bytes calldata _data + ) external payable override onlyCallByCounterpart nonReentrant { + if (msg.value > 0) { + revert ErrorNonZeroMsgValue(); + } + + uint256 downScaledAmount = _amount / scale; + IERC20Upgradeable(gasToken).safeTransfer(_to, downScaledAmount); + _doCallback(_to, _data); + + emit FinalizeWithdrawETH(_from, _to, downScaledAmount, _data); + } + + /// @inheritdoc IMessageDropCallback + function onDropMessage(bytes calldata _message) external payable virtual onlyInDropContext nonReentrant { + // _message should start with 0x232e8748 => finalizeDepositETH(address,address,uint256,bytes) + if (bytes4(_message[0:4]) != IL2ETHGateway.finalizeDepositETH.selector) { + revert ErrorInvalidSelector(); + } + + // decode (receiver, amount) + (address _receiver, , uint256 _amount, ) = abi.decode(_message[4:], (address, address, uint256, bytes)); + uint256 downScaledAmount = _amount / scale; + + IERC20Upgradeable(gasToken).safeTransfer(_receiver, downScaledAmount); + + emit RefundETH(_receiver, downScaledAmount); + } + + /********************** + * Internal Functions * + **********************/ + + /// @dev The internal ETH deposit implementation. + /// @param _to The address of recipient's account on L2. + /// @param _amount The amount of ETH to be deposited. + /// @param _data Optional data to forward to recipient's account. + /// @param _gasLimit Gas limit required to complete the deposit on L2. + function _deposit( + address _to, + uint256 _amount, + bytes memory _data, + uint256 _gasLimit + ) internal virtual nonReentrant { + // 1. Extract real sender if this call is from L1GatewayRouter. + address _from = _msgSender(); + + if (router == _from) { + (_from, _data) = abi.decode(_data, (address, bytes)); + } + + // 2. transfer gas token from caller + uint256 _before = IERC20Upgradeable(gasToken).balanceOf(address(this)); + IERC20Upgradeable(gasToken).safeTransferFrom(_from, address(this), _amount); + uint256 _after = IERC20Upgradeable(gasToken).balanceOf(address(this)); + _amount = _after - _before; + if (_amount == 0) { + revert ErrorDepositZeroGasToken(); + } + + uint256 upScaledAmount = _amount * scale; + + // 3. Generate message passed to L1ScrollMessenger. + bytes memory _message = abi.encodeCall(IL2ETHGateway.finalizeDepositETH, (_from, _to, upScaledAmount, _data)); + + IL1ScrollMessenger(messenger).sendMessage{value: msg.value}( + counterpart, + upScaledAmount, + _message, + _gasLimit, + _from + ); + + emit DepositETH(_from, _to, _amount, _data); + } +} diff --git a/src/alternative-gas-token/L1ScrollMessengerNonETH.sol b/src/alternative-gas-token/L1ScrollMessengerNonETH.sol new file mode 100644 index 0000000..969e7aa --- /dev/null +++ b/src/alternative-gas-token/L1ScrollMessengerNonETH.sol @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: MIT + +pragma solidity =0.8.24; + +import {AddressUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; + +import {IL1MessageQueue} from "../L1/rollup/IL1MessageQueue.sol"; +import {IScrollChain} from "../L1/rollup/IScrollChain.sol"; +import {L1ScrollMessenger} from "../L1/L1ScrollMessenger.sol"; +import {IMessageDropCallback} from "../libraries/callbacks/IMessageDropCallback.sol"; +import {ScrollConstants} from "../libraries/constants/ScrollConstants.sol"; +import {WithdrawTrieVerifier} from "../libraries/verifier/WithdrawTrieVerifier.sol"; + +contract L1ScrollMessengerNonETH is L1ScrollMessenger { + /********** + * Errors * + **********/ + + /// @dev Thrown when the message is duplicated. + error ErrorDuplicatedMessage(); + + /// @dev Thrown when caller pass non-zero value in `sendMessage`. + error ErrorNonZeroValueFromCaller(); + + /// @dev Thrown when caller pass non-zero value in `relayMessageWithProof`. + error ErrorNonZeroValueFromCrossDomainCaller(); + + /// @dev Thrown when the `msg.value` cannot cover cross domain fee. + error ErrorInsufficientMsgValue(); + + /// @dev Thrown when the message is executed before. + error ErrorMessageExecuted(); + + /// @dev Thrown when the message has not enqueued before. + error ErrorMessageNotEnqueued(); + + /// @dev Thrown when the message is dropped before. + error ErrorMessageDropped(); + + /// @dev Thrown when relay a message belonging to an unfinalized batch. + error ErrorBatchNotFinalized(); + + /// @dev Thrown when the provided merkle proof is invalid. + error ErrorInvalidMerkleProof(); + + /// @dev Thrown when call to message queue. + error ErrorForbidToCallMessageQueue(); + + /// @dev Thrown when the message sender is invalid. + error ErrorInvalidMessageSender(); + + /************* + * Constants * + *************/ + + /// @notice The address of `L1NativeTokenGateway` contract. + address public immutable nativeTokenGateway; + + /*************** + * Constructor * + ***************/ + + constructor( + address _nativeTokenGateway, + address _counterpart, + address _rollup, + address _messageQueue + ) L1ScrollMessenger(_counterpart, _rollup, _messageQueue) { + nativeTokenGateway = _nativeTokenGateway; + } + + /********************** + * Internal Functions * + **********************/ + + /// @inheritdoc L1ScrollMessenger + function _sendMessage( + address _to, + uint256 _l2GasTokenValue, + bytes memory _message, + uint256 _gasLimit, + address _refundAddress + ) internal override { + // if we want to pass value to L2, must call from `L1NativeTokenGateway`. + if (_l2GasTokenValue > 0 && _msgSender() != nativeTokenGateway) { + revert ErrorNonZeroValueFromCaller(); + } + + // compute the actual cross domain message calldata. + uint256 _messageNonce = IL1MessageQueue(messageQueue).nextCrossDomainMessageIndex(); + bytes memory _xDomainCalldata = _encodeXDomainCalldata( + _msgSender(), + _to, + _l2GasTokenValue, + _messageNonce, + _message + ); + + // compute and deduct the messaging fee to fee vault. + uint256 _fee = IL1MessageQueue(messageQueue).estimateCrossDomainMessageFee(_gasLimit); + if (msg.value < _fee) { + revert ErrorInsufficientMsgValue(); + } + if (_fee > 0) { + AddressUpgradeable.sendValue(payable(feeVault), _fee); + } + + // append message to L1MessageQueue + IL1MessageQueue(messageQueue).appendCrossDomainMessage(counterpart, _gasLimit, _xDomainCalldata); + + // record the message hash for future use. + bytes32 _xDomainCalldataHash = keccak256(_xDomainCalldata); + + // normally this won't happen, since each message has different nonce, but just in case. + if (messageSendTimestamp[_xDomainCalldataHash] != 0) { + revert ErrorDuplicatedMessage(); + } + messageSendTimestamp[_xDomainCalldataHash] = block.timestamp; + + emit SentMessage(_msgSender(), _to, _l2GasTokenValue, _messageNonce, _gasLimit, _message); + + // refund fee to `_refundAddress` + unchecked { + uint256 _refund = msg.value - _fee; + if (_refund > 0) { + AddressUpgradeable.sendValue(payable(_refundAddress), _refund); + } + } + } + + /// @inheritdoc L1ScrollMessenger + function _relayMessageWithProof( + address _from, + address _to, + uint256 _l2GasTokenValue, + uint256 _nonce, + bytes memory _message, + L2MessageProof memory _proof + ) internal virtual override { + // if we want to pass value to L1, must call to `L1NativeTokenGateway`. + if (_l2GasTokenValue > 0 && _to != nativeTokenGateway) { + revert ErrorNonZeroValueFromCrossDomainCaller(); + } + + bytes32 _xDomainCalldataHash = keccak256( + _encodeXDomainCalldata(_from, _to, _l2GasTokenValue, _nonce, _message) + ); + if (isL2MessageExecuted[_xDomainCalldataHash]) { + revert ErrorMessageExecuted(); + } + + { + if (!IScrollChain(rollup).isBatchFinalized(_proof.batchIndex)) { + revert ErrorBatchNotFinalized(); + } + bytes32 _messageRoot = IScrollChain(rollup).withdrawRoots(_proof.batchIndex); + if ( + !WithdrawTrieVerifier.verifyMerkleProof(_messageRoot, _xDomainCalldataHash, _nonce, _proof.merkleProof) + ) { + revert ErrorInvalidMerkleProof(); + } + } + + // @note check more `_to` address to avoid attack in the future when we add more gateways. + if (_to == messageQueue) { + revert ErrorForbidToCallMessageQueue(); + } + _validateTargetAddress(_to); + + // @note This usually will never happen, just in case. + if (_from == xDomainMessageSender) { + revert ErrorInvalidMessageSender(); + } + + xDomainMessageSender = _from; + (bool success, ) = _to.call(_message); + // reset value to refund gas. + xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER; + + if (success) { + isL2MessageExecuted[_xDomainCalldataHash] = true; + emit RelayedMessage(_xDomainCalldataHash); + } else { + emit FailedRelayedMessage(_xDomainCalldataHash); + } + } + + /// @inheritdoc L1ScrollMessenger + function _dropMessage( + address _from, + address _to, + uint256 _l2GasTokenValue, + uint256 _messageNonce, + bytes memory _message + ) internal virtual override { + // The criteria for dropping a message: + // 1. The message is a L1 message. + // 2. The message has not been dropped before. + // 3. the message and all of its replacement are finalized in L1. + // 4. the message and all of its replacement are skipped. + // + // Possible denial of service attack: + // + replayMessage is called every time someone want to drop the message. + // + replayMessage is called so many times for a skipped message, thus results a long list. + // + // We limit the number of `replayMessage` calls of each message, which may solve the above problem. + + // check message exists + bytes memory _xDomainCalldata = _encodeXDomainCalldata(_from, _to, _l2GasTokenValue, _messageNonce, _message); + bytes32 _xDomainCalldataHash = keccak256(_xDomainCalldata); + if (messageSendTimestamp[_xDomainCalldataHash] == 0) { + revert ErrorMessageNotEnqueued(); + } + + // check message not dropped + if (isL1MessageDropped[_xDomainCalldataHash]) { + revert ErrorMessageDropped(); + } + + // check message is finalized + uint256 _lastIndex = replayStates[_xDomainCalldataHash].lastIndex; + if (_lastIndex == 0) _lastIndex = _messageNonce; + + // check message is skipped and drop it. + // @note If the list is very long, the message may never be dropped. + while (true) { + IL1MessageQueue(messageQueue).dropCrossDomainMessage(_lastIndex); + _lastIndex = prevReplayIndex[_lastIndex]; + if (_lastIndex == 0) break; + unchecked { + _lastIndex = _lastIndex - 1; + } + } + + isL1MessageDropped[_xDomainCalldataHash] = true; + + // set execution context + xDomainMessageSender = ScrollConstants.DROP_XDOMAIN_MESSAGE_SENDER; + IMessageDropCallback(_from).onDropMessage(_message); + // clear execution context + xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER; + } +} diff --git a/src/alternative-gas-token/L1WrappedTokenGateway.sol b/src/alternative-gas-token/L1WrappedTokenGateway.sol new file mode 100644 index 0000000..065a00f --- /dev/null +++ b/src/alternative-gas-token/L1WrappedTokenGateway.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: MIT + +pragma solidity =0.8.24; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + +import {IL1ERC20Gateway} from "../L1/gateways/IL1ERC20Gateway.sol"; +import {IWETH} from "../interfaces/IWETH.sol"; + +contract L1WrappedTokenGateway { + using SafeERC20 for IERC20; + + /********** + * Events * + **********/ + + /// @notice Emitted when someone wrap ETH to WETH and then deposit WETH from L1 to L2. + /// @param from The address of sender in L1. + /// @param to The address of recipient in L2. + /// @param amount The amount of ETH will be deposited from L1 to L2. + event DepositWrappedToken(address indexed from, address indexed to, uint256 amount); + + /********* + * Error * + *********/ + + /// @dev Thrown when someone try to send ETH to this contract. + error ErrorCallNotFromFeeRefund(); + + /************* + * Constants * + *************/ + + /// @dev The safe gas limit used to bridge WETH to L2. + uint256 private constant SAFE_GAS_LIMIT = 450000; + + /// @dev The default value of `sender`. + address private constant DEFAULT_SENDER = address(1); + + /// @notice The address of Wrapped Ether. + address public immutable WETH; + + /// @notice The address of ERC20 gateway used to bridge WETH. + address public immutable gateway; + + /************* + * Variables * + *************/ + + /// @notice The address of caller who called `deposit`. + /// @dev This will be reset after call `gateway.depositERC20`, which is used to + /// prevent malicious user sending ETH to this contract. + address public sender; + + /*************** + * Constructor * + ***************/ + + constructor(address _weth, address _gateway) { + WETH = _weth; + gateway = _gateway; + + sender = DEFAULT_SENDER; + } + + /***************************** + * Public Mutating Functions * + *****************************/ + + /// @dev Only receive cross domain fee refund + receive() external payable { + if (sender == DEFAULT_SENDER) { + revert ErrorCallNotFromFeeRefund(); + } + } + + /// @notice Deposit ETH. + /// @dev This will wrap ETH to WETH first and then deposit as WETH. + /// @param _to The address of recipient in L2. + /// @param _amount The amount of ETH to deposit. + function deposit(address _to, uint256 _amount) external payable { + IWETH(WETH).deposit{value: _amount}(); + + IERC20(WETH).safeApprove(gateway, 0); + IERC20(WETH).safeApprove(gateway, _amount); + sender = msg.sender; + IL1ERC20Gateway(gateway).depositERC20{value: msg.value - _amount}(WETH, _to, _amount, SAFE_GAS_LIMIT); + sender = DEFAULT_SENDER; + + emit DepositWrappedToken(msg.sender, _to, _amount); + + // refund exceed fee + uint256 balance = address(this).balance; + if (balance > 0) { + Address.sendValue(payable(msg.sender), balance); + } + } +} diff --git a/src/alternative-gas-token/L2TxFeeVaultWithGasToken.sol b/src/alternative-gas-token/L2TxFeeVaultWithGasToken.sol new file mode 100644 index 0000000..7234fb6 --- /dev/null +++ b/src/alternative-gas-token/L2TxFeeVaultWithGasToken.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT + +pragma solidity =0.8.24; + +import {L2TxFeeVault} from "../L2/predeploys/L2TxFeeVault.sol"; +import {IL2ETHGateway} from "../L2/gateways/IL2ETHGateway.sol"; + +contract L2TxFeeVaultWithGasToken is L2TxFeeVault { + /************* + * Constants * + *************/ + + /// @notice The address of `L2ETHGateway` contract. + address public ETHGateway; + + /*************** + * Constructor * + ***************/ + + constructor( + address _ETHGateway, + address _owner, + address _recipient, + uint256 _minWithdrawalAmount + ) L2TxFeeVault(_owner, _recipient, _minWithdrawalAmount) { + ETHGateway = _ETHGateway; + } + + /************************ + * Restricted Functions * + ************************/ + + /// @notice Update the address of ETHGateway. + /// @param _newETHGateway The address of ETHGateway to update. + function updateNativeTokenGateway(address _newETHGateway) external onlyOwner { + address _oldETHGateway = ETHGateway; + ETHGateway = _newETHGateway; + + emit UpdateMessenger(_oldETHGateway, _newETHGateway); + } + + /********************** + * Internal Functions * + **********************/ + + /// @inheritdoc L2TxFeeVault + function sendWithdrawMessage(address _recipient, uint256 _value) internal override { + // no fee provided + IL2ETHGateway(ETHGateway).withdrawETH{value: _value}( + _recipient, + _value, + 0 // _gasLimit can be zero for fee vault. + ); + } +} diff --git a/src/test/L1GasPriceOracle.t.sol b/src/test/L1GasPriceOracle.t.sol index 3d845ad..d4497e3 100644 --- a/src/test/L1GasPriceOracle.t.sol +++ b/src/test/L1GasPriceOracle.t.sol @@ -19,7 +19,7 @@ contract L1GasPriceOracleTest is DSTestPlus { function setUp() public { whitelist = new Whitelist(address(this)); - oracle = new L1GasPriceOracle(address(this)); + oracle = new L1GasPriceOracle(address(this), false); oracle.updateWhitelist(address(whitelist)); address[] memory _accounts = new address[](1); diff --git a/src/test/L2GatewayTestBase.t.sol b/src/test/L2GatewayTestBase.t.sol index 180fba2..5637267 100644 --- a/src/test/L2GatewayTestBase.t.sol +++ b/src/test/L2GatewayTestBase.t.sol @@ -69,7 +69,7 @@ abstract contract L2GatewayTestBase is DSTestPlus { whitelist = new Whitelist(address(this)); l1BlockContainer = new L1BlockContainer(address(this)); l2MessageQueue = new L2MessageQueue(address(this)); - l1GasOracle = new L1GasPriceOracle(address(this)); + l1GasOracle = new L1GasPriceOracle(address(this), false); l2Messenger = L2ScrollMessenger(payable(_deployProxy(address(0)))); // Upgrade the L2ScrollMessenger implementation and initialize diff --git a/src/test/L2ScrollMessenger.t.sol b/src/test/L2ScrollMessenger.t.sol index 6dea30e..313e96a 100644 --- a/src/test/L2ScrollMessenger.t.sol +++ b/src/test/L2ScrollMessenger.t.sol @@ -34,7 +34,7 @@ contract L2ScrollMessengerTest is DSTestPlus { whitelist = new Whitelist(address(this)); l1BlockContainer = new L1BlockContainer(address(this)); l2MessageQueue = new L2MessageQueue(address(this)); - l1GasOracle = new L1GasPriceOracle(address(this)); + l1GasOracle = new L1GasPriceOracle(address(this), false); l2Messenger = L2ScrollMessenger( payable( new ERC1967Proxy( diff --git a/src/test/alternative-gas-token/AlternativeGasTokenTestBase.t.sol b/src/test/alternative-gas-token/AlternativeGasTokenTestBase.t.sol new file mode 100644 index 0000000..1bf3aa1 --- /dev/null +++ b/src/test/alternative-gas-token/AlternativeGasTokenTestBase.t.sol @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: MIT + +pragma solidity =0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {Vm} from "forge-std/Vm.sol"; + +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {ITransparentUpgradeableProxy, TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import {L1GasTokenGateway} from "../../alternative-gas-token/L1GasTokenGateway.sol"; +import {L1ScrollMessengerNonETH} from "../../alternative-gas-token/L1ScrollMessengerNonETH.sol"; +import {L1GatewayRouter} from "../../L1/gateways/L1GatewayRouter.sol"; +import {EnforcedTxGateway} from "../../L1/gateways/EnforcedTxGateway.sol"; +import {L1MessageQueueWithGasPriceOracle} from "../../L1/rollup/L1MessageQueueWithGasPriceOracle.sol"; +import {L2GasPriceOracle} from "../../L1/rollup/L2GasPriceOracle.sol"; +import {ScrollChain, IScrollChain} from "../../L1/rollup/ScrollChain.sol"; +import {L2GatewayRouter} from "../../L2/gateways/L2GatewayRouter.sol"; +import {L2ETHGateway} from "../../L2/gateways/L2ETHGateway.sol"; +import {L2MessageQueue} from "../../L2/predeploys/L2MessageQueue.sol"; +import {Whitelist} from "../../L2/predeploys/Whitelist.sol"; +import {L2ScrollMessenger, IL2ScrollMessenger} from "../../L2/L2ScrollMessenger.sol"; +import {AddressAliasHelper} from "../../libraries/common/AddressAliasHelper.sol"; +import {EmptyContract} from "../../misc/EmptyContract.sol"; + +import {ScrollChainMockBlob} from "../../mocks/ScrollChainMockBlob.sol"; +import {MockRollupVerifier} from "../mocks/MockRollupVerifier.sol"; + +abstract contract AlternativeGasTokenTestBase is Test { + // from L1MessageQueue + event QueueTransaction( + address indexed sender, + address indexed target, + uint256 value, + uint64 queueIndex, + uint256 gasLimit, + bytes data + ); + + // from L1ScrollMessengerNonETH + event SentMessage( + address indexed sender, + address indexed target, + uint256 value, + uint256 messageNonce, + uint256 gasLimit, + bytes message + ); + event RelayedMessage(bytes32 indexed messageHash); + event FailedRelayedMessage(bytes32 indexed messageHash); + + bytes32 private constant SENT_MESSAGE_TOPIC = + keccak256("SentMessage(address,address,uint256,uint256,uint256,bytes)"); + + ProxyAdmin internal admin; + EmptyContract private placeholder; + + // L1 contracts + L1ScrollMessengerNonETH internal l1Messenger; + L1MessageQueueWithGasPriceOracle internal l1MessageQueue; + ScrollChainMockBlob internal rollup; + L1GasTokenGateway internal l1GasTokenGateway; + L1GatewayRouter internal l1Router; + address internal l1FeeVault; + + // L2 contracts + L2ScrollMessenger internal l2Messenger; + L2MessageQueue internal l2MessageQueue; + L2ETHGateway internal l2ETHGateway; + L2GatewayRouter internal l2Router; + + uint256 private lastFromL2LogIndex; + uint256 private lastFromL1LogIndex; + + function __AlternativeGasTokenTestBase_setUp(uint64 l2ChainId, address gasToken) internal { + admin = new ProxyAdmin(); + placeholder = new EmptyContract(); + + // deploy proxy and contracts in L1 + l1FeeVault = address(uint160(address(this)) - 1); + l1MessageQueue = L1MessageQueueWithGasPriceOracle(_deployProxy(address(0))); + rollup = ScrollChainMockBlob(_deployProxy(address(0))); + l1Messenger = L1ScrollMessengerNonETH(payable(_deployProxy(address(0)))); + l1GasTokenGateway = L1GasTokenGateway(_deployProxy(address(0))); + l1Router = L1GatewayRouter(_deployProxy(address(0))); + L2GasPriceOracle gasOracle = L2GasPriceOracle(_deployProxy(address(new L2GasPriceOracle()))); + Whitelist whitelist = new Whitelist(address(this)); + MockRollupVerifier verifier = new MockRollupVerifier(); + + // deploy proxy and contracts in L2 + l2MessageQueue = new L2MessageQueue(address(this)); + l2Messenger = L2ScrollMessenger(payable(_deployProxy(address(0)))); + l2ETHGateway = L2ETHGateway(payable(_deployProxy(address(0)))); + l2Router = L2GatewayRouter(_deployProxy(address(0))); + + // Upgrade the L1ScrollMessengerNonETH implementation and initialize + admin.upgrade( + ITransparentUpgradeableProxy(address(l1Messenger)), + address( + new L1ScrollMessengerNonETH( + address(l1GasTokenGateway), + address(l2Messenger), + address(rollup), + address(l1MessageQueue) + ) + ) + ); + l1Messenger.initialize(address(l2Messenger), l1FeeVault, address(rollup), address(l1MessageQueue)); + + // initialize L2GasPriceOracle + gasOracle.initialize(1, 2, 1, 1); + gasOracle.updateWhitelist(address(whitelist)); + + // Upgrade the L1MessageQueueWithGasPriceOracle implementation and initialize + admin.upgrade( + ITransparentUpgradeableProxy(address(l1MessageQueue)), + address(new L1MessageQueueWithGasPriceOracle(address(l1Messenger), address(rollup), address(1))) + ); + l1MessageQueue.initialize(address(l1Messenger), address(rollup), address(this), address(gasOracle), 10000000); + l1MessageQueue.initializeV2(); + + // Upgrade the ScrollChain implementation and initialize + admin.upgrade( + ITransparentUpgradeableProxy(address(rollup)), + address(new ScrollChainMockBlob(l2ChainId, address(l1MessageQueue), address(verifier))) + ); + rollup.initialize(address(l1MessageQueue), address(verifier), 44); + + // Upgrade the L1GasTokenGateway implementation and initialize + admin.upgrade( + ITransparentUpgradeableProxy(address(l1GasTokenGateway)), + address(new L1GasTokenGateway(gasToken, address(l2ETHGateway), address(l1Router), address(l1Messenger))) + ); + l1GasTokenGateway.initialize(); + + // Upgrade the L1GatewayRouter implementation and initialize + admin.upgrade(ITransparentUpgradeableProxy(address(l1Router)), address(new L1GatewayRouter())); + l1Router.initialize(address(l1GasTokenGateway), address(0)); + + // L2ScrollMessenger + admin.upgrade( + ITransparentUpgradeableProxy(address(l2Messenger)), + address(new L2ScrollMessenger(address(l1Messenger), address(l2MessageQueue))) + ); + l2Messenger.initialize(address(0)); + l2MessageQueue.initialize(address(l2Messenger)); + + // L2ETHGateway + admin.upgrade( + ITransparentUpgradeableProxy(address(l2ETHGateway)), + address(new L2ETHGateway(address(l1GasTokenGateway), address(l2Router), address(l2Messenger))) + ); + l2ETHGateway.initialize(address(l1GasTokenGateway), address(l2Router), address(l2Messenger)); + + // L2GatewayRouter + admin.upgrade(ITransparentUpgradeableProxy(address(l2Router)), address(new L2GatewayRouter())); + l2Router.initialize(address(l2ETHGateway), address(0)); + + // Setup whitelist in L1 + address[] memory _accounts = new address[](1); + _accounts[0] = address(this); + whitelist.updateWhitelistStatus(_accounts, true); + + // Make nonzero block.timestamp + vm.warp(1); + + // Allocate balance to l2Messenger + vm.deal(address(l2Messenger), type(uint256).max / 2); + } + + function _deployProxy(address _logic) internal returns (address) { + if (_logic == address(0)) _logic = address(placeholder); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(_logic, address(admin), new bytes(0)); + return address(proxy); + } + + function relayFromL1() internal { + address malias = AddressAliasHelper.applyL1ToL2Alias(address(l1Messenger)); + + // Read all L1 -> L2 messages and relay them + Vm.Log[] memory allLogs = vm.getRecordedLogs(); + for (; lastFromL1LogIndex < allLogs.length; lastFromL1LogIndex++) { + Vm.Log memory _log = allLogs[lastFromL1LogIndex]; + if (_log.topics[0] == SENT_MESSAGE_TOPIC && _log.emitter == address(l1Messenger)) { + address sender = address(uint160(uint256(_log.topics[1]))); + address target = address(uint160(uint256(_log.topics[2]))); + (uint256 value, uint256 nonce, uint256 gasLimit, bytes memory message) = abi.decode( + _log.data, + (uint256, uint256, uint256, bytes) + ); + vm.prank(malias); + IL2ScrollMessenger(l2Messenger).relayMessage{gas: gasLimit}(sender, target, value, nonce, message); + } + } + } + + function relayFromL2() internal { + // Read all L2 -> L1 messages and relay them + // Note: We bypass the L1 messenger relay here because it's easier to not have to generate valid state roots / merkle proofs + Vm.Log[] memory allLogs = vm.getRecordedLogs(); + for (; lastFromL2LogIndex < allLogs.length; lastFromL2LogIndex++) { + Vm.Log memory _log = allLogs[lastFromL2LogIndex]; + if (_log.topics[0] == SENT_MESSAGE_TOPIC && _log.emitter == address(l2Messenger)) { + address sender = address(uint160(uint256(_log.topics[1]))); + address target = address(uint160(uint256(_log.topics[2]))); + (, , , bytes memory message) = abi.decode(_log.data, (uint256, uint256, uint256, bytes)); + // Set xDomainMessageSender + vm.store(address(l1Messenger), bytes32(uint256(201)), bytes32(uint256(uint160(sender)))); + vm.startPrank(address(l1Messenger)); + (bool success, bytes memory response) = target.call(message); + vm.stopPrank(); + vm.store(address(l1Messenger), bytes32(uint256(201)), bytes32(uint256(1))); + if (!success) { + assembly { + revert(add(response, 32), mload(response)) + } + } + } + } + } + + function encodeXDomainCalldata( + address _sender, + address _target, + uint256 _value, + uint256 _messageNonce, + bytes memory _message + ) internal pure returns (bytes memory) { + return abi.encodeCall(IL2ScrollMessenger.relayMessage, (_sender, _target, _value, _messageNonce, _message)); + } + + function prepareFinalizedBatch(bytes32 messageHash) internal { + rollup.addSequencer(address(0)); + rollup.addProver(address(0)); + + // import genesis batch + bytes memory batchHeader0 = new bytes(89); + assembly { + mstore(add(batchHeader0, add(0x20, 25)), 1) + } + rollup.importGenesisBatch(batchHeader0, bytes32(uint256(1))); + bytes32 batchHash0 = rollup.committedBatches(0); + + // from https://etherscan.io/blob/0x013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757?bid=740652 + bytes32 blobVersionedHash = 0x013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757; + bytes + memory blobDataProof = hex"2c9d777660f14ad49803a6442935c0d24a0d83551de5995890bf70a17d24e68753ab0fe6807c7081f0885fe7da741554d658a03730b1fa006f8319f8b993bcb0a5a0c9e8a145c5ef6e415c245690effa2914ec9393f58a7251d30c0657da1453d9ad906eae8b97dd60c9a216f81b4df7af34d01e214e1ec5865f0133ecc16d7459e49dab66087340677751e82097fbdd20551d66076f425775d1758a9dfd186b"; + rollup.setBlobVersionedHash(blobVersionedHash); + + // commit one batch + bytes[] memory chunks = new bytes[](1); + bytes memory chunk0 = new bytes(1 + 60); + chunk0[0] = bytes1(uint8(1)); // one block in this chunk + chunks[0] = chunk0; + vm.startPrank(address(0)); + rollup.commitBatch(1, batchHeader0, chunks, new bytes(0)); + vm.stopPrank(); + + bytes memory batchHeader1 = new bytes(121); + assembly { + mstore8(add(batchHeader1, 0x20), 1) // version + mstore(add(batchHeader1, add(0x20, 1)), shl(192, 1)) // batchIndex + mstore(add(batchHeader1, add(0x20, 9)), 0) // l1MessagePopped + mstore(add(batchHeader1, add(0x20, 17)), 0) // totalL1MessagePopped + mstore(add(batchHeader1, add(0x20, 25)), 0x246394445f4fe64ed5598554d55d1682d6fb3fe04bf58eb54ef81d1189fafb51) // dataHash + mstore(add(batchHeader1, add(0x20, 57)), blobVersionedHash) // blobVersionedHash + mstore(add(batchHeader1, add(0x20, 89)), batchHash0) // parentBatchHash + } + + vm.startPrank(address(0)); + rollup.finalizeBatchWithProof4844( + batchHeader1, + bytes32(uint256(1)), + bytes32(uint256(2)), + messageHash, + blobDataProof, + new bytes(0) + ); + vm.stopPrank(); + } +} diff --git a/src/test/alternative-gas-token/GasTokenDecimalGateway.t.sol b/src/test/alternative-gas-token/GasTokenDecimalGateway.t.sol new file mode 100644 index 0000000..1a3c558 --- /dev/null +++ b/src/test/alternative-gas-token/GasTokenDecimalGateway.t.sol @@ -0,0 +1,484 @@ +// SPDX-License-Identifier: MIT + +pragma solidity =0.8.24; + +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; + +import {ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import {L1GasTokenGateway} from "../../alternative-gas-token/L1GasTokenGateway.sol"; +import {IL1ScrollMessenger} from "../../L1/IL1ScrollMessenger.sol"; +import {IL2ETHGateway} from "../../L2/gateways/IL2ETHGateway.sol"; +import {AddressAliasHelper} from "../../libraries/common/AddressAliasHelper.sol"; +import {ScrollConstants} from "../../libraries/constants/ScrollConstants.sol"; +import {IScrollGateway} from "../../libraries/gateway/IScrollGateway.sol"; + +import {AlternativeGasTokenTestBase} from "./AlternativeGasTokenTestBase.t.sol"; + +import {MockGatewayRecipient} from "../mocks/MockGatewayRecipient.sol"; +import {MockScrollMessenger} from "../mocks/MockScrollMessenger.sol"; + +contract L1GasTokenGatewayForTest is L1GasTokenGateway { + constructor( + address _gasToken, + address _counterpart, + address _router, + address _messenger + ) L1GasTokenGateway(_gasToken, _counterpart, _router, _messenger) {} + + function reentrantCall(address target, bytes calldata data) external payable nonReentrant { + (bool success, ) = target.call{value: msg.value}(data); + if (!success) { + // solhint-disable-next-line no-inline-assembly + assembly { + let ptr := mload(0x40) + let size := returndatasize() + returndatacopy(ptr, 0, size) + revert(ptr, size) + } + } + } +} + +abstract contract GasTokenGatewayTest is AlternativeGasTokenTestBase { + // from L1GasTokenGateway + event DepositETH(address indexed from, address indexed to, uint256 amount, bytes data); + event FinalizeWithdrawETH(address indexed from, address indexed to, uint256 amount, bytes data); + event RefundETH(address indexed recipient, uint256 amount); + + uint256 private constant NONZERO_TIMESTAMP = 123456; + + MockERC20 private gasToken; + uint256 private tokenScale; + + struct DepositParams { + uint256 methodType; + uint256 amount; + address recipient; + bytes dataToCall; + uint256 gasLimit; + uint256 feeToPay; + uint256 exceedValue; + } + + receive() external payable {} + + function __GasTokenGatewayTest_setUp(uint8 decimals) internal { + gasToken = new MockERC20("X", "Y", decimals); + + __AlternativeGasTokenTestBase_setUp(1234, address(gasToken)); + + admin.upgrade( + ITransparentUpgradeableProxy(address(l1GasTokenGateway)), + address( + new L1GasTokenGatewayForTest( + address(gasToken), + address(l2ETHGateway), + address(l1Router), + address(l1Messenger) + ) + ) + ); + + gasToken.mint(address(this), type(uint128).max); + vm.warp(NONZERO_TIMESTAMP); + + gasToken.approve(address(l1GasTokenGateway), type(uint256).max); + tokenScale = 10**(18 - decimals); + } + + function testDepositETH(DepositParams memory params) external { + params.methodType = 0; + params.recipient = address(this); + params.dataToCall = new bytes(0); + _depositETH(false, params); + } + + function testDepositETHWithRecipient(DepositParams memory params) external { + params.methodType = 1; + params.dataToCall = new bytes(0); + _depositETH(false, params); + } + + function testDepositETHAndCall(DepositParams memory params) external { + params.methodType = 2; + _depositETH(false, params); + } + + function testDepositETHWithRouter(DepositParams memory params) external { + params.methodType = 0; + params.recipient = address(this); + params.dataToCall = new bytes(0); + _depositETH(true, params); + } + + function testDepositETHWithRecipientWithRouter(DepositParams memory params) external { + params.methodType = 1; + params.dataToCall = new bytes(0); + _depositETH(true, params); + } + + function testDepositETHAndCallWithRouter(DepositParams memory params) external { + params.methodType = 2; + _depositETH(true, params); + } + + function testFinalizeWithdrawETH( + address sender, + address target, + uint256 amount, + bytes memory dataToCall + ) external { + vm.assume(target != address(0)); + amount = bound(amount, 1, type(uint128).max); + + // revert when ErrorCallerIsNotMessenger + vm.expectRevert(IScrollGateway.ErrorCallerIsNotMessenger.selector); + l1GasTokenGateway.finalizeWithdrawETH(sender, target, amount, dataToCall); + + MockScrollMessenger mockMessenger = new MockScrollMessenger(); + admin.upgrade( + ITransparentUpgradeableProxy(address(l1GasTokenGateway)), + address( + new L1GasTokenGatewayForTest( + address(gasToken), + address(l2ETHGateway), + address(l1Router), + address(mockMessenger) + ) + ) + ); + + bytes memory message = abi.encodeCall( + L1GasTokenGateway.finalizeWithdrawETH, + (sender, target, amount, dataToCall) + ); + // revert when ErrorCallerIsNotCounterpartGateway + vm.expectRevert(IScrollGateway.ErrorCallerIsNotCounterpartGateway.selector); + mockMessenger.callTarget(address(l1GasTokenGateway), message); + + // revert when reentrant + mockMessenger.setXDomainMessageSender(address(l2ETHGateway)); + vm.expectRevert("ReentrancyGuard: reentrant call"); + L1GasTokenGatewayForTest(address(l1GasTokenGateway)).reentrantCall( + address(mockMessenger), + abi.encodeCall(mockMessenger.callTarget, (address(l1GasTokenGateway), message)) + ); + + // revert when ErrorNonZeroMsgValue + vm.expectRevert(L1GasTokenGateway.ErrorNonZeroMsgValue.selector); + mockMessenger.callTarget{value: 1}(address(l1GasTokenGateway), message); + + admin.upgrade( + ITransparentUpgradeableProxy(address(l1GasTokenGateway)), + address( + new L1GasTokenGatewayForTest( + address(gasToken), + address(l2ETHGateway), + address(l1Router), + address(l1Messenger) + ) + ) + ); + + // succeed when finalize + uint256 scaledAmount = amount / tokenScale; + gasToken.mint(address(l1GasTokenGateway), type(uint128).max); + MockGatewayRecipient recipient = new MockGatewayRecipient(); + message = abi.encodeCall( + L1GasTokenGateway.finalizeWithdrawETH, + (sender, address(recipient), amount, dataToCall) + ); + bytes32 messageHash = keccak256( + encodeXDomainCalldata(address(l2ETHGateway), address(l1GasTokenGateway), 0, 0, message) + ); + prepareFinalizedBatch(messageHash); + IL1ScrollMessenger.L2MessageProof memory proof; + proof.batchIndex = rollup.lastFinalizedBatchIndex(); + + // should emit FinalizeWithdrawETH from L1GasTokenGateway + { + vm.expectEmit(true, true, true, true); + emit FinalizeWithdrawETH(sender, address(recipient), scaledAmount, dataToCall); + } + // should emit RelayedMessage from L1ScrollMessenger + { + vm.expectEmit(true, false, false, true); + emit RelayedMessage(messageHash); + } + + uint256 gatewayBalance = gasToken.balanceOf(address(l1GasTokenGateway)); + uint256 recipientBalance = gasToken.balanceOf(address(recipient)); + assertEq(false, l1Messenger.isL2MessageExecuted(messageHash)); + l1Messenger.relayMessageWithProof(address(l2ETHGateway), address(l1GasTokenGateway), 0, 0, message, proof); + assertEq(true, l1Messenger.isL2MessageExecuted(messageHash)); + assertEq(recipientBalance + scaledAmount, gasToken.balanceOf(address(recipient))); + assertEq(gatewayBalance - scaledAmount, gasToken.balanceOf(address(l1GasTokenGateway))); + } + + function testDropMessage(uint256 amount, address recipient) external { + vm.assume(recipient != address(0)); + + amount = bound(amount, 1, gasToken.balanceOf(address(this))); + uint256 scaledAmount = amount * tokenScale; + bytes memory message = abi.encodeCall( + IL2ETHGateway.finalizeDepositETH, + (address(this), recipient, scaledAmount, new bytes(0)) + ); + l1GasTokenGateway.depositETH(recipient, amount, 1000000); + + // revert when ErrorCallerIsNotMessenger + vm.expectRevert(IScrollGateway.ErrorCallerIsNotMessenger.selector); + l1GasTokenGateway.onDropMessage(message); + + MockScrollMessenger mockMessenger = new MockScrollMessenger(); + admin.upgrade( + ITransparentUpgradeableProxy(address(l1GasTokenGateway)), + address( + new L1GasTokenGatewayForTest( + address(gasToken), + address(l2ETHGateway), + address(l1Router), + address(mockMessenger) + ) + ) + ); + + // revert not in drop context + vm.expectRevert(IScrollGateway.ErrorNotInDropMessageContext.selector); + mockMessenger.callTarget( + address(l1GasTokenGateway), + abi.encodeCall(l1GasTokenGateway.onDropMessage, (message)) + ); + + // revert when reentrant + mockMessenger.setXDomainMessageSender(ScrollConstants.DROP_XDOMAIN_MESSAGE_SENDER); + vm.expectRevert("ReentrancyGuard: reentrant call"); + L1GasTokenGatewayForTest(address(l1GasTokenGateway)).reentrantCall( + address(mockMessenger), + abi.encodeCall( + mockMessenger.callTarget, + (address(l1GasTokenGateway), abi.encodeCall(l1GasTokenGateway.onDropMessage, (message))) + ) + ); + + // revert when invalid selector + vm.expectRevert(L1GasTokenGateway.ErrorInvalidSelector.selector); + mockMessenger.callTarget( + address(l1GasTokenGateway), + abi.encodeCall(l1GasTokenGateway.onDropMessage, (new bytes(4))) + ); + + admin.upgrade( + ITransparentUpgradeableProxy(address(l1GasTokenGateway)), + address( + new L1GasTokenGatewayForTest( + address(gasToken), + address(l2ETHGateway), + address(l1Router), + address(l1Messenger) + ) + ) + ); + + // succeed on drop + // skip message 0 + vm.startPrank(address(rollup)); + l1MessageQueue.popCrossDomainMessage(0, 1, 0x1); + l1MessageQueue.finalizePoppedCrossDomainMessage(1); + assertEq(l1MessageQueue.nextUnfinalizedQueueIndex(), 1); + assertEq(l1MessageQueue.pendingQueueIndex(), 1); + vm.stopPrank(); + + // should emit RefundERC20 + vm.expectEmit(true, true, false, true); + emit RefundETH(address(this), amount); + + uint256 balance = gasToken.balanceOf(address(this)); + uint256 gatewayBalance = gasToken.balanceOf(address(l1GasTokenGateway)); + l1Messenger.dropMessage(address(l1GasTokenGateway), address(l2ETHGateway), scaledAmount, 0, message); + assertEq(gatewayBalance - amount, gasToken.balanceOf(address(l1GasTokenGateway))); + assertEq(balance + amount, gasToken.balanceOf(address(this))); + } + + function testRelayFromL1ToL2(uint256 l1Amount, address recipient) external { + vm.assume(recipient.code.length == 0); // only send to EOA to avoid revert + vm.assume(uint256(uint160(recipient)) > 2**152); // ignore some precompile contracts + vm.recordLogs(); + + l1Amount = bound(l1Amount, 1, gasToken.balanceOf(address(this))); + uint256 l2Amount = l1Amount * tokenScale; + + l1GasTokenGateway.depositETH(recipient, l1Amount, 1000000); + + uint256 recipientBalance = recipient.balance; + uint256 l2MessengerBalance = address(l2Messenger).balance; + relayFromL1(); + assertEq(recipientBalance + l2Amount, recipient.balance); + assertEq(l2MessengerBalance - l2Amount, address(l2Messenger).balance); + } + + function testRelayFromL2ToL1(uint256 l2Amount, address recipient) external { + vm.assume(recipient.code.length == 0); // only send to EOA to avoid revert + vm.assume(uint256(uint160(recipient)) > 2**152); // ignore some precompile contracts + vm.recordLogs(); + + l2Amount = bound(l2Amount, 1, address(this).balance); + uint256 l1Amount = l2Amount / tokenScale; + + gasToken.mint(address(l1GasTokenGateway), type(uint128).max); + l2ETHGateway.withdrawETH{value: l2Amount}(recipient, l2Amount, 1000000); + + uint256 recipientBalance = gasToken.balanceOf(recipient); + uint256 gatewayBalance = gasToken.balanceOf(address(l1GasTokenGateway)); + relayFromL2(); + assertEq(recipientBalance + l1Amount, gasToken.balanceOf(recipient)); + assertEq(gatewayBalance - l1Amount, gasToken.balanceOf(address(l1GasTokenGateway))); + } + + function _depositETH(bool useRouter, DepositParams memory params) private { + vm.assume(params.recipient != address(0)); + + params.amount = bound(params.amount, 1, gasToken.balanceOf(address(this))); + uint256 scaledAmount = params.amount * tokenScale; + + bytes memory message = abi.encodeCall( + IL2ETHGateway.finalizeDepositETH, + (address(this), params.recipient, scaledAmount, params.dataToCall) + ); + bytes memory xDomainCalldata = encodeXDomainCalldata( + address(l1GasTokenGateway), + address(l2ETHGateway), + scaledAmount, + 0, + message + ); + + params.gasLimit = bound(params.gasLimit, xDomainCalldata.length * 16 + 21000, 1000000); + params.feeToPay = bound(params.feeToPay, 0, 1 ether); + params.exceedValue = bound(params.exceedValue, 0, 1 ether); + + l1MessageQueue.setL2BaseFee(params.feeToPay); + params.feeToPay = params.feeToPay * params.gasLimit; + + // revert when reentrant + { + bytes memory reentrantData; + if (params.methodType == 0) { + reentrantData = abi.encodeWithSignature("depositETH(uint256,uint256)", params.amount, params.gasLimit); + } else if (params.methodType == 1) { + reentrantData = abi.encodeWithSignature( + "depositETH(address,uint256,uint256)", + params.recipient, + params.amount, + params.gasLimit + ); + } else if (params.methodType == 2) { + reentrantData = abi.encodeCall( + l1GasTokenGateway.depositETHAndCall, + (params.recipient, params.amount, params.dataToCall, params.gasLimit) + ); + } + vm.expectRevert("ReentrancyGuard: reentrant call"); + L1GasTokenGatewayForTest(address(l1GasTokenGateway)).reentrantCall( + useRouter ? address(l1Router) : address(l1GasTokenGateway), + reentrantData + ); + } + + // revert when ErrorDepositZeroGasToken + { + uint256 amount = params.amount; + params.amount = 0; + vm.expectRevert(L1GasTokenGateway.ErrorDepositZeroGasToken.selector); + _invokeDepositETHCall(useRouter, params); + params.amount = amount; + } + + // succeed to deposit + // should emit QueueTransaction from L1MessageQueue + { + vm.expectEmit(true, true, false, true); + address sender = AddressAliasHelper.applyL1ToL2Alias(address(l1Messenger)); + emit QueueTransaction(sender, address(l2Messenger), 0, 0, params.gasLimit, xDomainCalldata); + } + // should emit SentMessage from L1ScrollMessenger + { + vm.expectEmit(true, true, false, true); + emit SentMessage( + address(l1GasTokenGateway), + address(l2ETHGateway), + scaledAmount, + 0, + params.gasLimit, + message + ); + } + // should emit DepositERC20 from L1CustomERC20Gateway + { + vm.expectEmit(true, true, false, true); + emit DepositETH(address(this), params.recipient, params.amount, params.dataToCall); + } + + uint256 gatewayBalance = gasToken.balanceOf(address(l1GasTokenGateway)); + uint256 feeVaultBalance = l1FeeVault.balance; + uint256 thisBalance = gasToken.balanceOf(address(this)); + assertEq(l1Messenger.messageSendTimestamp(keccak256(xDomainCalldata)), 0); + uint256 balance = address(this).balance; + _invokeDepositETHCall(useRouter, params); + assertEq(balance - params.feeToPay, address(this).balance); // extra value is transferred back + assertEq(l1Messenger.messageSendTimestamp(keccak256(xDomainCalldata)), NONZERO_TIMESTAMP); + assertEq(thisBalance - params.amount, gasToken.balanceOf(address(this))); + assertEq(feeVaultBalance + params.feeToPay, l1FeeVault.balance); + assertEq(gatewayBalance + params.amount, gasToken.balanceOf(address(l1GasTokenGateway))); + } + + function _invokeDepositETHCall(bool useRouter, DepositParams memory params) private { + uint256 value = params.feeToPay + params.exceedValue; + if (useRouter) { + if (params.methodType == 0) { + l1Router.depositETH{value: value}(params.amount, params.gasLimit); + } else if (params.methodType == 1) { + l1Router.depositETH{value: value}(params.recipient, params.amount, params.gasLimit); + } else if (params.methodType == 2) { + l1Router.depositETHAndCall{value: value}( + params.recipient, + params.amount, + params.dataToCall, + params.gasLimit + ); + } + } else { + if (params.methodType == 0) { + l1GasTokenGateway.depositETH{value: value}(params.amount, params.gasLimit); + } else if (params.methodType == 1) { + l1GasTokenGateway.depositETH{value: value}(params.recipient, params.amount, params.gasLimit); + } else if (params.methodType == 2) { + l1GasTokenGateway.depositETHAndCall{value: value}( + params.recipient, + params.amount, + params.dataToCall, + params.gasLimit + ); + } + } + } +} + +contract GasTokenDecimal18GatewayTest is GasTokenGatewayTest { + function setUp() external { + __GasTokenGatewayTest_setUp(18); + } +} + +contract GasTokenDecimal8GatewayTest is GasTokenGatewayTest { + function setUp() external { + __GasTokenGatewayTest_setUp(8); + } +} + +contract GasTokenDecimal6GatewayTest is GasTokenGatewayTest { + function setUp() external { + __GasTokenGatewayTest_setUp(6); + } +} diff --git a/src/test/alternative-gas-token/L1ScrollMessengerNonETH.t.sol b/src/test/alternative-gas-token/L1ScrollMessengerNonETH.t.sol new file mode 100644 index 0000000..3b0f845 --- /dev/null +++ b/src/test/alternative-gas-token/L1ScrollMessengerNonETH.t.sol @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: MIT + +pragma solidity =0.8.24; + +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; + +import {ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import {L1ScrollMessengerNonETH} from "../../alternative-gas-token/L1ScrollMessengerNonETH.sol"; +import {IL1ScrollMessenger} from "../../L1/IL1ScrollMessenger.sol"; +import {AddressAliasHelper} from "../../libraries/common/AddressAliasHelper.sol"; +import {ScrollConstants} from "../../libraries/constants/ScrollConstants.sol"; + +import {AlternativeGasTokenTestBase} from "./AlternativeGasTokenTestBase.t.sol"; + +contract L1ScrollMessengerNonETHForTest is L1ScrollMessengerNonETH { + constructor( + address _nativeTokenGateway, + address _counterpart, + address _rollup, + address _messageQueue + ) L1ScrollMessengerNonETH(_nativeTokenGateway, _counterpart, _rollup, _messageQueue) {} + + function setMessageSendTimestamp(bytes32 hash, uint256 value) external { + messageSendTimestamp[hash] = value; + } +} + +contract L1ScrollMessengerNonETHTest is AlternativeGasTokenTestBase { + event OnDropMessageCalled(uint256, bytes); + + event OnRelayMessageWithProof(uint256, bytes); + + MockERC20 private gasToken; + + receive() external payable {} + + function setUp() external { + gasToken = new MockERC20("X", "Y", 18); + + __AlternativeGasTokenTestBase_setUp(1234, address(gasToken)); + } + + function testInitialization() external view { + assertEq(l1Messenger.nativeTokenGateway(), address(l1GasTokenGateway)); + assertEq(l1Messenger.messageQueue(), address(l1MessageQueue)); + assertEq(l1Messenger.rollup(), address(rollup)); + } + + function testSendMessageRevertOnErrorNonZeroValueFromCaller(uint256 value) external { + vm.assume(value > 0); + // revert ErrorNonZeroValueFromCaller + vm.expectRevert(L1ScrollMessengerNonETH.ErrorNonZeroValueFromCaller.selector); + l1Messenger.sendMessage(address(0), value, new bytes(0), 0); + } + + function testSendMessageRevertOnErrorInsufficientMsgValue( + uint256 l2BaseFee, + uint256 gasLimit, + bytes memory message + ) external { + bytes memory encoded = encodeXDomainCalldata(address(this), address(0), 0, 0, message); + vm.assume(encoded.length < 60000); + gasLimit = bound(gasLimit, encoded.length * 16 + 21000, 1000000); + l2BaseFee = bound(l2BaseFee, 1, 1 ether); + + l1MessageQueue.setL2BaseFee(l2BaseFee); + + // revert ErrorInsufficientMsgValue + vm.expectRevert(L1ScrollMessengerNonETH.ErrorInsufficientMsgValue.selector); + l1Messenger.sendMessage{value: gasLimit * l2BaseFee - 1}(address(0), 0, message, gasLimit); + } + + function testSendMessageRevertOnErrorDuplicatedMessage( + address target, + uint256 gasLimit, + bytes memory message + ) external { + bytes memory encoded = encodeXDomainCalldata(address(this), target, 0, 0, message); + vm.assume(encoded.length < 60000); + gasLimit = bound(gasLimit, encoded.length * 16 + 21000, 1000000); + + admin.upgrade( + ITransparentUpgradeableProxy(address(l1Messenger)), + address( + new L1ScrollMessengerNonETHForTest( + address(l1GasTokenGateway), + address(l2Messenger), + address(rollup), + address(l1MessageQueue) + ) + ) + ); + L1ScrollMessengerNonETHForTest(payable(address(l1Messenger))).setMessageSendTimestamp(keccak256(encoded), 1); + l1MessageQueue.setL2BaseFee(0); + + // revert ErrorDuplicatedMessage + vm.expectRevert(L1ScrollMessengerNonETH.ErrorDuplicatedMessage.selector); + l1Messenger.sendMessage(target, 0, message, gasLimit); + } + + function testSendMessage( + uint256 l2BaseFee, + address target, + uint256 gasLimit, + bytes memory message, + uint256 exceedValue, + address refundAddress + ) external { + vm.assume(refundAddress.code.length == 0); // only refund to EOA to avoid revert + vm.assume(uint256(uint160(refundAddress)) > 2**152); // ignore some precompile contracts + vm.assume(refundAddress != l1FeeVault); + + uint256 NONZERO_TIMESTAMP = 123456; + vm.warp(NONZERO_TIMESTAMP); + + bytes memory encoded0 = encodeXDomainCalldata(address(this), target, 0, 0, message); + bytes memory encoded1 = encodeXDomainCalldata(address(this), target, 0, 1, message); + bytes memory encoded2 = encodeXDomainCalldata(address(this), target, 0, 2, message); + vm.assume(encoded0.length < 60000); + + gasLimit = bound(gasLimit, encoded0.length * 16 + 21000, 1000000); + exceedValue = bound(exceedValue, 1, address(this).balance / 2); + l2BaseFee = bound(l2BaseFee, 1, 1 ether); + + l1MessageQueue.setL2BaseFee(l2BaseFee); + + assertEq(l1MessageQueue.nextCrossDomainMessageIndex(), 0); + + // send message 0, exact fee + // emit QueueTransaction from L1MessageQueue + { + vm.expectEmit(true, true, false, true); + address sender = AddressAliasHelper.applyL1ToL2Alias(address(l1Messenger)); + emit QueueTransaction(sender, address(l2Messenger), 0, 0, gasLimit, encoded0); + } + // emit SentMessage from L1ScrollMessengerNonETH + { + vm.expectEmit(true, true, false, true); + emit SentMessage(address(this), target, 0, 0, gasLimit, message); + } + uint256 thisBalance = address(this).balance; + assertEq(l1Messenger.messageSendTimestamp(keccak256(encoded0)), 0); + l1Messenger.sendMessage{value: gasLimit * l2BaseFee}(target, 0, message, gasLimit); + assertEq(address(this).balance, thisBalance - gasLimit * l2BaseFee); + assertEq(l1MessageQueue.nextCrossDomainMessageIndex(), 1); + assertEq(l1Messenger.messageSendTimestamp(keccak256(encoded0)), NONZERO_TIMESTAMP); + + // send message 1, over fee, refund to self + // emit QueueTransaction from L1MessageQueue + { + vm.expectEmit(true, true, false, true); + address sender = AddressAliasHelper.applyL1ToL2Alias(address(l1Messenger)); + emit QueueTransaction(sender, address(l2Messenger), 0, 1, gasLimit, encoded1); + } + // emit SentMessage from L1ScrollMessengerNonETH + { + vm.expectEmit(true, true, false, true); + emit SentMessage(address(this), target, 0, 1, gasLimit, message); + } + thisBalance = address(this).balance; + assertEq(l1Messenger.messageSendTimestamp(keccak256(encoded1)), 0); + l1Messenger.sendMessage{value: gasLimit * l2BaseFee + exceedValue}(target, 0, message, gasLimit); + assertEq(address(this).balance, thisBalance - gasLimit * l2BaseFee); + assertEq(l1MessageQueue.nextCrossDomainMessageIndex(), 2); + assertEq(l1Messenger.messageSendTimestamp(keccak256(encoded1)), NONZERO_TIMESTAMP); + + // send message 2, over fee, refund to other + // emit QueueTransaction from L1MessageQueue + { + vm.expectEmit(true, true, false, true); + address sender = AddressAliasHelper.applyL1ToL2Alias(address(l1Messenger)); + emit QueueTransaction(sender, address(l2Messenger), 0, 2, gasLimit, encoded2); + } + // emit SentMessage from L1ScrollMessengerNonETH + { + vm.expectEmit(true, true, false, true); + emit SentMessage(address(this), target, 0, 2, gasLimit, message); + } + thisBalance = address(this).balance; + uint256 refundBalance = refundAddress.balance; + assertEq(l1Messenger.messageSendTimestamp(keccak256(encoded2)), 0); + l1Messenger.sendMessage{value: gasLimit * l2BaseFee + exceedValue}(target, 0, message, gasLimit, refundAddress); + assertEq(address(this).balance, thisBalance - gasLimit * l2BaseFee - exceedValue); + assertEq(refundAddress.balance, refundBalance + exceedValue); + assertEq(l1MessageQueue.nextCrossDomainMessageIndex(), 3); + assertEq(l1Messenger.messageSendTimestamp(keccak256(encoded2)), NONZERO_TIMESTAMP); + } + + function testRelayMessageWithProofRevertOnErrorNonZeroValueFromCrossDomainCaller( + address sender, + address target, + uint256 value, + uint256 nonce, + bytes memory message, + IL1ScrollMessenger.L2MessageProof memory proof + ) external { + vm.assume(value > 0); + vm.assume(target != address(l1GasTokenGateway)); + + // revert ErrorNonZeroValueFromCrossDomainCaller + vm.expectRevert(L1ScrollMessengerNonETH.ErrorNonZeroValueFromCrossDomainCaller.selector); + l1Messenger.relayMessageWithProof(sender, target, value, nonce, message, proof); + } + + function testRelayMessageWithProofRevertOnErrorMessageExecuted( + address sender, + address target, + uint256 nonce, + bytes memory message + ) external { + vm.assume(target.code.length == 0); // only refund to EOA to avoid revert + vm.assume(uint256(uint160(target)) > 2**152); // ignore some precompile contracts + vm.assume(uint256(uint160(sender)) > 2**152); // ignore some precompile contracts + + prepareFinalizedBatch(keccak256(encodeXDomainCalldata(sender, target, 0, nonce, message))); + IL1ScrollMessenger.L2MessageProof memory proof; + proof.batchIndex = rollup.lastFinalizedBatchIndex(); + + l1Messenger.relayMessageWithProof(sender, target, 0, nonce, message, proof); + + // revert ErrorMessageExecuted + vm.expectRevert(L1ScrollMessengerNonETH.ErrorMessageExecuted.selector); + l1Messenger.relayMessageWithProof(sender, target, 0, nonce, message, proof); + } + + function testRelayMessageWithProofRevertOnErrorBatchNotFinalized( + address sender, + address target, + uint256 nonce, + bytes memory message + ) external { + vm.assume(target.code.length == 0); // only refund to EOA to avoid revert + vm.assume(uint256(uint160(target)) > 2**152); // ignore some precompile contracts + + prepareFinalizedBatch(keccak256(encodeXDomainCalldata(sender, target, 0, nonce, message))); + IL1ScrollMessenger.L2MessageProof memory proof; + proof.batchIndex = rollup.lastFinalizedBatchIndex() + 1; + + // revert ErrorBatchNotFinalized + vm.expectRevert(L1ScrollMessengerNonETH.ErrorBatchNotFinalized.selector); + l1Messenger.relayMessageWithProof(sender, target, 0, nonce, message, proof); + } + + function testRelayMessageWithProofRevertOnErrorInvalidMerkleProof( + address sender, + address target, + uint256 nonce, + bytes memory message, + IL1ScrollMessenger.L2MessageProof memory proof + ) external { + vm.assume(target.code.length == 0); // only refund to EOA to avoid revert + vm.assume(uint256(uint160(target)) > 2**152); // ignore some precompile contracts + vm.assume(proof.merkleProof.length > 0); + vm.assume(proof.merkleProof.length % 32 == 0); + + prepareFinalizedBatch(keccak256(encodeXDomainCalldata(sender, target, 0, nonce, message))); + proof.batchIndex = rollup.lastFinalizedBatchIndex(); + + // revert ErrorInvalidMerkleProof + vm.expectRevert(L1ScrollMessengerNonETH.ErrorInvalidMerkleProof.selector); + l1Messenger.relayMessageWithProof(sender, target, 0, nonce, message, proof); + } + + function testRelayMessageWithProofRevertOnErrorForbidToCallMessageQueue( + address sender, + uint256 nonce, + bytes memory message + ) external { + address target = address(l1MessageQueue); + + prepareFinalizedBatch(keccak256(encodeXDomainCalldata(sender, target, 0, nonce, message))); + IL1ScrollMessenger.L2MessageProof memory proof; + proof.batchIndex = rollup.lastFinalizedBatchIndex(); + + // revert ErrorForbidToCallMessageQueue + vm.expectRevert(L1ScrollMessengerNonETH.ErrorForbidToCallMessageQueue.selector); + l1Messenger.relayMessageWithProof(sender, target, 0, nonce, message, proof); + } + + function testRelayMessageWithProofRevertOnCallSelfFromL2( + address sender, + uint256 nonce, + bytes memory message + ) external { + address target = address(l1Messenger); + + prepareFinalizedBatch(keccak256(encodeXDomainCalldata(sender, target, 0, nonce, message))); + IL1ScrollMessenger.L2MessageProof memory proof; + proof.batchIndex = rollup.lastFinalizedBatchIndex(); + + // revert when call self + vm.expectRevert("Forbid to call self"); + l1Messenger.relayMessageWithProof(sender, target, 0, nonce, message, proof); + } + + function testRelayMessageWithProofRevertOnErrorInvalidMessageSender( + address target, + uint256 nonce, + bytes memory message + ) external { + vm.assume(target.code.length == 0); // only refund to EOA to avoid revert + vm.assume(uint256(uint160(target)) > 2**152); // ignore some precompile contracts + address sender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER; + + prepareFinalizedBatch(keccak256(encodeXDomainCalldata(sender, target, 0, nonce, message))); + IL1ScrollMessenger.L2MessageProof memory proof; + proof.batchIndex = rollup.lastFinalizedBatchIndex(); + + // revert ErrorInvalidMessageSender + vm.expectRevert(L1ScrollMessengerNonETH.ErrorInvalidMessageSender.selector); + l1Messenger.relayMessageWithProof(sender, target, 0, nonce, message, proof); + } + + bool revertOnRelayMessageWithProof; + + function onRelayMessageWithProof(bytes memory message) external payable { + emit OnRelayMessageWithProof(msg.value, message); + + if (revertOnRelayMessageWithProof) revert(); + } + + function testRelayMessageWithProofFailed( + address sender, + uint256 nonce, + bytes memory message + ) external { + vm.assume(sender != ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER); + + revertOnRelayMessageWithProof = true; + bytes memory encoded = abi.encodeCall(L1ScrollMessengerNonETHTest.onRelayMessageWithProof, (message)); + address target = address(this); + + bytes32 hash = keccak256(encodeXDomainCalldata(sender, target, 0, nonce, encoded)); + prepareFinalizedBatch(hash); + IL1ScrollMessenger.L2MessageProof memory proof; + proof.batchIndex = rollup.lastFinalizedBatchIndex(); + + assertEq(l1Messenger.isL2MessageExecuted(hash), false); + vm.expectEmit(true, false, false, true); + emit FailedRelayedMessage(hash); + l1Messenger.relayMessageWithProof(sender, target, 0, nonce, encoded, proof); + assertEq(l1Messenger.isL2MessageExecuted(hash), false); + } + + function testRelayMessageWithProofSucceed( + address sender, + uint256 nonce, + bytes memory message + ) external { + vm.assume(sender != ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER); + + revertOnRelayMessageWithProof = false; + bytes memory encoded = abi.encodeCall(L1ScrollMessengerNonETHTest.onRelayMessageWithProof, (message)); + address target = address(this); + + bytes32 hash = keccak256(encodeXDomainCalldata(sender, target, 0, nonce, encoded)); + prepareFinalizedBatch(hash); + IL1ScrollMessenger.L2MessageProof memory proof; + proof.batchIndex = rollup.lastFinalizedBatchIndex(); + + assertEq(l1Messenger.isL2MessageExecuted(hash), false); + vm.expectEmit(false, false, false, true); + emit OnRelayMessageWithProof(0, message); + vm.expectEmit(true, false, false, true); + emit RelayedMessage(hash); + l1Messenger.relayMessageWithProof(sender, target, 0, nonce, encoded, proof); + assertEq(l1Messenger.isL2MessageExecuted(hash), true); + } + + function onDropMessage(bytes memory message) external payable { + emit OnDropMessageCalled(msg.value, message); + } + + function testDropMessageRevertOnErrorMessageNotEnqueued( + address sender, + address target, + uint256 value, + uint256 messageNonce, + bytes memory message + ) external { + // revert on ErrorMessageNotEnqueued + vm.expectRevert(L1ScrollMessengerNonETH.ErrorMessageNotEnqueued.selector); + l1Messenger.dropMessage(sender, target, value, messageNonce, message); + } + + function testDropMessage( + address target, + bytes memory message, + uint32 gasLimit + ) external { + bytes memory encoded = encodeXDomainCalldata(address(this), target, 0, 0, message); + vm.assume(encoded.length < 60000); + gasLimit = uint32(bound(gasLimit, encoded.length * 16 + 21000, 1000000)); + + l1MessageQueue.setL2BaseFee(0); + + // send one message with nonce 0 + l1Messenger.sendMessage(target, 0, message, gasLimit); + assertEq(l1MessageQueue.nextCrossDomainMessageIndex(), 1); + + // drop pending message, revert + vm.expectRevert("cannot drop pending message"); + l1Messenger.dropMessage(address(this), target, 0, 0, message); + + l1Messenger.updateMaxReplayTimes(10); + + // replay 1 time + l1Messenger.replayMessage(address(this), target, 0, 0, message, gasLimit, address(0)); + assertEq(l1MessageQueue.nextCrossDomainMessageIndex(), 2); + + // skip all 2 messages + vm.startPrank(address(rollup)); + l1MessageQueue.popCrossDomainMessage(0, 2, 0x3); + l1MessageQueue.finalizePoppedCrossDomainMessage(2); + assertEq(l1MessageQueue.nextUnfinalizedQueueIndex(), 2); + assertEq(l1MessageQueue.pendingQueueIndex(), 2); + vm.stopPrank(); + for (uint256 i = 0; i < 2; ++i) { + assertEq(l1MessageQueue.isMessageSkipped(i), true); + assertEq(l1MessageQueue.isMessageDropped(i), false); + } + vm.expectEmit(false, false, false, true); + emit OnDropMessageCalled(0, message); + l1Messenger.dropMessage(address(this), target, 0, 0, message); + for (uint256 i = 0; i < 2; ++i) { + assertEq(l1MessageQueue.isMessageSkipped(i), true); + assertEq(l1MessageQueue.isMessageDropped(i), true); + } + + // send one message with nonce 2 and replay 3 times + l1Messenger.sendMessage(target, 0, message, gasLimit); + assertEq(l1MessageQueue.nextCrossDomainMessageIndex(), 3); + for (uint256 i = 0; i < 3; i++) { + l1Messenger.replayMessage(address(this), target, 0, 2, message, gasLimit, address(0)); + } + assertEq(l1MessageQueue.nextCrossDomainMessageIndex(), 6); + + // only first 3 are skipped + vm.startPrank(address(rollup)); + l1MessageQueue.popCrossDomainMessage(2, 4, 0x7); + l1MessageQueue.finalizePoppedCrossDomainMessage(6); + assertEq(l1MessageQueue.nextUnfinalizedQueueIndex(), 6); + assertEq(l1MessageQueue.pendingQueueIndex(), 6); + vm.stopPrank(); + for (uint256 i = 2; i < 6; i++) { + assertEq(l1MessageQueue.isMessageSkipped(i), i < 5); + assertEq(l1MessageQueue.isMessageDropped(i), false); + } + + // drop non-skipped message, revert + vm.expectRevert("drop non-skipped message"); + l1Messenger.dropMessage(address(this), target, 0, 2, message); + + // send one message with nonce 6 and replay 4 times + l1Messenger.sendMessage(target, 0, message, gasLimit); + for (uint256 i = 0; i < 4; i++) { + l1Messenger.replayMessage(address(this), target, 0, 6, message, gasLimit, address(0)); + } + assertEq(l1MessageQueue.nextCrossDomainMessageIndex(), 11); + + // skip all 5 messages + vm.startPrank(address(rollup)); + l1MessageQueue.popCrossDomainMessage(6, 5, 0x1f); + l1MessageQueue.finalizePoppedCrossDomainMessage(11); + assertEq(l1MessageQueue.nextUnfinalizedQueueIndex(), 11); + assertEq(l1MessageQueue.pendingQueueIndex(), 11); + vm.stopPrank(); + for (uint256 i = 6; i < 11; ++i) { + assertEq(l1MessageQueue.isMessageSkipped(i), true); + assertEq(l1MessageQueue.isMessageDropped(i), false); + } + vm.expectEmit(false, false, false, true); + emit OnDropMessageCalled(0, message); + l1Messenger.dropMessage(address(this), target, 0, 6, message); + for (uint256 i = 6; i < 11; ++i) { + assertEq(l1MessageQueue.isMessageSkipped(i), true); + assertEq(l1MessageQueue.isMessageDropped(i), true); + } + + // Message already dropped, revert + vm.expectRevert(L1ScrollMessengerNonETH.ErrorMessageDropped.selector); + l1Messenger.dropMessage(address(this), target, 0, 0, message); + vm.expectRevert(L1ScrollMessengerNonETH.ErrorMessageDropped.selector); + l1Messenger.dropMessage(address(this), target, 0, 6, message); + + // replay dropped message, revert + vm.expectRevert("Message already dropped"); + l1Messenger.replayMessage(address(this), target, 0, 0, message, gasLimit, address(0)); + vm.expectRevert("Message already dropped"); + l1Messenger.replayMessage(address(this), target, 0, 6, message, gasLimit, address(0)); + } +} diff --git a/src/test/alternative-gas-token/L1WrappedTokenGateway.t.sol b/src/test/alternative-gas-token/L1WrappedTokenGateway.t.sol new file mode 100644 index 0000000..6d9fcda --- /dev/null +++ b/src/test/alternative-gas-token/L1WrappedTokenGateway.t.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: MIT + +pragma solidity =0.8.24; + +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; +import {WETH} from "solmate/tokens/WETH.sol"; + +import {ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import {L1WrappedTokenGateway} from "../../alternative-gas-token/L1WrappedTokenGateway.sol"; +import {L1StandardERC20Gateway} from "../../L1/gateways/L1StandardERC20Gateway.sol"; +import {L2StandardERC20Gateway} from "../../L2/gateways/L2StandardERC20Gateway.sol"; +import {ScrollStandardERC20} from "../../libraries/token/ScrollStandardERC20.sol"; +import {ScrollStandardERC20Factory} from "../../libraries/token/ScrollStandardERC20Factory.sol"; + +import {AlternativeGasTokenTestBase} from "./AlternativeGasTokenTestBase.t.sol"; + +contract L1WrappedTokenGatewayTest is AlternativeGasTokenTestBase { + event OnDropMessageCalled(uint256, bytes); + + event OnRelayMessageWithProof(uint256, bytes); + + MockERC20 private gasToken; + + ScrollStandardERC20 private template; + ScrollStandardERC20Factory private factory; + + L1StandardERC20Gateway private l1ERC20Gateway; + L2StandardERC20Gateway private l2ERC20Gateway; + + WETH private weth; + L1WrappedTokenGateway private gateway; + + receive() external payable {} + + function setUp() external { + gasToken = new MockERC20("X", "Y", 18); + + __AlternativeGasTokenTestBase_setUp(1234, address(gasToken)); + + template = new ScrollStandardERC20(); + factory = new ScrollStandardERC20Factory(address(template)); + l1ERC20Gateway = L1StandardERC20Gateway(_deployProxy(address(0))); + l2ERC20Gateway = L2StandardERC20Gateway(_deployProxy(address(0))); + + admin.upgrade( + ITransparentUpgradeableProxy(address(l1ERC20Gateway)), + address( + new L1StandardERC20Gateway( + address(l2ERC20Gateway), + address(l1Router), + address(l1Messenger), + address(template), + address(factory) + ) + ) + ); + admin.upgrade( + ITransparentUpgradeableProxy(address(l2ERC20Gateway)), + address( + new L2StandardERC20Gateway( + address(l1ERC20Gateway), + address(l2Router), + address(l2Messenger), + address(factory) + ) + ) + ); + + weth = new WETH(); + gateway = new L1WrappedTokenGateway(address(weth), address(l1ERC20Gateway)); + } + + function testInitialization() external view { + assertEq(gateway.WETH(), address(weth)); + assertEq(gateway.gateway(), address(l1ERC20Gateway)); + assertEq(gateway.sender(), address(1)); + } + + function testReceive(uint256 amount) external { + amount = bound(amount, 0, address(this).balance); + + vm.expectRevert(L1WrappedTokenGateway.ErrorCallNotFromFeeRefund.selector); + payable(address(gateway)).transfer(amount); + } + + function testDeposit( + uint256 amount, + address recipient, + uint256 l2BaseFee, + uint256 exceedValue + ) external { + amount = bound(amount, 1, address(this).balance / 2); + l2BaseFee = bound(l2BaseFee, 0, 10**9); + exceedValue = bound(exceedValue, 0, 1 ether); + + l1MessageQueue.setL2BaseFee(l2BaseFee); + uint256 fee = l2BaseFee * 450000; + + uint256 ethBalance = address(this).balance; + uint256 wethBalance = weth.balanceOf(address(l1ERC20Gateway)); + gateway.deposit{value: amount + fee + exceedValue}(recipient, amount); + assertEq(ethBalance - amount - fee, address(this).balance); + assertEq(wethBalance + amount, weth.balanceOf(address(l1ERC20Gateway))); + } +} diff --git a/src/test/deterministic/DeployScroll.t.sol b/src/test/deterministic/DeployScroll.t.sol new file mode 100644 index 0000000..de069ca --- /dev/null +++ b/src/test/deterministic/DeployScroll.t.sol @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: MIT + +pragma solidity =0.8.24; + +import {TestBase} from "forge-std/Base.sol"; +import {StdAssertions} from "forge-std/StdAssertions.sol"; + +import {DeployScroll} from "../../../scripts/deterministic/DeployScroll.s.sol"; +import {DeterministicDeployment} from "../../../scripts/deterministic/DeterministicDeployment.sol"; + +// DeployScrollTest tests the deterministic addresses generated by the DeployScroll script. +// This test allows us to detect changes to the deterministic deployment addresses. +contract DeployScrollTest is TestBase, StdAssertions, DeployScroll { + function setUp() public { + // use a specific deployment salt + DEPLOYMENT_SALT = "test-123"; + + // skip reading config from file, work with default (empty) values + DeterministicDeployment.initialize(ScriptMode.EmptyConfig); + + // need to set this to a non-zero address + L1_PLONK_VERIFIER_ADDR = address(1); + } + + function testDefaultAddresses() public { + predictAllContracts(); + checkCommonAddresses(); + + assertEq(0x15046c2d09354551A19d9eaD13ca19A41AAa6345, L1_SCROLL_CHAIN_IMPLEMENTATION_ADDR); + assertEq(0x904699c3146cE384a5B47Ebf7Ddf8c592E203F2C, L1_ETH_GATEWAY_PROXY_ADDR); + assertEq(0x5e27Eea664f5aB1849025195DF2b7e619f904358, L1_WETH_GATEWAY_PROXY_ADDR); + assertEq(0x0000000000000000000000000000000000000000, L1_GAS_TOKEN_ADDR); + assertEq(0x0000000000000000000000000000000000000000, L1_GAS_TOKEN_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0x0000000000000000000000000000000000000000, L1_GAS_TOKEN_GATEWAY_PROXY_ADDR); + assertEq(0x0000000000000000000000000000000000000000, L1_WRAPPED_TOKEN_GATEWAY_ADDR); + assertEq(0x6D7Aff5a2D9bF44cE086199FEF11BD865E089f9d, L2_WETH_GATEWAY_PROXY_ADDR); + assertEq(0xd7aDC5e99A29f9f76E3F638Fd6a1C5e9692C2d69, L1_SCROLL_MESSENGER_IMPLEMENTATION_ADDR); + assertEq(0x19f984928A8c1c08e7411c29D3ed07694C8905Cb, L1_ETH_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0xFdDfA03a778fE77DDf0E1E479DEdCD1Aea37E808, L1_WETH_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0x3DAD8B5526C420Ae43bf1a7125643ea217A220A1, L2_ETH_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0xded0C0D084A9eb3bA7E86dC1C0826A13055F9d7d, L2_WETH_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0x9Fea3cb89B6E60b49048b7D66F66c8cD18f28d11, L2_TX_FEE_VAULT_ADDR); + } + + function testMockFinalizeAddresses() public { + TEST_ENV_MOCK_FINALIZE_ENABLED = true; + + predictAllContracts(); + checkCommonAddresses(); + + assertEq(0x123f428B99F6B65B335eb756a5abA2a59AB56F40, L1_SCROLL_CHAIN_IMPLEMENTATION_ADDR); + assertEq(0x904699c3146cE384a5B47Ebf7Ddf8c592E203F2C, L1_ETH_GATEWAY_PROXY_ADDR); + assertEq(0x5e27Eea664f5aB1849025195DF2b7e619f904358, L1_WETH_GATEWAY_PROXY_ADDR); + assertEq(0x0000000000000000000000000000000000000000, L1_GAS_TOKEN_ADDR); + assertEq(0x0000000000000000000000000000000000000000, L1_GAS_TOKEN_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0x0000000000000000000000000000000000000000, L1_GAS_TOKEN_GATEWAY_PROXY_ADDR); + assertEq(0x0000000000000000000000000000000000000000, L1_WRAPPED_TOKEN_GATEWAY_ADDR); + assertEq(0x6D7Aff5a2D9bF44cE086199FEF11BD865E089f9d, L2_WETH_GATEWAY_PROXY_ADDR); + assertEq(0xd7aDC5e99A29f9f76E3F638Fd6a1C5e9692C2d69, L1_SCROLL_MESSENGER_IMPLEMENTATION_ADDR); + assertEq(0x19f984928A8c1c08e7411c29D3ed07694C8905Cb, L1_ETH_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0xFdDfA03a778fE77DDf0E1E479DEdCD1Aea37E808, L1_WETH_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0x3DAD8B5526C420Ae43bf1a7125643ea217A220A1, L2_ETH_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0xded0C0D084A9eb3bA7E86dC1C0826A13055F9d7d, L2_WETH_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0x9Fea3cb89B6E60b49048b7D66F66c8cD18f28d11, L2_TX_FEE_VAULT_ADDR); + } + + function testAltGasTokenAddresses() public { + ALTERNATIVE_GAS_TOKEN_ENABLED = true; + + predictAllContracts(); + checkCommonAddresses(); + + assertEq(0x15046c2d09354551A19d9eaD13ca19A41AAa6345, L1_SCROLL_CHAIN_IMPLEMENTATION_ADDR); + assertEq(0x0000000000000000000000000000000000000000, L1_ETH_GATEWAY_PROXY_ADDR); + assertEq(0x0000000000000000000000000000000000000000, L1_WETH_GATEWAY_PROXY_ADDR); + assertEq(0xd7919F1390711D610961cb27D2BE0BD2Ec1E5704, L1_GAS_TOKEN_ADDR); + assertEq(0xfFfbB2b3Df00048D4fE12f342D425C98ca709450, L1_GAS_TOKEN_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0x4F5264c7a2A14B4D68C40369255A8f07D91D8a68, L1_GAS_TOKEN_GATEWAY_PROXY_ADDR); + assertEq(0x78664dbFB260D8053f49F1241ffCe06B0C3a533e, L1_WRAPPED_TOKEN_GATEWAY_ADDR); + assertEq(0x0000000000000000000000000000000000000000, L2_WETH_GATEWAY_PROXY_ADDR); + assertEq(0xEf4d0C615D480d20475d50A5127e74C9B704E563, L1_SCROLL_MESSENGER_IMPLEMENTATION_ADDR); + assertEq(0x0000000000000000000000000000000000000000, L1_ETH_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0x0000000000000000000000000000000000000000, L1_WETH_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0xFA99FF03e6Dd89e12aaF70DA363D02B5Ab5c2d8F, L2_ETH_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0x0000000000000000000000000000000000000000, L2_WETH_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0x747d2EB150F88EBA24Dc99841baA19E56e2BF901, L2_TX_FEE_VAULT_ADDR); + } + + function checkCommonAddresses() internal view { + assertEq(0x157A45eF5dFAb2C26b8905077c08A9F2018f48FD, L1_WETH_ADDR); + assertEq(0x4Bd916ecac9c5DBd6f15208c8632802Fd2c49e82, L1_PROXY_ADMIN_ADDR); + assertEq(0x28b7e53497D08F70476001cc41C719e68425D161, L1_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR); + assertEq(0x602EEf44E8898cC7Cd2e1f54B01E77B4Ca855b8C, L1_WHITELIST_ADDR); + assertEq(0x7C68fab1e8c32A321069866b6F1D4403F05C5f44, L1_SCROLL_CHAIN_PROXY_ADDR); + assertEq(0xC97658507021A2EB494298CAA815B83BC3DE935b, L1_SCROLL_MESSENGER_PROXY_ADDR); + assertEq(0x02476A470215Bd2F268179492431230C5Dc607C8, L1_ENFORCED_TX_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0xcCBBecF7B9D6e1CD2dF1aEFab510c0697d04AC1f, L1_ENFORCED_TX_GATEWAY_PROXY_ADDR); + assertEq(0x7e3852FA81eB752e20F2Ef22dC31eaF0E79dB1C1, L1_ZKEVM_VERIFIER_V2_ADDR); + assertEq(0x2Fd0514AfEC744cA51fF5FC3567Bf4CeaB4356f7, L1_MULTIPLE_VERSION_ROLLUP_VERIFIER_ADDR); + assertEq(0x650522433606B6f232cA0C8d0D5245b6571c180a, L1_MESSAGE_QUEUE_IMPLEMENTATION_ADDR); + assertEq(0x09a4C2780ECBfF76a58957b93ea66D9727eF6A4C, L1_MESSAGE_QUEUE_PROXY_ADDR); + assertEq(0xd18137D9b43061477C3DA4453E2F3D1B03453efc, L1_GATEWAY_ROUTER_IMPLEMENTATION_ADDR); + assertEq(0xdE659359688d66932d951A75C31c106D3105a55E, L1_GATEWAY_ROUTER_PROXY_ADDR); + assertEq(0xeF4e9d1C5CFd6A007D3353E1A7Beed94b645709E, L1_STANDARD_ERC20_GATEWAY_PROXY_ADDR); + assertEq(0xEf32c7F1326e781b0f04C54883Eedb0d6a9A2646, L1_CUSTOM_ERC20_GATEWAY_PROXY_ADDR); + assertEq(0x0c65C12Ac7490627A84D8A55a08FE0da7C6Ec850, L1_ERC721_GATEWAY_PROXY_ADDR); + assertEq(0x610b392EBBdA6cf34140ffFD8522D0A01cF4d8A6, L1_ERC1155_GATEWAY_PROXY_ADDR); + assertEq(0xDEa0476e9F32C3218ADF8823eba61995D5f5558B, L2_MESSAGE_QUEUE_ADDR); + assertEq(0x70E868415c7532E436d67b7aE417Bd1551142A9d, L1_GAS_PRICE_ORACLE_ADDR); + assertEq(0xaFbB29CC183006748fBF1d6a50426699801B3f6e, L2_WHITELIST_ADDR); + assertEq(0x26fE70D2D9aA9f3DF3031A8a36bd7585b56f3e1c, L2_WETH_ADDR); + assertEq(0x639F9169025652D95b5c30080ee65177ff539e2d, L2_PROXY_ADMIN_ADDR); + assertEq(0x1454F7362F8b982177b74CAc02F0428ef6044d27, L2_PROXY_IMPLEMENTATION_PLACEHOLDER_ADDR); + assertEq(0xEE6FE96bA71AdCB8AE724032F7a9DB8A3e7f87E7, L2_SCROLL_MESSENGER_PROXY_ADDR); + assertEq(0xD855e6939648a2625DDfBE97070Ec7C48FC5F68c, L2_ETH_GATEWAY_PROXY_ADDR); + assertEq(0xA0E064607751ef6BfC0d1257438aD021962cD641, L2_STANDARD_ERC20_GATEWAY_PROXY_ADDR); + assertEq(0x96ae0854179483429E89F7507642Aa83B28ebB9e, L2_CUSTOM_ERC20_GATEWAY_PROXY_ADDR); + assertEq(0xc1DC112B26e925Ce0f797Ea6e3438871dc7Be72D, L2_ERC721_GATEWAY_PROXY_ADDR); + assertEq(0x2B04b351d5BD976A739DE30190bE5fEEf3145C27, L2_ERC1155_GATEWAY_PROXY_ADDR); + assertEq(0x923e805f714b2cE849DB9D7E76C9C3ddCa907102, L2_SCROLL_STANDARD_ERC20_ADDR); + assertEq(0x00C080bc75e59b1e56331a5D15FC1aB50766B4D2, L2_SCROLL_STANDARD_ERC20_FACTORY_ADDR); + assertEq(0x8b284df9bDfC51029323788111ABCf26a2EDcA2C, L1_STANDARD_ERC20_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0x7d1802E55Bb5563aFAf7Dd196E85Bd5af297EFd9, L1_CUSTOM_ERC20_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0x8EC6F24852F44f4d329aC981cAD28c06C99aFCF3, L1_ERC721_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0x6a7d33D8eD31dDf30eB9cb0f6DBFF7e18A129959, L1_ERC1155_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0x89fE9D485E04097E8eAf2bb3Ed9d61F506c71d1B, L2_SCROLL_MESSENGER_IMPLEMENTATION_ADDR); + assertEq(0xb2367f9a1F5F190788676743176F2b572F285c91, L2_GATEWAY_ROUTER_IMPLEMENTATION_ADDR); + assertEq(0x63d77812d2b762F329DB713c0030AC85A388B7f2, L2_GATEWAY_ROUTER_PROXY_ADDR); + assertEq(0xDbe21F0Cf7c8F4F912cD29f9932311C49975a0b7, L2_STANDARD_ERC20_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0x59afd1A478Cc0336EE8e97d10b6f59E64089eB4D, L2_CUSTOM_ERC20_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0x82206B0f4c561e7E4780E824833217D1b371D4a9, L2_ERC721_GATEWAY_IMPLEMENTATION_ADDR); + assertEq(0x505cf5aEC35CED635Bf24eE5F15B7634D15A2a67, L2_ERC1155_GATEWAY_IMPLEMENTATION_ADDR); + } +}