diff --git a/.circleci/config.yml b/.circleci/config.yml index 5250db39416..db70f0527b3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -140,6 +140,21 @@ commands: # Configure ADC echo "export GOOGLE_APPLICATION_CREDENTIALS='<< parameters.gcp_cred_config_file_path >>'" | tee -a "$BASH_ENV" + clean-old-acceptor-logs: + description: "Delete op-acceptor testrun logs older than 60 days (prevents disk bloat across workspace attaches/reruns)." + steps: + - run: + name: Cleanup old op-acceptor testrun logs (>60 days) + command: | + set -eu + for base in "op-acceptor/logs" "op-acceptance-tests/logs"; do + if [ -d "$base" ]; then + echo "Scanning $base for old testrun-* directories..." + # Remove any testrun-* directories older than 60 days + find "$base" -type d -name 'testrun-*' -mtime +60 -print -exec rm -rf {} + + fi + done + check-changed: description: "Conditionally halts a step if certain modules change" parameters: @@ -160,7 +175,6 @@ commands: pip3 install -r requirements.txt python3 main.py "<>" - install-solc-compilers: description: "Install the solc compilers" parameters: @@ -245,33 +259,46 @@ commands: echo "Resolved TARGET_BRANCH=$TARGET_BRANCH" echo "export TARGET_BRANCH=$TARGET_BRANCH" >> "$BASH_ENV" - setup-dev-features: - description: "Set up dev feature environment variables from comma-separated list" + setup-features: + description: "Set up dev and system feature environment variables. Features are auto-classified based on system_features registry." parameters: - dev_features: - description: "Comma-separated list of dev features to enable" + features: + description: "Comma-separated list of features (can mix dev and system features, e.g., 'OPTIMISM_PORTAL_INTEROP,CUSTOM_GAS_TOKEN')" type: string default: "" + system_features: + description: "Registry of system features (others treated as dev features)" + type: string + default: "CUSTOM_GAS_TOKEN" steps: - run: - name: Set dev feature environment variables + name: Set feature environment variables command: | - # Set dev feature environment variables if provided - if [ -n "<>" ]; then - DEV_FEATURES_STRING="<>" + # Define which features are system features (registry) + SYSTEM_FEATURES="<>" + + if [ -n "<>" ]; then + FEATURES_STRING="<>" - # Check if this is just "main" (baseline with no dev features) - if [ "$(echo "$DEV_FEATURES_STRING" | tr '[:upper:]' '[:lower:]')" = "main" ]; then - echo "Running with baseline configuration (no dev features enabled)" + # Check if this is just "main" (baseline with no features) + if [ "$(echo "$FEATURES_STRING" | tr '[:upper:]' '[:lower:]')" = "main" ]; then + echo "Running with baseline configuration (no features enabled)" else - echo "Enabling dev features: <>" + echo "Processing features: <>" IFS=',' - for feature in $DEV_FEATURES_STRING; do + for feature in $FEATURES_STRING; do feature=$(echo "$feature" | xargs) # trim whitespace if [ -n "$feature" ] && [ "$(echo "$feature" | tr '[:upper:]' '[:lower:]')" != "main" ]; then - env_var="DEV_FEATURE__${feature}" - echo "Setting ${env_var}=true" - echo "export ${env_var}=true" >> $BASH_ENV + # Check if this feature is in the system features registry + if echo "$SYSTEM_FEATURES" | grep -qw "$feature"; then + env_var="SYS_FEATURE__${feature}" + echo "Setting ${env_var}=true (system feature)" + echo "export ${env_var}=true" >> $BASH_ENV + else + env_var="DEV_FEATURE__${feature}" + echo "Setting ${env_var}=true (dev feature)" + echo "export ${env_var}=true" >> $BASH_ENV + fi fi done unset IFS @@ -492,7 +519,7 @@ jobs: default: false steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - attach_workspace: at: . - check-changed: @@ -544,7 +571,7 @@ jobs: resource_class: xlarge steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - run: name: Check `RISCV.sol` bytecode working_directory: packages/contracts-bedrock @@ -594,7 +621,7 @@ jobs: default: ci steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - install-zstd - install-contracts-dependencies - run: @@ -692,7 +719,9 @@ jobs: docker_layer_caching: true # we rely on this for faster builds, and actively warm it up for builds with common stages steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless + - attach_workspace: + at: . - run: command: mkdir -p /tmp/docker_images - when: @@ -913,13 +942,13 @@ jobs: description: List of changed files to run tests on type: string default: contracts-bedrock - dev_features: - description: Comma-separated list of dev features to enable (e.g., "OPTIMISM_PORTAL_INTEROP,ANOTHER_FEATURE") + features: + description: Comma-separated list of features to enable (e.g., "OPTIMISM_PORTAL_INTEROP", "CUSTOM_GAS_TOKEN") type: string default: "" steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: full - install-zstd - run: name: Check if test list is empty @@ -949,8 +978,8 @@ jobs: working_directory: packages/contracts-bedrock - go-save-cache: namespace: packages/contracts-bedrock/scripts/go-ffi - - setup-dev-features: - dev_features: <> + - setup-features: + features: <> - run: name: Run tests command: | @@ -983,7 +1012,7 @@ jobs: resource_class: 2xlarge steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: full - install-contracts-dependencies - install-zstd - run: @@ -1032,7 +1061,7 @@ jobs: resource_class: medium steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - run: name: Check Python version command: python3 --version @@ -1046,21 +1075,8 @@ jobs: destination: log.json - run: name: Prepare Slack notification - command: | - set -eo pipefail - LOG_FILE="ops/ai-eng/contracts-test-maintenance/log.json" - - PR_URL=$(jq -r '.pull_request_url // empty' "$LOG_FILE") - TEST_FILE=$(jq -r '.selected_files.test_path | split("/") | .[-1]' "$LOG_FILE") - - if [ -n "$PR_URL" ]; then - MESSAGE=$' AI Contracts Test Maintenance System created a PR for '"${TEST_FILE}"$'\n<'"${PR_URL}"$'|View PR> | ' - SLACK_JSON=$(jq -n --arg msg "$MESSAGE" '{"text": $msg}') - echo "export AI_PR_SLACK_TEMPLATE='${SLACK_JSON}'" >> $BASH_ENV - else - echo "No PR created, skipping notification" - echo "export AI_PR_SLACK_TEMPLATE=''" >> $BASH_ENV - fi + command: just prepare-slack-notification >> $BASH_ENV + working_directory: ops/ai-eng when: always - slack/notify: channel: C050F1GUHDG @@ -1086,13 +1102,13 @@ jobs: description: Profile to use for testing type: string default: ci - dev_features: - description: Comma-separated list of dev features to enable (e.g., "OPTIMISM_PORTAL_INTEROP,ANOTHER_FEATURE") + features: + description: Comma-separated list of features to enable (e.g., "OPTIMISM_PORTAL_INTEROP", "CUSTOM_GAS_TOKEN") type: string default: "" steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: full - install-contracts-dependencies - install-zstd - attach_workspace: @@ -1123,8 +1139,8 @@ jobs: - restore_cache: name: Restore forked state key: forked-state-contracts-bedrock-tests-upgrade-{{ checksum "packages/contracts-bedrock/pinnedBlockNumber.txt" }} - - setup-dev-features: - dev_features: <> + - setup-features: + features: <> - run: name: Build go-ffi command: just build-go-ffi @@ -1178,8 +1194,8 @@ jobs: fork_base_rpc: description: Fork Base RPC type: string - dev_features: - description: Comma-separated list of dev features to enable (e.g., "OPTIMISM_PORTAL_INTEROP,ANOTHER_FEATURE") + features: + description: Comma-separated list of features to enable (e.g., "OPTIMISM_PORTAL_INTEROP", "CUSTOM_GAS_TOKEN") type: string default: "" docker: @@ -1214,8 +1230,8 @@ jobs: - restore_cache: name: Restore forked state key: forked-state-contracts-bedrock-tests-upgrade-<>-<>-{{ checksum "packages/contracts-bedrock/pinnedBlockNumber.txt" }} - - setup-dev-features: - dev_features: <> + - setup-features: + features: <> - run: name: Run tests command: just test-upgrade @@ -1353,7 +1369,7 @@ jobs: resource_class: xlarge steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - check-changed: patterns: "<>" - attach_workspace: @@ -1385,7 +1401,7 @@ jobs: resource_class: large steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - restore_cache: key: golangci-v1-{{ checksum ".golangci.yaml" }} - run: @@ -1434,7 +1450,7 @@ jobs: parallelism: <> steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - attach_workspace: at: . - restore_cache: @@ -1509,7 +1525,7 @@ jobs: resource_class: <> steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - attach_workspace: at: . - run: @@ -1573,7 +1589,7 @@ jobs: circleci_ip_ranges: true steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless # Restore cached Go modules - restore_cache: keys: @@ -1671,7 +1687,7 @@ jobs: resource_class: 2xlarge+ steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - attach_workspace: at: . # Restore cached Go modules @@ -1765,7 +1781,7 @@ jobs: parallelism: << pipeline.parameters.flake-shake-workers >> steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - restore_cache: keys: - go-mod-v1-{{ checksum "go.sum" }} @@ -1812,9 +1828,10 @@ jobs: resource_class: large steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - attach_workspace: at: . + - clean-old-acceptor-logs - run: name: Lint/Vet/Build op-acceptance-tests/cmd working_directory: op-acceptance-tests @@ -1863,7 +1880,7 @@ jobs: resource_class: large steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - run: name: Lint/Vet/Build op-acceptance-tests/cmd working_directory: op-acceptance-tests @@ -1921,14 +1938,15 @@ jobs: name: Slack - Sending Notification command: | set -euo pipefail - # The Slack orb conditionals evaluate at compile time; guard at runtime instead. if [ -z "${SLACK_BLOCKS_PAYLOAD:-}" ] || [ "${SLACK_BLOCKS_PAYLOAD}" = "[]" ]; then - echo "SLACK_BLOCKS is empty or doesn't exist. Skipping it..." - exit 0 + echo "SLACK_BLOCKS is empty or doesn't exist. Skipping Slack notification." + # Halt this job here to avoid invoking the Slack orb step. + circleci-agent step halt + else + printf '%s' "$SLACK_BLOCKS_PAYLOAD" | jq '.' > /tmp/blocks.json + jq -c '{blocks: .}' /tmp/blocks.json > /tmp/slack_template.json fi - echo "$SLACK_BLOCKS_PAYLOAD" | jq '.' > /tmp/blocks.json - jq -c '{blocks: .}' /tmp/blocks.json > /tmp/slack_template.json - echo 'export SLACK_TEMPLATE=$(cat /tmp/slack_template.json)' >> $BASH_ENV + echo 'export SLACK_TEMPLATE=$(cat /tmp/slack_template.json)' >> "$BASH_ENV" - slack/notify: channel: C03N11M0BBN # "notify-ci" channel event: always @@ -1942,7 +1960,7 @@ jobs: resource_class: large steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - run: name: Install tools command: | @@ -1965,7 +1983,7 @@ jobs: resource_class: xlarge steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - restore_cache: name: Restore cannon prestate cache key: cannon-prestate-{{ checksum "./cannon/bin/cannon" }}-{{ checksum "op-program/bin/op-program-client.elf" }} @@ -1993,7 +2011,7 @@ jobs: - image: <> steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - setup_remote_docker - run: name: Build prestates @@ -2009,7 +2027,7 @@ jobs: docker_layer_caching: true # we rely on this for faster builds, and actively warm it up for builds with common stages steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - restore_cache: name: Restore kona cache key: kona-prestate-{{ checksum "./kona/justfile" }}-{{ checksum "./kona/version.json" }} @@ -2032,7 +2050,7 @@ jobs: - image: <> steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - restore_cache: name: Restore kona host cache key: kona-host-{{ checksum "./kona/justfile" }}-{{ checksum "./kona/version.json" }} @@ -2205,7 +2223,7 @@ jobs: resource_class: xlarge steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - setup_remote_docker - run: name: Run Analyzer @@ -2219,7 +2237,7 @@ jobs: resource_class: large steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - run: name: Verify Compatibility command: | @@ -2232,7 +2250,7 @@ jobs: resource_class: large steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - check-changed: patterns: op-node - run: @@ -2245,7 +2263,7 @@ jobs: resource_class: large steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - check-changed: patterns: op-service - run: @@ -2257,7 +2275,7 @@ jobs: - image: <> steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - run: command: just check-forge-version working_directory: op-deployer @@ -2361,7 +2379,7 @@ jobs: resource_class: medium steps: - utils/checkout-with-mise: - checkout-method: blobless + checkout-method: blobless - install-contracts-dependencies - run: name: Build contracts @@ -2552,51 +2570,52 @@ workflows: - circleci-repo-readonly-authenticated-github-token - contracts-bedrock-tests: # Heavily fuzz any fuzz tests within added or modified test files. - name: contracts-bedrock-tests-heavy-fuzz-modified <> + name: contracts-bedrock-tests-heavy-fuzz-modified <> test_list: git diff origin/develop...HEAD --name-only --diff-filter=AM -- './test/**/*.t.sol' | sed 's|packages/contracts-bedrock/||' test_timeout: 1h test_profile: ciheavy - dev_features: <> + features: <> matrix: parameters: - dev_features: &dev_features_matrix + features: &features_matrix - main - OPTIMISM_PORTAL_INTEROP - CANNON_KONA,DEPLOY_V2_DISPUTE_GAMES - - CUSTOM_GAS_TOKEN - OPCM_V2 + - OPCM_V2,OPTIMISM_PORTAL_INTEROP + - CUSTOM_GAS_TOKEN context: - circleci-repo-readonly-authenticated-github-token - contracts-bedrock-tests: - name: contracts-bedrock-tests <> + name: contracts-bedrock-tests <> test_list: find test -name "*.t.sol" - dev_features: <> + features: <> matrix: parameters: - dev_features: *dev_features_matrix + features: *features_matrix context: - circleci-repo-readonly-authenticated-github-token check_changed_patterns: contracts-bedrock,op-node - contracts-bedrock-coverage: # Generate coverage reports. - name: contracts-bedrock-coverage <> + name: contracts-bedrock-coverage <> test_timeout: 1h test_profile: cicoverage - dev_features: <> + features: <> matrix: parameters: - dev_features: *dev_features_matrix + features: *features_matrix context: - circleci-repo-readonly-authenticated-github-token - contracts-bedrock-tests-upgrade: - name: contracts-bedrock-tests-upgrade op-mainnet <> + name: contracts-bedrock-tests-upgrade op-mainnet <> fork_op_chain: op fork_base_chain: mainnet fork_base_rpc: https://ci-mainnet-l1-archive.optimism.io - dev_features: <> + features: <> matrix: parameters: - dev_features: *dev_features_matrix + features: *features_matrix context: - circleci-repo-readonly-authenticated-github-token - contracts-bedrock-tests-upgrade: diff --git a/.github/actions/docker-build-prep/action.yml b/.github/actions/docker-build-prep/action.yml new file mode 100644 index 00000000000..8f44ee69ea2 --- /dev/null +++ b/.github/actions/docker-build-prep/action.yml @@ -0,0 +1,40 @@ +name: 'Docker Build Prep' +description: 'Prepare environment for docker builds (checkout, kona version, git versions)' + +outputs: + versions: + description: 'JSON object mapping image names to their GIT_VERSION' + value: ${{ steps.compute_versions.outputs.versions }} + kona_version: + description: 'KONA_VERSION from kona/version.json' + value: ${{ steps.kona.outputs.version }} + date: + description: 'Current date in YYYYMMDD format' + value: ${{ steps.date.outputs.date }} + +runs: + using: 'composite' + steps: + - name: Get date + id: date + shell: bash + run: | + DATE=$(date +%Y%m%d) + echo "date=$DATE" >> $GITHUB_OUTPUT + + - name: Read KONA_VERSION from kona/version.json + id: kona + shell: bash + run: | + KONA_VERSION=$(jq -r .version kona/version.json) + echo "version=$KONA_VERSION" >> $GITHUB_OUTPUT + echo "KONA_VERSION: $KONA_VERSION" + + - name: Compute GIT_VERSION for all images + id: compute_versions + shell: bash + run: | + VERSIONS=$(GIT_COMMIT="${{ github.sha }}" make compute-git-versions) + echo "versions=$VERSIONS" >> $GITHUB_OUTPUT + echo "Computed versions: $VERSIONS" + diff --git a/.github/workflows/branches.yaml b/.github/workflows/branches.yaml new file mode 100644 index 00000000000..ff1b7de0306 --- /dev/null +++ b/.github/workflows/branches.yaml @@ -0,0 +1,122 @@ +name: branch build + +on: + push: + branches: + - 'develop' + pull_request: + branches: + - 'develop' + paths: + - 'ops/docker/**' + - 'packages/contracts-bedrock/**' + - 'docker-bake.hcl' + - '.github/workflows/branches.yaml' + - 'ops/scripts/compute-git-versions.sh' + +jobs: + prep: + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + versions: ${{ steps.prep.outputs.versions }} + kona_version: ${{ steps.prep.outputs.kona_version }} + date: ${{ steps.prep.outputs.date }} + steps: + - name: Harden the runner + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2 + with: + egress-policy: audit + - name: Checkout + uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6 + with: + fetch-depth: 0 + - uses: ./.github/actions/docker-build-prep + id: prep + + local: + needs: prep + # only build if push to develop, or PR from a local branch (not a fork) + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) + strategy: + fail-fast: false + matrix: + image_name: + - op-node + - op-batcher + - op-deployer + - op-faucet + - op-program + - op-proposer + - op-challenger + - op-dispute-mon + - op-conductor + - da-server + - op-supervisor + - op-supernode + - op-test-sequencer + - cannon + - op-dripper + - op-interop-mon + uses: ethereum-optimism/factory/.github/workflows/docker.yaml@d04222c229c50320f513afe678b3264869ea11a9 + with: + mode: bake + image_name: ${{ matrix.image_name }} + bake_file: docker-bake.hcl + target: ${{ matrix.image_name }} + tag: ${{ github.event_name == 'push' && 'develop' || format('pr-{0}', github.event.pull_request.number) }} + gcp_project_id: ${{ vars.GCP_PROJECT_ID_OPLABS_TOOLS_ARTIFACTS }} + registry: us-docker.pkg.dev/oplabs-tools-artifacts/oss + env: | + GIT_VERSION=${{ fromJson(needs.prep.outputs.versions)[matrix.image_name] }} + KONA_VERSION=${{ needs.prep.outputs.kona_version }} + set: | + *.args.GIT_COMMIT=${{ github.sha }} + *.args.GIT_DATE=${{ needs.prep.outputs.date }} + permissions: + contents: read + id-token: write + attestations: write + + fork: + needs: prep + # only build if PR from a fork + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository + strategy: + fail-fast: false + matrix: + image_name: + - op-node + - op-batcher + - op-deployer + - op-faucet + - op-program + - op-proposer + - op-challenger + - op-dispute-mon + - op-conductor + - da-server + - op-supervisor + - op-supernode + - op-test-sequencer + - cannon + - op-dripper + - op-interop-mon + uses: ethereum-optimism/factory/.github/workflows/docker.yaml@d04222c229c50320f513afe678b3264869ea11a9 + with: + mode: bake + image_name: ${{ matrix.image_name }} + bake_file: docker-bake.hcl + target: ${{ matrix.image_name }} + tag: 24h + registry: ttl.sh/${{ github.sha }} + env: | + GIT_VERSION=${{ fromJson(needs.prep.outputs.versions)[matrix.image_name] }} + KONA_VERSION=${{ needs.prep.outputs.kona_version }} + set: | + *.args.GIT_COMMIT=${{ github.sha }} + *.args.GIT_DATE=${{ needs.prep.outputs.date }} + permissions: + contents: read + diff --git a/.github/workflows/protected.yaml b/.github/workflows/protected.yaml deleted file mode 100644 index 4dba40e36b6..00000000000 --- a/.github/workflows/protected.yaml +++ /dev/null @@ -1,81 +0,0 @@ -name: bake - -on: - push: - tags: - - '*' - branches: - - 'develop' - -jobs: - prep: - runs-on: ubuntu-latest - outputs: - sanitised_ref_name: ${{ steps.sanitize.outputs.ref_name }} - versions: ${{ steps.compute_versions.outputs.versions }} - kona_version: ${{ steps.kona.outputs.version }} - steps: - - name: harden-runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2 - with: - egress-policy: audit - - name: Checkout - uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6 - with: - fetch-depth: 0 # Need full history for git tag operations - - name: Sanitize ref_name - id: sanitize - run: echo "ref_name=$(echo ${{ github.ref_name }} | sed 's/[^a-zA-Z0-9.]/-/g')" >> $GITHUB_OUTPUT - - name: Read KONA_VERSION from kona/version.json - id: kona - run: | - KONA_VERSION=$(jq -r .version kona/version.json) - echo "version=$KONA_VERSION" >> $GITHUB_OUTPUT - echo "KONA_VERSION: $KONA_VERSION" - - name: Compute GIT_VERSION for all images - id: compute_versions - run: | - VERSIONS=$(GIT_COMMIT="${{ github.sha }}" make compute-git-versions) - echo "versions=$VERSIONS" >> $GITHUB_OUTPUT - echo "Computed versions: $VERSIONS" - - build: - needs: prep - strategy: - fail-fast: false - matrix: - image_name: - - op-node - - op-batcher - - op-deployer - - op-faucet - - op-program - - op-proposer - - op-challenger - - op-dispute-mon - - op-conductor - - da-server - - op-supervisor - - op-supernode - - op-test-sequencer - - cannon - - op-dripper - - op-interop-mon - uses: ethereum-optimism/factory/.github/workflows/docker-bake.yaml@2ff3f9bba03d59a6ad10fdf660ed253f53956188 - with: - image_name: ${{ matrix.image_name }} - bake_file: docker-bake.hcl - target: ${{ matrix.image_name }} - tag: ${{ needs.prep.outputs.sanitised_ref_name }} - gcp_project_id: ${{ vars.GCP_PROJECT_ID_OPLABS_TOOLS_ARTIFACTS }} - registry: us-docker.pkg.dev/oplabs-tools-artifacts/oss - env: | - GIT_VERSION=${{ fromJson(needs.prep.outputs.versions)[matrix.image_name] }} - KONA_VERSION=${{ needs.prep.outputs.kona_version }} - set: | - *.args.GIT_COMMIT=${{ github.sha }} - *.args.GIT_DATE=${{ github.event.head_commit.timestamp }} - permissions: - contents: read - id-token: write - attestations: write diff --git a/.github/workflows/tags.yaml b/.github/workflows/tags.yaml new file mode 100644 index 00000000000..dc5dd5984e8 --- /dev/null +++ b/.github/workflows/tags.yaml @@ -0,0 +1,53 @@ +name: tag build + +on: + push: + tags: + - '*/v*' # Match tags like op-node/v1.2.3 + +jobs: + prep: + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + image_name: ${{ steps.parse-tag.outputs.image_name }} + version: ${{ steps.parse-tag.outputs.version }} + versions: ${{ steps.prep.outputs.versions }} + kona_version: ${{ steps.prep.outputs.kona_version }} + steps: + - name: Harden the runner + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2 + with: + egress-policy: audit + - name: Checkout + uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6 + with: + fetch-depth: 0 + - uses: ./.github/actions/docker-build-prep + id: prep + - name: Parse tag + uses: ethereum-optimism/factory/actions/parse-tag@240b16167a5f5aa789270fa9c0efbfa9f010b7e7 + id: parse-tag + + release: + needs: prep + uses: ethereum-optimism/factory/.github/workflows/docker.yaml@d04222c229c50320f513afe678b3264869ea11a9 + with: + mode: bake + image_name: ${{ needs.prep.outputs.image_name }} + bake_file: docker-bake.hcl + target: ${{ needs.prep.outputs.image_name }} + tag: ${{ needs.prep.outputs.version }} + gcp_project_id: ${{ vars.GCP_PROJECT_ID_OPLABS_TOOLS_ARTIFACTS }} + registry: us-docker.pkg.dev/oplabs-tools-artifacts/oss + env: | + GIT_VERSION=${{ fromJson(needs.prep.outputs.versions)[needs.prep.outputs.image_name] }} + KONA_VERSION=${{ needs.prep.outputs.kona_version }} + set: | + *.args.GIT_COMMIT=${{ github.sha }} + *.args.GIT_DATE=${{ github.event.head_commit.timestamp }} + permissions: + contents: read + id-token: write + attestations: write diff --git a/.github/workflows/unprotected.yaml b/.github/workflows/unprotected.yaml deleted file mode 100644 index 6fc5e44296c..00000000000 --- a/.github/workflows/unprotected.yaml +++ /dev/null @@ -1,87 +0,0 @@ -name: bake (PR) - -on: - pull_request: - branches: - - 'develop' - paths: - - 'ops/docker/**' - - 'packages/contracts-bedrock/**' - - 'docker-bake.hcl' - - '.github/workflows/unprotected.yaml' - - 'ops/scripts/compute-git-versions.sh' - -jobs: - prep: - runs-on: ubuntu-latest - outputs: - versions: ${{ steps.compute_versions.outputs.versions }} - kona_version: ${{ steps.kona.outputs.version }} - date: ${{ steps.date.outputs.date }} - steps: - - name: Get date - id: date - run: | - DATE=$(date +%Y%m%d) - echo "date=$DATE" >> $GITHUB_OUTPUT - - name: harden-runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2 - with: - egress-policy: audit - - name: Checkout - uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6 - with: - fetch-depth: 0 # Need full history for git tag operations - - name: Read KONA_VERSION from kona/version.json - id: kona - run: | - KONA_VERSION=$(jq -r .version kona/version.json) - echo "version=$KONA_VERSION" >> $GITHUB_OUTPUT - echo "KONA_VERSION: $KONA_VERSION" - - name: Compute GIT_VERSION for all images - id: compute_versions - run: | - VERSIONS=$(GIT_COMMIT="${{ github.sha }}" make compute-git-versions) - echo "versions=$VERSIONS" >> $GITHUB_OUTPUT - echo "Computed versions: $VERSIONS" - - build: - needs: prep - strategy: - fail-fast: false - matrix: - image_name: - - op-node - - op-batcher - - op-deployer - - op-faucet - - op-program - - op-proposer - - op-challenger - - op-dispute-mon - - op-conductor - - da-server - - op-supervisor - - op-supernode - - op-test-sequencer - - cannon - - op-dripper - - op-interop-mon - uses: ethereum-optimism/factory/.github/workflows/docker-bake.yaml@2ff3f9bba03d59a6ad10fdf660ed253f53956188 - with: - image_name: ${{ matrix.image_name }} - bake_file: docker-bake.hcl - target: ${{ matrix.image_name }} - tag: 24h - registry: ttl.sh/${{ github.sha }} - push_provenance: false - env: | - GIT_VERSION=${{ fromJson(needs.prep.outputs.versions)[matrix.image_name] }} - KONA_VERSION=${{ needs.prep.outputs.kona_version }} - set: | - *.args.GIT_COMMIT=${{ github.sha }} - *.args.GIT_DATE=${{ needs.prep.outputs.date }} - permissions: - contents: read - id-token: write - attestations: write diff --git a/.semgrep/rules/sol-rules.yaml b/.semgrep/rules/sol-rules.yaml index 35bab3b758c..fda362b1da9 100644 --- a/.semgrep/rules/sol-rules.yaml +++ b/.semgrep/rules/sol-rules.yaml @@ -322,6 +322,8 @@ rules: - packages/contracts-bedrock/src/L1/OPContractsManager.sol - packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol - packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContainer.sol + - packages/contracts-bedrock/src/L1/opcm/OPContractsManagerUtils.sol + - packages/contracts-bedrock/src/L1/opcm/OPContractsManagerUtilsCaller.sol - packages/contracts-bedrock/src/L1/OptimismPortal2.sol - packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol - packages/contracts-bedrock/src/L2/FeeVault.sol diff --git a/Makefile b/Makefile index 6dd03ee1538..b5a7f61d890 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,7 @@ golang-docker: ## Builds Docker images for Go components using buildx GIT_COMMIT=$$(git rev-parse HEAD) \ GIT_DATE=$$(git show -s --format='%ct') \ IMAGE_TAGS=$$(git rev-parse HEAD),latest \ + KONA_VERSION=$$(jq -r .version kona/version.json) \ docker buildx bake \ --progress plain \ --load \ diff --git a/docker-bake.hcl b/docker-bake.hcl index d19ab640f31..7a96988365f 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -10,10 +10,6 @@ variable "KONA_VERSION" { default = "none" } -variable "ASTERISC_VERSION" { - default = "v1.3.0" -} - variable "GIT_COMMIT" { default = "dev" } @@ -149,7 +145,6 @@ target "op-challenger" { GIT_DATE = "${GIT_DATE}" OP_CHALLENGER_VERSION = "${OP_CHALLENGER_VERSION}" KONA_VERSION="${KONA_VERSION}" - ASTERISC_VERSION="${ASTERISC_VERSION}" } target = "op-challenger-target" platforms = split(",", PLATFORMS) diff --git a/go.mod b/go.mod index e43b7732082..6bfc9a7027f 100644 --- a/go.mod +++ b/go.mod @@ -309,7 +309,7 @@ require ( lukechampine.com/blake3 v1.3.0 // indirect ) -replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101604.0-synctest.0 +replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101604.0-synctest.0.0.20251208094937-ba6bdcfef423 // replace github.com/ethereum/go-ethereum => ../op-geth diff --git a/go.sum b/go.sum index 9e71502956c..56cab806065 100644 --- a/go.sum +++ b/go.sum @@ -238,8 +238,8 @@ github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.4-0.20251001155152-4eb15ccedf7e h1:iy1vBIzACYUyOVyoADUwvAiq2eOPC0yVsDUdolPwQjk= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.4-0.20251001155152-4eb15ccedf7e/go.mod h1:DYj7+vYJ4cIB7zera9mv4LcAynCL5u4YVfoeUu6Wa+w= -github.com/ethereum-optimism/op-geth v1.101604.0-synctest.0 h1:cWnKry8Cgworpw3X+TDEr57DCYefjN9Tmy2eJ9elMzE= -github.com/ethereum-optimism/op-geth v1.101604.0-synctest.0/go.mod h1:fCNAwDynfAP6EKsmLqwSDUDgi+GtJIir74Ui3fXXMps= +github.com/ethereum-optimism/op-geth v1.101604.0-synctest.0.0.20251208094937-ba6bdcfef423 h1:5xVkCCBRWkOt+bzVWL1p3mOwrpZLjxi/+yWUsja0E48= +github.com/ethereum-optimism/op-geth v1.101604.0-synctest.0.0.20251208094937-ba6bdcfef423/go.mod h1:fCNAwDynfAP6EKsmLqwSDUDgi+GtJIir74Ui3fXXMps= github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20251009180028-9b4658b9b7af h1:WWz0gJM/boaUImtJnROecPirAerKCLpAU4m6Tx0ArOg= github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20251009180028-9b4658b9b7af/go.mod h1:NZ816PzLU1TLv1RdAvYAb6KWOj4Zm5aInT0YpDVml2Y= github.com/ethereum/c-kzg-4844/v2 v2.1.5 h1:aVtoLK5xwJ6c5RiqO8g8ptJ5KU+2Hdquf6G3aXiHh5s= diff --git a/mise.toml b/mise.toml index c934514ea67..7eeb19d7bae 100644 --- a/mise.toml +++ b/mise.toml @@ -39,7 +39,7 @@ anvil = "1.2.3" codecov-uploader = "0.8.0" goreleaser-pro = "2.11.2" kurtosis = "1.8.1" -op-acceptor = "op-acceptor/v3.6.6" +op-acceptor = "op-acceptor/v3.8.0" # Fake dependencies # Put things here if you need to track versions of tools or projects that can't diff --git a/op-acceptance-tests/justfile b/op-acceptance-tests/justfile index 633894f24d8..a13688a4924 100644 --- a/op-acceptance-tests/justfile +++ b/op-acceptance-tests/justfile @@ -1,6 +1,6 @@ REPO_ROOT := `realpath ..` # path to the root of the optimism monorepo KURTOSIS_DIR := REPO_ROOT + "/kurtosis-devnet" -ACCEPTOR_VERSION := env_var_or_default("ACCEPTOR_VERSION", "v3.6.6") +ACCEPTOR_VERSION := env_var_or_default("ACCEPTOR_VERSION", "v3.8.0") DOCKER_REGISTRY := env_var_or_default("DOCKER_REGISTRY", "us-docker.pkg.dev/oplabs-tools-artifacts/images") ACCEPTOR_IMAGE := env_var_or_default("ACCEPTOR_IMAGE", DOCKER_REGISTRY + "/op-acceptor:" + ACCEPTOR_VERSION) diff --git a/op-acceptance-tests/tests/base/deposit/deposit_test.go b/op-acceptance-tests/tests/base/deposit/deposit_test.go index 5ca47d2499a..c26f065d8af 100644 --- a/op-acceptance-tests/tests/base/deposit/deposit_test.go +++ b/op-acceptance-tests/tests/base/deposit/deposit_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/custom_gas_token" "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/dsl/contract" "github.com/ethereum-optimism/optimism/op-devstack/presets" @@ -19,6 +20,9 @@ func TestL1ToL2Deposit(gt *testing.T) { t := devtest.SerialT(gt) sys := presets.NewMinimal(t) + // Skip this test if CGT is enabled + custom_gas_token.SkipIfCGT(t, sys) + // Wait for L1 node to be responsive sys.L1Network.WaitForOnline() diff --git a/op-acceptance-tests/tests/batcher/batcher_test.go b/op-acceptance-tests/tests/batcher/batcher_test.go new file mode 100644 index 00000000000..5cfc2b58036 --- /dev/null +++ b/op-acceptance-tests/tests/batcher/batcher_test.go @@ -0,0 +1,116 @@ +package batcher + +import ( + "testing" + "time" + + "github.com/davecgh/go-spew/spew" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-devstack/stack/match" + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/txplan" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestBatcherFullChannelsAfterDowntime(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewSingleChainMultiNodeWithTestSeq(t) + l := t.Logger() + ts_L2 := sys.TestSequencer.Escape().ControlAPI(sys.L2EL.ChainID()) + + alice := sys.FunderL2.NewFundedEOA(eth.OneWei) + cathrine := sys.FunderL2.NewFundedEOA(eth.OneTenthEther) + + cl := sys.L1Network.Escape().L1CLNode(match.FirstL1CL) + + sys.ControlPlane.FakePoSState(cl.ID(), stack.Stop) + + latestUnsafe_A := sys.L2CL.StopSequencer() + l.Info("Latest unsafe block after stopping the L2 sequencer", "latestUnsafe", latestUnsafe_A) + + parent := latestUnsafe_A + nonce := uint64(0) + for j := 0; j < 200; j++ { + l1Origin := sys.L1EL.BlockRefByLabel(eth.Unsafe).Hash + + for i := 0; i < 5; i++ { + l.Debug("Sequencing L2 block", "iteration", i, "parent", parent) + sequenceBlockWithL1Origin(t, ts_L2, parent, l1Origin, alice, cathrine, nonce) + nonce++ + + parent = sys.L2CL.HeadBlockRef(types.LocalUnsafe).Hash + + sys.AdvanceTime(time.Second * 2) + time.Sleep(20 * time.Millisecond) // failed to force-include tx: type: 2 sender; err: nonce too high + } + + l.Debug("Sequencing L1 block", "iteration_j", j) + sys.TestSequencer.SequenceBlock(t, sys.L1Network.ChainID(), common.Hash{}) + } + + sys.L2CL.StartSequencer() + + l.Info("Current L1 unsafe block", "currentL1Unsafe", sys.L1EL.BlockRefByLabel(eth.Unsafe)) + sys.ControlPlane.FakePoSState(cl.ID(), stack.Start) + + sys.L2Batcher.Start() + + channels, channelFrames, l2Txs := sys.L2Chain.DeriveData(4) // over the next 4 blocks, collect batches/channels/frames submitted by the batcher on the L1 network, and parse them + { + for _, c := range channels { + l.Info("Channel details", "channelID", c.String(), "frameCount", len(channelFrames[c]), "dataLength_frame0", len(channelFrames[c][0].Data)) + } + + require.Equal(t, 2, len(channels)) // we expect a total of 2 channels + + // values are dependent on: + // - MaxPendingTransactions + // - number of blocks and transactions sent in the test - 1000 L2 blocks with 1 transaction from cathrine to alice + // - MaxL1TxSize (this is set to 40_000 bytes for test purposes) + sizeRanges := []struct { + min int + max int + note string + }{ + {min: 30_000, max: 40_000, note: "channel 0 - filled to the max capacity"}, + {min: 30_000, max: 40_000, note: "channel 1 - remaining data, filling channel close to max capacity"}, + } + + for i, entry := range sizeRanges { + require.LessOrEqual(t, len(channelFrames[channels[i]][0].Data), entry.max, entry.note) + require.GreaterOrEqual(t, len(channelFrames[channels[i]][0].Data), entry.min, entry.note) + } + + require.Equal(t, len(l2Txs[cathrine.Address()]), 1000) // we expect 1000 transactions total sent from cathrine to alice + } + + status := sys.L2CL.SyncStatus() + spew.Dump(status) +} + +func sequenceBlockWithL1Origin(t devtest.T, ts apis.TestSequencerControlAPI, parent common.Hash, l1Origin common.Hash, alice *dsl.EOA, cathrine *dsl.EOA, nonce uint64) { + require.NoError(t, ts.New(t.Ctx(), seqtypes.BuildOpts{Parent: parent, L1Origin: &l1Origin})) + + // include simple transfer tx in opened block + { + to := cathrine.PlanTransfer(alice.Address(), eth.OneWei) + opt := txplan.Combine(to, txplan.WithStaticNonce(nonce)) + ptx := txplan.NewPlannedTx(opt) + signed_tx, err := ptx.Signed.Eval(t.Ctx()) + require.NoError(t, err, "Expected to be able to evaluate a planned transaction on op-test-sequencer, but got error") + txdata, err := signed_tx.MarshalBinary() + require.NoError(t, err, "Expected to be able to marshal a signed transaction on op-test-sequencer, but got error") + + err = ts.IncludeTx(t.Ctx(), txdata) + require.NoError(t, err, "Expected to be able to include a signed transaction on op-test-sequencer, but got error") + } + + require.NoError(t, ts.Next(t.Ctx())) +} diff --git a/op-acceptance-tests/tests/batcher/init_test.go b/op-acceptance-tests/tests/batcher/init_test.go new file mode 100644 index 00000000000..1a86b3b9589 --- /dev/null +++ b/op-acceptance-tests/tests/batcher/init_test.go @@ -0,0 +1,33 @@ +package batcher + +import ( + "testing" + "time" + + bss "github.com/ethereum-optimism/optimism/op-batcher/batcher" + "github.com/ethereum-optimism/optimism/op-devstack/compat" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" +) + +func TestMain(m *testing.M) { + presets.DoMain(m, presets.WithSingleChainMultiNode(), + presets.WithExecutionLayerSyncOnVerifiers(), + presets.WithCompatibleTypes(compat.SysGo), + presets.WithNoDiscovery(), + presets.WithTimeTravel(), + stack.MakeCommon(sysgo.WithBatcherOption(func(id stack.L2BatcherID, cfg *bss.CLIConfig) { + cfg.Stopped = true + + // set the blob max size to 40_000 bytes for test purposes + cfg.MaxL1TxSize = 40_000 + cfg.TestUseMaxTxSizeForBlobs = true + + cfg.PollInterval = 1000 * time.Millisecond + + cfg.MaxChannelDuration = 50 + cfg.MaxPendingTransactions = 7 + })), + ) +} diff --git a/op-acceptance-tests/tests/custom_gas_token/helpers.go b/op-acceptance-tests/tests/custom_gas_token/helpers.go index f76228df3a6..f353e192e5a 100644 --- a/op-acceptance-tests/tests/custom_gas_token/helpers.go +++ b/op-acceptance-tests/tests/custom_gas_token/helpers.go @@ -23,48 +23,83 @@ var ( l2BridgeAddr = common.HexToAddress("0x4200000000000000000000000000000000000010") ) -// ensureCGTOrSkip probes L2 L1Block for CGT mode. If not enabled, the test is skipped. -// Returns (name, symbol). -func ensureCGTOrSkip(t devtest.T, sys *presets.Minimal) (string, string) { +// isCGTEnabled checks if CGT mode is enabled without skipping the test. +// Returns true if CGT is enabled, false if native ETH mode, and false if the check fails. +func isCGTEnabled(t devtest.T, sys *presets.Minimal) bool { l2 := sys.L2EL.Escape().L2EthClient() - isCustomGasTokenFunc := w3.MustNewFunc("isCustomGasToken()", "bool") - gasPayingTokenNameFunc := w3.MustNewFunc("gasPayingTokenName()", "string") - gasPayingTokenSymbolFunc := w3.MustNewFunc("gasPayingTokenSymbol()", "string") ctx, cancel := context.WithTimeout(t.Ctx(), 20*time.Second) defer cancel() - // isCustomGasToken() data, _ := isCustomGasTokenFunc.EncodeArgs() out, err := l2.Call(ctx, ethereum.CallMsg{To: &l1BlockAddr, Data: data}, rpc.LatestBlockNumber) if err != nil { - t.Skipf("CGT not enabled (isCustomGasToken() call failed): %v", err) + return false } + var isCustom bool if err := isCustomGasTokenFunc.DecodeReturns(out, &isCustom); err != nil { - t.Require().NoError(err) - } - if !isCustom { - t.Skip("CGT disabled on this devnet (native ETH mode detected)") + return false } - // Read metadata (name/symbol) - data, _ = gasPayingTokenNameFunc.EncodeArgs() - out, err = l2.Call(ctx, ethereum.CallMsg{To: &l1BlockAddr, Data: data}, rpc.LatestBlockNumber) - t.Require().NoError(err) + return isCustom +} + +// getCGTMetadata retrieves the name and symbol of the custom gas token. +// Returns empty strings if CGT is not enabled or if the call fails. +func getCGTMetadata(t devtest.T, sys *presets.Minimal) (string, string) { + l2 := sys.L2EL.Escape().L2EthClient() + gasPayingTokenNameFunc := w3.MustNewFunc("gasPayingTokenName()", "string") + gasPayingTokenSymbolFunc := w3.MustNewFunc("gasPayingTokenSymbol()", "string") + + ctx, cancel := context.WithTimeout(t.Ctx(), 20*time.Second) + defer cancel() + + // Read name + data, _ := gasPayingTokenNameFunc.EncodeArgs() + out, err := l2.Call(ctx, ethereum.CallMsg{To: &l1BlockAddr, Data: data}, rpc.LatestBlockNumber) + if err != nil { + return "", "" + } var name string if err := gasPayingTokenNameFunc.DecodeReturns(out, &name); err != nil { - t.Require().NoError(err) + return "", "" } + // Read symbol data, _ = gasPayingTokenSymbolFunc.EncodeArgs() out, err = l2.Call(ctx, ethereum.CallMsg{To: &l1BlockAddr, Data: data}, rpc.LatestBlockNumber) - t.Require().NoError(err) + if err != nil { + return "", "" + } var symbol string if err := gasPayingTokenSymbolFunc.DecodeReturns(out, &symbol); err != nil { - t.Require().NoError(err) + return "", "" } return name, symbol } + +// ensureCGTOrSkip probes L2 L1Block for CGT mode. If not enabled, the test is skipped. +// Returns (name, symbol). +func ensureCGTOrSkip(t devtest.T, sys *presets.Minimal) (string, string) { + if !isCGTEnabled(t, sys) { + t.Skip("CGT disabled on this devnet (native ETH mode detected)") + } + + name, symbol := getCGTMetadata(t, sys) + if name == "" || symbol == "" { + t.Skip("Failed to retrieve CGT metadata") + } + + return name, symbol +} + +// SkipIfCGT probes L2 L1Block for CGT mode. If CGT is enabled, the test is skipped. +// This is useful for tests that should only run with native ETH. +func SkipIfCGT(t devtest.T, sys *presets.Minimal) { + if isCGTEnabled(t, sys) { + t.Skip("Test skipped: CGT is enabled (test requires native ETH)") + } +} diff --git a/op-acceptance-tests/tests/flashblocks/flashblocks_stream_test.go b/op-acceptance-tests/tests/flashblocks/flashblocks_stream_test.go index cd08cb0e20c..323ffe51759 100644 --- a/op-acceptance-tests/tests/flashblocks/flashblocks_stream_test.go +++ b/op-acceptance-tests/tests/flashblocks/flashblocks_stream_test.go @@ -82,7 +82,7 @@ func TestFlashblocksStream(gt *testing.T) { defer close(builderOutput) builderDone := make(chan struct{}) go func() { - err := oprbuilderNode.FlashblocksClient().ListenFor(ctx, logger.With("stream_source", "op-rbuilder"), testDuration, builderOutput, builderDone) + err := oprbuilderNode.FlashblocksClient().ReadAll(ctx, logger.With("stream_source", "op-rbuilder"), testDuration, builderOutput, builderDone) require.NoError(t, err) }() builderMessages := make([]string, 0) @@ -92,7 +92,7 @@ func TestFlashblocksStream(gt *testing.T) { doneListening := make(chan struct{}) streamedMessages := make([]string, 0) go func() { - err := rollupBoostNode.FlashblocksClient().ListenFor(ctx, logger.With("stream_source", "rollup-boost"), testDuration, output, doneListening) + err := rollupBoostNode.FlashblocksClient().ReadAll(ctx, logger.With("stream_source", "rollup-boost"), testDuration, output, doneListening) require.NoError(t, err) }() diff --git a/op-acceptance-tests/tests/flashblocks/flashblocks_transfer_test.go b/op-acceptance-tests/tests/flashblocks/flashblocks_transfer_test.go index 77c925955c4..80cf2e3c8f6 100644 --- a/op-acceptance-tests/tests/flashblocks/flashblocks_transfer_test.go +++ b/op-acceptance-tests/tests/flashblocks/flashblocks_transfer_test.go @@ -45,9 +45,6 @@ func TestFlashblocksTransfer(gt *testing.T) { _, span = tracer.Start(ctx, fmt.Sprintf("test chain %s", sys.L2Chain.String())) defer span.End() - doneListening := make(chan struct{}) - output := make(chan []byte, 100) - // Drive a couple blocks on the test sequencer so the faucet L2 funding tx has a chance to land before we rely on it. driveViaTestSequencer(t, sys, 2) @@ -55,11 +52,15 @@ func TestFlashblocksTransfer(gt *testing.T) { bob := sys.Wallet.NewEOA(sys.L2EL) bobAddress := bob.Address().Hex() - // flashblocks listener + // flashblocks listener - start goroutine and wait for it to be running flashblocksClient := sys.L2RollupBoost.FlashblocksClient() + output := make(chan []byte, 100) + doneListening := make(chan struct{}) go func() { - err := flashblocksClient.ListenFor(ctx, logger, 20*time.Second, output, doneListening) - t.Require().NoError(err, "failed to listen for flashblocks") + err := flashblocksClient.ReadAll(ctx, logger.With("stream_source", "rollup-boost"), 20*time.Second, output, doneListening) + if err != nil { + t.Require().NoError(err, "failed to listen for flashblocks") + } }() var executedTransaction *txplan.PlannedTx diff --git a/op-acceptance-tests/tests/interop/reorgs/l2_reorgs_after_l1_reorg_test.go b/op-acceptance-tests/tests/interop/reorgs/l2_reorgs_after_l1_reorg_test.go index 9c7592a1c87..a46eb0a7de8 100644 --- a/op-acceptance-tests/tests/interop/reorgs/l2_reorgs_after_l1_reorg_test.go +++ b/op-acceptance-tests/tests/interop/reorgs/l2_reorgs_after_l1_reorg_test.go @@ -8,10 +8,8 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/presets" "github.com/ethereum-optimism/optimism/op-devstack/stack" "github.com/ethereum-optimism/optimism/op-devstack/stack/match" - "github.com/ethereum-optimism/optimism/op-service/apis" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" - "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) @@ -66,7 +64,6 @@ func testL2ReorgAfterL1Reorg(gt *testing.T, n int, preChecks, postChecks checksF ctx := t.Ctx() sys := presets.NewSimpleInterop(t) - ts := sys.TestSequencer.Escape().ControlAPI(sys.L1Network.ChainID()) cl := sys.L1Network.Escape().L1CLNode(match.FirstL1CL) @@ -76,7 +73,7 @@ func testL2ReorgAfterL1Reorg(gt *testing.T, n int, preChecks, postChecks checksF // sequence a few L1 and L2 blocks for range n + 1 { - sequenceL1Block(t, ts, common.Hash{}) + sys.TestSequencer.SequenceBlock(t, sys.L1Network.ChainID(), common.Hash{}) sys.L2ChainA.WaitForBlock() sys.L2ChainA.WaitForBlock() @@ -101,7 +98,7 @@ func testL2ReorgAfterL1Reorg(gt *testing.T, n int, preChecks, postChecks checksF tipL2_preReorg := sys.L2ELA.BlockRefByLabel(eth.Unsafe) // reorg the L1 chain -- sequence an alternative L1 block from divergence block parent - sequenceL1Block(t, ts, divergence.ParentHash) + sys.TestSequencer.SequenceBlock(t, sys.L1Network.ChainID(), divergence.ParentHash) // continue building on the alternative L1 chain sys.ControlPlane.FakePoSState(cl.ID(), stack.Start) @@ -160,8 +157,3 @@ func testL2ReorgAfterL1Reorg(gt *testing.T, n int, preChecks, postChecks checksF // post reorg test validations and checks postChecks(t, sys) } - -func sequenceL1Block(t devtest.T, ts apis.TestSequencerControlAPI, parent common.Hash) { - require.NoError(t, ts.New(t.Ctx(), seqtypes.BuildOpts{Parent: parent})) - require.NoError(t, ts.Next(t.Ctx())) -} diff --git a/op-acceptance-tests/tests/isthmus/withdrawal_root/withdrawals_root_test.go b/op-acceptance-tests/tests/isthmus/withdrawal_root/withdrawals_root_test.go index ff3a9507836..a639c944405 100644 --- a/op-acceptance-tests/tests/isthmus/withdrawal_root/withdrawals_root_test.go +++ b/op-acceptance-tests/tests/isthmus/withdrawal_root/withdrawals_root_test.go @@ -3,6 +3,7 @@ package withdrawal import ( "testing" + "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/custom_gas_token" "github.com/ethereum-optimism/optimism/op-core/forks" "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/dsl" @@ -13,6 +14,10 @@ import ( func TestWithdrawalRoot(gt *testing.T) { t := devtest.SerialT(gt) sys := presets.NewMinimal(t) + + // Skip this test if CGT is enabled + custom_gas_token.SkipIfCGT(t, sys) + require := sys.T.Require() require.True(sys.L2Chain.IsForkActive(forks.Isthmus), "Isthmus fork must be active for this test") diff --git a/op-acceptance-tests/tests/jovian/min_base_fee.go b/op-acceptance-tests/tests/jovian/min_base_fee.go index 346d7783028..db53e7a3265 100644 --- a/op-acceptance-tests/tests/jovian/min_base_fee.go +++ b/op-acceptance-tests/tests/jovian/min_base_fee.go @@ -83,7 +83,7 @@ func (mbf *minBaseFeeEnv) verifyMinBaseFee(t devtest.T, minBase *big.Int) { // waitForMinBaseFeeConfigChangeOnL2 waits until the L2 latest payload extra-data encodes the expected min base fee. func (mbf *minBaseFeeEnv) waitForMinBaseFeeConfigChangeOnL2(t devtest.T, expected uint64) { client := mbf.l2EL.Escape().L2EthClient() - expectedExtraData := eth.BytesMax32(eip1559.EncodeMinBaseFeeExtraData(250, 6, expected)) + expectedExtraData := eth.BytesMax32(eip1559.EncodeJovianExtraData(250, 6, expected)) // Check extradata in block header (for all clients) var actualBlockExtraData []byte diff --git a/op-batcher/batcher/batch_submitter.go b/op-batcher/batcher/batch_submitter.go index a35b9ad1a18..91225c810aa 100644 --- a/op-batcher/batcher/batch_submitter.go +++ b/op-batcher/batcher/batch_submitter.go @@ -29,6 +29,6 @@ func Main(version string) cliapp.LifecycleAction { opservice.ValidateEnvVars(flags.EnvVarPrefix, flags.Flags, l) l.Info("Initializing Batch Submitter") - return BatcherServiceFromCLIConfig(cliCtx.Context, version, cfg, l) + return BatcherServiceFromCLIConfig(cliCtx.Context, closeApp, version, cfg, l) } } diff --git a/op-batcher/batcher/channel.go b/op-batcher/batcher/channel.go index b9299d11bf4..82dd79b03c4 100644 --- a/op-batcher/batcher/channel.go +++ b/op-batcher/batcher/channel.go @@ -27,7 +27,7 @@ type channel struct { } func newChannel(log log.Logger, metr metrics.Metricer, cfg ChannelConfig, rollupCfg *rollup.Config, latestL1OriginBlockNum uint64, channelOut derive.ChannelOut) *channel { - cb := NewChannelBuilderWithChannelOut(cfg, rollupCfg, latestL1OriginBlockNum, channelOut) + cb := NewChannelBuilderWithChannelOut(log, cfg, rollupCfg, latestL1OriginBlockNum, channelOut) return &channel{ ChannelBuilder: cb, log: log, diff --git a/op-batcher/batcher/channel_builder.go b/op-batcher/batcher/channel_builder.go index eab2f203726..966b43653b4 100644 --- a/op-batcher/batcher/channel_builder.go +++ b/op-batcher/batcher/channel_builder.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/queue" + "github.com/ethereum/go-ethereum/log" ) var ( @@ -47,6 +48,7 @@ type frameData struct { // ChannelBuilder uses a ChannelOut to create a channel with output frame // size approximation. type ChannelBuilder struct { + log log.Logger cfg ChannelConfig rollupCfg *rollup.Config @@ -87,8 +89,9 @@ type ChannelBuilder struct { outputBytes int } -func NewChannelBuilderWithChannelOut(cfg ChannelConfig, rollupCfg *rollup.Config, latestL1OriginBlockNum uint64, channelOut derive.ChannelOut) *ChannelBuilder { +func NewChannelBuilderWithChannelOut(log log.Logger, cfg ChannelConfig, rollupCfg *rollup.Config, latestL1OriginBlockNum uint64, channelOut derive.ChannelOut) *ChannelBuilder { cb := &ChannelBuilder{ + log: log.With("channel_id", channelOut.ID()), cfg: cfg, rollupCfg: rollupCfg, co: channelOut, @@ -224,8 +227,7 @@ func (c *ChannelBuilder) updateDurationTimeout(l1BlockNum uint64) { if c.cfg.MaxChannelDuration == 0 { return } - timeout := l1BlockNum + c.cfg.MaxChannelDuration - c.updateTimeout(timeout, ErrMaxDurationReached) + c.updateTimeout(l1BlockNum+c.cfg.MaxChannelDuration, ErrMaxDurationReached) } // updateSwTimeout updates the block timeout with the sequencer window timeout @@ -244,6 +246,7 @@ func (c *ChannelBuilder) updateSwTimeout(l1InfoNumber uint64) { // full error reason in case the timeout is hit in the future. func (c *ChannelBuilder) updateTimeout(timeoutBlockNum uint64, reason error) { if c.timeout == 0 || c.timeout > timeoutBlockNum { + c.log.Debug("setting timeout", "number", timeoutBlockNum, "timeout", c.timeout) c.timeout = timeoutBlockNum c.timeoutReason = reason } @@ -252,17 +255,12 @@ func (c *ChannelBuilder) updateTimeout(timeoutBlockNum uint64, reason error) { // CheckTimeout checks if the channel is timed out at the given block number and // in this case marks the channel as full, if it wasn't full already. func (c *ChannelBuilder) CheckTimeout(l1BlockNum uint64) { - if !c.IsFull() && c.TimedOut(l1BlockNum) { + if c.timeout != 0 && !c.IsFull() && l1BlockNum >= c.timeout { + c.log.Debug("checking timeout", "l1blockNum", l1BlockNum, "timeout", c.timeout) c.setFullErr(c.timeoutReason) } } -// TimedOut returns whether the passed block number is after the timeout block -// number. If no block timeout is set yet, it returns false. -func (c *ChannelBuilder) TimedOut(blockNum uint64) bool { - return c.timeout != 0 && blockNum >= c.timeout -} - // IsFull returns whether the channel is full. // FullErr returns the reason for the channel being full. func (c *ChannelBuilder) IsFull() bool { diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index becb123c667..4596dd48c6a 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -14,8 +14,10 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup/derive" dtest "github.com/ethereum-optimism/optimism/op-node/rollup/derive/test" "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" @@ -32,13 +34,13 @@ var defaultTestRollupConfig = &rollup.Config{ // newChannelBuilder creates a new channel builder or returns an error if the // channel out could not be created. // it acts as a factory for either a span or singular channel out -func newChannelBuilder(cfg ChannelConfig, rollupCfg *rollup.Config, latestL1OriginBlockNum uint64) (*ChannelBuilder, error) { +func newChannelBuilder(log log.Logger, cfg ChannelConfig, rollupCfg *rollup.Config, latestL1OriginBlockNum uint64) (*ChannelBuilder, error) { co, err := NewChannelOut(cfg, rollupCfg) if err != nil { return nil, fmt.Errorf("creating channel out: %w", err) } - return NewChannelBuilderWithChannelOut(cfg, rollupCfg, latestL1OriginBlockNum, co), nil + return NewChannelBuilderWithChannelOut(log, cfg, rollupCfg, latestL1OriginBlockNum, co), nil } // addMiniBlock adds a minimal valid L2 block to the channel builder using the @@ -166,23 +168,6 @@ func addTooManyBlocks(cb *ChannelBuilder, blockCount int) (int, error) { return blockCount, nil } -// FuzzDurationTimeoutZeroMaxChannelDuration ensures that when whenever the MaxChannelDuration -// is set to 0, the channel builder cannot have a duration timeout. -func FuzzDurationTimeoutZeroMaxChannelDuration(f *testing.F) { - for i := range [10]int{} { - f.Add(uint64(i)) - } - f.Fuzz(func(t *testing.T, l1BlockNum uint64) { - channelConfig := defaultTestChannelConfig() - channelConfig.MaxChannelDuration = 0 - cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) - require.NoError(t, err) - cb.timeout = 0 - cb.updateDurationTimeout(l1BlockNum) - require.False(t, cb.TimedOut(l1BlockNum)) - }) -} - // FuzzChannelBuilder_DurationZero ensures that when whenever the MaxChannelDuration // is not set to 0, the channel builder will always have a duration timeout // as long as the channel builder's timeout is set to 0. @@ -195,10 +180,11 @@ func FuzzChannelBuilder_DurationZero(f *testing.F) { t.Skip("Max channel duration cannot be 0") } + log := testlog.Logger(t, log.LvlInfo) // Create the channel builder channelConfig := defaultTestChannelConfig() channelConfig.MaxChannelDuration = maxChannelDuration - cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(log, channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Whenever the timeout is set to 0, the channel builder should have a duration timeout @@ -222,10 +208,11 @@ func FuzzDurationTimeoutMaxChannelDuration(f *testing.F) { t.Skip("Max channel duration cannot be 0") } + log := testlog.Logger(t, log.LvlInfo) // Create the channel builder channelConfig := defaultTestChannelConfig() channelConfig.MaxChannelDuration = maxChannelDuration - cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(log, channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Whenever the timeout is greater than the l1BlockNum, @@ -255,11 +242,12 @@ func FuzzChannelCloseTimeout(f *testing.F) { f.Add(uint64(i), uint64(i), uint64(i), uint64(i*5)) } f.Fuzz(func(t *testing.T, l1BlockNum uint64, channelTimeout uint64, subSafetyMargin uint64, timeout uint64) { + log := testlog.Logger(t, log.LvlInfo) // Create the channel builder channelConfig := defaultTestChannelConfig() channelConfig.ChannelTimeout = channelTimeout channelConfig.SubSafetyMargin = subSafetyMargin - cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(log, channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Check the timeout @@ -283,11 +271,12 @@ func FuzzChannelZeroCloseTimeout(f *testing.F) { f.Add(uint64(i), uint64(i), uint64(i)) } f.Fuzz(func(t *testing.T, l1BlockNum uint64, channelTimeout uint64, subSafetyMargin uint64) { + log := testlog.Logger(t, log.LvlInfo) // Create the channel builder channelConfig := defaultTestChannelConfig() channelConfig.ChannelTimeout = channelTimeout channelConfig.SubSafetyMargin = subSafetyMargin - cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(log, channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Check the timeout @@ -310,11 +299,12 @@ func FuzzSeqWindowClose(f *testing.F) { f.Add(uint64(i), uint64(i), uint64(i), uint64(i*5)) } f.Fuzz(func(t *testing.T, epochNum uint64, seqWindowSize uint64, subSafetyMargin uint64, timeout uint64) { + log := testlog.Logger(t, log.LvlInfo) // Create the channel builder channelConfig := defaultTestChannelConfig() channelConfig.SeqWindowSize = seqWindowSize channelConfig.SubSafetyMargin = subSafetyMargin - cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(log, channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Check the timeout @@ -338,11 +328,12 @@ func FuzzSeqWindowZeroTimeoutClose(f *testing.F) { f.Add(uint64(i), uint64(i), uint64(i)) } f.Fuzz(func(t *testing.T, epochNum uint64, seqWindowSize uint64, subSafetyMargin uint64) { + log := testlog.Logger(t, log.LvlInfo) // Create the channel builder channelConfig := defaultTestChannelConfig() channelConfig.SeqWindowSize = seqWindowSize channelConfig.SubSafetyMargin = subSafetyMargin - cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(log, channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Check the timeout @@ -387,10 +378,11 @@ func TestChannelBuilderBatchType(t *testing.T) { // TestChannelBuilder_NextFrame tests calling NextFrame on a ChannelBuilder with only one frame func TestChannelBuilder_NextFrame(t *testing.T) { + log := testlog.Logger(t, log.LvlInfo) channelConfig := defaultTestChannelConfig() // Create a new channel builder - cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(log, channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Mock the internals of `ChannelBuilder.outputFrame` @@ -427,11 +419,12 @@ func TestChannelBuilder_NextFrame(t *testing.T) { // TestChannelBuilder_OutputWrongFramePanic tests that a panic is thrown when we try to rewind the cursor with an invalid frame id func ChannelBuilder_OutputWrongFramePanic(t *testing.T, batchType uint) { + log := testlog.Logger(t, log.LvlInfo) channelConfig := defaultTestChannelConfig() channelConfig.BatchType = batchType // Construct a channel builder - cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(log, channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Mock the internals of `ChannelBuilder.outputFrame` @@ -461,13 +454,14 @@ func ChannelBuilder_OutputWrongFramePanic(t *testing.T, batchType uint) { // TestChannelBuilder_OutputFrames tests [ChannelBuilder.OutputFrames] for singular batches. func TestChannelBuilder_OutputFrames(t *testing.T) { + log := testlog.Logger(t, log.LvlInfo) channelConfig := defaultTestChannelConfig() channelConfig.MaxFrameSize = derive.FrameV0OverHeadSize + 1 channelConfig.TargetNumFrames = 1000 channelConfig.InitNoneCompressor() // Construct the channel builder - cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(log, channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) require.False(t, cb.IsFull()) require.Equal(t, 0, cb.PendingFrames()) @@ -510,6 +504,7 @@ func TestChannelBuilder_OutputFrames_SpanBatch(t *testing.T) { } func ChannelBuilder_OutputFrames_SpanBatch(t *testing.T, algo derive.CompressionAlgo) { + log := testlog.Logger(t, log.LvlInfo) channelConfig := defaultTestChannelConfig() channelConfig.MaxFrameSize = 20 + derive.FrameV0OverHeadSize if algo.IsBrotli() { @@ -521,7 +516,7 @@ func ChannelBuilder_OutputFrames_SpanBatch(t *testing.T, algo derive.Compression channelConfig.InitRatioCompressor(1, algo) // Construct the channel builder - cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(log, channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) require.False(t, cb.IsFull()) require.Equal(t, 0, cb.PendingFrames()) @@ -572,6 +567,7 @@ func ChannelBuilder_OutputFrames_SpanBatch(t *testing.T, algo derive.Compression // function errors when the max RLP bytes per channel is reached. func ChannelBuilder_MaxRLPBytesPerChannel(t *testing.T, batchType uint) { t.Parallel() + log := testlog.Logger(t, log.LvlInfo) channelConfig := defaultTestChannelConfig() chainSpec := rollup.NewChainSpec(defaultTestRollupConfig) channelConfig.MaxFrameSize = chainSpec.MaxRLPBytesPerChannel(latestL1BlockOrigin) * 2 @@ -579,7 +575,7 @@ func ChannelBuilder_MaxRLPBytesPerChannel(t *testing.T, batchType uint) { channelConfig.BatchType = batchType // Construct the channel builder - cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(log, channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Add a block that overflows the [ChannelOut] @@ -594,6 +590,7 @@ func ChannelBuilder_MaxRLPBytesPerChannel(t *testing.T, batchType uint) { // then check postFjord w/ double the amount of blocks func ChannelBuilder_MaxRLPBytesPerChannelFjord(t *testing.T, batchType uint) { t.Parallel() + log := testlog.Logger(t, log.LvlInfo) channelConfig := defaultTestChannelConfig() chainSpec := rollup.NewChainSpec(defaultTestRollupConfig) channelConfig.MaxFrameSize = chainSpec.MaxRLPBytesPerChannel(latestL1BlockOrigin) * 2 @@ -601,7 +598,7 @@ func ChannelBuilder_MaxRLPBytesPerChannelFjord(t *testing.T, batchType uint) { channelConfig.BatchType = batchType // Construct the channel builder - cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(log, channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Count how many a block that overflows the [ChannelOut] @@ -622,7 +619,7 @@ func ChannelBuilder_MaxRLPBytesPerChannelFjord(t *testing.T, batchType uint) { channelConfig.InitNoneCompressor() channelConfig.BatchType = batchType - cb, err = newChannelBuilder(channelConfig, rollupConfig, latestL1BlockOrigin) + cb, err = newChannelBuilder(log, channelConfig, rollupConfig, latestL1BlockOrigin) require.NoError(t, err) // try add double the amount of block, it should not error @@ -634,6 +631,7 @@ func ChannelBuilder_MaxRLPBytesPerChannelFjord(t *testing.T, batchType uint) { // ChannelBuilder_OutputFramesMaxFrameIndex tests the [ChannelBuilder.OutputFrames] // function errors when the max frame index is reached. func ChannelBuilder_OutputFramesMaxFrameIndex(t *testing.T, batchType uint) { + log := testlog.Logger(t, log.LvlInfo) channelConfig := defaultTestChannelConfig() channelConfig.MaxFrameSize = derive.FrameV0OverHeadSize + 1 channelConfig.TargetNumFrames = math.MaxUint16 + 1 @@ -645,7 +643,7 @@ func ChannelBuilder_OutputFramesMaxFrameIndex(t *testing.T, batchType uint) { // Continuously add blocks until the max frame index is reached // This should cause the [ChannelBuilder.OutputFrames] function // to error - cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(log, channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) require.False(t, cb.IsFull()) require.Equal(t, 0, cb.PendingFrames()) @@ -673,6 +671,7 @@ func ChannelBuilder_OutputFramesMaxFrameIndex(t *testing.T, batchType uint) { // [derive.FrameV0OverHeadSize] in [MaxDataSize] is omitted, which has been the // case before it got fixed it #9887. func TestChannelBuilder_FullShadowCompressor(t *testing.T) { + log := testlog.Logger(t, log.LvlInfo) require := require.New(t) cfg := ChannelConfig{ MaxFrameSize: 752, @@ -681,7 +680,7 @@ func TestChannelBuilder_FullShadowCompressor(t *testing.T) { } cfg.InitShadowCompressor(derive.Zlib) - cb, err := newChannelBuilder(cfg, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(log, cfg, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(err) rng := rand.New(rand.NewSource(420)) @@ -703,6 +702,7 @@ func TestChannelBuilder_FullShadowCompressor(t *testing.T) { } func ChannelBuilder_AddBlock(t *testing.T, batchType uint) { + log := testlog.Logger(t, log.LvlInfo) channelConfig := defaultTestChannelConfig() channelConfig.BatchType = batchType @@ -713,7 +713,7 @@ func ChannelBuilder_AddBlock(t *testing.T, batchType uint) { channelConfig.InitRatioCompressor(1, derive.Zlib) // Construct the channel builder - cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(log, channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Add a nonsense block to the channel builder @@ -736,10 +736,11 @@ func ChannelBuilder_AddBlock(t *testing.T, batchType uint) { } func TestChannelBuilder_CheckTimeout(t *testing.T) { + log := testlog.Logger(t, log.LvlInfo) channelConfig := defaultTestChannelConfig() // Construct the channel builder - cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(log, channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Assert timeout is setup correctly @@ -757,14 +758,34 @@ func TestChannelBuilder_CheckTimeout(t *testing.T) { require.ErrorIs(t, cb.FullErr(), ErrMaxDurationReached) } +func TestChannelBuilder_MaxChannelDurationZero(t *testing.T) { + log := testlog.Logger(t, log.LvlInfo) + channelConfig := defaultTestChannelConfig() + channelConfig.MaxChannelDuration = 0 + + // Construct the channel builder + cb, err := newChannelBuilder(log, channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + require.NoError(t, err) + + // Assert timeout is setup correctly + require.Equal(t, uint64(0), channelConfig.MaxChannelDuration) + + // Check a new L1 block which should not update the timeout, due to config setting MaxChannelDuration to 0 + cb.CheckTimeout(uint64(12345)) + + require.Equal(t, uint64(0), cb.timeout) + require.NoError(t, cb.FullErr()) +} + func TestChannelBuilder_CheckTimeoutZeroMaxChannelDuration(t *testing.T) { + log := testlog.Logger(t, log.LvlInfo) channelConfig := defaultTestChannelConfig() // Set the max channel duration to 0 channelConfig.MaxChannelDuration = 0 // Construct the channel builder - cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(log, channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Without a max channel duration, timeout should not be set @@ -781,13 +802,14 @@ func TestChannelBuilder_CheckTimeoutZeroMaxChannelDuration(t *testing.T) { } func TestChannelBuilder_FramePublished(t *testing.T) { + log := testlog.Logger(t, log.LvlInfo) cfg := defaultTestChannelConfig() cfg.MaxChannelDuration = 10_000 cfg.ChannelTimeout = 1000 cfg.SubSafetyMargin = 100 // Construct the channel builder - cb, err := newChannelBuilder(cfg, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(log, cfg, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) require.Equal(t, latestL1BlockOrigin+cfg.MaxChannelDuration, cb.timeout) @@ -804,9 +826,10 @@ func TestChannelBuilder_FramePublished(t *testing.T) { } func TestChannelBuilder_LatestL1Origin(t *testing.T) { - cb, err := newChannelBuilder(defaultTestChannelConfig(), defaultTestRollupConfig, latestL1BlockOrigin) + log := testlog.Logger(t, log.LvlInfo) + cb, err := newChannelBuilder(log, defaultTestChannelConfig(), defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) - require.Equal(t, eth.BlockID{}, cb.LatestL1Origin()) + require.Equal(t, eth.BlockID{}, cb.LatestL1Origin(), "LatestL1Origin should be empty") _, err = cb.AddBlock(SizedBlock{Block: newMiniL2BlockWithNumberParentAndL1Information(0, big.NewInt(1), common.Hash{}, 1, 100)}) require.NoError(t, err) @@ -826,7 +849,8 @@ func TestChannelBuilder_LatestL1Origin(t *testing.T) { } func TestChannelBuilder_OldestL1Origin(t *testing.T) { - cb, err := newChannelBuilder(defaultTestChannelConfig(), defaultTestRollupConfig, latestL1BlockOrigin) + log := testlog.Logger(t, log.LvlInfo) + cb, err := newChannelBuilder(log, defaultTestChannelConfig(), defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) require.Equal(t, eth.BlockID{}, cb.OldestL1Origin()) @@ -848,7 +872,8 @@ func TestChannelBuilder_OldestL1Origin(t *testing.T) { } func TestChannelBuilder_LatestL2(t *testing.T) { - cb, err := newChannelBuilder(defaultTestChannelConfig(), defaultTestRollupConfig, latestL1BlockOrigin) + log := testlog.Logger(t, log.LvlInfo) + cb, err := newChannelBuilder(log, defaultTestChannelConfig(), defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) require.Equal(t, eth.BlockID{}, cb.LatestL2()) @@ -870,7 +895,8 @@ func TestChannelBuilder_LatestL2(t *testing.T) { } func TestChannelBuilder_OldestL2(t *testing.T) { - cb, err := newChannelBuilder(defaultTestChannelConfig(), defaultTestRollupConfig, latestL1BlockOrigin) + log := testlog.Logger(t, log.LvlInfo) + cb, err := newChannelBuilder(log, defaultTestChannelConfig(), defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) require.Equal(t, eth.BlockID{}, cb.OldestL2()) @@ -892,6 +918,7 @@ func TestChannelBuilder_OldestL2(t *testing.T) { } func ChannelBuilder_PendingFrames_TotalFrames(t *testing.T, batchType uint) { + log := testlog.Logger(t, log.LvlInfo) const tnf = 9 rng := rand.New(rand.NewSource(94572314)) require := require.New(t) @@ -900,7 +927,7 @@ func ChannelBuilder_PendingFrames_TotalFrames(t *testing.T, batchType uint) { cfg.TargetNumFrames = tnf cfg.BatchType = batchType cfg.InitShadowCompressor(derive.Zlib) - cb, err := newChannelBuilder(cfg, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(log, cfg, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(err) // initial builder should be empty @@ -936,6 +963,7 @@ func ChannelBuilder_PendingFrames_TotalFrames(t *testing.T, batchType uint) { } func ChannelBuilder_InputBytes(t *testing.T, batchType uint) { + log := testlog.Logger(t, log.LvlInfo) require := require.New(t) rng := rand.New(rand.NewSource(4982432)) cfg := defaultTestChannelConfig() @@ -945,7 +973,7 @@ func ChannelBuilder_InputBytes(t *testing.T, batchType uint) { chainId := big.NewInt(1234) spanBatch = derive.NewSpanBatch(uint64(0), chainId) } - cb, err := newChannelBuilder(cfg, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(log, cfg, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(err) require.Zero(cb.InputBytes()) @@ -975,6 +1003,7 @@ func ChannelBuilder_InputBytes(t *testing.T, batchType uint) { } func ChannelBuilder_OutputBytes(t *testing.T, batchType uint) { + log := testlog.Logger(t, log.LvlInfo) require := require.New(t) rng := rand.New(rand.NewSource(9860372)) cfg := defaultTestChannelConfig() @@ -982,7 +1011,7 @@ func ChannelBuilder_OutputBytes(t *testing.T, batchType uint) { cfg.TargetNumFrames = 16 cfg.BatchType = batchType cfg.InitRatioCompressor(1.0, derive.Zlib) - cb, err := newChannelBuilder(cfg, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(log, cfg, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(err, "NewChannelBuilder") require.Zero(cb.OutputBytes()) diff --git a/op-batcher/batcher/channel_manager.go b/op-batcher/batcher/channel_manager.go index 4e1ca799cc3..f65844a6745 100644 --- a/op-batcher/batcher/channel_manager.go +++ b/op-batcher/batcher/channel_manager.go @@ -109,7 +109,6 @@ func (s *channelManager) TxFailed(_id txID) { // TxConfirmed marks a transaction as confirmed on L1. Only if the channel timed out // the channelManager's state is modified. func (s *channelManager) TxConfirmed(_id txID, inclusionBlock eth.BlockID) { - id := _id.String() if channel, ok := s.txChannels[id]; ok { delete(s.txChannels, id) @@ -225,8 +224,8 @@ func (s *channelManager) nextTxData(channel *channel) (txData, error) { // It will decide whether to switch DA type automatically. // When switching DA type, the channelManager state will be rebuilt // with a new ChannelConfig. -func (s *channelManager) TxData(l1Head eth.BlockID, isPectra, isThrottling, forcePublish bool) (txData, error) { - channel, err := s.getReadyChannel(l1Head, forcePublish) +func (s *channelManager) TxData(l1Head eth.BlockID, isPectra bool, isThrottling bool, pi pubInfo) (txData, error) { + channel, err := s.getReadyChannel(l1Head, pi) if err != nil { return emptyTxData, err } @@ -260,13 +259,23 @@ func (s *channelManager) TxData(l1Head eth.BlockID, isPectra, isThrottling, forc s.defaultCfg = newCfg // Try again to get data to send on chain. - channel, err = s.getReadyChannel(l1Head, forcePublish) + channel, err = s.getReadyChannel(l1Head, pi) if err != nil { return emptyTxData, err } return s.nextTxData(channel) } +// pubInfo is a struct that contains signal information sent on the publishSignal channel +type pubInfo struct { + // forcePublish is set to true if the current channel should be force-closed and submitted now. + forcePublish bool + + // ignoreMaxChannelDuration is set to true if we should keep the current channel open even if it's duration is exceeded. + // For example, if we know there are more blocks to load and we want to pack those into the current channel before sending it. + ignoreMaxChannelDuration bool +} + // getReadyChannel returns the next channel ready to submit data, or an error. // It will create a new channel if necessary. // If there is no data ready to send, it adds blocks from the block queue @@ -275,9 +284,8 @@ func (s *channelManager) TxData(l1Head eth.BlockID, isPectra, isThrottling, forc // there is no channel with txData // If forcePublish is true, it will force close channels and // generate frames for them. -func (s *channelManager) getReadyChannel(l1Head eth.BlockID, forcePublish bool) (*channel, error) { - - if forcePublish && s.currentChannel.TotalFrames() == 0 { +func (s *channelManager) getReadyChannel(l1Head eth.BlockID, pi pubInfo) (*channel, error) { + if pi.forcePublish && s.currentChannel.TotalFrames() == 0 { s.log.Info("Force-closing channel and creating frames", "channel_id", s.currentChannel.ID()) s.currentChannel.Close() if err := s.currentChannel.OutputFrames(); err != nil { @@ -315,10 +323,14 @@ func (s *channelManager) getReadyChannel(l1Head eth.BlockID, forcePublish bool) return nil, err } - // Register current L1 head only after all pending blocks have been - // processed. Even if a timeout will be triggered now, it is better to have - // all pending blocks be included in this channel for submission. - s.registerL1Block(l1Head) + if !pi.ignoreMaxChannelDuration { + // Register current L1 head (which checks for the max duration timeout) + // only after all blocks in the manager's state have been + // processed, and only if we weren't told to ignore the max channel duration. + // The aim is to prefer to optimally pack blocks into channels when + // instead of timing out the channel when more blocks soon to be processed. + s.registerL1Block(l1Head) + } if err := s.outputFrames(); err != nil { return nil, err @@ -461,6 +473,8 @@ func (s *channelManager) outputFrames() error { s.log.Info("Channel closed", "id", s.currentChannel.ID(), "blocks_pending", s.pendingBlocks(), + "block_cursor", s.blockCursor, + "blocks_len", s.blocks.Len(), "num_frames", s.currentChannel.TotalFrames(), "input_bytes", inBytes, "output_bytes", outBytes, @@ -540,7 +554,6 @@ func (s *channelManager) PruneChannels(num int) { if clearCurrentChannel { s.currentChannel = nil } - } // PendingDABytes returns the current number of bytes pending to be written to the DA layer (from blocks fetched from L2 diff --git a/op-batcher/batcher/channel_manager_memory_test.go b/op-batcher/batcher/channel_manager_memory_test.go index 47b89c6e9a7..fe305aa2fe6 100644 --- a/op-batcher/batcher/channel_manager_memory_test.go +++ b/op-batcher/batcher/channel_manager_memory_test.go @@ -124,7 +124,7 @@ func runMemoryTest(t *testing.T, batchType uint, compressorType string, compress require.NoError(t, m.processBlocks()) // Try to get transaction data to fill channels - _, err := m.TxData(eth.BlockID{}, false, false, false) + _, err := m.TxData(eth.BlockID{}, false, false, pubInfo{}) // It's okay if there's no data ready (io.EOF) if err != nil && err.Error() != "EOF" { require.NoError(t, err) diff --git a/op-batcher/batcher/channel_manager_test.go b/op-batcher/batcher/channel_manager_test.go index 2aafe1f315d..0c4feb9e0eb 100644 --- a/op-batcher/batcher/channel_manager_test.go +++ b/op-batcher/batcher/channel_manager_test.go @@ -103,9 +103,9 @@ func ChannelManagerReturnsErrReorgWhenDrained(t *testing.T, batchType uint) { require.NoError(t, m.AddL2Block(a)) - _, err := m.TxData(eth.BlockID{}, false, false, false) + _, err := m.TxData(eth.BlockID{}, false, false, pubInfo{}) require.NoError(t, err) - _, err = m.TxData(eth.BlockID{}, false, false, false) + _, err = m.TxData(eth.BlockID{}, false, false, pubInfo{}) require.ErrorIs(t, err, io.EOF) require.ErrorIs(t, m.AddL2Block(x), ErrReorg) @@ -207,7 +207,7 @@ func ChannelManager_TxResend(t *testing.T, batchType uint) { require.NoError(m.AddL2Block(a)) - txdata0, err := m.TxData(eth.BlockID{}, false, false, false) + txdata0, err := m.TxData(eth.BlockID{}, false, false, pubInfo{}) require.NoError(err) txdata0bytes := txdata0.CallData() data0 := make([]byte, len(txdata0bytes)) @@ -215,13 +215,13 @@ func ChannelManager_TxResend(t *testing.T, batchType uint) { copy(data0, txdata0bytes) // ensure channel is drained - _, err = m.TxData(eth.BlockID{}, false, false, false) + _, err = m.TxData(eth.BlockID{}, false, false, pubInfo{}) require.ErrorIs(err, io.EOF) // requeue frame m.TxFailed(txdata0.ID()) - txdata1, err := m.TxData(eth.BlockID{}, false, false, false) + txdata1, err := m.TxData(eth.BlockID{}, false, false, pubInfo{}) require.NoError(err) data1 := txdata1.CallData() @@ -318,6 +318,48 @@ func newFakeDynamicEthChannelConfig(lgr log.Logger, } } +// TestChannelManager_IgnoreMaxChannelDuration tests that the channel manager will not time out +// when ignoreMaxChannelDuration is set to true in the signal struct. +func TestChannelManager_IgnoreMaxChannelDuration(t *testing.T) { + l := testlog.Logger(t, log.LevelCrit) + + cfg := channelManagerTestConfig(10000, derive.SingularBatchType) + cfg.MaxChannelDuration = 20 + cfg.InitNoneCompressor() + + m := NewChannelManager(l, metrics.NoopMetrics, cfg, defaultTestRollupConfig) + + // Seed channel manager with blocks + rng := rand.New(rand.NewSource(99)) + for range 2 { + block := derivetest.RandomL2BlockWithChainId(rng, 2, defaultTestRollupConfig.L2ChainID) + m.blocks.Enqueue(SizedBlock{Block: block}) + } + + // Call TxData a first time - if `ignoreMaxChannelDuration` is `false`, channel would be timed out, + // but since `ignoreMaxChannelDuration` is `true`, we expect it to be not timed out. + _, err := m.TxData(eth.BlockID{Number: 21}, false, false, pubInfo{ignoreMaxChannelDuration: true}) + require.ErrorIs(t, err, io.EOF) + + // Add more blocks to the channel manager + for range 2 { + block := derivetest.RandomL2BlockWithChainId(rng, 2, defaultTestRollupConfig.L2ChainID) + m.blocks.Enqueue(SizedBlock{Block: block}) + } + + require.NotEmpty(t, m.channelQueue) + require.False(t, m.channelQueue[0].IsFull()) + + // Call TxData again, with ignoreMaxChannelDuration unset. + _, err = m.TxData(eth.BlockID{Number: 22}, false, false, pubInfo{}) + require.NoError(t, err) + require.NotEmpty(t, m.channelQueue) + + // Given that ignoreMaxChannelDuration was unset, the channel should be timed out + require.True(t, m.channelQueue[0].IsFull()) + require.ErrorIs(t, m.channelQueue[0].FullErr(), ErrMaxDurationReached) +} + // TestChannelManager_TxData seeds the channel manager with blocks and triggers the // blocks->channels pipeline multiple times. Values are chosen such that a channel // is created under one set of market conditions, and then submitted under a different @@ -364,7 +406,7 @@ func TestChannelManager_TxData(t *testing.T) { m.blocks = queue.Queue[SizedBlock]{SizedBlock{Block: blockA}} // Call TxData a first time to trigger blocks->channels pipeline - _, err := m.TxData(eth.BlockID{}, false, false, false) + _, err := m.TxData(eth.BlockID{}, false, false, pubInfo{}) require.ErrorIs(t, err, io.EOF) // The test requires us to have something in the channel queue @@ -383,7 +425,7 @@ func TestChannelManager_TxData(t *testing.T) { var data txData for { m.blocks.Enqueue(SizedBlock{Block: blockA}) - data, err = m.TxData(eth.BlockID{}, false, false, false) + data, err = m.TxData(eth.BlockID{}, false, false, pubInfo{}) if err == nil && data.Len() > 0 { break } @@ -607,11 +649,12 @@ func TestChannelManager_PruneBlocks(t *testing.T) { func TestChannelManager_PruneChannels(t *testing.T) { cfg := channelManagerTestConfig(100, derive.SingularBatchType) - A, err := newChannelWithChannelOut(nil, metrics.NoopMetrics, cfg, defaultTestRollupConfig, 0) + l := testlog.Logger(t, log.LevelCrit) + A, err := newChannelWithChannelOut(l, metrics.NoopMetrics, cfg, defaultTestRollupConfig, 0) require.NoError(t, err) - B, err := newChannelWithChannelOut(nil, metrics.NoopMetrics, cfg, defaultTestRollupConfig, 0) + B, err := newChannelWithChannelOut(l, metrics.NoopMetrics, cfg, defaultTestRollupConfig, 0) require.NoError(t, err) - C, err := newChannelWithChannelOut(nil, metrics.NoopMetrics, cfg, defaultTestRollupConfig, 0) + C, err := newChannelWithChannelOut(l, metrics.NoopMetrics, cfg, defaultTestRollupConfig, 0) require.NoError(t, err) type testCase struct { @@ -710,7 +753,7 @@ func TestChannelManager_TxData_ForcePublish(t *testing.T) { m.blocks = queue.Queue[SizedBlock]{SizedBlock{Block: blockA}} // Call TxData a first time to trigger blocks->channels pipeline - txData, err := m.TxData(eth.BlockID{}, false, false, false) + txData, err := m.TxData(eth.BlockID{}, false, false, pubInfo{}) require.ErrorIs(t, err, io.EOF) require.Zero(t, txData.Len(), 0) @@ -720,7 +763,7 @@ func TestChannelManager_TxData_ForcePublish(t *testing.T) { require.False(t, m.channelQueue[0].IsFull()) // Call TxData with force publish enabled - txData, err = m.TxData(eth.BlockID{}, false, false, true) + txData, err = m.TxData(eth.BlockID{}, false, false, pubInfo{forcePublish: true}) // Despite no additional blocks being added, we should have tx data: require.NoError(t, err) @@ -827,7 +870,7 @@ func TestChannelManagerUnsafeBytes(t *testing.T) { _, err = manager.TxData(eth.BlockID{ Hash: common.Hash{}, Number: 0, - }, true, false, false) + }, true, false, pubInfo{}) } assert.Equal(t, tc.afterAddingToChannel, manager.UnsafeDABytes()) diff --git a/op-batcher/batcher/driver.go b/op-batcher/batcher/driver.go index 4630df4a6f3..3fac88ac25f 100644 --- a/op-batcher/batcher/driver.go +++ b/op-batcher/batcher/driver.go @@ -83,6 +83,7 @@ type RollupClient interface { // DriverSetup is the collection of input/output interfaces and configuration that the driver operates on. type DriverSetup struct { + closeApp context.CancelCauseFunc Log log.Logger Metr metrics.Metricer RollupConfig *rollup.Config @@ -99,7 +100,6 @@ type DriverSetup struct { // batches to L1 for availability. type BatchSubmitter struct { DriverSetup - wg *sync.WaitGroup shutdownCtx, killCtx context.Context cancelShutdownCtx, cancelKillCtx context.CancelFunc @@ -117,7 +117,7 @@ type BatchSubmitter struct { throttleController *throttler.ThrottleController - publishSignal chan bool // true if we should force a tx to be published now, false if we should check the usual conditions (timeouts) + publishSignal chan pubInfo } // NewBatchSubmitter initializes the BatchSubmitter driver from a preconfigured DriverSetup @@ -173,7 +173,7 @@ func (l *BatchSubmitter) StartBatchSubmitting() error { // Channels used to signal between the loops unsafeBytesUpdated := make(chan int64, 1) - publishSignal := make(chan bool, 1) + publishSignal := make(chan pubInfo, 1) l.publishSignal = publishSignal // DA throttling loop should always be started except for testing (indicated by ThrottleThreshold == 0) @@ -268,13 +268,13 @@ func (l *BatchSubmitter) Flush(ctx context.Context) error { } l.Log.Info("Flushing Batch Submitter") - trySignal(l.publishSignal, true) + l.tryPublishSignal(l.publishSignal, pubInfo{forcePublish: true}) return nil } // loadBlocksIntoState loads the blocks between start and end (inclusive). // If there is a reorg, it will return an error. -func (l *BatchSubmitter) loadBlocksIntoState(ctx context.Context, start, end uint64, publishSignal chan bool, unsafeBytesUpdated chan int64) error { +func (l *BatchSubmitter) loadBlocksIntoState(ctx context.Context, start, end uint64, publishSignal chan pubInfo, unsafeBytesUpdated chan int64) error { if end < start { return fmt.Errorf("start number is > end number %d,%d", start, end) } @@ -302,7 +302,7 @@ func (l *BatchSubmitter) loadBlocksIntoState(ctx context.Context, start, end uin // This allows the batcher to start publishing sooner in the // case of a large backlog of blocks to load. l.sendToThrottlingLoop(unsafeBytesUpdated) - trySignal(publishSignal, false) + l.tryPublishSignal(publishSignal, pubInfo{ignoreMaxChannelDuration: i < end}) } } @@ -328,7 +328,6 @@ func (l *BatchSubmitter) loadBlockIntoState(ctx context.Context, blockNumber uin defer cancel() block, err := l2Client.BlockByNumber(cCtx, new(big.Int).SetUint64(blockNumber)) - if err != nil { return nil, fmt.Errorf("getting L2 block: %w", err) } @@ -437,12 +436,13 @@ func (l *BatchSubmitter) sendToThrottlingLoop(unsafeBytesUpdated chan int64) { } } -// trySignal tries to send an empty struct on the provided channel. +// trySignal tries to send the provided value on the provided channel. // It is not blocking, no signal will be sent if the channel is full. -func trySignal(c chan bool, value bool) { +func (l *BatchSubmitter) tryPublishSignal(c chan pubInfo, value pubInfo) { select { case c <- value: default: + l.Log.Warn("publishSignal channel is full, skipping signal") } } @@ -487,7 +487,7 @@ func (l *BatchSubmitter) syncAndPrune(syncStatus *eth.SyncStatus) *inclusiveBloc // - waits for a signal that blocks have been loaded // - drives the creation of channels and frames // - sends transactions to the DA layer -func (l *BatchSubmitter) publishingLoop(ctx context.Context, wg *sync.WaitGroup, receiptsCh chan txmgr.TxReceipt[txRef], publishSignal chan bool) { +func (l *BatchSubmitter) publishingLoop(ctx context.Context, wg *sync.WaitGroup, receiptsCh chan txmgr.TxReceipt[txRef], publishSignal chan pubInfo) { defer close(receiptsCh) defer wg.Done() @@ -499,9 +499,9 @@ func (l *BatchSubmitter) publishingLoop(ctx context.Context, wg *sync.WaitGroup, } txQueue := txmgr.NewQueue[txRef](ctx, l.Txmgr, l.Config.MaxPendingTransactions) - for forcePublish := range publishSignal { - l.Log.Debug("publishing loop received signal", "force_publish", forcePublish) - l.publishStateToL1(ctx, txQueue, receiptsCh, daGroup, forcePublish) + for pi := range l.publishSignal { + l.Log.Debug("publishing loop received signal", "force_publish", pi.forcePublish) + l.publishStateToL1(ctx, txQueue, receiptsCh, daGroup, pi) } // First wait for all DA requests to finish to prevent new transactions being queued @@ -524,7 +524,7 @@ func (l *BatchSubmitter) publishingLoop(ctx context.Context, wg *sync.WaitGroup, // - polls the sequencer, // - prunes the channel manager state (i.e. safe blocks) // - loads unsafe blocks from the sequencer -func (l *BatchSubmitter) blockLoadingLoop(ctx context.Context, wg *sync.WaitGroup, unsafeBytesUpdated chan int64, publishSignal chan bool) { +func (l *BatchSubmitter) blockLoadingLoop(ctx context.Context, wg *sync.WaitGroup, unsafeBytesUpdated chan int64, publishSignal chan pubInfo) { ticker := time.NewTicker(l.Config.PollInterval) defer ticker.Stop() defer close(unsafeBytesUpdated) @@ -556,7 +556,7 @@ func (l *BatchSubmitter) blockLoadingLoop(ctx context.Context, wg *sync.WaitGrou l.sendToThrottlingLoop(unsafeBytesUpdated) // we have increased the unsafe data. Signal the throttling loop to check if it should throttle. } } - trySignal(publishSignal, false) // always signal the write loop to ensure we periodically publish even if we aren't loading blocks + l.tryPublishSignal(publishSignal, pubInfo{}) // always signal the publishing loop to ensure we periodically publish even if we aren't loading blocks case <-ctx.Done(): l.Log.Info("blockLoadingLoop returning") return @@ -585,6 +585,10 @@ func (l *BatchSubmitter) receiptsLoop(wg *sync.WaitGroup, receiptsCh chan txmgr. l.Log.Info("receiptsLoop returning") } +func ErrSetMaxDASizeRPCMethodUnavailable(endpoint string, err error) error { + return fmt.Errorf("%s unavailable at %s, either enable it or disable throttling: %w", SetMaxDASizeMethod, endpoint, err) +} + // singleEndpointThrottler handles throttling for a specific endpoint func (l *BatchSubmitter) singleEndpointThrottler(wg *sync.WaitGroup, throttleSignal chan struct{}, endpoint string) { defer wg.Done() @@ -623,20 +627,13 @@ func (l *BatchSubmitter) singleEndpointThrottler(wg *sync.WaitGroup, throttleSig return } - var rpcErr rpc.Error - if errors.As(err, &rpcErr) && eth.ErrorCode(rpcErr.ErrorCode()).IsGenericRPCError() { - l.Log.Error("SetMaxDASize RPC method unavailable on endpoint, shutting down. Either enable it or disable throttling.", - "endpoint", endpoint, "err", err) - - // We have a strict requirement that all endpoints must have the SetMaxDASize endpoint, and shut down if this RPC method is not available - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - // Call StopBatchSubmitting in another goroutine to avoid deadlock. - go func() { - _ = l.StopBatchSubmitting(ctx) - }() + if isCriticalThrottlingRPCError(err) { + // We have a strict requirement that all endpoints must have the SetMaxDASize endpoint, + // and shut down if this RPC method is not available or returns another application-level error. + l.shutdownOnCriticalError(ErrSetMaxDASizeRPCMethodUnavailable(endpoint, err)) return } else if err != nil { + // Transport-level errors are retried. l.Log.Warn("SetMaxDASize RPC failed for endpoint, retrying.", "endpoint", endpoint, "err", err) retryTimer.Reset(retryInterval) return @@ -671,6 +668,21 @@ func (l *BatchSubmitter) singleEndpointThrottler(wg *sync.WaitGroup, throttleSig } } +func isCriticalThrottlingRPCError(err error) bool { + var rpcErr rpc.Error + return errors.As(err, &rpcErr) && eth.ErrorCode(rpcErr.ErrorCode()).IsGenericRPCError() +} + +func (l *BatchSubmitter) shutdownOnCriticalError(err error) { + l.Log.Error("Critical error detected, attempting batcher shut down", "err", err) + if l.closeApp != nil { + // Call closeApp to trigger process to exit (gracefully) if l.closeApp is set. + l.closeApp(err) + } else { + l.Log.Warn("No closeApp function set, cannot shut down batcher on critical error", "err", err) + } +} + // throttlingLoop acts as a distributor that spawns individual throttling loops for each endpoint // and fans out the unsafe bytes updates to each endpoint func (l *BatchSubmitter) throttlingLoop(wg *sync.WaitGroup, unsafeBytesUpdated chan int64) { @@ -771,7 +783,7 @@ func (l *BatchSubmitter) waitNodeSync() error { // publishStateToL1 queues up all pending TxData to be published to the L1, returning when there is no more data to // queue for publishing or if there was an error queuing the data. -func (l *BatchSubmitter) publishStateToL1(ctx context.Context, queue *txmgr.Queue[txRef], receiptsCh chan txmgr.TxReceipt[txRef], daGroup *errgroup.Group, forcePublish bool) { +func (l *BatchSubmitter) publishStateToL1(ctx context.Context, queue *txmgr.Queue[txRef], receiptsCh chan txmgr.TxReceipt[txRef], daGroup *errgroup.Group, pi pubInfo) { for { select { case <-ctx.Done(): @@ -788,7 +800,7 @@ func (l *BatchSubmitter) publishStateToL1(ctx context.Context, queue *txmgr.Queu return } - err := l.publishTxToL1(ctx, queue, receiptsCh, daGroup, forcePublish) + err := l.publishTxToL1(ctx, queue, receiptsCh, daGroup, pi) if err != nil { if err != io.EOF { l.Log.Error("Error publishing tx to l1", "err", err) @@ -842,7 +854,7 @@ func (l *BatchSubmitter) clearState(ctx context.Context) { } // publishTxToL1 submits a single state tx to the L1 -func (l *BatchSubmitter) publishTxToL1(ctx context.Context, queue *txmgr.Queue[txRef], receiptsCh chan txmgr.TxReceipt[txRef], daGroup *errgroup.Group, forcePublish bool) error { +func (l *BatchSubmitter) publishTxToL1(ctx context.Context, queue *txmgr.Queue[txRef], receiptsCh chan txmgr.TxReceipt[txRef], daGroup *errgroup.Group, pi pubInfo) error { // send all available transactions l1tip, isPectra, err := l.l1Tip(ctx) if err != nil { @@ -855,7 +867,7 @@ func (l *BatchSubmitter) publishTxToL1(ctx context.Context, queue *txmgr.Queue[t // Collect next transaction data. This pulls data out of the channel, so we need to make sure // to put it back if ever da or txmgr requests fail, by calling l.recordFailedDARequest/recordFailedTx. l.channelMgrMutex.Lock() - txdata, err := l.channelMgr.TxData(l1tip.ID(), isPectra, params.IsThrottling(), forcePublish) + txdata, err := l.channelMgr.TxData(l1tip.ID(), isPectra, params.IsThrottling(), pi) l.channelMgrMutex.Unlock() if err == io.EOF { diff --git a/op-batcher/batcher/driver_test.go b/op-batcher/batcher/driver_test.go index 454ade01ee7..a58521b377a 100644 --- a/op-batcher/batcher/driver_test.go +++ b/op-batcher/batcher/driver_test.go @@ -4,9 +4,11 @@ import ( "context" "encoding/json" "errors" + "fmt" "net" "net/http" "net/http/httptest" + "slices" "sync" "testing" "time" @@ -20,6 +22,7 @@ import ( "github.com/ethereum-optimism/optimism/op-service/testutils" "github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -49,13 +52,20 @@ func (p *mockL2EndpointProvider) Close() {} const genesisL1Origin = uint64(123) -func setup(t *testing.T) (*BatchSubmitter, *mockL2EndpointProvider) { +func setup(t *testing.T, closeAppFn context.CancelCauseFunc) (*BatchSubmitter, *mockL2EndpointProvider) { ep := newEndpointProvider() cfg := defaultTestRollupConfig cfg.Genesis.L1.Number = genesisL1Origin + if closeAppFn == nil { + closeAppFn = func(cause error) { + t.Fatalf("closeAppFn called, batcher hit a critical error: %v", cause) + } + } + return NewBatchSubmitter(DriverSetup{ + closeApp: closeAppFn, Log: testlog.Logger(t, log.LevelDebug), Metr: metrics.NoopMetrics, RollupConfig: cfg, @@ -70,7 +80,7 @@ func setup(t *testing.T) (*BatchSubmitter, *mockL2EndpointProvider) { } func TestBatchSubmitter_SafeL1Origin(t *testing.T) { - bs, ep := setup(t) + bs, ep := setup(t, nil) tests := []struct { name string @@ -123,7 +133,7 @@ func TestBatchSubmitter_SafeL1Origin(t *testing.T) { } func TestBatchSubmitter_SafeL1Origin_FailsToResolveRollupClient(t *testing.T) { - bs, ep := setup(t) + bs, ep := setup(t, nil) ep.rollupClientErr = errors.New("failed to resolve rollup client") @@ -145,7 +155,7 @@ func (q *MockTxQueue) Load(id string) txmgr.TxCandidate { } func TestBatchSubmitter_sendTx_FloorDataGas(t *testing.T) { - bs, _ := setup(t) + bs, _ := setup(t, nil) q := new(MockTxQueue) @@ -173,9 +183,17 @@ func TestBatchSubmitter_sendTx_FloorDataGas(t *testing.T) { require.GreaterOrEqual(t, candidateOut.GasLimit, expectedFloorDataGas) } +type handlerFailureMode string + +const ( + noFailure handlerFailureMode = "none" + internalError handlerFailureMode = "internal_error" + methodNotFound handlerFailureMode = "method_not_found" +) + // createHTTPHandler creates a mock HTTP handler for testing, it accepts a callback which // is invoked when the expected request is received. -func createHTTPHandler(t *testing.T, cb func(), alwaysFails bool) http.HandlerFunc { +func createHTTPHandler(t *testing.T, cb func(), failureMode handlerFailureMode) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { var req struct { @@ -185,16 +203,22 @@ func createHTTPHandler(t *testing.T, cb func(), alwaysFails bool) http.HandlerFu ID interface{} `json:"id"` } if err := json.NewDecoder(r.Body).Decode(&req); err == nil { + cb() - if alwaysFails { + switch failureMode { + case noFailure: + w.Header().Set("Content-Type", "application/json") + _, err := w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":true}`)) + if err != nil { + t.Logf("Error writing response: %v", err) + } + return + case internalError: http.Error(w, "Simulated failure", http.StatusInternalServerError) - cb() return - } - if req.Method == "miner_setMaxDASize" && len(req.Params) == 2 { - cb() + case methodNotFound: w.Header().Set("Content-Type", "application/json") - _, err := w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":true}`)) + _, err := w.Write([]byte(fmt.Sprintf(`{"jsonrpc":"2.0","id":1,"error":{"code":%d,"message":"method not found"}}`, eth.MethodNotFound))) if err != nil { t.Logf("Error writing response: %v", err) } @@ -207,7 +231,8 @@ func createHTTPHandler(t *testing.T, cb func(), alwaysFails bool) http.HandlerFu } func TestBatchSubmitter_ThrottlingEndpoints(t *testing.T) { - + // Set a very long timeout to avoid flakiness + timeout := time.Second * 120 testThrottlingEndpoints := func(numHealthyServers, numUnhealthyServers int) func(t *testing.T) { return func(t *testing.T) { @@ -220,12 +245,12 @@ func TestBatchSubmitter_ThrottlingEndpoints(t *testing.T) { urls := make([]string, 0, numHealthyServers+numUnhealthyServers) for i := range healthyCalls { - healthyServers[i] = httptest.NewServer(createHTTPHandler(t, func() { healthyCalls[i]++ }, false)) + healthyServers[i] = httptest.NewServer(createHTTPHandler(t, func() { healthyCalls[i]++ }, noFailure)) urls = append(urls, healthyServers[i].URL) defer healthyServers[i].Close() } for i := range unHealthyCalls { - unhealthyServers[i] = httptest.NewServer(createHTTPHandler(t, func() { unHealthyCalls[i]++ }, true)) + unhealthyServers[i] = httptest.NewServer(createHTTPHandler(t, func() { unHealthyCalls[i]++ }, internalError)) urls = append(urls, unhealthyServers[i].URL) defer unhealthyServers[i].Close() } @@ -238,8 +263,12 @@ func TestBatchSubmitter_ThrottlingEndpoints(t *testing.T) { t.Log("Throttling endpoints:", urls) + var batcherShutdownError error + // Create test BatchSubmitter using the setup function - bs, _ := setup(t) + bs, _ := setup(t, func(cause error) { + batcherShutdownError = cause + }) bs.shutdownCtx = ctx bs.Config.NetworkTimeout = time.Second bs.Config.ThrottleParams.Endpoints = urls @@ -290,15 +319,8 @@ func TestBatchSubmitter_ThrottlingEndpoints(t *testing.T) { require.Eventually(t, func() bool { // Check that all endpoints were called - for i := range healthyCalls { - if healthyCalls[i] == 0 { - return false - } - } - for i := range unHealthyCalls { - if unHealthyCalls[i] == 0 { - return false - } + if slices.Contains(healthyCalls, 0) || slices.Contains(unHealthyCalls, 0) { + return false } return true }, time.Second*10, time.Millisecond*10, "All endpoints should have been called within 10s") @@ -322,18 +344,66 @@ func TestBatchSubmitter_ThrottlingEndpoints(t *testing.T) { addr := healthyServers[0].Listener.Addr().String() healthyServers[0].Close() time.Sleep(time.Second * 2) - startTestServerAtAddr(addr, createHTTPHandler(t, func() { restartedServerCalled = true }, false)) + startTestServerAtAddr(addr, createHTTPHandler(t, func() { restartedServerCalled = true }, noFailure)) defer healthyServers[0].Close() t.Log("restarted server at", addr) require.Eventually(t, func() bool { return restartedServerCalled - }, time.Second*2, time.Millisecond*10, "Restarted server should have been called within 2s") + }, timeout, time.Millisecond*10, "Restarted server should have been called within 2s") } + // Take an unhealthy server down, wait 2s and bring it back up with misconfiguration. Check the batcher exits. + if len(unhealthyServers) > 0 { + restartedServerCalled := false + + addr := unhealthyServers[0].Listener.Addr().String() + unhealthyServers[0].Close() + time.Sleep(time.Second * 2) + startTestServerAtAddr(addr, createHTTPHandler(t, func() { restartedServerCalled = true }, methodNotFound)) + defer unhealthyServers[0].Close() + t.Log("restarted server at", addr) + + require.Eventually(t, func() bool { + return restartedServerCalled + }, timeout, time.Millisecond*10, "Restarted server should have been called within 2s") + + require.Eventually(t, func() bool { + return batcherShutdownError != nil + }, timeout, time.Millisecond*10, "Batcher should have triggered self shutdown within 2s") + + require.Equal(t, batcherShutdownError.Error(), ErrSetMaxDASizeRPCMethodUnavailable("http://"+addr, errors.New("method not found")).Error(), "Batcher shutdown error should be the same as the expected error") + } } } t.Run("two normal endpoints", testThrottlingEndpoints(2, 0)) t.Run("two failing endpoints", testThrottlingEndpoints(0, 2)) t.Run("one normal endpoint, one failing endpoint", testThrottlingEndpoints(1, 1)) } + +func TestBatchSubmitter_CriticalError(t *testing.T) { + criticalErrors := []error{ + eth.InputError{ + Code: eth.MethodNotFound, + }, + eth.InputError{ + Code: eth.InvalidParams, + }, + } + + for _, e := range criticalErrors { + assert.True(t, isCriticalThrottlingRPCError(e), "false positive: %s", e) + } + + nonCriticalErrors := []error{ + eth.InputError{ + Code: eth.UnsupportedFork, + }, + errors.New("timeout"), + } + + for _, e := range nonCriticalErrors { + assert.False(t, isCriticalThrottlingRPCError(e), "false negative: %s", e) + } + +} diff --git a/op-batcher/batcher/service.go b/op-batcher/batcher/service.go index d12285e2a18..e253420b1ac 100644 --- a/op-batcher/batcher/service.go +++ b/op-batcher/batcher/service.go @@ -55,6 +55,7 @@ type BatcherConfig struct { // BatcherService represents a full batch-submitter instance and its resources, // and conforms to the op-service CLI Lifecycle interface. type BatcherService struct { + closeApp context.CancelCauseFunc Log log.Logger Metrics metrics.Metricer L1Client *ethclient.Client @@ -86,15 +87,16 @@ type DriverSetupOption func(setup *DriverSetup) // BatcherServiceFromCLIConfig creates a new BatcherService from a CLIConfig. // The service components are fully started, except for the driver, // which will not be submitting batches (if it was configured to) until the Start part of the lifecycle. -func BatcherServiceFromCLIConfig(ctx context.Context, version string, cfg *CLIConfig, log log.Logger, opts ...DriverSetupOption) (*BatcherService, error) { +func BatcherServiceFromCLIConfig(ctx context.Context, closeApp context.CancelCauseFunc, version string, cfg *CLIConfig, log log.Logger, opts ...DriverSetupOption) (*BatcherService, error) { var bs BatcherService - if err := bs.initFromCLIConfig(ctx, version, cfg, log, opts...); err != nil { + if err := bs.initFromCLIConfig(ctx, closeApp, version, cfg, log, opts...); err != nil { return nil, errors.Join(err, bs.Stop(ctx)) // try to clean up our failed initialization attempt } return &bs, nil } -func (bs *BatcherService) initFromCLIConfig(ctx context.Context, version string, cfg *CLIConfig, log log.Logger, opts ...DriverSetupOption) error { +func (bs *BatcherService) initFromCLIConfig(ctx context.Context, closeApp context.CancelCauseFunc, version string, cfg *CLIConfig, log log.Logger, opts ...DriverSetupOption) error { + bs.closeApp = closeApp bs.Version = version bs.Log = log bs.NotSubmittingOnStart = cfg.Stopped @@ -385,6 +387,7 @@ func (bs *BatcherService) initMetricsServer(cfg *CLIConfig) error { func (bs *BatcherService) initDriver(opts ...DriverSetupOption) { ds := DriverSetup{ + closeApp: bs.closeApp, Log: bs.Log, Metr: bs.Metrics, RollupConfig: bs.RollupConfig, diff --git a/op-chain-ops/addresses/contracts.go b/op-chain-ops/addresses/contracts.go index eb9ee2c888d..88ab066a52a 100644 --- a/op-chain-ops/addresses/contracts.go +++ b/op-chain-ops/addresses/contracts.go @@ -34,6 +34,7 @@ type ImplementationsContracts struct { OpcmUpgraderImpl common.Address OpcmInteropMigratorImpl common.Address OpcmStandardValidatorImpl common.Address + OpcmUtilsImpl common.Address OpcmV2Impl common.Address OpcmContainerImpl common.Address DelayedWethImpl common.Address diff --git a/op-chain-ops/cmd/check-jovian/main.go b/op-chain-ops/cmd/check-jovian/main.go index 4bf7e7d2583..1bb3c19a2e2 100644 --- a/op-chain-ops/cmd/check-jovian/main.go +++ b/op-chain-ops/cmd/check-jovian/main.go @@ -248,12 +248,12 @@ func checkExtraData(ctx context.Context, env *actionEnv) error { extra := latest.Extra // Validate using op-geth's validation function - if err := eip1559.ValidateMinBaseFeeExtraData(extra); err != nil { + if err := eip1559.ValidateJovianExtraData(extra); err != nil { return fmt.Errorf("invalid extraData format: %w", err) } // Decode the validated extra data using op-geth's decode function - denominator, elasticity, minBaseFee := eip1559.DecodeMinBaseFeeExtraData(extra) + denominator, elasticity, minBaseFee := eip1559.DecodeJovianExtraData(extra) env.log.Info("ExtraData format test: success", "blockNumber", latest.Number, diff --git a/op-chain-ops/genesis/genesis.go b/op-chain-ops/genesis/genesis.go index a7a437fc172..e89d2e26936 100644 --- a/op-chain-ops/genesis/genesis.go +++ b/op-chain-ops/genesis/genesis.go @@ -23,7 +23,7 @@ const defaultGasLimit = 30_000_000 var HoloceneExtraData = eip1559.EncodeHoloceneExtraData(250, 6) // MinBaseFeeExtraData represents the default extra data for Jovian-genesis chains. -var MinBaseFeeExtraData = eip1559.EncodeMinBaseFeeExtraData(250, 6, 0) +var MinBaseFeeExtraData = eip1559.EncodeJovianExtraData(250, 6, 0) // NewL2Genesis will create a new L2 genesis func NewL2Genesis(config *DeployConfig, l1StartHeader *eth.BlockRef) (*core.Genesis, error) { @@ -123,7 +123,7 @@ func NewL2Genesis(config *DeployConfig, l1StartHeader *eth.BlockRef) (*core.Gene if optimismChainConfig.IsIsthmus(genesis.Timestamp) { genesis.Alloc[params.HistoryStorageAddress] = types.Account{Nonce: 1, Code: params.HistoryStorageCode, Balance: common.Big0} } - if optimismChainConfig.IsMinBaseFee(genesis.Timestamp) { + if optimismChainConfig.IsJovian(genesis.Timestamp) { genesis.ExtraData = MinBaseFeeExtraData } diff --git a/op-chain-ops/interopgen/deployments.go b/op-chain-ops/interopgen/deployments.go index 12c9223e429..86bde012dc0 100644 --- a/op-chain-ops/interopgen/deployments.go +++ b/op-chain-ops/interopgen/deployments.go @@ -17,6 +17,7 @@ type Implementations struct { OpcmUpgrader common.Address `json:"OPCMUpgrader"` OpcmInteropMigrator common.Address `json:"OPCMInteropMigrator"` OpcmStandardValidator common.Address `json:"OPCMStandardValidator"` + OpcmUtils common.Address `json:"OPCMUtils"` OpcmV2 common.Address `json:"OPCMV2"` OpcmContainer common.Address `json:"OPCMContainer"` DelayedWETHImpl common.Address `json:"DelayedWETHImpl"` diff --git a/op-challenger/cmd/main_test.go b/op-challenger/cmd/main_test.go index 2e5ae1652b3..0647e675f2c 100644 --- a/op-challenger/cmd/main_test.go +++ b/op-challenger/cmd/main_test.go @@ -150,7 +150,7 @@ func TestOpSupervisor(t *testing.T) { func TestGameTypes(t *testing.T) { t.Run("Default", func(t *testing.T) { - expectedDefault := []gameTypes.GameType{gameTypes.CannonGameType, gameTypes.AsteriscKonaGameType, gameTypes.CannonKonaGameType} + expectedDefault := []gameTypes.GameType{gameTypes.CannonGameType, gameTypes.CannonKonaGameType} cfg := configForArgs(t, addRequiredArgsForMultipleGameTypesExcept(expectedDefault, "--game-types")) require.Equal(t, expectedDefault, cfg.GameTypes) }) @@ -1409,7 +1409,7 @@ func requiredArgs(gameType gameTypes.GameType) map[string]string { addRequiredSuperCannonKonaArgs(args) case gameTypes.SuperAsteriscKonaGameType: addRequiredSuperAsteriscKonaArgs(args) - case gameTypes.AlphabetGameType, gameTypes.FastGameType: + case gameTypes.OptimisticZKGameType, gameTypes.AlphabetGameType, gameTypes.FastGameType: addRequiredOutputRootArgs(args) } return args diff --git a/op-challenger/config/config.go b/op-challenger/config/config.go index 469fba793f2..fa37498de70 100644 --- a/op-challenger/config/config.go +++ b/op-challenger/config/config.go @@ -365,6 +365,11 @@ func (c Config) Check() error { return err } } + if c.GameTypeEnabled(gameTypes.OptimisticZKGameType) { + if c.RollupRpc == "" { + return ErrMissingRollupRpc + } + } if c.GameTypeEnabled(gameTypes.AlphabetGameType) || c.GameTypeEnabled(gameTypes.FastGameType) { if c.RollupRpc == "" { return ErrMissingRollupRpc diff --git a/op-challenger/config/config_test.go b/op-challenger/config/config_test.go index 3b1b0107b48..86b38b175e1 100644 --- a/op-challenger/config/config_test.go +++ b/op-challenger/config/config_test.go @@ -150,6 +150,10 @@ func applyValidConfigForSuperAsteriscKona(t *testing.T, cfg *Config) { applyValidConfigForAsteriscKona(t, cfg) } +func applyValidConfigForOptimisticZK(cfg *Config) { + cfg.RollupRpc = validRollupRpc +} + func validConfig(t *testing.T, gameType gameTypes.GameType) Config { cfg := NewConfig(validGameFactoryAddress, validL1EthRpc, validL1BeaconUrl, validRollupRpc, validL2Rpc, validDatadir, gameType) if gameType == gameTypes.SuperCannonGameType || gameType == gameTypes.SuperPermissionedGameType { @@ -173,6 +177,9 @@ func validConfig(t *testing.T, gameType gameTypes.GameType) Config { if gameType == gameTypes.SuperAsteriscKonaGameType { applyValidConfigForSuperAsteriscKona(t, &cfg) } + if gameType == gameTypes.OptimisticZKGameType { + applyValidConfigForOptimisticZK(&cfg) + } return cfg } diff --git a/op-challenger/flags/flags.go b/op-challenger/flags/flags.go index 1a9c4c2efd2..d5626fab855 100644 --- a/op-challenger/flags/flags.go +++ b/op-challenger/flags/flags.go @@ -88,7 +88,7 @@ var ( Aliases: []string{"trace-type"}, // For backwards compatibility Usage: "The game types to support. Valid options: " + openum.EnumStringer(gameTypes.SupportedGameTypes), EnvVars: prefixEnvVars("GAME_TYPES", "TRACE_TYPE"), - Value: cli.NewStringSlice(gameTypes.CannonGameType.String(), gameTypes.AsteriscKonaGameType.String(), gameTypes.CannonKonaGameType.String()), + Value: cli.NewStringSlice(gameTypes.CannonGameType.String(), gameTypes.CannonKonaGameType.String()), } DatadirFlag = &cli.StringFlag{ Name: "datadir", @@ -589,7 +589,7 @@ func CheckRequired(ctx *cli.Context, types []gameTypes.GameType) error { if err := CheckSuperAsteriscKonaFlags(ctx); err != nil { return err } - case gameTypes.AlphabetGameType, gameTypes.FastGameType: + case gameTypes.OptimisticZKGameType, gameTypes.AlphabetGameType, gameTypes.FastGameType: if err := checkOutputProviderFlags(ctx); err != nil { return err } diff --git a/op-challenger/game/fault/agent.go b/op-challenger/game/fault/agent.go index c76331ce667..72249268bed 100644 --- a/op-challenger/game/fault/agent.go +++ b/op-challenger/game/fault/agent.go @@ -9,17 +9,41 @@ import ( "sync/atomic" "time" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/claims" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/preimages" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/responder" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/solver" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + "github.com/ethereum-optimism/optimism/op-challenger/game/generic" gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-service/clock" + "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" + "github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) +type TxSender interface { + From() common.Address + SendAndWaitSimple(txPurpose string, txs ...txmgr.TxCandidate) error +} + +type GameContract interface { + generic.GenericGameLoader + preimages.PreimageGameContract + responder.GameContract + claims.BondContract + ClaimLoader + GetStatus(ctx context.Context) (gameTypes.GameStatus, error) + GetMaxGameDepth(ctx context.Context) (types.Depth, error) + GetMaxClockDuration(ctx context.Context) (time.Duration, error) + GetOracle(ctx context.Context) (contracts.PreimageOracleContract, error) + GetL1Head(ctx context.Context) (common.Hash, error) +} + // Responder takes a response action & executes. // For full op-challenger this means executing the transaction on chain. type Responder interface { @@ -31,6 +55,7 @@ type Responder interface { } type ClaimLoader interface { + GetClaimCount(ctx context.Context) (uint64, error) GetAllClaims(ctx context.Context, block rpcblock.Block) ([]types.Claim, error) IsL2BlockNumberChallenged(ctx context.Context, block rpcblock.Block) (bool, error) GetClockExtension(ctx context.Context) (time.Duration, error) @@ -39,6 +64,8 @@ type ClaimLoader interface { GetOracle(ctx context.Context) (contracts.PreimageOracleContract, error) } +type resourceCreator func(ctx context.Context, logger log.Logger, gameDepth types.Depth, l1Head eth.BlockID, dir string) (types.TraceAccessor, error) + type Agent struct { metrics metrics.Metricer systemClock clock.Clock @@ -56,6 +83,71 @@ type Agent struct { responseCount atomic.Uint64 // Number of responses made in this game } +func AgentCreator( + systemClock clock.Clock, + l1Clock types.ClockReader, + m metrics.Metricer, + dir string, + txSender TxSender, + loader GameContract, + creator resourceCreator, + selective bool, + claimants []common.Address, + responseDelay time.Duration, + responseDelayAfter uint64, +) generic.ActorCreator { + return func(ctx context.Context, logger log.Logger, l1Head eth.BlockID) (generic.Actor, error) { + maxClockDuration, err := loader.GetMaxClockDuration(ctx) + if err != nil { + return nil, fmt.Errorf("failed to fetch the game duration: %w", err) + } + + gameDepth, err := loader.GetMaxGameDepth(ctx) + if err != nil { + return nil, fmt.Errorf("failed to fetch the game depth: %w", err) + } + + accessor, err := creator(ctx, logger, gameDepth, l1Head, dir) + if err != nil { + return nil, fmt.Errorf("failed to create trace accessor: %w", err) + } + + oracle, err := loader.GetOracle(ctx) + if err != nil { + return nil, fmt.Errorf("failed to load oracle: %w", err) + } + + minLargePreimageSize, err := oracle.MinLargePreimageSize(ctx) + if err != nil { + return nil, fmt.Errorf("failed to load min large preimage size: %w", err) + } + direct := preimages.NewDirectPreimageUploader(logger, txSender, loader) + large := preimages.NewLargePreimageUploader(logger, l1Clock, txSender, oracle) + uploader := preimages.NewSplitPreimageUploader(direct, large, minLargePreimageSize) + responder, err := responder.NewFaultResponder(logger, txSender, loader, uploader, oracle) + if err != nil { + return nil, fmt.Errorf("failed to create the responder: %w", err) + } + + agent := NewAgent( + m, + systemClock, + l1Clock, + loader, + gameDepth, + maxClockDuration, + accessor, + responder, + logger, + selective, + claimants, + responseDelay, + responseDelayAfter, + ) + return agent, nil + } +} + func NewAgent( m metrics.Metricer, systemClock clock.Clock, @@ -126,6 +218,14 @@ func (a *Agent) Act(ctx context.Context) error { return nil } +func (a *Agent) AdditionalStatus(ctx context.Context) ([]any, error) { + claimCount, err := a.loader.GetClaimCount(ctx) + if err != nil { + return nil, err + } + return []any{"claims", claimCount}, nil +} + func (a *Agent) performAction(ctx context.Context, wg *sync.WaitGroup, game types.Game, action types.Action) { defer wg.Done() actionLog := a.log.New("action", action.Type) diff --git a/op-challenger/game/fault/agent_test.go b/op-challenger/game/fault/agent_test.go index 8f274dbe106..669c5a23df0 100644 --- a/op-challenger/game/fault/agent_test.go +++ b/op-challenger/game/fault/agent_test.go @@ -236,6 +236,10 @@ func (s *stubClaimLoader) IsL2BlockNumberChallenged(_ context.Context, _ rpcbloc return s.blockNumChallenged, nil } +func (s *stubClaimLoader) GetClaimCount(_ context.Context) (uint64, error) { + return uint64(len(s.claims)), nil +} + func (s *stubClaimLoader) GetAllClaims(_ context.Context, _ rpcblock.Block) ([]types.Claim, error) { s.callCount++ if s.callCount > s.maxLoads && s.maxLoads != 0 { diff --git a/op-challenger/game/fault/contracts/disputegame.go b/op-challenger/game/fault/contracts/disputegame.go index 5dd756d5099..611e0e0cf60 100644 --- a/op-challenger/game/fault/contracts/disputegame.go +++ b/op-challenger/game/fault/contracts/disputegame.go @@ -23,6 +23,7 @@ type GenericGameMetadata struct { } type DisputeGameContract interface { + Addr() common.Address GetL1Head(ctx context.Context) (common.Hash, error) GetStatus(ctx context.Context) (gameTypes.GameStatus, error) GetGameRange(ctx context.Context) (prestateBlock uint64, poststateBlock uint64, retErr error) diff --git a/op-challenger/game/fault/contracts/faultdisputegame.go b/op-challenger/game/fault/contracts/faultdisputegame.go index 8d5f3fa2baa..d6239633c71 100644 --- a/op-challenger/game/fault/contracts/faultdisputegame.go +++ b/op-challenger/game/fault/contracts/faultdisputegame.go @@ -162,6 +162,10 @@ func mustParseAbi(json []byte) *abi.ABI { return &loaded } +func (f *FaultDisputeGameContractLatest) Addr() common.Address { + return f.contract.Addr() +} + // GetBalanceAndDelay returns the total amount of ETH controlled by this contract. // Note that the ETH is actually held by the DelayedWETH contract which may be shared by multiple games. // Returns the balance and the address of the contract that actually holds the balance. diff --git a/op-challenger/game/fault/contracts/gamefactory.go b/op-challenger/game/fault/contracts/gamefactory.go index 4727d0b9cc5..29097433a80 100644 --- a/op-challenger/game/fault/contracts/gamefactory.go +++ b/op-challenger/game/fault/contracts/gamefactory.go @@ -106,15 +106,29 @@ func (f *DisputeGameFactoryContract) GetGameCount(ctx context.Context, blockHash return result.GetBigInt(0).Uint64(), nil } -func (f *DisputeGameFactoryContract) GetGame(ctx context.Context, idx uint64, blockHash common.Hash) (gameTypes.GameMetadata, error) { +func (f *DisputeGameFactoryContract) GetGame(ctx context.Context, idx uint64, block rpcblock.Block) (gameTypes.GameMetadata, error) { defer f.metrics.StartContractRequest("GetGame")() - result, err := f.multiCaller.SingleCall(ctx, rpcblock.ByHash(blockHash), f.contract.Call(methodGameAtIndex, new(big.Int).SetUint64(idx))) + result, err := f.multiCaller.SingleCall(ctx, block, f.contract.Call(methodGameAtIndex, new(big.Int).SetUint64(idx))) if err != nil { return gameTypes.GameMetadata{}, fmt.Errorf("failed to load game %v: %w", idx, err) } return f.decodeGame(idx, result), nil } +func (f *DisputeGameFactoryContract) GetGameStatus(ctx context.Context, idx uint64) (gameTypes.GameStatus, error) { + defer f.metrics.StartContractRequest("GetGameStatus")() + game, err := f.GetGame(ctx, idx, rpcblock.Latest) + if err != nil { + return 0, fmt.Errorf("failed to load game status: %w", err) + } + + gameContract, err := NewDisputeGameContract(ctx, f.metrics, f.multiCaller, gameTypes.GameType(game.GameType), game.Proxy) + if err != nil { + return 0, fmt.Errorf("failed to create contract bindings for game %s: %w", game.Proxy, err) + } + return gameContract.GetStatus(ctx) +} + func (f *DisputeGameFactoryContract) getGameImpl(ctx context.Context, gameType gameTypes.GameType) (common.Address, error) { defer f.metrics.StartContractRequest("GetGameImpl")() result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodGameImpls, gameType)) diff --git a/op-challenger/game/fault/contracts/gamefactory_test.go b/op-challenger/game/fault/contracts/gamefactory_test.go index bdd468c49f4..b451628ef0b 100644 --- a/op-challenger/game/fault/contracts/gamefactory_test.go +++ b/op-challenger/game/fault/contracts/gamefactory_test.go @@ -123,8 +123,8 @@ func TestLoadGame(t *testing.T) { } expectedGames := []gameTypes.GameMetadata{game0, game1, game2} for idx, expected := range expectedGames { - expectGetGame(stubRpc, idx, blockHash, expected) - actual, err := factory.GetGame(context.Background(), uint64(idx), blockHash) + expectGetGame(stubRpc, idx, rpcblock.ByHash(blockHash), expected) + actual, err := factory.GetGame(context.Background(), uint64(idx), rpcblock.ByHash(blockHash)) require.NoError(t, err) require.Equal(t, expected, actual) } @@ -132,6 +132,28 @@ func TestLoadGame(t *testing.T) { } } +func TestGetGameStatus(t *testing.T) { + for _, version := range factoryVersions { + t.Run(version.String(), func(t *testing.T) { + stubRpc, factory := setupDisputeGameFactoryTest(t, version) + game0 := gameTypes.GameMetadata{ + Index: 0, + GameType: 0, + Timestamp: 1234, + Proxy: common.Address{0xaa}, + } + expectGetGame(stubRpc, 0, rpcblock.Latest, game0) + stubRpc.AddContract(game0.Proxy, snapshots.LoadFaultDisputeGameABI()) + expectedStatus := gameTypes.GameStatusChallengerWon + stubRpc.SetResponse(game0.Proxy, methodVersion, rpcblock.Latest, nil, []interface{}{versLatest}) + stubRpc.SetResponse(game0.Proxy, methodStatus, rpcblock.Latest, nil, []interface{}{expectedStatus}) + actual, err := factory.GetGameStatus(context.Background(), 0) + require.NoError(t, err) + require.Equal(t, expectedStatus, actual) + }) + } +} + func TestGetAllGames(t *testing.T) { for _, version := range factoryVersions { t.Run(version.String(), func(t *testing.T) { @@ -159,7 +181,7 @@ func TestGetAllGames(t *testing.T) { expectedGames := []gameTypes.GameMetadata{game0, game1, game2} stubRpc.SetResponse(factoryAddr, methodGameCount, rpcblock.ByHash(blockHash), nil, []interface{}{big.NewInt(int64(len(expectedGames)))}) for idx, expected := range expectedGames { - expectGetGame(stubRpc, idx, blockHash, expected) + expectGetGame(stubRpc, idx, rpcblock.ByHash(blockHash), expected) } actualGames, err := factory.GetAllGames(context.Background(), blockHash) require.NoError(t, err) @@ -201,7 +223,7 @@ func TestGetAllGamesAtOrAfter(t *testing.T) { stubRpc.SetResponse(factoryAddr, methodGameCount, rpcblock.ByHash(blockHash), nil, []interface{}{big.NewInt(int64(len(allGames)))}) for idx, expected := range allGames { - expectGetGame(stubRpc, idx, blockHash, expected) + expectGetGame(stubRpc, idx, rpcblock.ByHash(blockHash), expected) } // Set an earliest timestamp that's in the middle of a batch earliestTimestamp := uint64(test.earliestGameIdx) @@ -427,11 +449,11 @@ func TestDecodeDisputeGameCreatedLog(t *testing.T) { } } -func expectGetGame(stubRpc *batchingTest.AbiBasedRpc, idx int, blockHash common.Hash, game gameTypes.GameMetadata) { +func expectGetGame(stubRpc *batchingTest.AbiBasedRpc, idx int, block rpcblock.Block, game gameTypes.GameMetadata) { stubRpc.SetResponse( factoryAddr, methodGameAtIndex, - rpcblock.ByHash(blockHash), + block, []interface{}{big.NewInt(int64(idx))}, []interface{}{ game.GameType, diff --git a/op-challenger/game/fault/contracts/optimisticzkdisputegame.go b/op-challenger/game/fault/contracts/optimisticzkdisputegame.go index 0a497d99ed2..02ee2f28f04 100644 --- a/op-challenger/game/fault/contracts/optimisticzkdisputegame.go +++ b/op-challenger/game/fault/contracts/optimisticzkdisputegame.go @@ -3,6 +3,7 @@ package contracts import ( "context" "fmt" + "math/big" "time" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" @@ -14,8 +15,55 @@ import ( "github.com/ethereum/go-ethereum/common" ) +type ProposalStatus uint8 + +const ( + ProposalStatusUnchallenged ProposalStatus = iota + ProposalStatusChallenged + ProposalStatusUnchallengedAndValidProofProvided + ProposalStatusChallengedAndValidProofProvided + ProposalStatusResolved +) + +func (p ProposalStatus) String() string { + switch p { + case ProposalStatusUnchallenged: + return "Unchallenged" + case ProposalStatusChallenged: + return "Challenged" + case ProposalStatusUnchallengedAndValidProofProvided: + return "UnchallengedAndValidProofProvided" + case ProposalStatusChallengedAndValidProofProvided: + return "ChallengedAndValidProofProvided" + case ProposalStatusResolved: + return "Resolved" + default: + return fmt.Sprintf("ProposalStatus(%d)", uint8(p)) + } +} + +var ( + methodChallenge = "challenge" + methodChallengerBond = "challengerBond" + methodClaimData = "claimData" +) + +type claimData struct { + ParentIndex uint32 + CounteredBy common.Address + Prover common.Address + Claim common.Hash + Status ProposalStatus + Deadline uint64 +} + type OptimisticZKDisputeGameContract interface { DisputeGameContract + ChallengeTx(ctx context.Context) (txmgr.TxCandidate, error) + GetProposal(ctx context.Context) (common.Hash, uint64, error) + GetChallengerMetadata(ctx context.Context, block rpcblock.Block) (ChallengerMetadata, error) + GetCredit(ctx context.Context, recipient common.Address) (*big.Int, gameTypes.GameStatus, error) + ClaimCreditTx(ctx context.Context, recipient common.Address) (txmgr.TxCandidate, error) } type OptimisticZKDisputeGameContractLatest struct { @@ -24,6 +72,37 @@ type OptimisticZKDisputeGameContractLatest struct { contract *batching.BoundContract } +func (g *OptimisticZKDisputeGameContractLatest) GetCredit(ctx context.Context, recipient common.Address) (*big.Int, gameTypes.GameStatus, error) { + defer g.metrics.StartContractRequest("GetCredit")() + results, err := g.multiCaller.Call(ctx, rpcblock.Latest, + g.contract.Call(methodCredit, recipient), + g.contract.Call(methodStatus)) + if err != nil { + return nil, gameTypes.GameStatusInProgress, err + } + if len(results) != 2 { + return nil, gameTypes.GameStatusInProgress, fmt.Errorf("expected 2 results but got %v", len(results)) + } + credit := results[0].GetBigInt(0) + status, err := gameTypes.GameStatusFromUint8(results[1].GetUint8(0)) + if err != nil { + return nil, gameTypes.GameStatusInProgress, fmt.Errorf("invalid game status %v: %w", status, err) + } + return credit, status, nil +} + +func (g *OptimisticZKDisputeGameContractLatest) ClaimCreditTx(ctx context.Context, recipient common.Address) (txmgr.TxCandidate, error) { + defer g.metrics.StartContractRequest("ClaimCredit")() + call := g.contract.Call(methodClaimCredit, recipient) + _, err := g.multiCaller.SingleCall(ctx, rpcblock.Latest, call) + if err != nil { + return txmgr.TxCandidate{}, fmt.Errorf("%w: %w", ErrSimulationFailed, err) + } + return call.ToTxCandidate() +} + +var _ OptimisticZKDisputeGameContract = (*OptimisticZKDisputeGameContractLatest)(nil) + func NewOptimisticZKDisputeGameContract( m metrics.ContractMetricer, addr common.Address, @@ -37,6 +116,10 @@ func NewOptimisticZKDisputeGameContract( }, nil } +func (g *OptimisticZKDisputeGameContractLatest) Addr() common.Address { + return g.contract.Addr() +} + // GetMetadata returns the basic game metadata func (g *OptimisticZKDisputeGameContractLatest) GetMetadata(ctx context.Context, block rpcblock.Block) (GenericGameMetadata, error) { defer g.metrics.StartContractRequest("GetMetadata")() @@ -103,6 +186,61 @@ func (g *OptimisticZKDisputeGameContractLatest) GetGameRange(ctx context.Context return } +type ChallengerMetadata struct { + ParentIndex uint32 + ProposalStatus ProposalStatus + ProposedRoot common.Hash + L2SequenceNumber uint64 + Deadline time.Time +} + +func (g *OptimisticZKDisputeGameContractLatest) GetChallengerMetadata(ctx context.Context, block rpcblock.Block) (ChallengerMetadata, error) { + defer g.metrics.StartContractRequest("GetChallengerMetadata")() + results, err := g.multiCaller.Call(ctx, block, + g.contract.Call(methodClaimData), + g.contract.Call(methodL2SequenceNumber)) + if err != nil { + return ChallengerMetadata{}, fmt.Errorf("failed to retrieve challenger metadata: %w", err) + } + if len(results) != 2 { + return ChallengerMetadata{}, fmt.Errorf("expected 2 results but got %v", len(results)) + } + data := g.decodeClaimData(results[0]) + l2SeqNum := results[1].GetBigInt(0).Uint64() + return ChallengerMetadata{ + ParentIndex: data.ParentIndex, + ProposalStatus: data.Status, + ProposedRoot: data.Claim, + L2SequenceNumber: l2SeqNum, + Deadline: time.Unix(int64(data.Deadline), 0), + }, nil +} + +func (g *OptimisticZKDisputeGameContractLatest) ChallengeTx(ctx context.Context) (txmgr.TxCandidate, error) { + tx, err := g.contract.Call(methodChallenge).ToTxCandidate() + if err != nil { + return txmgr.TxCandidate{}, fmt.Errorf("failed to create challenge tx: %w", err) + } + + result, err := g.multiCaller.SingleCall(ctx, rpcblock.Latest, g.contract.Call(methodChallengerBond)) + if err != nil { + return txmgr.TxCandidate{}, fmt.Errorf("failed to retrieve challenger bond: %w", err) + } + tx.Value = result.GetBigInt(0) + return tx, nil +} + +func (g *OptimisticZKDisputeGameContractLatest) GetProposal(ctx context.Context) (common.Hash, uint64, error) { + results, err := g.multiCaller.Call(ctx, rpcblock.Latest, g.contract.Call(methodRootClaim), g.contract.Call(methodL2SequenceNumber)) + if err != nil { + return common.Hash{}, 0, fmt.Errorf("failed to retrieve proposal: %w", err) + } + if len(results) != 2 { + return common.Hash{}, 0, fmt.Errorf("expected 2 results but got %v", len(results)) + } + return results[0].GetHash(0), results[1].GetBigInt(0).Uint64(), nil +} + func (g *OptimisticZKDisputeGameContractLatest) GetResolvedAt(ctx context.Context, block rpcblock.Block) (time.Time, error) { defer g.metrics.StartContractRequest("GetResolvedAt")() result, err := g.multiCaller.SingleCall(ctx, block, g.contract.Call(methodResolvedAt)) @@ -132,4 +270,21 @@ func (g *OptimisticZKDisputeGameContractLatest) resolveCall() *batching.Contract return g.contract.Call(methodResolve) } +func (g *OptimisticZKDisputeGameContractLatest) decodeClaimData(result *batching.CallResult) claimData { + parentIndex := result.GetUint32(0) + counteredBy := result.GetAddress(1) + prover := result.GetAddress(2) + claim := result.GetHash(3) + status := result.GetUint8(4) + deadline := result.GetUint64(5) + return claimData{ + ParentIndex: parentIndex, + CounteredBy: counteredBy, + Prover: prover, + Claim: claim, + Status: ProposalStatus(status), + Deadline: deadline, + } +} + var _ DisputeGameContract = (*OptimisticZKDisputeGameContractLatest)(nil) diff --git a/op-challenger/game/fault/contracts/optimisticzkdisputegame_test.go b/op-challenger/game/fault/contracts/optimisticzkdisputegame_test.go index 9c9a393f2fc..0e994941cc8 100644 --- a/op-challenger/game/fault/contracts/optimisticzkdisputegame_test.go +++ b/op-challenger/game/fault/contracts/optimisticzkdisputegame_test.go @@ -2,6 +2,7 @@ package contracts import ( "context" + "errors" "math/big" "testing" "time" @@ -11,6 +12,7 @@ import ( "github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test" + "github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" @@ -20,6 +22,10 @@ const ( versZKLatest = "0.0.0" ) +var ( + zkGameAddr = common.Address{0x45, 0x44, 0x43} +) + var zkVersions = []contractVersion{ { version: versZKLatest, @@ -82,7 +88,7 @@ func TestZKSimpleGetters(t *testing.T) { t.Skip("Skipping for this version") } stubRpc, game := setupZKDisputeGameTest(t, version) - stubRpc.SetResponse(fdgAddr, test.method, rpcblock.Latest, nil, []interface{}{test.result}) + stubRpc.SetResponse(zkGameAddr, test.method, rpcblock.Latest, nil, []interface{}{test.result}) status, err := test.call(game) require.NoError(t, err) expected := test.expected @@ -106,10 +112,10 @@ func TestZKGetMetadata(t *testing.T) { expectedRootClaim := common.Hash{0x01, 0x02} expectedStatus := gameTypes.GameStatusChallengerWon block := rpcblock.ByNumber(889) - stubRpc.SetResponse(fdgAddr, methodL1Head, block, nil, []interface{}{expectedL1Head}) - stubRpc.SetResponse(fdgAddr, methodL2SequenceNumber, block, nil, []interface{}{new(big.Int).SetUint64(expectedL2BlockNumber)}) - stubRpc.SetResponse(fdgAddr, methodRootClaim, block, nil, []interface{}{expectedRootClaim}) - stubRpc.SetResponse(fdgAddr, methodStatus, block, nil, []interface{}{expectedStatus}) + stubRpc.SetResponse(zkGameAddr, methodL1Head, block, nil, []interface{}{expectedL1Head}) + stubRpc.SetResponse(zkGameAddr, methodL2SequenceNumber, block, nil, []interface{}{new(big.Int).SetUint64(expectedL2BlockNumber)}) + stubRpc.SetResponse(zkGameAddr, methodRootClaim, block, nil, []interface{}{expectedRootClaim}) + stubRpc.SetResponse(zkGameAddr, methodStatus, block, nil, []interface{}{expectedStatus}) actual, err := contract.GetMetadata(context.Background(), block) expected := GenericGameMetadata{ L1Head: expectedL1Head, @@ -130,8 +136,8 @@ func TestZKGetGameRange(t *testing.T) { stubRpc, contract := setupZKDisputeGameTest(t, version) expectedStart := uint64(65) expectedEnd := uint64(102) - stubRpc.SetResponse(fdgAddr, methodStartingBlockNumber, rpcblock.Latest, nil, []interface{}{new(big.Int).SetUint64(expectedStart)}) - stubRpc.SetResponse(fdgAddr, methodL2SequenceNumber, rpcblock.Latest, nil, []interface{}{new(big.Int).SetUint64(expectedEnd)}) + stubRpc.SetResponse(zkGameAddr, methodStartingBlockNumber, rpcblock.Latest, nil, []interface{}{new(big.Int).SetUint64(expectedStart)}) + stubRpc.SetResponse(zkGameAddr, methodL2SequenceNumber, rpcblock.Latest, nil, []interface{}{new(big.Int).SetUint64(expectedEnd)}) start, end, err := contract.GetGameRange(context.Background()) require.NoError(t, err) require.Equal(t, expectedStart, start) @@ -145,7 +151,7 @@ func TestZKResolveTx(t *testing.T) { version := version t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupZKDisputeGameTest(t, version) - stubRpc.SetResponse(fdgAddr, methodResolve, rpcblock.Latest, nil, nil) + stubRpc.SetResponse(zkGameAddr, methodResolve, rpcblock.Latest, nil, nil) tx, err := game.ResolveTx() require.NoError(t, err) stubRpc.VerifyTxCandidate(tx) @@ -153,21 +159,133 @@ func TestZKResolveTx(t *testing.T) { } } +func TestZKGetChallengerMetadata(t *testing.T) { + for _, version := range zkVersions { + version := version + t.Run(version.String(), func(t *testing.T) { + stubRpc, contract := setupZKDisputeGameTest(t, version) + expectedParentIndex := uint32(525) + expectedProposalStatus := ProposalStatusChallengedAndValidProofProvided + counteredBy := common.Address{0xad} + prover := common.Address{0xac} + expectedL2BlockNumber := uint64(123) + expectedRootClaim := common.Hash{0x01, 0x02} + expectedDeadline := time.Unix(84928429020, 0) + block := rpcblock.ByNumber(889) + stubRpc.SetResponse(zkGameAddr, methodClaimData, block, nil, []interface{}{ + expectedParentIndex, counteredBy, prover, expectedRootClaim, expectedProposalStatus, uint64(expectedDeadline.Unix()), + }) + stubRpc.SetResponse(zkGameAddr, methodL2SequenceNumber, block, nil, []interface{}{new(big.Int).SetUint64(expectedL2BlockNumber)}) + actual, err := contract.GetChallengerMetadata(context.Background(), block) + expected := ChallengerMetadata{ + ParentIndex: expectedParentIndex, + ProposalStatus: expectedProposalStatus, + ProposedRoot: expectedRootClaim, + L2SequenceNumber: expectedL2BlockNumber, + Deadline: expectedDeadline, + } + require.NoError(t, err) + require.Equal(t, expected, actual) + }) + } +} + +func TestZKChallengeTx(t *testing.T) { + for _, version := range zkVersions { + version := version + t.Run(version.String(), func(t *testing.T) { + bond := big.NewInt(97592472) + + stubRpc, game := setupZKDisputeGameTest(t, version) + stubRpc.SetResponse(zkGameAddr, methodChallengerBond, rpcblock.Latest, nil, []interface{}{bond}) + stubRpc.SetResponse(zkGameAddr, methodChallenge, rpcblock.Latest, nil, nil) + + tx, err := game.ChallengeTx(context.Background()) + require.NoError(t, err) + stubRpc.VerifyTxCandidate(tx) + }) + } +} + +func TestZKGetProposal(t *testing.T) { + for _, version := range zkVersions { + version := version + t.Run(version.String(), func(t *testing.T) { + rootClaim := common.Hash{0xaa} + l2SequenceNumber := big.NewInt(1236) + stubRpc, game := setupZKDisputeGameTest(t, version) + stubRpc.SetResponse(zkGameAddr, methodRootClaim, rpcblock.Latest, nil, []interface{}{rootClaim}) + stubRpc.SetResponse(zkGameAddr, methodL2SequenceNumber, rpcblock.Latest, nil, []interface{}{l2SequenceNumber}) + + actualClaim, actualSeqNum, err := game.GetProposal(context.Background()) + require.NoError(t, err) + require.Equal(t, rootClaim, actualClaim) + require.Equal(t, l2SequenceNumber.Uint64(), actualSeqNum) + }) + } +} + +func TestZKGame_GetCredit(t *testing.T) { + for _, version := range zkVersions { + version := version + t.Run(version.String(), func(t *testing.T) { + stubRpc, game := setupZKDisputeGameTest(t, version) + addr := common.Address{0x01} + expectedCredit := big.NewInt(4284) + expectedStatus := gameTypes.GameStatusChallengerWon + stubRpc.SetResponse(zkGameAddr, methodCredit, rpcblock.Latest, []interface{}{addr}, []interface{}{expectedCredit}) + stubRpc.SetResponse(zkGameAddr, methodStatus, rpcblock.Latest, nil, []interface{}{expectedStatus}) + + actualCredit, actualStatus, err := game.GetCredit(context.Background(), addr) + require.NoError(t, err) + require.Equal(t, expectedCredit, actualCredit) + require.Equal(t, expectedStatus, actualStatus) + }) + } +} + +func TestZKGame_ClaimCreditTx(t *testing.T) { + for _, version := range zkVersions { + version := version + t.Run(version.String(), func(t *testing.T) { + t.Run("Success", func(t *testing.T) { + stubRpc, game := setupZKDisputeGameTest(t, version) + addr := common.Address{0xaa} + + stubRpc.SetResponse(zkGameAddr, methodClaimCredit, rpcblock.Latest, []interface{}{addr}, nil) + tx, err := game.ClaimCreditTx(context.Background(), addr) + require.NoError(t, err) + stubRpc.VerifyTxCandidate(tx) + }) + + t.Run("SimulationFails", func(t *testing.T) { + stubRpc, game := setupZKDisputeGameTest(t, version) + addr := common.Address{0xaa} + + stubRpc.SetError(zkGameAddr, methodClaimCredit, rpcblock.Latest, []interface{}{addr}, errors.New("still locked")) + tx, err := game.ClaimCreditTx(context.Background(), addr) + require.ErrorIs(t, err, ErrSimulationFailed) + require.Equal(t, txmgr.TxCandidate{}, tx) + }) + }) + } +} + func setupZKDisputeGameTest(t *testing.T, version contractVersion) (*batchingTest.AbiBasedRpc, OptimisticZKDisputeGameContract) { fdgAbi := version.loadAbi() vmAbi := snapshots.LoadMIPSABI() oracleAbi := snapshots.LoadPreimageOracleABI() - stubRpc := batchingTest.NewAbiBasedRpc(t, fdgAddr, fdgAbi) + stubRpc := batchingTest.NewAbiBasedRpc(t, zkGameAddr, fdgAbi) stubRpc.AddContract(vmAddr, vmAbi) stubRpc.AddContract(oracleAddr, oracleAbi) caller := batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize) - stubRpc.SetResponse(fdgAddr, methodGameType, rpcblock.Latest, nil, []interface{}{uint32(version.gameType)}) - stubRpc.SetResponse(fdgAddr, methodVersion, rpcblock.Latest, nil, []interface{}{version.version}) + stubRpc.SetResponse(zkGameAddr, methodGameType, rpcblock.Latest, nil, []interface{}{uint32(version.gameType)}) + stubRpc.SetResponse(zkGameAddr, methodVersion, rpcblock.Latest, nil, []interface{}{version.version}) stubRpc.SetResponse(oracleAddr, methodVersion, rpcblock.Latest, nil, []interface{}{oracleLatest}) - game, err := NewOptimisticZKDisputeGameContract(contractMetrics.NoopContractMetrics, fdgAddr, caller) + game, err := NewOptimisticZKDisputeGameContract(contractMetrics.NoopContractMetrics, zkGameAddr, caller) require.NoError(t, err) return stubRpc, game } diff --git a/op-challenger/game/fault/register_task.go b/op-challenger/game/fault/register_task.go index f87aee3b27b..dbd10bd2fa4 100644 --- a/op-challenger/game/fault/register_task.go +++ b/op-challenger/game/fault/register_task.go @@ -20,6 +20,7 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + "github.com/ethereum-optimism/optimism/op-challenger/game/generic" "github.com/ethereum-optimism/optimism/op-challenger/game/scheduler" gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/metrics" @@ -35,7 +36,7 @@ type RegisterTask struct { gameType gameTypes.GameType skipPrestateValidation bool - syncValidator SyncValidator + syncValidator generic.SyncValidator getTopPrestateProvider func(ctx context.Context, prestateBlock uint64) (faultTypes.PrestateProvider, error) getBottomPrestateProvider func(ctx context.Context, prestateHash common.Hash) (faultTypes.PrestateProvider, error) @@ -51,11 +52,11 @@ type RegisterTask struct { poststateBlock uint64) (*trace.Accessor, error) } -func NewSuperCannonRegisterTask(gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, rootProvider super.RootProvider, syncValidator SyncValidator) *RegisterTask { +func NewSuperCannonRegisterTask(gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, rootProvider super.RootProvider, syncValidator generic.SyncValidator) *RegisterTask { return newSuperCannonVMRegisterTaskWithConfig(gameType, cfg, m, serverExecutor, rootProvider, syncValidator, cfg.Cannon, cfg.CannonAbsolutePreStateBaseURL, cfg.CannonAbsolutePreState) } -func NewSuperCannonKonaRegisterTask(gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, rootProvider super.RootProvider, syncValidator SyncValidator) *RegisterTask { +func NewSuperCannonKonaRegisterTask(gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, rootProvider super.RootProvider, syncValidator generic.SyncValidator) *RegisterTask { return newSuperCannonVMRegisterTaskWithConfig(gameType, cfg, m, serverExecutor, rootProvider, syncValidator, cfg.CannonKona, cfg.CannonKonaAbsolutePreStateBaseURL, cfg.CannonKonaAbsolutePreState) } @@ -65,7 +66,7 @@ func newSuperCannonVMRegisterTaskWithConfig( m caching.Metrics, serverExecutor vm.OracleServerExecutor, rootProvider super.RootProvider, - syncValidator SyncValidator, + syncValidator generic.SyncValidator, vmCfg vm.Config, preStateBaseURL *url.URL, preState string, @@ -105,11 +106,11 @@ func newSuperCannonVMRegisterTaskWithConfig( } } -func NewCannonRegisterTask(gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, l2Client utils.L2HeaderSource, rollupClient outputs.OutputRollupClient, syncValidator SyncValidator) *RegisterTask { +func NewCannonRegisterTask(gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, l2Client utils.L2HeaderSource, rollupClient outputs.OutputRollupClient, syncValidator generic.SyncValidator) *RegisterTask { return newCannonVMRegisterTaskWithConfig(gameType, cfg, m, serverExecutor, l2Client, rollupClient, syncValidator, cfg.Cannon, cfg.CannonAbsolutePreStateBaseURL, cfg.CannonAbsolutePreState) } -func NewCannonKonaRegisterTask(gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, l2Client utils.L2HeaderSource, rollupClient outputs.OutputRollupClient, syncValidator SyncValidator) *RegisterTask { +func NewCannonKonaRegisterTask(gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, l2Client utils.L2HeaderSource, rollupClient outputs.OutputRollupClient, syncValidator generic.SyncValidator) *RegisterTask { return newCannonVMRegisterTaskWithConfig(gameType, cfg, m, serverExecutor, l2Client, rollupClient, syncValidator, cfg.CannonKona, cfg.CannonKonaAbsolutePreStateBaseURL, cfg.CannonKonaAbsolutePreState) } @@ -120,7 +121,7 @@ func newCannonVMRegisterTaskWithConfig( serverExecutor vm.OracleServerExecutor, l2Client utils.L2HeaderSource, rollupClient outputs.OutputRollupClient, - syncValidator SyncValidator, + syncValidator generic.SyncValidator, vmCfg vm.Config, preStateBaseURL *url.URL, preState string, @@ -162,7 +163,7 @@ func newCannonVMRegisterTaskWithConfig( } } -func NewAsteriscRegisterTask(gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, l2Client utils.L2HeaderSource, rollupClient outputs.OutputRollupClient, syncValidator SyncValidator) *RegisterTask { +func NewAsteriscRegisterTask(gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, l2Client utils.L2HeaderSource, rollupClient outputs.OutputRollupClient, syncValidator generic.SyncValidator) *RegisterTask { stateConverter := asterisc.NewStateConverter(cfg.Asterisc) return &RegisterTask{ gameType: gameType, @@ -196,7 +197,7 @@ func NewAsteriscRegisterTask(gameType gameTypes.GameType, cfg *config.Config, m } } -func NewAsteriscKonaRegisterTask(gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, l2Client utils.L2HeaderSource, rollupClient outputs.OutputRollupClient, syncValidator SyncValidator) *RegisterTask { +func NewAsteriscKonaRegisterTask(gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, l2Client utils.L2HeaderSource, rollupClient outputs.OutputRollupClient, syncValidator generic.SyncValidator) *RegisterTask { stateConverter := asterisc.NewStateConverter(cfg.Asterisc) return &RegisterTask{ gameType: gameType, @@ -230,7 +231,7 @@ func NewAsteriscKonaRegisterTask(gameType gameTypes.GameType, cfg *config.Config } } -func NewSuperAsteriscKonaRegisterTask(gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, rootProvider super.RootProvider, syncValidator SyncValidator) *RegisterTask { +func NewSuperAsteriscKonaRegisterTask(gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, rootProvider super.RootProvider, syncValidator generic.SyncValidator) *RegisterTask { stateConverter := asterisc.NewStateConverter(cfg.AsteriscKona) return &RegisterTask{ gameType: gameType, @@ -266,7 +267,7 @@ func NewSuperAsteriscKonaRegisterTask(gameType gameTypes.GameType, cfg *config.C } } -func NewAlphabetRegisterTask(gameType gameTypes.GameType, l2Client utils.L2HeaderSource, rollupClient outputs.OutputRollupClient, syncValidator SyncValidator) *RegisterTask { +func NewAlphabetRegisterTask(gameType gameTypes.GameType, l2Client utils.L2HeaderSource, rollupClient outputs.OutputRollupClient, syncValidator generic.SyncValidator) *RegisterTask { return &RegisterTask{ gameType: gameType, syncValidator: syncValidator, @@ -323,7 +324,7 @@ func (e *RegisterTask) Register( txSender TxSender, gameFactory *contracts.DisputeGameFactoryContract, caller *batching.MultiCaller, - l1HeaderSource L1HeaderSource, + l1HeaderSource generic.L1HeaderSource, selective bool, claimants []common.Address, responseDelay time.Duration, @@ -357,27 +358,32 @@ func (e *RegisterTask) Register( if err != nil { return nil, fmt.Errorf("failed to load split depth: %w", err) } - l1HeadID, err := loadL1Head(contract, ctx, l1HeaderSource) - if err != nil { - return nil, err - } prestateProvider, err := e.getTopPrestateProvider(ctx, prestateBlock) if err != nil { return nil, fmt.Errorf("failed to create top prestate provider: %w", err) } - creator := func(ctx context.Context, logger log.Logger, gameDepth faultTypes.Depth, dir string) (faultTypes.TraceAccessor, error) { + creator := func(ctx context.Context, logger log.Logger, gameDepth faultTypes.Depth, l1HeadID eth.BlockID, dir string) (faultTypes.TraceAccessor, error) { accessor, err := e.newTraceAccessor(logger, m, prestateProvider, vmPrestateProvider, dir, l1HeadID, splitDepth, prestateBlock, poststateBlock) if err != nil { return nil, err } return accessor, nil } - var validators []Validator + var validators []generic.PrestateValidator if !e.skipPrestateValidation { validators = append(validators, NewPrestateValidator(e.gameType.String(), contract.GetAbsolutePrestateHash, vmPrestateProvider)) validators = append(validators, NewPrestateValidator("output root", contract.GetStartingRootHash, prestateProvider)) } - return NewGamePlayer(ctx, systemClock, l1Clock, logger, m, dir, game.Proxy, txSender, contract, e.syncValidator, validators, creator, l1HeaderSource, selective, claimants, responseDelay, responseDelayAfter) + return generic.NewGenericGamePlayer( + ctx, + logger, + game.Proxy, + contract, + e.syncValidator, + validators, + l1HeaderSource, + AgentCreator(systemClock, l1Clock, m, dir, txSender, contract, creator, selective, claimants, responseDelay, responseDelayAfter), + ) } err := registerOracle(ctx, logger, oracles, gameFactory, e.gameType) if err != nil { @@ -413,15 +419,3 @@ func registerOracle(ctx context.Context, logger log.Logger, oracles OracleRegist oracles.RegisterOracle(oracle) return nil } - -func loadL1Head(contract contracts.FaultDisputeGameContract, ctx context.Context, l1HeaderSource L1HeaderSource) (eth.BlockID, error) { - l1Head, err := contract.GetL1Head(ctx) - if err != nil { - return eth.BlockID{}, fmt.Errorf("failed to load L1 head: %w", err) - } - l1Header, err := l1HeaderSource.HeaderByHash(ctx, l1Head) - if err != nil { - return eth.BlockID{}, fmt.Errorf("failed to load L1 header: %w", err) - } - return eth.HeaderBlockID(l1Header), nil -} diff --git a/op-challenger/game/fault/player.go b/op-challenger/game/generic/player.go similarity index 52% rename from op-challenger/game/fault/player.go rename to op-challenger/game/generic/player.go index b1b83738b0f..5545a51b709 100644 --- a/op-challenger/game/fault/player.go +++ b/op-challenger/game/generic/player.go @@ -1,37 +1,35 @@ -package fault +package generic import ( "context" "errors" "fmt" - "time" "github.com/ethereum-optimism/optimism/op-challenger/game/client" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/claims" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/preimages" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/responder" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" - "github.com/ethereum-optimism/optimism/op-challenger/metrics" - "github.com/ethereum-optimism/optimism/op-service/clock" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum/go-ethereum/common" gethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" ) -type actor func(ctx context.Context) error +type PrestateValidator interface { + Validate(ctx context.Context) error +} + +type Actor interface { + Act(ctx context.Context) error + AdditionalStatus(ctx context.Context) ([]any, error) +} -type GameInfo interface { +type GenericGameLoader interface { + GetL1Head(context.Context) (common.Hash, error) GetStatus(context.Context) (gameTypes.GameStatus, error) - GetClaimCount(context.Context) (uint64, error) } type SyncValidator interface { // ValidateNodeSynced checks that the local node is sufficiently up to date to play the game. - // It returns types.ErrNotInSync if the node is too far behind. + // It returns client.ErrNotInSync if the node is too far behind. ValidateNodeSynced(ctx context.Context, gameL1Head eth.BlockID) error } @@ -39,58 +37,32 @@ type L1HeaderSource interface { HeaderByHash(context.Context, common.Hash) (*gethTypes.Header, error) } -type TxSender interface { - From() common.Address - SendAndWaitSimple(txPurpose string, txs ...txmgr.TxCandidate) error -} +type ActorCreator func(ctx context.Context, logger log.Logger, l1Head eth.BlockID) (Actor, error) type GamePlayer struct { - act actor - loader GameInfo + actor Actor + loader GenericGameLoader logger log.Logger syncValidator SyncValidator - prestateValidators []Validator + prestateValidators []PrestateValidator status gameTypes.GameStatus gameL1Head eth.BlockID } -type GameContract interface { - preimages.PreimageGameContract - responder.GameContract - claims.BondContract - GameInfo - ClaimLoader - GetStatus(ctx context.Context) (gameTypes.GameStatus, error) - GetMaxGameDepth(ctx context.Context) (types.Depth, error) - GetMaxClockDuration(ctx context.Context) (time.Duration, error) - GetOracle(ctx context.Context) (contracts.PreimageOracleContract, error) - GetL1Head(ctx context.Context) (common.Hash, error) -} - -var actNoop = func(ctx context.Context) error { - return nil -} +type actNoop struct{} -type resourceCreator func(ctx context.Context, logger log.Logger, gameDepth types.Depth, dir string) (types.TraceAccessor, error) +func (a *actNoop) Act(_ context.Context) error { return nil } +func (a *actNoop) AdditionalStatus(_ context.Context) ([]any, error) { return nil, nil } -func NewGamePlayer( +func NewGenericGamePlayer( ctx context.Context, - systemClock clock.Clock, - l1Clock types.ClockReader, logger log.Logger, - m metrics.Metricer, - dir string, addr common.Address, - txSender TxSender, - loader GameContract, + loader GenericGameLoader, syncValidator SyncValidator, - validators []Validator, - creator resourceCreator, + validators []PrestateValidator, l1HeaderSource L1HeaderSource, - selective bool, - claimants []common.Address, - responseDelay time.Duration, - responseDelayAfter uint64, + createActor ActorCreator, ) (*GamePlayer, error) { logger = logger.New("game", addr) @@ -107,30 +79,9 @@ func NewGamePlayer( prestateValidators: validators, status: status, // Act function does nothing because the game is already complete - act: actNoop, + actor: &actNoop{}, }, nil } - - maxClockDuration, err := loader.GetMaxClockDuration(ctx) - if err != nil { - return nil, fmt.Errorf("failed to fetch the game duration: %w", err) - } - - gameDepth, err := loader.GetMaxGameDepth(ctx) - if err != nil { - return nil, fmt.Errorf("failed to fetch the game depth: %w", err) - } - - accessor, err := creator(ctx, logger, gameDepth, dir) - if err != nil { - return nil, fmt.Errorf("failed to create trace accessor: %w", err) - } - - oracle, err := loader.GetOracle(ctx) - if err != nil { - return nil, fmt.Errorf("failed to load oracle: %w", err) - } - l1HeadHash, err := loader.GetL1Head(ctx) if err != nil { return nil, fmt.Errorf("failed to load game L1 head: %w", err) @@ -141,21 +92,13 @@ func NewGamePlayer( } l1Head := eth.HeaderBlockID(l1Header) - minLargePreimageSize, err := oracle.MinLargePreimageSize(ctx) - if err != nil { - return nil, fmt.Errorf("failed to load min large preimage size: %w", err) - } - direct := preimages.NewDirectPreimageUploader(logger, txSender, loader) - large := preimages.NewLargePreimageUploader(logger, l1Clock, txSender, oracle) - uploader := preimages.NewSplitPreimageUploader(direct, large, minLargePreimageSize) - responder, err := responder.NewFaultResponder(logger, txSender, loader, uploader, oracle) + actor, err := createActor(ctx, logger, l1Head) if err != nil { - return nil, fmt.Errorf("failed to create the responder: %w", err) + return nil, fmt.Errorf("failed to create actor: %w", err) } - agent := NewAgent(m, systemClock, l1Clock, loader, gameDepth, maxClockDuration, accessor, responder, logger, selective, claimants, responseDelay, responseDelayAfter) return &GamePlayer{ - act: agent.Act, + actor: actor, loader: loader, logger: logger, status: status, @@ -192,7 +135,7 @@ func (g *GamePlayer) ProgressGame(ctx context.Context) gameTypes.GameStatus { return g.status } g.logger.Trace("Checking if actions are required") - if err := g.act(ctx); err != nil { + if err := g.actor.Act(ctx); err != nil { g.logger.Error("Error when acting on game", "err", err) } status, err := g.loader.GetStatus(ctx) @@ -204,19 +147,20 @@ func (g *GamePlayer) ProgressGame(ctx context.Context) gameTypes.GameStatus { g.status = status if status != gameTypes.GameStatusInProgress { // Release the agent as we will no longer need to act on this game. - g.act = actNoop + g.actor = &actNoop{} } return status } func (g *GamePlayer) logGameStatus(ctx context.Context, status gameTypes.GameStatus) { if status == gameTypes.GameStatusInProgress { - claimCount, err := g.loader.GetClaimCount(ctx) + additionalStatus, err := g.actor.AdditionalStatus(ctx) if err != nil { - g.logger.Error("Failed to get claim count for in progress game", "err", err) + g.logger.Error("Failed to get additional status info for in progress game", "err", err) return } - g.logger.Info("Game info", "claims", claimCount, "status", status) + additionalStatus = append(additionalStatus, "status", g.status) + g.logger.Info("Game info", additionalStatus...) return } g.logger.Info("Game resolved", "status", status) diff --git a/op-challenger/game/fault/player_test.go b/op-challenger/game/generic/player_test.go similarity index 83% rename from op-challenger/game/fault/player_test.go rename to op-challenger/game/generic/player_test.go index 7c05525a882..ba3348e2c25 100644 --- a/op-challenger/game/fault/player_test.go +++ b/op-challenger/game/generic/player_test.go @@ -1,4 +1,4 @@ -package fault +package generic import ( "context" @@ -33,7 +33,7 @@ func TestProgressGame_LogErrorFromAct(t *testing.T) { msgFilter = testlog.NewMessageFilter("Game info") msg := handler.FindLog(levelFilter, msgFilter) require.NotNil(t, msg) - require.Equal(t, uint64(1), msg.AttrValue("claims")) + require.Equal(t, "statusValue", msg.AttrValue("extra")) } func TestProgressGame_LogGameStatus(t *testing.T) { @@ -93,7 +93,7 @@ func TestDoNotActOnCompleteGame(t *testing.T) { // Should have replaced the act function with a noop so callCount doesn't update even when called directly // This allows the agent resources to be GC'd - require.NoError(t, game.act(context.Background())) + require.NoError(t, game.actor.Act(context.Background())) require.Equal(t, 1, gameState.callCount) }) } @@ -113,27 +113,27 @@ func TestValidateLocalNodeSync(t *testing.T) { func TestValidatePrestate(t *testing.T) { tests := []struct { name string - validators []Validator + validators []PrestateValidator errors bool }{ { name: "SingleValidator", - validators: []Validator{&mockValidator{}}, + validators: []PrestateValidator{&mockValidator{}}, errors: false, }, { name: "MultipleValidators", - validators: []Validator{&mockValidator{}, &mockValidator{}}, + validators: []PrestateValidator{&mockValidator{}, &mockValidator{}}, errors: false, }, { name: "SingleValidator_Errors", - validators: []Validator{&mockValidator{true}}, + validators: []PrestateValidator{&mockValidator{true}}, errors: true, }, { name: "MultipleValidators_Errors", - validators: []Validator{&mockValidator{}, &mockValidator{true}}, + validators: []PrestateValidator{&mockValidator{}, &mockValidator{true}}, errors: true, }, } @@ -153,7 +153,7 @@ func TestValidatePrestate(t *testing.T) { } } -var _ Validator = (*mockValidator)(nil) +var _ PrestateValidator = (*mockValidator)(nil) type mockValidator struct { err bool @@ -171,7 +171,7 @@ func setupProgressGameTest(t *testing.T) (*testlog.CapturingHandler, *GamePlayer gameState := &stubGameState{claimCount: 1} syncValidator := &stubSyncValidator{} game := &GamePlayer{ - act: gameState.Act, + actor: gameState, loader: gameState, logger: logger, syncValidator: syncValidator, @@ -199,19 +199,27 @@ type stubGameState struct { Err error } -func (s *stubGameState) Act(ctx context.Context) error { +func (s *stubGameState) AdditionalStatus(_ context.Context) ([]any, error) { + return []any{"extra", "statusValue"}, nil +} + +func (s *stubGameState) Act(_ context.Context) error { s.callCount++ return s.actErr } -func (s *stubGameState) GetStatus(ctx context.Context) (types.GameStatus, error) { +func (s *stubGameState) GetL1Head(_ context.Context) (common.Hash, error) { + return common.Hash{0x1a}, nil +} + +func (s *stubGameState) GetStatus(_ context.Context) (types.GameStatus, error) { return s.status, nil } -func (s *stubGameState) GetClaimCount(ctx context.Context) (uint64, error) { +func (s *stubGameState) GetClaimCount(_ context.Context) (uint64, error) { return s.claimCount, nil } -func (s *stubGameState) GetAbsolutePrestateHash(ctx context.Context) (common.Hash, error) { +func (s *stubGameState) GetAbsolutePrestateHash(_ context.Context) (common.Hash, error) { return common.Hash{}, s.Err } diff --git a/op-challenger/game/service.go b/op-challenger/game/service.go index eb7be17c098..8188667f5ed 100644 --- a/op-challenger/game/service.go +++ b/op-challenger/game/service.go @@ -10,6 +10,7 @@ import ( challengerClient "github.com/ethereum-optimism/optimism/op-challenger/game/client" "github.com/ethereum-optimism/optimism/op-challenger/game/keccak" "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/fetcher" + "github.com/ethereum-optimism/optimism/op-challenger/game/zk" "github.com/ethereum-optimism/optimism/op-challenger/sender" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" @@ -217,6 +218,10 @@ func (s *Service) registerGameTypes(ctx context.Context, cfg *config.Config) err if err != nil { return err } + err = zk.RegisterGameTypes(ctx, s.l1Clock, s.logger, s.metrics, cfg, gameTypeRegistry, s.txSender, s.clientProvider, s.factoryContract) + if err != nil { + return err + } s.registry = gameTypeRegistry s.oracles = oracles return nil diff --git a/op-challenger/game/types/game_type.go b/op-challenger/game/types/game_type.go index aec84d80aa0..9e9943d0575 100644 --- a/op-challenger/game/types/game_type.go +++ b/op-challenger/game/types/game_type.go @@ -21,7 +21,7 @@ const ( SuperAsteriscKonaGameType GameType = 7 CannonKonaGameType GameType = 8 SuperCannonKonaGameType GameType = 9 - OptimisticZKGameType GameType = 10 // Not (yet) supported by op-challenger + OptimisticZKGameType GameType = 10 FastGameType GameType = 254 AlphabetGameType GameType = 255 KailuaGameType GameType = 1337 // Not supported by op-challenger @@ -42,6 +42,7 @@ var SupportedGameTypes = []GameType{ SuperCannonKonaGameType, SuperPermissionedGameType, SuperAsteriscKonaGameType, + OptimisticZKGameType, } // Set implements the Set method required by the [cli.Generic] interface. diff --git a/op-challenger/game/zk/actor.go b/op-challenger/game/zk/actor.go new file mode 100644 index 00000000000..84ce2e25401 --- /dev/null +++ b/op-challenger/game/zk/actor.go @@ -0,0 +1,184 @@ +package zk + +import ( + "context" + "errors" + "fmt" + "math" + "strings" + + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" + "github.com/ethereum-optimism/optimism/op-challenger/game/generic" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" + "github.com/ethereum-optimism/optimism/op-service/txmgr" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" +) + +var ( + errNoChallengeRequired = errors.New("no challenge required") + errNoResolutionRequired = errors.New("no resolution required") +) + +type RootProvider interface { + OutputAtBlock(ctx context.Context, blockNum uint64) (*eth.OutputResponse, error) +} + +type GameStatusProvider interface { + GetGameStatus(ctx context.Context, idx uint64) (gameTypes.GameStatus, error) +} + +type ChallengableContract interface { + Addr() common.Address + ChallengeTx(ctx context.Context) (txmgr.TxCandidate, error) + GetProposal(ctx context.Context) (common.Hash, uint64, error) + GetChallengerMetadata(ctx context.Context, block rpcblock.Block) (contracts.ChallengerMetadata, error) + ResolveTx() (txmgr.TxCandidate, error) +} + +type Actor struct { + logger log.Logger + l1Clock ClockReader + rootProvider RootProvider + gameStatusProvider GameStatusProvider + contract ChallengableContract + txSender TxSender + l1Head eth.BlockID +} + +func ActorCreator(l1Clock ClockReader, rootProvider RootProvider, gameStatusProvider GameStatusProvider, contract ChallengableContract, txSender TxSender) generic.ActorCreator { + return func(ctx context.Context, logger log.Logger, l1Head eth.BlockID) (generic.Actor, error) { + return &Actor{ + logger: logger, + l1Clock: l1Clock, + rootProvider: rootProvider, + gameStatusProvider: gameStatusProvider, + contract: contract, + txSender: txSender, + l1Head: l1Head, + }, nil + } +} + +func (a *Actor) Act(ctx context.Context) error { + gameState, err := a.contract.GetChallengerMetadata(ctx, rpcblock.Latest) + if err != nil { + return fmt.Errorf("failed to get zk game state: %w", err) + } + + var txs []txmgr.TxCandidate + if tx, err := a.createChallengeTx(ctx, gameState); errors.Is(err, errNoChallengeRequired) { + a.logger.Debug("No challenge required") + } else if err != nil { + return err + } else { + txs = append(txs, tx) + } + if tx, err := a.createResolveTx(ctx, gameState); errors.Is(err, errNoResolutionRequired) { + a.logger.Debug("No resolution required") + } else if err != nil { + return err + } else { + txs = append(txs, tx) + } + + if len(txs) == 0 { + return nil + } + if err := a.txSender.SendAndWaitSimple(fmt.Sprintf("respond to game %v", a.contract.Addr()), txs...); err != nil { + return fmt.Errorf("failed to send transactions for game %v: %w", a.contract.Addr(), err) + } + return nil +} + +func (a *Actor) createChallengeTx(ctx context.Context, gameState contracts.ChallengerMetadata) (txmgr.TxCandidate, error) { + if gameState.ProposalStatus != contracts.ProposalStatusUnchallenged || gameState.Deadline.Before(a.l1Clock.Now()) { + a.logger.Trace("Skipping unchallengeable zk game") + return txmgr.TxCandidate{}, errNoChallengeRequired + } + if valid, err := a.isValidProposal(ctx); err != nil { + return txmgr.TxCandidate{}, fmt.Errorf("failed to check if proposal is valid: %w", err) + } else if valid { + a.logger.Trace("Not challenging valid zk game") + return txmgr.TxCandidate{}, errNoChallengeRequired + } + + a.logger.Info("Challenging game") + return a.contract.ChallengeTx(ctx) +} + +func (a *Actor) isValidProposal(ctx context.Context) (bool, error) { + proposalHash, proposalSeqNum, err := a.contract.GetProposal(ctx) + if err != nil { + return false, fmt.Errorf("failed to get zk game proposal: %w", err) + } + canonicalOutput, err := a.rootProvider.OutputAtBlock(ctx, proposalSeqNum) + if err != nil { + var rpcErr rpc.Error + if errors.As(err, &rpcErr) { + if strings.Contains(strings.ToLower(rpcErr.Error()), "not found") { + // There is no valid output at the proposal sequence number (it's in the future) + return false, nil + } + } + return false, fmt.Errorf("failed to get canonical output at block %v: %w", proposalSeqNum, err) + } + if common.Hash(canonicalOutput.OutputRoot) != proposalHash { + // Output root doesn't match so can't be valid + return false, nil + } + if canonicalOutput.Status.SafeL2.Number < proposalSeqNum { + // Note this deliberately uses the simpler check of if the proposed block is currently unsafe + // The proposal is not necessarily supported by data on L1 up to the game's L1 head + // but we don't need to challenge it as long as supporting data has since become available + // and the output matches the canonical chain. + a.logger.Debug("Proposed block is not yet safe, treating as invalid", "safe", canonicalOutput.Status.SafeL2.Number, "proposed", proposalSeqNum) + return false, nil + } + return true, nil +} + +func (a *Actor) createResolveTx(ctx context.Context, gameState contracts.ChallengerMetadata) (txmgr.TxCandidate, error) { + if gameState.ProposalStatus == contracts.ProposalStatusResolved { + a.logger.Trace("Skipping resolution of resolved zk game") + return txmgr.TxCandidate{}, errNoResolutionRequired + } + deadlineExpired := gameState.Deadline.Before(a.l1Clock.Now()) + + if gameState.ParentIndex != math.MaxUint32 { + parentStatus, err := a.gameStatusProvider.GetGameStatus(ctx, uint64(gameState.ParentIndex)) + if err != nil { + return txmgr.TxCandidate{}, fmt.Errorf("failed to get parent game status: %w", err) + } + if parentStatus == gameTypes.GameStatusInProgress { + a.logger.Trace("Skipping resolution of zk game with parent in progress") + return txmgr.TxCandidate{}, errNoResolutionRequired + } + if parentStatus == gameTypes.GameStatusChallengerWon { + // Resolve if the parent game is invalid + return a.contract.ResolveTx() + } + } + + if gameState.ProposalStatus == contracts.ProposalStatusChallengedAndValidProofProvided || + gameState.ProposalStatus == contracts.ProposalStatusUnchallengedAndValidProofProvided { + // Resolve if a valid proof is provided + return a.contract.ResolveTx() + } + if deadlineExpired { + // Resolve if the deadline has expired (either for challenging or proving) + return a.contract.ResolveTx() + } + return txmgr.TxCandidate{}, errNoResolutionRequired +} + +func (a *Actor) AdditionalStatus(ctx context.Context) ([]any, error) { + metadata, err := a.contract.GetChallengerMetadata(ctx, rpcblock.Latest) + if err != nil { + return nil, fmt.Errorf("failed to get challenger metadata: %w", err) + } + return []any{"proposalStatus", metadata.ProposalStatus}, nil +} diff --git a/op-challenger/game/zk/actor_test.go b/op-challenger/game/zk/actor_test.go new file mode 100644 index 00000000000..e7823a04a09 --- /dev/null +++ b/op-challenger/game/zk/actor_test.go @@ -0,0 +1,370 @@ +package zk + +import ( + "context" + "errors" + "math" + "testing" + "time" + + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" + "github.com/ethereum-optimism/optimism/op-challenger/game/types" + "github.com/ethereum-optimism/optimism/op-service/clock" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-service/txmgr" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/require" +) + +var ( + challengeData = "challenge" + resolveData = "resolve" + l1Time = time.Unix(9892842, 0) +) + +type zkTestStubs struct { + rootProvider *stubRootProvider + contract *stubContract + sender *stubTxSender +} + +func TestActor(t *testing.T) { + // Output root: Valid, Invalid + // Safety: Safe, Unsafe, Beyond unsafe + // In challenge period, ChallengePeriodExpired, In proof period, ProvenWithoutChallenge, ProvenAfterChallenge, ProofPeriodExpired, Resolved + // No parent, parent in progress, parent valid, parent invalid + tests := []struct { + name string + setup func(t *testing.T, stubs *zkTestStubs) + challenge bool + resolve bool + }{ + { + name: "DoNotChallengeCorrectProposal", + setup: func(t *testing.T, stubs *zkTestStubs) { + stubs.contract.setDeadlineNotReached() + stubs.contract.proposalHash = stubs.rootProvider.root + stubs.contract.l2SequenceNumber = stubs.rootProvider.rootBlockNum + }, + }, + { + name: "ChallengeIncorrectProposal", + setup: func(t *testing.T, stubs *zkTestStubs) { + stubs.contract.proposalHash = common.Hash{0xba, 0xd0} + }, + challenge: true, + }, + { + name: "DoNothingIfAlreadyChallenged", + setup: func(t *testing.T, stubs *zkTestStubs) { + stubs.rootProvider.root = common.Hash{0xba, 0xd0} // Disagree but already challenged + stubs.contract.challenge(t) + }, + }, + { + name: "ChallengeProposalBeyondCurrentUnsafeHead", + setup: func(t *testing.T, stubs *zkTestStubs) { + stubs.rootProvider.root = common.Hash{0xba, 0xd0} + stubs.rootProvider.outputErr = mockNotFoundRPCError() + stubs.contract.proposalHash = stubs.rootProvider.root + stubs.contract.l2SequenceNumber = stubs.rootProvider.rootBlockNum + }, + challenge: true, + }, + { + name: "ChallengeCurrentlyUnsafeProposal", + setup: func(t *testing.T, stubs *zkTestStubs) { + stubs.contract.proposalHash = stubs.rootProvider.root + stubs.contract.l2SequenceNumber = stubs.rootProvider.rootBlockNum + stubs.rootProvider.safeBlockNum = stubs.rootProvider.rootBlockNum - 1 + }, + challenge: true, + }, + { + name: "ChallengeUnresolvableGameWithNoParent", + setup: func(t *testing.T, stubs *zkTestStubs) { + stubs.contract.proposalHash = common.Hash{0xba, 0xd0} + stubs.contract.parentIndex = math.MaxUint32 + }, + challenge: true, + }, + { + name: "ResolveGameWithNoParent", + setup: func(t *testing.T, stubs *zkTestStubs) { + stubs.contract.setDeadlineExpired() + stubs.contract.proposalHash = common.Hash{0xba, 0xd0} + stubs.contract.parentIndex = math.MaxUint32 + }, + resolve: true, + }, + { + name: "DoNothingWhenDeadlineExpiredButParentNotResolved", + setup: func(t *testing.T, stubs *zkTestStubs) { + stubs.contract.setDeadlineExpired() + // Proposal is invalid but can't challenge because the deadline is expired + stubs.contract.proposalHash = common.Hash{0xba, 0xd0} + // And can't resolve because the parent is still unresolved + stubs.contract.setParentStatus(types.GameStatusInProgress) + }, + }, + { + name: "InChallengePeriodWithInvalidParent", + setup: func(t *testing.T, stubs *zkTestStubs) { + // Game should be challenged + stubs.contract.proposalHash = common.Hash{0xba, 0xd0} + stubs.contract.setDeadlineNotReached() + // And is immediately resolvable because the parent is invalid + stubs.contract.setParentStatus(types.GameStatusChallengerWon) + }, + challenge: true, + resolve: true, + }, + { + name: "UnchallengedWithDeadlineExpired", + setup: func(t *testing.T, stubs *zkTestStubs) { + stubs.contract.setDeadlineExpired() + }, + resolve: true, + }, + { + name: "ChallengedWithDeadlineExpired", + setup: func(t *testing.T, stubs *zkTestStubs) { + stubs.contract.setDeadlineExpired() + stubs.contract.challenge(t) + }, + resolve: true, + }, + { + name: "ChallengedAndProvenWithDeadlineExpired", + setup: func(t *testing.T, stubs *zkTestStubs) { + stubs.contract.setDeadlineExpired() + stubs.contract.challenge(t) + stubs.contract.prove(t) + }, + resolve: true, + }, + { + name: "ChallengedAndProvenWithDeadlineNotReached", + setup: func(t *testing.T, stubs *zkTestStubs) { + stubs.contract.setDeadlineNotReached() + stubs.contract.challenge(t) + stubs.contract.prove(t) + }, + resolve: true, + }, + { + name: "UnchallengedAndProvenWithDeadlineExpired", + setup: func(t *testing.T, stubs *zkTestStubs) { + stubs.contract.setDeadlineExpired() + stubs.contract.prove(t) + }, + resolve: true, + }, + { + name: "UnchallengedAndProvenWithDeadlineNotReached", + setup: func(t *testing.T, stubs *zkTestStubs) { + stubs.contract.setDeadlineNotReached() + stubs.contract.prove(t) + }, + resolve: true, + }, + { + name: "AlreadyResolved", + setup: func(t *testing.T, stubs *zkTestStubs) { + stubs.contract.setDeadlineNotReached() + stubs.contract.markResolved() + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actor, stubs := setupActorTest(t) + if tt.setup != nil { + tt.setup(t, stubs) + } + err := actor.Act(context.Background()) + require.NoError(t, err) + expectedTxCount := 0 + if tt.challenge { + require.Contains(t, stubs.sender.sentData, challengeData) + expectedTxCount++ + } + if tt.resolve { + require.Contains(t, stubs.sender.sentData, resolveData) + expectedTxCount++ + } + require.Len(t, stubs.sender.sentData, expectedTxCount) + }) + } +} + +func setupActorTest(t *testing.T) (*Actor, *zkTestStubs) { + logger := testlog.Logger(t, log.LvlInfo) + l1Head := eth.BlockID{ + Hash: common.Hash{0x12}, + Number: 785, + } + rootBlockNum := uint64(28492) + rootProvider := &stubRootProvider{ + root: common.Hash{0x11}, + rootBlockNum: rootBlockNum, + safeBlockNum: rootBlockNum + 10, + } + // Default to a valid proposal + contract := &stubContract{ + proposalHash: rootProvider.root, + l2SequenceNumber: rootProvider.rootBlockNum, + parentStatus: types.GameStatusDefenderWon, + parentIndex: 482, + } + contract.setDeadlineNotReached() + txSender := &stubTxSender{} + l1Clock := clock.NewDeterministicClock(l1Time) + // Simplify the tests by using the same stub for the game and the dispute game factory + creator := ActorCreator(l1Clock, rootProvider, contract, contract, txSender) + genericActor, err := creator(context.Background(), logger, l1Head) + require.NoError(t, err, "failed to create actor") + actor, ok := genericActor.(*Actor) + require.True(t, ok, "actor is not of expected type") + return actor, &zkTestStubs{ + rootProvider: rootProvider, + contract: contract, + sender: txSender, + } +} + +type stubRootProvider struct { + outputErr error + rootBlockNum uint64 + root common.Hash + safeBlockNum uint64 +} + +func (s *stubRootProvider) OutputAtBlock(_ context.Context, blockNum uint64) (*eth.OutputResponse, error) { + if s.outputErr != nil { + return nil, s.outputErr + } + if blockNum != s.rootBlockNum { + return nil, errors.New("unexpected output request") + } + return ð.OutputResponse{ + OutputRoot: eth.Bytes32(s.root), + Status: ð.SyncStatus{ + SafeL2: eth.L2BlockRef{ + Number: s.safeBlockNum, + }, + }, + }, nil +} + +type stubContract struct { + parentIndex uint32 + parentStatus types.GameStatus + proposalStatus contracts.ProposalStatus + deadline time.Time + txCreated bool + proposalHash common.Hash + l2SequenceNumber uint64 +} + +func (s *stubContract) Addr() common.Address { + return common.Address{0x67, 0x67, 0x67} +} + +func (s *stubContract) challenge(t *testing.T) { + require.Equal(t, contracts.ProposalStatusUnchallenged, s.proposalStatus, "game not in challengable state") + s.proposalStatus = contracts.ProposalStatusChallenged +} + +func (s *stubContract) prove(t *testing.T) { + if s.proposalStatus == contracts.ProposalStatusUnchallenged { + s.proposalStatus = contracts.ProposalStatusUnchallengedAndValidProofProvided + return + } + require.Equal(t, contracts.ProposalStatusChallenged, s.proposalStatus, "game not in provable state") + s.proposalStatus = contracts.ProposalStatusChallengedAndValidProofProvided +} + +func (s *stubContract) setDeadlineExpired() { + s.deadline = l1Time.Add(-1 * time.Second) +} + +func (s *stubContract) setDeadlineNotReached() { + s.deadline = l1Time.Add(1 * time.Second) +} + +func (s *stubContract) markResolved() { + s.proposalStatus = contracts.ProposalStatusResolved +} + +func (s *stubContract) setParentStatus(status types.GameStatus) { + s.parentStatus = status +} + +func (s *stubContract) GetGameStatus(_ context.Context, idx uint64) (types.GameStatus, error) { + if idx != uint64(s.parentIndex) { + return 0, errors.New("unexpected parent index") + } + if idx == math.MaxUint32 { + return 0, errors.New("execution reverted") // no such game + } + return s.parentStatus, nil +} + +func (s *stubContract) GetChallengerMetadata(_ context.Context, _ rpcblock.Block) (contracts.ChallengerMetadata, error) { + return contracts.ChallengerMetadata{ + ParentIndex: s.parentIndex, + ProposalStatus: s.proposalStatus, + ProposedRoot: s.proposalHash, + L2SequenceNumber: s.l2SequenceNumber, + Deadline: s.deadline, + }, nil +} + +func (s *stubContract) ChallengeTx(_ context.Context) (txmgr.TxCandidate, error) { + s.txCreated = true + return txmgr.TxCandidate{ + TxData: []byte(challengeData), + }, nil +} + +func (s *stubContract) ResolveTx() (txmgr.TxCandidate, error) { + return txmgr.TxCandidate{ + TxData: []byte(resolveData), + }, nil +} + +func (s *stubContract) GetProposal(_ context.Context) (common.Hash, uint64, error) { + return s.proposalHash, s.l2SequenceNumber, nil +} + +type stubTxSender struct { + sentData []string + sendErr error +} + +func (s *stubTxSender) SendAndWaitSimple(_ string, candidates ...txmgr.TxCandidate) error { + for _, candidate := range candidates { + s.sentData = append(s.sentData, string(candidate.TxData)) + } + if s.sendErr != nil { + return s.sendErr + } + return nil +} + +// mockNotFoundRPCError creates a minimal rpc.Error that reports a "not found" message +// to exercise the JSON-RPC application error path in the enricher. +func mockNotFoundRPCError() rpc.Error { return testRPCError{msg: "not found", code: -32000} } + +type testRPCError struct { + msg string + code int +} + +func (e testRPCError) Error() string { return e.msg } +func (e testRPCError) ErrorCode() int { return e.code } diff --git a/op-challenger/game/zk/register.go b/op-challenger/game/zk/register.go new file mode 100644 index 00000000000..0676472e779 --- /dev/null +++ b/op-challenger/game/zk/register.go @@ -0,0 +1,70 @@ +package zk + +import ( + "context" + "fmt" + "time" + + "github.com/ethereum-optimism/optimism/op-challenger/config" + "github.com/ethereum-optimism/optimism/op-challenger/game/client" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/claims" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" + "github.com/ethereum-optimism/optimism/op-challenger/game/generic" + "github.com/ethereum-optimism/optimism/op-challenger/game/scheduler" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" + "github.com/ethereum-optimism/optimism/op-challenger/metrics" + "github.com/ethereum-optimism/optimism/op-service/txmgr" + "github.com/ethereum/go-ethereum/log" +) + +type ClockReader interface { + Now() time.Time +} + +type Registry interface { + RegisterGameType(gameType gameTypes.GameType, creator scheduler.PlayerCreator) + RegisterBondContract(gameType gameTypes.GameType, creator claims.BondContractCreator) +} + +type TxSender interface { + SendAndWaitSimple(txPurpose string, txs ...txmgr.TxCandidate) error +} + +func RegisterGameTypes( + ctx context.Context, + l1Clock ClockReader, + logger log.Logger, + m metrics.Metricer, + cfg *config.Config, + registry Registry, + txSender TxSender, + clients *client.Provider, + gameStatusProvider GameStatusProvider, +) error { + if cfg.GameTypeEnabled(gameTypes.OptimisticZKGameType) { + registry.RegisterGameType(gameTypes.OptimisticZKGameType, func(game gameTypes.GameMetadata, dir string) (scheduler.GamePlayer, error) { + rollupClient, syncValidator, err := clients.RollupClients() + if err != nil { + return nil, fmt.Errorf("failed to create rollup clients: %w", err) + } + contract, err := contracts.NewOptimisticZKDisputeGameContract(m, game.Proxy, clients.MultiCaller()) + if err != nil { + return nil, fmt.Errorf("failed to create optimistic zk dispute game bindings: %w", err) + } + return generic.NewGenericGamePlayer( + ctx, + logger, + game.Proxy, + contract, + syncValidator, + nil, + clients.L1Client(), + ActorCreator(l1Clock, rollupClient, gameStatusProvider, contract, txSender), + ) + }) + registry.RegisterBondContract(gameTypes.OptimisticZKGameType, func(game gameTypes.GameMetadata) (claims.BondContract, error) { + return contracts.NewOptimisticZKDisputeGameContract(m, game.Proxy, clients.MultiCaller()) + }) + } + return nil +} diff --git a/op-conductor/client/mocks/RollupBoostClient.go b/op-conductor/client/mocks/RollupBoostClient.go deleted file mode 100644 index 053e8c18346..00000000000 --- a/op-conductor/client/mocks/RollupBoostClient.go +++ /dev/null @@ -1,88 +0,0 @@ -// Code generated by mockery v2.x.x. DO NOT EDIT. - -package mocks - -import ( - context "context" - - client "github.com/ethereum-optimism/optimism/op-conductor/client" - mock "github.com/stretchr/testify/mock" -) - -// RollupBoostClient is an autogenerated mock type for the RollupBoostClient type -type RollupBoostClient struct { - mock.Mock -} - -// RollupBoostClient_Expecter is a helper object that allows for easy setup of method expectations -type RollupBoostClient_Expecter struct { - mock *mock.Mock -} - -// Expect returns an expecter for RollupBoostClient -func (_m *RollupBoostClient) EXPECT() *RollupBoostClient_Expecter { - return &RollupBoostClient_Expecter{mock: &_m.Mock} -} - -// Healthcheck provides a mock function with given fields: ctx -func (_m *RollupBoostClient) Healthcheck(ctx context.Context) (client.HealthStatus, error) { - ret := _m.Called(ctx) - - var r0 client.HealthStatus - if rf, ok := ret.Get(0).(func(context.Context) client.HealthStatus); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(client.HealthStatus) - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// RollupBoostClient_Healthcheck_Call is a *mock.Call that shadows Run/Return methods with type explicit versions for method 'Healthcheck' -type RollupBoostClient_Healthcheck_Call struct { - *mock.Call -} - -// Healthcheck is a helper method to define mock.On call -func (_e *RollupBoostClient_Expecter) Healthcheck(ctx interface{}) *RollupBoostClient_Healthcheck_Call { - return &RollupBoostClient_Healthcheck_Call{Call: _e.mock.On("Healthcheck", ctx)} -} - -func (_c *RollupBoostClient_Healthcheck_Call) Run(run func(ctx context.Context)) *RollupBoostClient_Healthcheck_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *RollupBoostClient_Healthcheck_Call) Return(status client.HealthStatus, err error) *RollupBoostClient_Healthcheck_Call { - _c.Call.Return(status, err) - return _c -} - -func (_c *RollupBoostClient_Healthcheck_Call) RunAndReturn(run func(context.Context) (client.HealthStatus, error)) *RollupBoostClient_Healthcheck_Call { - _c.Call.Return(run) - return _c -} - -type mockConstructorTestingTNewRollupBoostClient interface { - mock.TestingT - Cleanup(func()) -} - -// NewRollupBoostClient creates a new instance of RollupBoostClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewRollupBoostClient(t mockConstructorTestingTNewRollupBoostClient) *RollupBoostClient { - mock := &RollupBoostClient{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/op-conductor/client/mocks/RollupBoostHealthChecker.go b/op-conductor/client/mocks/RollupBoostHealthChecker.go new file mode 100644 index 00000000000..d57b6f94630 --- /dev/null +++ b/op-conductor/client/mocks/RollupBoostHealthChecker.go @@ -0,0 +1,89 @@ +// Code generated by mockery v2.x.x. DO NOT EDIT. + +package mocks + +import ( + context "context" + + client "github.com/ethereum-optimism/optimism/op-conductor/client" + mock "github.com/stretchr/testify/mock" +) + +// RollupBoostHealthChecker is an autogenerated mock type for the RollupBoostHealthChecker type +type RollupBoostHealthChecker struct { + mock.Mock +} + +// RollupBoostHealthChecker_Expecter is a helper object that allows for easy setup of method expectations +type RollupBoostHealthChecker_Expecter struct { + mock *mock.Mock +} + +// Expect returns an expecter for RollupBoostHealthChecker +func (_m *RollupBoostHealthChecker) EXPECT() *RollupBoostHealthChecker_Expecter { + return &RollupBoostHealthChecker_Expecter{mock: &_m.Mock} +} + +// Healthcheck provides a mock function with given fields: ctx +func (_m *RollupBoostHealthChecker) Healthcheck(ctx context.Context) (client.HealthStatus, error) { + ret := _m.Called(ctx) + + var r0 client.HealthStatus + if rf, ok := ret.Get(0).(func(context.Context) client.HealthStatus); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(client.HealthStatus) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RollupBoostHealthChecker_Healthcheck_Call is a *mock.Call that shadows Run/Return methods with type explicit versions for method 'Healthcheck' +type RollupBoostHealthChecker_Healthcheck_Call struct { + *mock.Call +} + +// Healthcheck is a helper method to define mock.On call +func (_e *RollupBoostHealthChecker_Expecter) Healthcheck(ctx interface{}) *RollupBoostHealthChecker_Healthcheck_Call { + return &RollupBoostHealthChecker_Healthcheck_Call{Call: _e.mock.On("Healthcheck", ctx)} +} + +func (_c *RollupBoostHealthChecker_Healthcheck_Call) Run(run func(ctx context.Context)) *RollupBoostHealthChecker_Healthcheck_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *RollupBoostHealthChecker_Healthcheck_Call) Return(status client.HealthStatus, err error) *RollupBoostHealthChecker_Healthcheck_Call { + _c.Call.Return(status, err) + return _c +} + +func (_c *RollupBoostHealthChecker_Healthcheck_Call) RunAndReturn(run func(context.Context) (client.HealthStatus, error)) *RollupBoostHealthChecker_Healthcheck_Call { + _c.Call.Return(run) + return _c +} + +type mockConstructorTestingTNewRollupBoostHealthChecker interface { + mock.TestingT + Cleanup(func()) +} + +// NewRollupBoostHealthChecker creates a new instance of RollupBoostHealthChecker. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewRollupBoostHealthChecker(t mockConstructorTestingTNewRollupBoostHealthChecker) *RollupBoostHealthChecker { + mock := &RollupBoostHealthChecker{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + diff --git a/op-conductor/client/rollup_boost.go b/op-conductor/client/rollupboost.go similarity index 61% rename from op-conductor/client/rollup_boost.go rename to op-conductor/client/rollupboost.go index 32d30f46f13..2de606f5069 100644 --- a/op-conductor/client/rollup_boost.go +++ b/op-conductor/client/rollupboost.go @@ -12,6 +12,7 @@ const ( HealthzEndpoint = "/healthz" ) +// HealthStatus represents the health state of rollup-boost. type HealthStatus string const ( @@ -20,26 +21,32 @@ const ( HealthStatusUnhealthy HealthStatus = "unhealthy" ) -type RollupBoostClient interface { +// RollupBoostHealthChecker is the common interface for rollup-boost health checking. +// Both RollupBoostClient and RollupBoostNextClient implement this interface. +type RollupBoostHealthChecker interface { Healthcheck(ctx context.Context) (HealthStatus, error) } -type rollupBoostClient struct { +// RollupBoostClient uses HTTP status codes to determine rollup-boost health. +type RollupBoostClient struct { baseURL string httpClient *http.Client } -func NewRollupBoostClient(baseURL string, httpClient *http.Client) RollupBoostClient { +// NewRollupBoostClient creates a client that interprets HTTP status codes for health. +func NewRollupBoostClient(baseURL string, httpClient *http.Client) *RollupBoostClient { if httpClient == nil { httpClient = http.DefaultClient } - return &rollupBoostClient{ + return &RollupBoostClient{ baseURL: baseURL, httpClient: httpClient, } } -func (c *rollupBoostClient) Healthcheck(ctx context.Context) (HealthStatus, error) { +// Healthcheck returns health status based on HTTP status codes: +// 200 OK = Healthy, 206 Partial Content = Partial, 503 Service Unavailable = Unhealthy +func (c *RollupBoostClient) Healthcheck(ctx context.Context) (HealthStatus, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+HealthzEndpoint, nil) if err != nil { return "", fmt.Errorf("failed to create request: %w", err) @@ -65,3 +72,6 @@ func (c *rollupBoostClient) Healthcheck(ctx context.Context) (HealthStatus, erro return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode) } } + +// Ensure RollupBoostClient implements RollupBoostHealthChecker +var _ RollupBoostHealthChecker = (*RollupBoostClient)(nil) diff --git a/op-conductor/client/rollupboost_next.go b/op-conductor/client/rollupboost_next.go new file mode 100644 index 00000000000..c68da339ba3 --- /dev/null +++ b/op-conductor/client/rollupboost_next.go @@ -0,0 +1,79 @@ +package client + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" +) + +// JSON API health status values returned by rollup-boost +const ( + jsonHealthStatusHealthy = "Healthy" + jsonHealthStatusPartial = "PartialContent" + jsonHealthStatusUnhealthy = "ServiceUnavailable" +) + +// RollupBoostNextClient retrieves rollup-boost health using the JSON-based healthcheck endpoint. +type RollupBoostNextClient struct { + url string + httpClient *http.Client +} + +// RollupBoostNextHealthResponse captures the JSON payload returned by the rollup-boost health endpoint. +type RollupBoostNextHealthResponse struct { + Version string `json:"version"` + RollupBoostHealth string `json:"rollup_boost_health"` +} + +// NewRollupBoostNextClient constructs a client for querying the rollup-boost health endpoint. +// The url parameter should be the full URL including path (e.g., "http://localhost:8080/healthz"). +func NewRollupBoostNextClient(url string, httpClient *http.Client) *RollupBoostNextClient { + if httpClient == nil { + httpClient = http.DefaultClient + } + return &RollupBoostNextClient{ + url: url, + httpClient: httpClient, + } +} + +// Healthcheck fetches the rollup-boost health endpoint and interprets the JSON payload. +func (c *RollupBoostNextClient) Healthcheck(ctx context.Context) (HealthStatus, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.url, nil) + if err != nil { + return "", fmt.Errorf("failed to create request: %w", err) + } + + resp, err := c.httpClient.Do(req) + if err != nil { + return "", fmt.Errorf("failed to make request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + var payload RollupBoostNextHealthResponse + // Limit response size to 1 MiB to prevent memory exhaustion from malicious servers + if err := json.NewDecoder(io.LimitReader(resp.Body, 1<<20)).Decode(&payload); err != nil { + return "", fmt.Errorf("failed to decode response: %w", err) + } + + // Map JSON API values to internal constants + switch payload.RollupBoostHealth { + case jsonHealthStatusHealthy: + return HealthStatusHealthy, nil + case jsonHealthStatusPartial: + return HealthStatusPartial, nil + case jsonHealthStatusUnhealthy: + return HealthStatusUnhealthy, nil + default: + return "", fmt.Errorf("unexpected rollup_boost_health: %q", payload.RollupBoostHealth) + } +} + +// Ensure RollupBoostNextClient implements RollupBoostHealthChecker +var _ RollupBoostHealthChecker = (*RollupBoostNextClient)(nil) diff --git a/op-conductor/client/rollupboost_next_test.go b/op-conductor/client/rollupboost_next_test.go new file mode 100644 index 00000000000..e33f2947748 --- /dev/null +++ b/op-conductor/client/rollupboost_next_test.go @@ -0,0 +1,106 @@ +package client + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestRollupBoostNextHealthcheck(t *testing.T) { + testCases := []struct { + name string + response interface{} + statusCode int + wantStatus HealthStatus + wantErr string + }{ + { + name: "healthy", + response: RollupBoostNextHealthResponse{ + Version: "1.0.0", + RollupBoostHealth: "Healthy", // JSON API value + }, + statusCode: http.StatusOK, + wantStatus: HealthStatusHealthy, + }, + { + name: "partial", + response: RollupBoostNextHealthResponse{ + Version: "1.0.0", + RollupBoostHealth: "PartialContent", // JSON API value + }, + statusCode: http.StatusOK, + wantStatus: HealthStatusPartial, + }, + { + name: "unhealthy", + response: RollupBoostNextHealthResponse{ + Version: "1.0.0", + RollupBoostHealth: "ServiceUnavailable", // JSON API value + }, + statusCode: http.StatusOK, + wantStatus: HealthStatusUnhealthy, + }, + { + name: "unexpected status code", + response: RollupBoostNextHealthResponse{ + Version: "1.0.0", + RollupBoostHealth: "Healthy", // JSON API value + }, + statusCode: http.StatusAccepted, + wantErr: "unexpected status code: 202", + }, + { + name: "malformed json", + response: "{not-json", + statusCode: http.StatusOK, + wantErr: "failed to decode response", + }, + { + name: "unknown health", + response: RollupBoostNextHealthResponse{ + Version: "1.0.0", + RollupBoostHealth: "Unknown", + }, + statusCode: http.StatusOK, + wantErr: `unexpected rollup_boost_health: "Unknown"`, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, HealthzEndpoint, r.URL.Path) + w.WriteHeader(tc.statusCode) + + switch v := tc.response.(type) { + case string: + _, _ = w.Write([]byte(v)) + default: + require.NoError(t, json.NewEncoder(w).Encode(v)) + } + })) + defer server.Close() + + // Pass full URL including path + client := NewRollupBoostNextClient(server.URL+HealthzEndpoint, server.Client()) + status, err := client.Healthcheck(context.Background()) + + if tc.wantErr != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tc.wantErr) + return + } + + require.NoError(t, err) + require.Equal(t, tc.wantStatus, status) + }) + } +} diff --git a/op-conductor/client/rollup_boost_test.go b/op-conductor/client/rollupboost_test.go similarity index 100% rename from op-conductor/client/rollup_boost_test.go rename to op-conductor/client/rollupboost_test.go diff --git a/op-conductor/conductor/config.go b/op-conductor/conductor/config.go index 90700b5a914..f7b45b6e6cc 100644 --- a/op-conductor/conductor/config.go +++ b/op-conductor/conductor/config.go @@ -66,12 +66,23 @@ type Config struct { // SupervisorRPC is the HTTP provider URL for supervisor. SupervisorRPC string - // RollupBoostEnabled is true if the rollup boost is enabled. + // RollupBoostEnabled enables the rollup-boost healthcheck (HTTP status codes). + // When enabled, healthchecks are performed against ExecutionRPC + "/healthz". + // The client internally appends the /healthz path to ExecutionRPC. RollupBoostEnabled bool - // RollupBoostHealthcheckTimeout is the timeout for rollup boost healthcheck. + // RollupBoostHealthcheckTimeout is the timeout for rollup-boost healthchecks (applies to both standard and next). RollupBoostHealthcheckTimeout time.Duration + // RollupBoostNextEnabled enables the next rollup-boost healthcheck (JSON-based). + // Requires RollupBoostNextHealthcheckURL to be set. + RollupBoostNextEnabled bool + + // RollupBoostNextHealthcheckURL is the full URL (including path) for the rollup-boost health endpoint. + // Must include the complete path (e.g., "http://localhost:8080/healthz"). + // Required when RollupBoostNextEnabled is true. + RollupBoostNextHealthcheckURL string + // Paused is true if the conductor should start in a paused state. Paused bool @@ -117,6 +128,12 @@ func (c *Config) Check() error { if c.ExecutionRPC == "" { return fmt.Errorf("missing geth RPC") } + if c.RollupBoostEnabled && c.RollupBoostNextEnabled { + return fmt.Errorf("only one of rollup-boost or rollup-boost next healthchecks can be enabled") + } + if c.RollupBoostNextEnabled && c.RollupBoostNextHealthcheckURL == "" { + return fmt.Errorf("missing rollup-boost next healthcheck URL") + } if err := c.HealthCheck.Check(); err != nil { return errors.Wrap(err, "invalid health check config") } @@ -154,7 +171,6 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*Config, error) { if executionP2pCheckApi == "" { executionP2pCheckApi = "net" } - return &Config{ ConsensusAddr: ctx.String(flags.ConsensusAddr.Name), ConsensusPort: ctx.Int(flags.ConsensusPort.Name), @@ -174,6 +190,8 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*Config, error) { SupervisorRPC: ctx.String(flags.SupervisorRPC.Name), RollupBoostEnabled: ctx.Bool(flags.RollupBoostEnabled.Name), RollupBoostHealthcheckTimeout: ctx.Duration(flags.RollupBoostHealthcheckTimeout.Name), + RollupBoostNextEnabled: ctx.Bool(flags.RollupBoostNextEnabled.Name), + RollupBoostNextHealthcheckURL: ctx.String(flags.RollupBoostNextHealthcheckURL.Name), Paused: ctx.Bool(flags.Paused.Name), HealthCheck: HealthCheckConfig{ Interval: ctx.Uint64(flags.HealthCheckInterval.Name), diff --git a/op-conductor/conductor/config_test.go b/op-conductor/conductor/config_test.go new file mode 100644 index 00000000000..a5744c3863a --- /dev/null +++ b/op-conductor/conductor/config_test.go @@ -0,0 +1,25 @@ +package conductor + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestConfigCheckRollupBoostAndNextMutuallyExclusive(t *testing.T) { + cfg := &Config{ + ConsensusAddr: "127.0.0.1", + ConsensusPort: 9000, + RaftServerID: "server-1", + RaftStorageDir: "/tmp/op-conductor", + NodeRPC: "http://node.example", + ExecutionRPC: "http://exec.example", + RollupBoostEnabled: true, + RollupBoostNextEnabled: true, + RollupBoostNextHealthcheckURL: "http://rollupboost.example", + } + + err := cfg.Check() + require.Error(t, err) + require.Contains(t, err.Error(), "only one of rollup-boost or rollup-boost next healthchecks can be enabled") +} diff --git a/op-conductor/conductor/service.go b/op-conductor/conductor/service.go index 33a23fea720..244a3ea5ecb 100644 --- a/op-conductor/conductor/service.go +++ b/op-conductor/conductor/service.go @@ -213,9 +213,14 @@ func (c *OpConductor) initHealthMonitor(ctx context.Context) error { } node := sources.NewRollupClient(nc) - var rb client.RollupBoostClient + // Create rollup-boost health checker (either standard or next, mutually exclusive) + var rollupBoostHealthChecker client.RollupBoostHealthChecker if c.cfg.RollupBoostEnabled { - rb = client.NewRollupBoostClient(c.cfg.ExecutionRPC, &http.Client{ + rollupBoostHealthChecker = client.NewRollupBoostClient(c.cfg.ExecutionRPC, &http.Client{ + Timeout: c.cfg.RollupBoostHealthcheckTimeout, + }) + } else if c.cfg.RollupBoostNextEnabled { + rollupBoostHealthChecker = client.NewRollupBoostNextClient(c.cfg.RollupBoostNextHealthcheckURL, &http.Client{ Timeout: c.cfg.RollupBoostHealthcheckTimeout, }) } @@ -260,7 +265,7 @@ func (c *OpConductor) initHealthMonitor(ctx context.Context) error { node, p2p, supervisor, - rb, + rollupBoostHealthChecker, elP2p, c.cfg.HealthCheck.ExecutionP2pMinPeerCount, c.cfg.HealthCheck.RollupBoostPartialHealthinessToleranceLimit, @@ -957,8 +962,8 @@ func (oc *OpConductor) shouldWaitForHealthRecovery() bool { return false } - // Don't wait if rollup boost is enabled and partially healthy - transfer leadership instead - if oc.cfg.RollupBoostEnabled && errors.Is(oc.hcerr, health.ErrRollupBoostPartiallyHealthy) { + // Don't wait if rollup boost healthcheck is enabled and partially healthy - transfer leadership instead + if (oc.cfg.RollupBoostEnabled || oc.cfg.RollupBoostNextEnabled) && errors.Is(oc.hcerr, health.ErrRollupBoostPartiallyHealthy) { return false } diff --git a/op-conductor/flags/flags.go b/op-conductor/flags/flags.go index 96e6eeafd51..2e4bfb04ac9 100644 --- a/op-conductor/flags/flags.go +++ b/op-conductor/flags/flags.go @@ -98,16 +98,27 @@ var ( } RollupBoostEnabled = &cli.BoolFlag{ Name: "rollup-boost.enabled", - Usage: "Should be set to true if execution.rpc points to a rollup boost instance, false otherwise. If true, rollup boost specific healthchecks will be performed against the rollup boost instance.", + Usage: "Enable the rollup-boost healthcheck that uses HTTP status codes (200/206/503). Healthchecks are performed against execution.rpc + '/healthz' (path appended automatically). Mutually exclusive with rollup-boost.next-enabled.", EnvVars: opservice.PrefixEnvVar(EnvVarPrefix, "ROLLUP_BOOST_ENABLED"), Value: false, } RollupBoostHealthcheckTimeout = &cli.DurationFlag{ Name: "rollup-boost.healthcheck-timeout", - Usage: "Timeout for rollup boost healthcheck", + Usage: "Timeout for rollup-boost healthchecks (applies to both standard and next)", EnvVars: opservice.PrefixEnvVar(EnvVarPrefix, "ROLLUP_BOOST_HEALTHCHECK_TIMEOUT"), Value: 5 * time.Second, } + RollupBoostNextEnabled = &cli.BoolFlag{ + Name: "rollup-boost.next-enabled", + Usage: "Enable rollup-boost healthcheck using JSON response parsing. Requires rollup-boost.next-healthcheck-url. Mutually exclusive with rollup-boost.enabled.", + EnvVars: opservice.PrefixEnvVar(EnvVarPrefix, "ROLLUP_BOOST_NEXT_ENABLED"), + Value: false, + } + RollupBoostNextHealthcheckURL = &cli.StringFlag{ + Name: "rollup-boost.next-healthcheck-url", + Usage: "Full URL including path for the rollup-boost health endpoint (e.g., 'http://localhost:8080/healthz'). Required when rollup-boost.next-enabled is true.", + EnvVars: opservice.PrefixEnvVar(EnvVarPrefix, "ROLLUP_BOOST_NEXT_HEALTHCHECK_URL"), + } HealthCheckInterval = &cli.Uint64Flag{ Name: "healthcheck.interval", Usage: "Interval between health checks", @@ -219,6 +230,8 @@ var optionalFlags = []cli.Flag{ SupervisorRPC, RollupBoostEnabled, RollupBoostHealthcheckTimeout, + RollupBoostNextEnabled, + RollupBoostNextHealthcheckURL, HealthcheckExecutionP2pEnabled, HealthcheckExecutionP2pMinPeerCount, HealthcheckExecutionP2pRPCUrl, diff --git a/op-conductor/health/monitor.go b/op-conductor/health/monitor.go index dc5bd88fbd4..67e568182b8 100644 --- a/op-conductor/health/monitor.go +++ b/op-conductor/health/monitor.go @@ -39,22 +39,23 @@ type HealthMonitor interface { // interval is the interval between health checks measured in seconds. // safeInterval is the interval between safe head progress measured in seconds. // minPeerCount is the minimum number of peers required for the sequencer to be healthy. -func NewSequencerHealthMonitor(log log.Logger, metrics metrics.Metricer, interval, unsafeInterval, safeInterval, minPeerCount uint64, safeEnabled bool, rollupCfg *rollup.Config, node dial.RollupClientInterface, p2p apis.P2PClient, supervisor SupervisorHealthAPI, rb client.RollupBoostClient, elP2pClient client.ElP2PClient, minElP2pPeers uint64, rollupBoostToleratePartialHealthinessToleranceLimit uint64, rollupBoostToleratePartialHealthinessToleranceIntervalSeconds uint64) HealthMonitor { +// rollupBoostHealthChecker is an optional health checker for rollup-boost (either standard or next client). +func NewSequencerHealthMonitor(log log.Logger, metrics metrics.Metricer, interval, unsafeInterval, safeInterval, minPeerCount uint64, safeEnabled bool, rollupCfg *rollup.Config, node dial.RollupClientInterface, p2p apis.P2PClient, supervisor SupervisorHealthAPI, rollupBoostHealthChecker client.RollupBoostHealthChecker, elP2pClient client.ElP2PClient, minElP2pPeers uint64, rollupBoostToleratePartialHealthinessToleranceLimit uint64, rollupBoostToleratePartialHealthinessToleranceIntervalSeconds uint64) HealthMonitor { hm := &SequencerHealthMonitor{ - log: log, - metrics: metrics, - interval: interval, - healthUpdateCh: make(chan error), - rollupCfg: rollupCfg, - unsafeInterval: unsafeInterval, - safeEnabled: safeEnabled, - safeInterval: safeInterval, - minPeerCount: minPeerCount, - timeProviderFn: currentTimeProvider, - node: node, - p2p: p2p, - supervisor: supervisor, - rb: rb, + log: log, + metrics: metrics, + interval: interval, + healthUpdateCh: make(chan error), + rollupCfg: rollupCfg, + unsafeInterval: unsafeInterval, + safeEnabled: safeEnabled, + safeInterval: safeInterval, + minPeerCount: minPeerCount, + timeProviderFn: currentTimeProvider, + node: node, + p2p: p2p, + supervisor: supervisor, + rollupBoostHealthChecker: rollupBoostHealthChecker, } if elP2pClient != nil { @@ -104,7 +105,7 @@ type SequencerHealthMonitor struct { node dial.RollupClientInterface p2p apis.P2PClient supervisor SupervisorHealthAPI - rb client.RollupBoostClient + rollupBoostHealthChecker client.RollupBoostHealthChecker elP2p *ElP2pHealthMonitor rollupBoostPartialHealthinessToleranceLimit uint64 rollupBoostPartialHealthinessToleranceCounter *timeBoundedRotatingCounter @@ -282,18 +283,22 @@ func (hm *SequencerHealthMonitor) checkNodePeerCount(ctx context.Context) error } func (hm *SequencerHealthMonitor) checkRollupBoost(ctx context.Context) error { - // Skip the check if rollup boost client is not configured - if hm.rb == nil { - hm.log.Info("rollup boost client is not configured, skipping health check") + // Skip the check if rollup boost health checker is not configured + if hm.rollupBoostHealthChecker == nil { + hm.log.Debug("rollup-boost health checker is not configured, skipping health check") return nil } - status, err := hm.rb.Healthcheck(ctx) + status, err := hm.rollupBoostHealthChecker.Healthcheck(ctx) if err != nil { - hm.log.Error("health monitor failed to get rollup boost status", "err", err) + hm.log.Error("health monitor failed to get rollup-boost status", "err", err) return ErrRollupBoostConnectionDown } + return hm.handleRollupBoostStatus(status) +} + +func (hm *SequencerHealthMonitor) handleRollupBoostStatus(status client.HealthStatus) error { switch status { case client.HealthStatusHealthy: return nil diff --git a/op-conductor/health/monitor_test.go b/op-conductor/health/monitor_test.go index af85b8ff427..a99047f58fe 100644 --- a/op-conductor/health/monitor_test.go +++ b/op-conductor/health/monitor_test.go @@ -99,12 +99,12 @@ func (s *HealthMonitorTestSuite) SetupMonitor( type monitorOpts func(*SequencerHealthMonitor) -// SetupMonitorWithRollupBoost creates a HealthMonitor that includes a RollupBoostClient +// SetupMonitorWithRollupBoost creates a HealthMonitor that includes a RollupBoostHealthChecker func (s *HealthMonitorTestSuite) SetupMonitorWithRollupBoost( now, unsafeInterval, safeInterval uint64, mockRollupClient *testutils.MockRollupClient, mockP2P *p2pMocks.API, - mockRollupBoost *clientmocks.RollupBoostClient, + mockRollupBoostHealthChecker *clientmocks.RollupBoostHealthChecker, elP2pClient client.ElP2PClient, opts ...monitorOpts, ) *SequencerHealthMonitor { @@ -130,8 +130,8 @@ func (s *HealthMonitorTestSuite) SetupMonitorWithRollupBoost( node: mockRollupClient, p2p: mockP2P, } - if mockRollupBoost != nil { - monitor.rb = mockRollupBoost + if mockRollupBoostHealthChecker != nil { + monitor.rollupBoostHealthChecker = mockRollupBoostHealthChecker } if elP2pClient != nil { monitor.elP2p = &ElP2pHealthMonitor{ @@ -359,26 +359,21 @@ func (s *HealthMonitorTestSuite) TestRollupBoostConnectionDown() { s.T().Parallel() now := uint64(time.Now().Unix()) - // Setup healthy node conditions rc := &testutils.MockRollupClient{} ss1 := mockSyncStatus(now-1, 1, now-3, 0) rc.ExpectSyncStatus(ss1, nil) - // Setup healthy peer count pc := &p2pMocks.API{} ps1 := &p2p.PeerStats{ Connected: healthyPeerCount, } pc.EXPECT().PeerStats(mock.Anything).Return(ps1, nil) - // Setup rollup boost connection failure - rb := &clientmocks.RollupBoostClient{} - rb.EXPECT().Healthcheck(mock.Anything).Return(client.HealthStatus(""), errors.New("connection refused")) + rbChecker := &clientmocks.RollupBoostHealthChecker{} + rbChecker.EXPECT().Healthcheck(mock.Anything).Return(client.HealthStatus(""), errors.New("connection refused")) - // Start monitor with all dependencies - monitor := s.SetupMonitorWithRollupBoost(now, 60, 60, rc, pc, rb, nil) + monitor := s.SetupMonitorWithRollupBoost(now, 60, 60, rc, pc, rbChecker, nil) - // Check for connection down error healthUpdateCh := monitor.Subscribe() healthFailure := <-healthUpdateCh s.Equal(ErrRollupBoostConnectionDown, healthFailure) @@ -390,26 +385,21 @@ func (s *HealthMonitorTestSuite) TestRollupBoostNotHealthy() { s.T().Parallel() now := uint64(time.Now().Unix()) - // Setup healthy node conditions rc := &testutils.MockRollupClient{} ss1 := mockSyncStatus(now-1, 1, now-3, 0) rc.ExpectSyncStatus(ss1, nil) - // Setup healthy peer count pc := &p2pMocks.API{} ps1 := &p2p.PeerStats{ Connected: healthyPeerCount, } pc.EXPECT().PeerStats(mock.Anything).Return(ps1, nil) - // Setup unhealthy rollup boost - rb := &clientmocks.RollupBoostClient{} - rb.EXPECT().Healthcheck(mock.Anything).Return(client.HealthStatusUnhealthy, nil) + rbChecker := &clientmocks.RollupBoostHealthChecker{} + rbChecker.EXPECT().Healthcheck(mock.Anything).Return(client.HealthStatusUnhealthy, nil) - // Start monitor with all dependencies - monitor := s.SetupMonitorWithRollupBoost(now, 60, 60, rc, pc, rb, nil) + monitor := s.SetupMonitorWithRollupBoost(now, 60, 60, rc, pc, rbChecker, nil) - // Check for unhealthy status healthUpdateCh := monitor.Subscribe() healthFailure := <-healthUpdateCh s.Equal(ErrRollupBoostNotHealthy, healthFailure) @@ -421,26 +411,21 @@ func (s *HealthMonitorTestSuite) TestRollupBoostPartialStatus() { s.T().Parallel() now := uint64(time.Now().Unix()) - // Setup healthy node conditions rc := &testutils.MockRollupClient{} ss1 := mockSyncStatus(now-1, 1, now-3, 0) rc.ExpectSyncStatus(ss1, nil) - // Setup healthy peer count pc := &p2pMocks.API{} ps1 := &p2p.PeerStats{ Connected: healthyPeerCount, } pc.EXPECT().PeerStats(mock.Anything).Return(ps1, nil) - // Setup partial rollup boost status (treated as unhealthy) - rb := &clientmocks.RollupBoostClient{} - rb.EXPECT().Healthcheck(mock.Anything).Return(client.HealthStatusPartial, nil) + rbChecker := &clientmocks.RollupBoostHealthChecker{} + rbChecker.EXPECT().Healthcheck(mock.Anything).Return(client.HealthStatusPartial, nil) - // Start monitor with all dependencies - monitor := s.SetupMonitorWithRollupBoost(now, 60, 60, rc, pc, rb, nil) + monitor := s.SetupMonitorWithRollupBoost(now, 60, 60, rc, pc, rbChecker, nil) - // Check for unhealthy status healthUpdateCh := monitor.Subscribe() healthFailure := <-healthUpdateCh s.Equal(ErrRollupBoostPartiallyHealthy, healthFailure) @@ -452,25 +437,21 @@ func (s *HealthMonitorTestSuite) TestRollupBoostPartialStatusWithTolerance() { s.T().Parallel() now := uint64(time.Now().Unix()) - // Setup healthy node conditions rc := &testutils.MockRollupClient{} ss1 := mockSyncStatus(now-1, 1, now-3, 0) - // because 6 healthchecks are going to be expected cause 6 calls of sync status for i := 0; i < 6; i++ { rc.ExpectSyncStatus(ss1, nil) } - // Setup healthy peer count pc := &p2pMocks.API{} ps1 := &p2p.PeerStats{ Connected: healthyPeerCount, } pc.EXPECT().PeerStats(mock.Anything).Return(ps1, nil) - // Setup partial rollup boost status (treated as unhealthy) - rb := &clientmocks.RollupBoostClient{} - rb.EXPECT().Healthcheck(mock.Anything).Return(client.HealthStatusPartial, nil) + rbChecker := &clientmocks.RollupBoostHealthChecker{} + rbChecker.EXPECT().Healthcheck(mock.Anything).Return(client.HealthStatusPartial, nil) toleranceLimit := uint64(2) toleranceIntervalSeconds := uint64(6) @@ -480,12 +461,9 @@ func (s *HealthMonitorTestSuite) TestRollupBoostPartialStatusWithTolerance() { tp := &timeProvider{now: 1758792282} - // Start monitor with all dependencies as well as tolerance of 2 rollup-boost partial unhealthiness per 3s period - monitor := s.SetupMonitorWithRollupBoost(now, 60, 60, rc, pc, rb, nil, func(shm *SequencerHealthMonitor) { + monitor := s.SetupMonitorWithRollupBoost(now, 60, 60, rc, pc, rbChecker, nil, func(shm *SequencerHealthMonitor) { timeBoundedRotatingCounter.timeProviderFn = tp.Now - // pollute the cache of timeBoundRotatingCounter with 998 elements so as to later test the lazy cleanup - // note: the 999th and 1000th element will be added by the first healthcheck run for i := 0; i < 999; i++ { timeBoundedRotatingCounter.temporalCache[int64(i)] = uint64(1) } @@ -508,9 +486,7 @@ func (s *HealthMonitorTestSuite) TestRollupBoostPartialStatusWithTolerance() { s.Nil(secondHealthStatus) s.Equal(ErrRollupBoostPartiallyHealthy, thirdHealthStatus) - tp.Now() // simulate another second passing - // by now, because of three healthchecks, six seconds (CurrentValue + Increment + CurrentValue + Increment + CurrentValue + tp.Now()) have been simulated to pass (by the timeProviderFn) - // this should reset the time bound counter, thereby allowing partial unhealthiness failures to be tolerated again + tp.Now() fourthHealthStatus := <-healthUpdateCh fifthHealthStatus := <-healthUpdateCh @@ -528,7 +504,6 @@ func (s *HealthMonitorTestSuite) TestRollupBoostHealthy() { now := uint64(time.Now().Unix()) numSecondsToWait := interval + 1 - // Setup healthy node conditions rc := &testutils.MockRollupClient{} ss1 := mockSyncStatus(now-1, 1, now-3, 0) @@ -536,22 +511,17 @@ func (s *HealthMonitorTestSuite) TestRollupBoostHealthy() { rc.ExpectSyncStatus(ss1, nil) } - // Setup healthy peer count pc := &p2pMocks.API{} ps1 := &p2p.PeerStats{ Connected: healthyPeerCount, } pc.EXPECT().PeerStats(mock.Anything).Return(ps1, nil) - // Setup healthy rollup boost - rb := &clientmocks.RollupBoostClient{} - // // Wait for longer than healthcheck interval before returning healthy status, to verify nothing breaks if rb is slow to respond - rb.EXPECT().Healthcheck(mock.Anything).After(time.Duration(numSecondsToWait)*time.Second).Return(client.HealthStatusHealthy, nil) + rbChecker := &clientmocks.RollupBoostHealthChecker{} + rbChecker.EXPECT().Healthcheck(mock.Anything).After(time.Duration(numSecondsToWait)*time.Second).Return(client.HealthStatusHealthy, nil) - // Start monitor with all dependencies - monitor := s.SetupMonitorWithRollupBoost(now, 60, 60, rc, pc, rb, nil) + monitor := s.SetupMonitorWithRollupBoost(now, 60, 60, rc, pc, rbChecker, nil) - // Should report healthy status healthUpdateCh := monitor.Subscribe() healthStatus := <-healthUpdateCh s.Nil(healthStatus) @@ -563,43 +533,22 @@ func (s *HealthMonitorTestSuite) TestRollupBoostNilClient() { s.T().Parallel() now := uint64(time.Now().Unix()) - // Setup healthy node conditions rc := &testutils.MockRollupClient{} ss1 := mockSyncStatus(now-1, 1, now-3, 0) rc.ExpectSyncStatus(ss1, nil) - // Setup healthy peer count pc := &p2pMocks.API{} ps1 := &p2p.PeerStats{ Connected: healthyPeerCount, } pc.EXPECT().PeerStats(mock.Anything).Return(ps1, nil) - // Explicitly create a monitor with all other components but nil rollup boost client - tp := &timeProvider{now: now} - monitor := &SequencerHealthMonitor{ - log: s.log, - interval: s.interval, - metrics: &metrics.NoopMetricsImpl{}, - healthUpdateCh: make(chan error), - rollupCfg: s.rollupCfg, - unsafeInterval: 60, - safeInterval: 60, - safeEnabled: true, - minPeerCount: s.minPeerCount, - timeProviderFn: tp.Now, - node: rc, - p2p: pc, - rb: nil, // Explicitly set to nil - } - - err := monitor.Start(context.Background()) - s.NoError(err) + // No rollup boost health checker configured + monitor := s.SetupMonitorWithRollupBoost(now, 60, 60, rc, pc, nil, nil) - // Health check should succeed even with nil rb healthUpdateCh := monitor.Subscribe() healthStatus := <-healthUpdateCh - s.Nil(healthStatus, "Health check should succeed with nil rollup boost client") + s.Nil(healthStatus, "Health check should succeed with nil rollup boost health checker") s.NoError(monitor.Stop()) } @@ -609,7 +558,6 @@ func (s *HealthMonitorTestSuite) TestElP2pHealthy() { now := uint64(time.Now().Unix()) numSecondsToWait := interval + 1 - // Setup healthy node conditions rc := &testutils.MockRollupClient{} ss1 := mockSyncStatus(now-1, 1, now-3, 0) @@ -617,26 +565,20 @@ func (s *HealthMonitorTestSuite) TestElP2pHealthy() { rc.ExpectSyncStatus(ss1, nil) } - // Setup healthy rollup boost - rb := &clientmocks.RollupBoostClient{} - // // Wait for longer than healthcheck interval before returning healthy status, to verify nothing breaks if rb is slow to respond - rb.EXPECT().Healthcheck(mock.Anything).After(time.Duration(numSecondsToWait)*time.Second).Return(client.HealthStatusHealthy, nil) + rbChecker := &clientmocks.RollupBoostHealthChecker{} + rbChecker.EXPECT().Healthcheck(mock.Anything).After(time.Duration(numSecondsToWait)*time.Second).Return(client.HealthStatusHealthy, nil) - // Setup healthy peer count pc := &p2pMocks.API{} ps1 := &p2p.PeerStats{ Connected: healthyPeerCount, } pc.EXPECT().PeerStats(mock.Anything).Return(ps1, nil) - // Setup healthy el p2p elP2pClient := &clientmocks.ElP2PClient{} elP2pClient.EXPECT().PeerCount(mock.Anything).Return(healthyElP2pPeerCount, nil) - // Start monitor with all dependencies - monitor := s.SetupMonitorWithRollupBoost(now, 60, 60, rc, pc, rb, elP2pClient) + monitor := s.SetupMonitorWithRollupBoost(now, 60, 60, rc, pc, rbChecker, elP2pClient) - // Should report healthy status healthUpdateCh := monitor.Subscribe() healthStatus := <-healthUpdateCh s.Nil(healthStatus) @@ -649,7 +591,6 @@ func (s *HealthMonitorTestSuite) TestElP2pHealthyNilClient() { now := uint64(time.Now().Unix()) numSecondsToWait := interval + 1 - // Setup healthy node conditions rc := &testutils.MockRollupClient{} ss1 := mockSyncStatus(now-1, 1, now-3, 0) @@ -657,17 +598,14 @@ func (s *HealthMonitorTestSuite) TestElP2pHealthyNilClient() { rc.ExpectSyncStatus(ss1, nil) } - // Setup healthy peer count pc := &p2pMocks.API{} ps1 := &p2p.PeerStats{ Connected: healthyPeerCount, } pc.EXPECT().PeerStats(mock.Anything).Return(ps1, nil) - // Start monitor with all dependencies monitor := s.SetupMonitorWithRollupBoost(now, 60, 60, rc, pc, nil, nil) - // Should report healthy status healthUpdateCh := monitor.Subscribe() healthStatus := <-healthUpdateCh s.Nil(healthStatus) diff --git a/op-conductor/rpc/ws/flashblocks_handler.go b/op-conductor/rpc/ws/flashblocks_handler.go index c58a86bfb2f..b4b159c5eb2 100644 --- a/op-conductor/rpc/ws/flashblocks_handler.go +++ b/op-conductor/rpc/ws/flashblocks_handler.go @@ -10,6 +10,7 @@ import ( "github.com/coder/websocket" "github.com/ethereum-optimism/optimism/op-conductor/metrics" + opclient "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/httputil" "github.com/ethereum-optimism/optimism/op-service/retry" "github.com/ethereum/go-ethereum/log" @@ -54,7 +55,7 @@ type Handler struct { log log.Logger isLeaderFn func(context.Context) bool metrics metrics.Metricer - rollupBoostConn *websocket.Conn + rollupBoostConn *opclient.WSClient rollupBoostCtx context.Context rollupBoostWsCancel context.CancelFunc httpServer *httputil.HTTPServer @@ -83,18 +84,14 @@ func NewHandler(cfg Config, log log.Logger, isLeaderFn func(context.Context) boo // Try to establish initial connection to rollup boost WebSocket maxConnectionAttempts := 5 - var err error - handler.rollupBoostConn, err = retry.Do(context.Background(), maxConnectionAttempts, retry.Fixed(reconnectDelay), func() (*websocket.Conn, error) { - log.Info("attempting to connect to rollup boost WebSocket", "url", cfg.RollupBoostWsURL) - dialCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - conn, resp, err := websocket.Dial(dialCtx, cfg.RollupBoostWsURL, nil) - if resp != nil && resp.Body != nil { - resp.Body.Close() - } - return conn, err + conn, err := opclient.DialWS(context.Background(), opclient.WSConfig{ + URL: cfg.RollupBoostWsURL, + DialTimeout: 5 * time.Second, + MaxAttempts: maxConnectionAttempts, + Backoff: retry.Fixed(reconnectDelay), + Log: log, }) - + handler.rollupBoostConn = conn if err != nil { return nil, fmt.Errorf("failed to connect to rollup boost WebSocket: %w", err) } @@ -204,13 +201,12 @@ func (h *Handler) listenToRollupBoost(ctx context.Context) { h.log.Info("reconnecting to rollup boost WebSocket", "url", h.cfg.RollupBoostWsURL) // Connect with timeout - dialCtx, cancel := context.WithTimeout(ctx, 5*time.Second) - defer cancel() - conn, resp, err := websocket.Dial(dialCtx, h.cfg.RollupBoostWsURL, nil) - if resp != nil && resp.Body != nil { - resp.Body.Close() - } - + conn, err := opclient.DialWS(ctx, opclient.WSConfig{ + URL: h.cfg.RollupBoostWsURL, + DialTimeout: 5 * time.Second, + MaxAttempts: 1, + Log: h.log, + }) if err != nil { h.log.Warn("failed to connect to rollup boost WebSocket, will retry", "err", err, "retryIn", reconnectDelay) @@ -227,8 +223,8 @@ func (h *Handler) listenToRollupBoost(ctx context.Context) { // Read with timeout readCtx, cancel := context.WithTimeout(ctx, 30*time.Second) - defer cancel() _, message, err := h.rollupBoostConn.Read(readCtx) + cancel() if err != nil { h.log.Warn("error reading from rollup boost WebSocket", "err", err) diff --git a/op-deployer/pkg/deployer/artifacts/cmd/mktar/main.go b/op-deployer/pkg/deployer/artifacts/cmd/mktar/main.go index 75be53e8e48..b3b0826e19c 100644 --- a/op-deployer/pkg/deployer/artifacts/cmd/mktar/main.go +++ b/op-deployer/pkg/deployer/artifacts/cmd/mktar/main.go @@ -6,9 +6,11 @@ import ( "io" "log" "os" + "os/exec" "path" "path/filepath" "strings" + "time" "github.com/klauspost/compress/zstd" ) @@ -150,6 +152,13 @@ func main() { log.Fatalf("walk: %v", err) } + // Add COMMIT file if git is available + if commit := getGitCommit(absBase); commit != "" { + if err := addCommitFile(tw, commit); err != nil { + log.Fatalf("failed to add COMMIT file: %v", err) + } + } + if err := tw.Flush(); err != nil { log.Fatalf("flush tar: %v", err) } @@ -216,3 +225,36 @@ func linkTarget(path string, info os.FileInfo) string { } return target } + +func getGitCommit(baseDir string) string { + cmd := exec.Command("git", "rev-parse", "HEAD") + cmd.Dir = baseDir + output, err := cmd.Output() + if err != nil { + return "" + } + return strings.TrimSpace(string(output)) +} + +func addCommitFile(tw *tar.Writer, commit string) error { + hdr := &tar.Header{ + Name: "COMMIT", + Size: int64(len(commit)), + Mode: 0644, + ModTime: time.Now(), + AccessTime: time.Now(), + ChangeTime: time.Now(), + Typeflag: tar.TypeReg, + } + + if err := tw.WriteHeader(hdr); err != nil { + return err + } + + if _, err := tw.Write([]byte(commit)); err != nil { + return err + } + + log.Printf("a COMMIT") + return nil +} diff --git a/op-deployer/pkg/deployer/devfeatures.go b/op-deployer/pkg/deployer/devfeatures.go index 6a8867d43a2..fefd390e06b 100644 --- a/op-deployer/pkg/deployer/devfeatures.go +++ b/op-deployer/pkg/deployer/devfeatures.go @@ -17,9 +17,6 @@ var ( // DeployV2DisputeGamesDevFlag enables deployment of V2 dispute game contracts. DeployV2DisputeGamesDevFlag = common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000100") - - // CustomGasTokenDevFlag enables the custom gas token. - CustomGasTokenDevFlag = common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000001000") ) // IsDevFeatureEnabled checks if a specific development feature is enabled in a feature bitmap. diff --git a/op-deployer/pkg/deployer/integration_test/apply_test.go b/op-deployer/pkg/deployer/integration_test/apply_test.go index bd5dc5c88af..b028b09f82e 100644 --- a/op-deployer/pkg/deployer/integration_test/apply_test.go +++ b/op-deployer/pkg/deployer/integration_test/apply_test.go @@ -321,10 +321,6 @@ func TestEndToEndApply(t *testing.T) { Symbol: "CGT", InitialLiquidity: (*hexutil.Big)(amount), } - // CGT config for OPCM - intent.GlobalDeployOverrides = map[string]interface{}{ - "devFeatureBitmap": deployer.CustomGasTokenDevFlag, - } require.NoError(t, deployer.ApplyPipeline(ctx, deployer.ApplyPipelineOpts{ DeploymentTarget: deployer.DeploymentTargetLive, diff --git a/op-deployer/pkg/deployer/integration_test/cli/verify_test.go b/op-deployer/pkg/deployer/integration_test/cli/verify_test.go index 558387d8e5e..95bf1d0d943 100644 --- a/op-deployer/pkg/deployer/integration_test/cli/verify_test.go +++ b/op-deployer/pkg/deployer/integration_test/cli/verify_test.go @@ -117,7 +117,7 @@ func TestCLIVerify(t *testing.T) { "--artifacts-locator", "embedded", }, nil) - require.Contains(t, output, "Contract verified successfully") + require.Contains(t, output, "Contract already verified") require.Contains(t, output, "superchainConfigProxyAddress") }) @@ -138,7 +138,8 @@ func TestCLIVerify(t *testing.T) { require.Contains(t, output, "Starting automatic contract verification") require.Contains(t, output, "Verification Summary") - require.Contains(t, output, "verified=5") + require.Contains(t, output, "verified=0") + require.Contains(t, output, "skipped=5") require.Contains(t, output, "failed=0") }) @@ -165,6 +166,19 @@ func setupMockBlockscout(t *testing.T) string { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") + if strings.HasPrefix(r.URL.Path, "/api/v2/smart-contracts/") { + response := map[string]interface{}{ + "message": "OK", + "is_verified": true, + "is_fully_verified": true, + "is_partially_verified": false, + "is_verified_via_eth_bytecode_db": false, + "is_verified_via_sourcify": false, + } + _ = json.NewEncoder(w).Encode(response) + return + } + var action string if r.Method == http.MethodPost { if err := r.ParseForm(); err != nil { diff --git a/op-deployer/pkg/deployer/opcm/implementations.go b/op-deployer/pkg/deployer/opcm/implementations.go index 12ed97dbb9b..07c367cc1d8 100644 --- a/op-deployer/pkg/deployer/opcm/implementations.go +++ b/op-deployer/pkg/deployer/opcm/implementations.go @@ -35,6 +35,7 @@ type DeployImplementationsOutput struct { OpcmUpgrader common.Address `json:"opcmUpgraderAddress"` OpcmInteropMigrator common.Address `json:"opcmInteropMigratorAddress"` OpcmStandardValidator common.Address `json:"opcmStandardValidatorAddress"` + OpcmUtils common.Address `json:"opcmUtilsAddress"` OpcmV2 common.Address `json:"opcmV2Address"` OpcmContainer common.Address `json:"opcmContainerAddress"` DelayedWETHImpl common.Address `json:"delayedWETHImplAddress"` diff --git a/op-deployer/pkg/deployer/standard/standard.go b/op-deployer/pkg/deployer/standard/standard.go index 9533d29f1ee..bcfb6062db6 100644 --- a/op-deployer/pkg/deployer/standard/standard.go +++ b/op-deployer/pkg/deployer/standard/standard.go @@ -46,8 +46,7 @@ const ( CurrentTag = ContractsV410Tag ) -// TODO(#17505): This address should be updated to the actual address once deployed -var L1FeesDepositor = common.HexToAddress("0x81c01427DFA9A2512b4EBf1462868856BA4aA91a") +var L1FeesDepositor = common.HexToAddress("0xed9B99a703BaD32AC96FDdc313c0652e379251Fd") var DisputeAbsolutePrestate = common.HexToHash("0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c") diff --git a/op-deployer/pkg/deployer/verify/artifacts.go b/op-deployer/pkg/deployer/verify/artifacts.go index 1f64b0e5ee1..7c598c8efda 100644 --- a/op-deployer/pkg/deployer/verify/artifacts.go +++ b/op-deployer/pkg/deployer/verify/artifacts.go @@ -45,6 +45,7 @@ var contractNameExceptions = map[string]string{ "OpcmStandardValidator": "OPContractsManagerStandardValidator.sol/OPContractsManagerStandardValidator.json", "OpcmV2": "OPContractsManagerV2.sol/OPContractsManagerV2.json", "OpcmContainer": "OPContractsManagerContainer.sol/OPContractsManagerContainer.json", + "OpcmUtils": "OPContractsManagerUtils.sol/OPContractsManagerUtils.json", "Mips": "MIPS64.sol/MIPS64.json", "EthLockbox": "ETHLockbox.sol/ETHLockbox.json", } diff --git a/op-deployer/scripts/test-sepolia-op-deployer.sh b/op-deployer/scripts/test-sepolia-op-deployer.sh index 4828aaefad7..16721b82951 100755 --- a/op-deployer/scripts/test-sepolia-op-deployer.sh +++ b/op-deployer/scripts/test-sepolia-op-deployer.sh @@ -10,6 +10,7 @@ NC='\033[0m' SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" OUTPUT_DIR="$REPO_ROOT/.deployer-output" +OP_DEPLOYER_BASE_CMD=("go" "run" "./cmd/op-deployer") mkdir -p "$OUTPUT_DIR" cd "$REPO_ROOT" @@ -36,6 +37,51 @@ read_env_var() { echo "$value" } +download_op_deployer_binary() { + local version="$1" + local os + os="$(uname -s | tr '[:upper:]' '[:lower:]')" + local arch + arch="$(uname -m)" + case "$arch" in + x86_64) arch="amd64" ;; + arm64|aarch64) arch="arm64" ;; + *) echo -e "${RED}Unsupported arch: $arch${NC}" >&2; exit 1 ;; + esac + + local name="op-deployer-${version}-${os}-${arch}" + local url="https://github.com/ethereum-optimism/optimism/releases/download/op-deployer%2Fv${version}/${name}.tar.gz" + local dest_dir + dest_dir="$(mktemp -d "${TMPDIR:-/tmp}/op-deployer.XXXXXX")" + local tar_path="$dest_dir/${name}.tar.gz" + local extract_dir="$dest_dir/${name}" + local bin_path="$extract_dir/op-deployer" + + echo -e "${BLUE}Downloading op-deployer ${version} (${os}/${arch})...${NC}" + if ! curl -L --fail --retry 3 "$url" -o "$tar_path"; then + echo -e "${RED}Download failed from:${NC} $url" >&2 + exit 1 + fi + + if ! tar -xzf "$tar_path" -C "$dest_dir"; then + echo -e "${RED}Failed to extract:${NC} $tar_path" >&2 + exit 1 + fi + + if [ ! -x "$bin_path" ]; then + echo -e "${RED}Binary not found after extract:${NC} $bin_path" >&2 + exit 1 + fi + + chmod +x "$bin_path" + if [ "$os" = "darwin" ] && command -v xattr >/dev/null 2>&1; then + xattr -d com.apple.quarantine "$bin_path" 2>/dev/null || true + fi + + OP_DEPLOYER_BASE_CMD=("$bin_path") + echo -e "${GREEN}✓ Using downloaded binary:${NC} $bin_path" +} + select_verifier() { local context="$1" local prefill_type="${DEPLOYER_VERIFIER_TYPE:-}" @@ -111,7 +157,7 @@ select_verifier() { build_verify_cmd() { local input_file="$1" - local cmd=("go" "run" "./cmd/op-deployer" "verify" + local cmd=("${OP_DEPLOYER_BASE_CMD[@]}" "verify" "--l1-rpc-url" "$L1_RPC_URL" "--input-file" "$input_file" "--verifier" "$VERIFIER_TYPE" @@ -125,7 +171,7 @@ build_verify_cmd() { build_validate_cmd() { local workdir="$1" - local cmd=("go" "run" "./cmd/op-deployer" "validate" "auto" + local cmd=("${OP_DEPLOYER_BASE_CMD[@]}" "validate" "auto" "--l1-rpc-url" "$L1_RPC_URL" "--workdir" "$workdir" "--fail" @@ -162,6 +208,58 @@ $field = \"$value\" fi } +select_runner() { + local prefill="${DEPLOYER_RUNNER:-}" + local prefill_choice="" + if [ "$prefill" == "docker" ]; then + prefill_choice="2" + echo -e "${GREEN} (Pre-filled from DEPLOYER_RUNNER: docker - auto-selecting option 2)${NC}" + elif [ "$prefill" == "go" ]; then + prefill_choice="1" + echo -e "${GREEN} (Pre-filled from DEPLOYER_RUNNER: go - auto-selecting option 1)${NC}" + elif [ "$prefill" == "binary" ]; then + prefill_choice="3" + echo -e "${GREEN} (Pre-filled from DEPLOYER_RUNNER: binary - auto-selecting option 3)${NC}" + fi + + echo "" + echo -e "${BLUE}How would you like to run op-deployer?${NC}" + echo " 1) Local go run (default)" + echo " 2) Docker image" + echo " 3) Prebuilt binary (download from GitHub release)" + echo "" + + local choice="$prefill_choice" + if [ -z "$choice" ]; then + read -r -p "Enter choice [1-3]: " choice + fi + + case "$choice" in + 2) + local default_tag="${DEPLOYER_IMAGE_TAG:-latest}" + if [ -n "${DEPLOYER_IMAGE:-}" ]; then + OP_DEPLOYER_IMAGE="$DEPLOYER_IMAGE" + else + read -r -p "Docker image tag [${default_tag}]: " selected_tag + selected_tag="${selected_tag:-$default_tag}" + OP_DEPLOYER_IMAGE="us-docker.pkg.dev/oplabs-tools-artifacts/images/op-deployer:${selected_tag}" + fi + OP_DEPLOYER_BASE_CMD=("docker" "run" "--rm" "-v" "$REPO_ROOT:$REPO_ROOT" "-w" "$REPO_ROOT" "$OP_DEPLOYER_IMAGE" "op-deployer") + echo -e "${GREEN}✓ Using docker image:${NC} $OP_DEPLOYER_IMAGE" + ;; + 3) + local default_version="${DEPLOYER_VERSION:-0.5.1}" + read -r -p "Release version [${default_version}]: " selected_version + selected_version="${selected_version:-$default_version}" + download_op_deployer_binary "$selected_version" + ;; + *) + OP_DEPLOYER_BASE_CMD=("go" "run" "./cmd/op-deployer") + echo -e "${GREEN}✓ Using local go run (./cmd/op-deployer)${NC}" + ;; + esac +} + echo -e "${BLUE}═══════════════════════════════════════════════════════════════${NC}" echo -e "${BLUE} OP Deployer - Sepolia Deployment & Verification Script${NC}" echo -e "${BLUE}═══════════════════════════════════════════════════════════════${NC}" @@ -182,6 +280,8 @@ echo "" L1_RPC_URL=$(read_env_var "L1_RPC_URL" "Enter Sepolia RPC URL: ") +select_runner + if [ "$DEPLOY_TYPE" != "4" ] && [ "$DEPLOY_TYPE" != "5" ]; then if [ -z "$DEPLOYER_PRIVATE_KEY" ]; then echo "" @@ -353,7 +453,7 @@ case "$DEPLOY_TYPE" in echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" - INIT_CMD=("go" "run" "./cmd/op-deployer" "init" + INIT_CMD=("${OP_DEPLOYER_BASE_CMD[@]}" "init" "--l1-chain-id" "$L1_CHAIN_ID" "--l2-chain-ids" "$L2_CHAIN_ID" "--workdir" "$WORKDIR" @@ -524,7 +624,7 @@ case "$DEPLOY_TYPE" in echo " - Or there's an issue with the OPCM contract configuration" echo "" - APPLY_CMD=("go" "run" "./cmd/op-deployer" "apply" + APPLY_CMD=("${OP_DEPLOYER_BASE_CMD[@]}" "apply" "--l1-rpc-url" "$L1_RPC_URL" "--workdir" "$WORKDIR" "--private-key" "$PRIVATE_KEY" @@ -696,7 +796,7 @@ if [ "$DEPLOY_TYPE" == "1" ] || [ "$DEPLOY_TYPE" == "2" ]; then echo -e "${BLUE}═══════════════════════════════════════════════════════════════${NC}" echo "" - CMD=("go" "run" "./cmd/op-deployer") + CMD=("${OP_DEPLOYER_BASE_CMD[@]}") if [ "$DEPLOY_TYPE" == "1" ]; then CMD+=( diff --git a/op-devstack/dsl/bridge.go b/op-devstack/dsl/bridge.go index 26ae4f0b84f..0b5ca7362e6 100644 --- a/op-devstack/dsl/bridge.go +++ b/op-devstack/dsl/bridge.go @@ -605,7 +605,7 @@ func gasCost(rcpt *types.Receipt, rollupCfg *rollup.Config, blockTimestamp *uint } operatorCost := new(big.Int).SetUint64(rcpt.GasUsed) operatorCost.Mul(operatorCost, new(big.Int).SetUint64(*rcpt.OperatorFeeScalar)) - if rollupCfg.IsOperatorFeeFix(*blockTimestamp) { + if rollupCfg.IsJovian(*blockTimestamp) { operatorCost.Mul(operatorCost, big.NewInt(100)) } else { operatorCost.Div(operatorCost, big.NewInt(1_000_000)) diff --git a/op-devstack/dsl/el.go b/op-devstack/dsl/el.go index 5d5856a67eb..d0245b11ba0 100644 --- a/op-devstack/dsl/el.go +++ b/op-devstack/dsl/el.go @@ -38,7 +38,7 @@ func (el *elNode) WaitForBlock() eth.BlockRef { func (el *elNode) WaitForLabel(label eth.BlockLabel, predicate func(eth.BlockInfo) (bool, error)) eth.BlockInfo { var block eth.BlockInfo - err := wait.For(el.ctx, 500*time.Millisecond, func() (bool, error) { + err := wait.For(el.ctx, 200*time.Millisecond, func() (bool, error) { var err error block, err = el.inner.EthClient().InfoByLabel(el.ctx, label) if err != nil { diff --git a/op-devstack/dsl/l2_network.go b/op-devstack/dsl/l2_network.go index dfb6ef0a83b..d2ae3f694a0 100644 --- a/op-devstack/dsl/l2_network.go +++ b/op-devstack/dsl/l2_network.go @@ -1,7 +1,9 @@ package dsl import ( + "bytes" "fmt" + "io" "math" "time" @@ -12,9 +14,11 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/stack/match" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/retry" "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" ) // L2Network wraps a stack.L2Network interface for DSL operations @@ -261,3 +265,262 @@ func (n *L2Network) DisputeGameFactoryProxyAddr() common.Address { func (n *L2Network) DepositContractAddr() common.Address { return n.inner.RollupConfig().DepositContractAddress } + +func (n *L2Network) DeriveData(blocks int) (channels []derive.ChannelID, channelFrames map[derive.ChannelID][]derive.Frame, l2Txs map[common.Address][]*ethtypes.Transaction) { + l := n.log + ctx := n.ctx + + channelFrames = make(map[derive.ChannelID][]derive.Frame) + channels = make([]derive.ChannelID, 0) + l2Txs = make(map[common.Address][]*ethtypes.Transaction) + + rollupCfg := n.inner.RollupConfig() + batchInboxAddr := rollupCfg.BatchInboxAddress + + l1EC := n.inner.L1().L1ELNode(match.FirstL1EL).EthClient() + + // Get current L1 block number before starting to monitor + startBlockRef, err := l1EC.BlockRefByLabel(ctx, eth.Unsafe) + n.require.NoError(err, "Failed to get start block number") + + seenChannels := make(map[derive.ChannelID]bool) + lastBlockRef := startBlockRef + + // Monitor L1 blocks for batch transactions + for range blocks { + NewL1ELNode(n.inner.L1().L1ELNode(match.FirstL1EL)).WaitForBlock() + + // Get current block number + currentBlockRef, err := l1EC.BlockRefByLabel(ctx, eth.Unsafe) + n.require.NoError(err, "Failed to get current block number") + blockNum := currentBlockRef.Number + lastBlockRef = currentBlockRef + + _, txs, err := l1EC.InfoAndTxsByNumber(ctx, blockNum) + n.require.NoError(err, "Failed to get block %d", blockNum) + + // Process transactions in this block + for _, tx := range txs { + // Check if transaction is targeted to BatchInbox + if tx.To() != nil && *tx.To() == batchInboxAddr { + // Get transaction sender + chainID := n.inner.L1().ChainID() + chainIDBig := chainID.ToBig() + signer := ethtypes.LatestSignerForChainID(chainIDBig) + sender, err := signer.Sender(tx) + n.require.NoError(err, "Failed to get transaction sender") + + l.Debug("Found batch transaction", + "txHash", tx.Hash(), + "block", blockNum, + "sender", sender) + + var datas [][]byte + if tx.Type() != ethtypes.BlobTxType { + // Regular transaction - data is in tx.Data() + datas = append(datas, tx.Data()) + } else { + // Blob transaction - need to fetch blobs from beacon + // For now, log that we found a blob tx but skip detailed parsing + // as it requires beacon API access + l.Error("Found blob transaction (skipping blob fetch for now)", + "txHash", tx.Hash(), + "blobHashes", tx.BlobHashes()) + continue + } + + // Parse frames from transaction data + for _, data := range datas { + frames, err := derive.ParseFrames(data) + if err != nil { + l.Warn("Failed to parse frames from transaction", + "txHash", tx.Hash(), + "error", err) + n.require.NoError(err) + } + + l.Debug("Parsed frames from transaction", + "txHash", tx.Hash(), + "frameCount", len(frames)) + + // Process each frame + for _, frame := range frames { + channelID := frame.ID + if !seenChannels[channelID] { + seenChannels[channelID] = true + l.Debug("Found new channel", + "channelID", channelID.String(), + "txHash", tx.Hash(), + "block", blockNum) + channels = append(channels, channelID) + } + channelFrames[channelID] = append(channelFrames[channelID], frame) + l.Debug("Frame added to channel", + "channelID", channelID.String(), + "frameNumber", frame.FrameNumber, + "dataLength", len(frame.Data), + "isLast", frame.IsLast, + "txHash", tx.Hash()) + } + } + } + } + } + + // Reassemble channels and extract batches + for channelID, frames := range channelFrames { + l.Debug("Processing channel", + "channelID", channelID.String(), + "frameCount", len(frames)) + + // Sort frames by frame number + sortedFrames := make([]derive.Frame, len(frames)) + copy(sortedFrames, frames) + for i := 0; i < len(sortedFrames); i++ { + for j := i + 1; j < len(sortedFrames); j++ { + if sortedFrames[i].FrameNumber > sortedFrames[j].FrameNumber { + sortedFrames[i], sortedFrames[j] = sortedFrames[j], sortedFrames[i] + } + } + } + + // Create a channel and add frames to it + // We need an L1 block ref for the channel - use the last processed block as the origin + originBlock := lastBlockRef + ch := derive.NewChannel(channelID, originBlock, false) + + for _, frame := range sortedFrames { + err := ch.AddFrame(frame, originBlock) + if err != nil { + l.Warn("Failed to add frame to channel", + "channelID", channelID.String(), + "frameNumber", frame.FrameNumber, + "error", err) + continue + } + } + + l.Debug("Channel is ready, extracting batches", + "channelID", channelID.String(), + "size", ch.Size()) + + channelReader := ch.Reader() + channelData, err := io.ReadAll(channelReader) + if err != nil { + l.Warn("Failed to read channel data", + "channelID", channelID.String(), + "error", err) + continue + } + + l.Debug("Read channel data", + "channelID", channelID.String(), + "dataLength", len(channelData)) + + spec := rollup.NewChainSpec(rollupCfg) + maxRLPBytes := spec.MaxRLPBytesPerChannel(originBlock.Time) + isFjord := rollupCfg.IsFjord(originBlock.Time) + batchReader, err := derive.BatchReader(bytes.NewReader(channelData), maxRLPBytes, isFjord) + if err != nil { + l.Warn("Failed to create batch reader", + "channelID", channelID.String(), + "error", err) + continue + } + + // Read all batches from the channel + batchCount := 0 + for { + batchData, err := batchReader() + if err == io.EOF { + break + } + if err != nil { + l.Warn("Failed to read batch from channel", + "channelID", channelID.String(), + "batchCount", batchCount, + "error", err) + break + } + + batchCount++ + batchType := batchData.GetBatchType() + + l.Debug("Found batch in channel", + "channelID", channelID.String(), + "batchNumber", batchCount, + "batchType", batchType, + "compressionAlgo", batchData.ComprAlgo) + + // Decode the batch based on type + if batchType == derive.SingularBatchType { + singularBatch, err := derive.GetSingularBatch(batchData) + if err != nil { + l.Warn("Failed to decode singular batch", + "channelID", channelID.String(), + "batchNumber", batchCount, + "error", err) + n.require.NoError(err) + } + + for _, txData := range singularBatch.Transactions { + var tx ethtypes.Transaction + n.require.NoError(tx.UnmarshalBinary(txData)) + + signer := ethtypes.LatestSignerForChainID(rollupCfg.L2ChainID) + fromAddr, err := signer.Sender(&tx) + n.require.NoError(err) + + l2Txs[fromAddr] = append(l2Txs[fromAddr], &tx) + } + + } else if batchType == derive.SpanBatchType { + spanBatch, err := derive.DeriveSpanBatch( + batchData, + rollupCfg.BlockTime, + rollupCfg.Genesis.L2Time, + rollupCfg.L2ChainID, + ) + if err != nil { + l.Warn("Failed to decode span batch", + "channelID", channelID.String(), + "batchNumber", batchCount, + "error", err) + continue + } + + for blockIdx, batchElement := range spanBatch.Batches { + l.Debug("L2 block in span batch", + "channelID", channelID.String(), + "batchNumber", batchCount, + "blockIndex", blockIdx, + "epochNum", batchElement.EpochNum, + "timestamp", batchElement.Timestamp, + "txCount", len(batchElement.Transactions)) + + for _, txData := range batchElement.Transactions { + var tx ethtypes.Transaction + n.require.NoError(tx.UnmarshalBinary(txData)) + + signer := ethtypes.LatestSignerForChainID(rollupCfg.L2ChainID) + fromAddr, err := signer.Sender(&tx) + n.require.NoError(err) + + l2Txs[fromAddr] = append(l2Txs[fromAddr], &tx) + } + } + } else { + l.Warn("Unknown batch type", + "channelID", channelID.String(), + "batchNumber", batchCount, + "batchType", batchType) + } + } + + l.Debug("Finished processing channel", + "channelID", channelID.String(), + "totalBatches", batchCount) + } + + return channels, channelFrames, l2Txs +} diff --git a/op-devstack/dsl/l2_op_rbuilder.go b/op-devstack/dsl/l2_op_rbuilder.go index 78f395c51e1..fef26ccfde5 100644 --- a/op-devstack/dsl/l2_op_rbuilder.go +++ b/op-devstack/dsl/l2_op_rbuilder.go @@ -1,11 +1,9 @@ package dsl import ( - "context" - "time" + opclient "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-devstack/stack" - "github.com/ethereum/go-ethereum/log" ) type OPRBuilderNodeSet []*OPRBuilderNode @@ -21,7 +19,7 @@ func NewOPRBuilderNodeSet(inner []stack.OPRBuilderNode, control stack.ControlPla type OPRBuilderNode struct { commonImpl inner stack.OPRBuilderNode - wsClient *FlashblocksWSClient + wsClient *opclient.WSClient control stack.ControlPlane } @@ -29,7 +27,7 @@ func NewOPRBuilderNode(inner stack.OPRBuilderNode, control stack.ControlPlane) * return &OPRBuilderNode{ commonImpl: commonFromT(inner.T()), inner: inner, - wsClient: NewFlashblocksWSClient(inner.FlashblocksClient()), + wsClient: inner.FlashblocksClient(), control: control, } } @@ -42,8 +40,8 @@ func (c *OPRBuilderNode) Escape() stack.OPRBuilderNode { return c.inner } -func (c *OPRBuilderNode) ListenFor(ctx context.Context, logger log.Logger, duration time.Duration, output chan<- []byte, done chan<- struct{}) error { - return c.wsClient.ListenFor(ctx, logger, duration, output, done) +func (c *OPRBuilderNode) FlashblocksClient() *opclient.WSClient { + return c.wsClient } func (el *OPRBuilderNode) Stop() { @@ -54,7 +52,3 @@ func (el *OPRBuilderNode) Stop() { func (el *OPRBuilderNode) Start() { el.control.OPRBuilderNodeState(el.inner.ID(), stack.Start) } - -func (el *OPRBuilderNode) FlashblocksClient() *FlashblocksWSClient { - return NewFlashblocksWSClient(el.inner.FlashblocksClient()) -} diff --git a/op-devstack/dsl/rollup_boost.go b/op-devstack/dsl/rollup_boost.go index 1638944c1bb..37c98803d38 100644 --- a/op-devstack/dsl/rollup_boost.go +++ b/op-devstack/dsl/rollup_boost.go @@ -1,6 +1,10 @@ package dsl -import "github.com/ethereum-optimism/optimism/op-devstack/stack" +import ( + opclient "github.com/ethereum-optimism/optimism/op-service/client" + + "github.com/ethereum-optimism/optimism/op-devstack/stack" +) type RollupBoostNodesSet []*RollupBoostNode @@ -30,6 +34,6 @@ func NewRollupBoostNode(inner stack.RollupBoostNode, control stack.ControlPlane) } } -func (r *RollupBoostNode) FlashblocksClient() *FlashblocksWSClient { - return NewFlashblocksWSClient(r.inner.FlashblocksClient()) +func (r *RollupBoostNode) FlashblocksClient() *opclient.WSClient { + return r.inner.FlashblocksClient() } diff --git a/op-devstack/dsl/sequencer.go b/op-devstack/dsl/sequencer.go index b68db6ce573..e3b20605496 100644 --- a/op-devstack/dsl/sequencer.go +++ b/op-devstack/dsl/sequencer.go @@ -1,7 +1,12 @@ package dsl import ( + "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" ) type TestSequencer struct { @@ -24,3 +29,10 @@ func (s *TestSequencer) String() string { func (s *TestSequencer) Escape() stack.TestSequencer { return s.inner } + +func (s *TestSequencer) SequenceBlock(t devtest.T, chainID eth.ChainID, parent common.Hash) { + ca := s.Escape().ControlAPI(chainID) + + require.NoError(t, ca.New(t.Ctx(), seqtypes.BuildOpts{Parent: parent})) + require.NoError(t, ca.Next(t.Ctx())) +} diff --git a/op-devstack/shim/op_rbuilder.go b/op-devstack/shim/op_rbuilder.go index 8e931142136..a1fca59679f 100644 --- a/op-devstack/shim/op_rbuilder.go +++ b/op-devstack/shim/op_rbuilder.go @@ -6,21 +6,22 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/stack" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/apis" + opclient "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/sources" ) type OPRBuilderNodeConfig struct { ELNodeConfig - RollupCfg *rollup.Config - ID stack.OPRBuilderNodeID - FlashblocksWsClient stack.FlashblocksWSClient + RollupCfg *rollup.Config + ID stack.OPRBuilderNodeID + FlashblocksClient *opclient.WSClient } type OPRBuilderNode struct { rpcELNode - id stack.OPRBuilderNodeID - engineClient *sources.EngineClient - flashblocksWsClient stack.FlashblocksWSClient + id stack.OPRBuilderNodeID + engineClient *sources.EngineClient + flashblocksClient *opclient.WSClient } var _ stack.OPRBuilderNode = (*OPRBuilderNode)(nil) @@ -33,10 +34,10 @@ func NewOPRBuilderNode(cfg OPRBuilderNodeConfig) *OPRBuilderNode { require.NoError(cfg.T, err) return &OPRBuilderNode{ - rpcELNode: newRpcELNode(cfg.ELNodeConfig), - engineClient: l2EngineClient, - id: cfg.ID, - flashblocksWsClient: cfg.FlashblocksWsClient, + rpcELNode: newRpcELNode(cfg.ELNodeConfig), + engineClient: l2EngineClient, + id: cfg.ID, + flashblocksClient: cfg.FlashblocksClient, } } @@ -48,8 +49,8 @@ func (r *OPRBuilderNode) L2EthClient() apis.L2EthClient { return r.engineClient.L2Client } -func (r *OPRBuilderNode) FlashblocksClient() stack.FlashblocksWSClient { - return r.flashblocksWsClient +func (r *OPRBuilderNode) FlashblocksClient() *opclient.WSClient { + return r.flashblocksClient } func (r *OPRBuilderNode) L2EngineClient() apis.EngineClient { diff --git a/op-devstack/shim/rollup_boost.go b/op-devstack/shim/rollup_boost.go index 6b2024f4f13..be4fa4ef6e7 100644 --- a/op-devstack/shim/rollup_boost.go +++ b/op-devstack/shim/rollup_boost.go @@ -1,18 +1,20 @@ package shim import ( + "github.com/stretchr/testify/require" + "github.com/ethereum-optimism/optimism/op-devstack/stack" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/apis" + opclient "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/sources" - "github.com/stretchr/testify/require" ) type RollupBoostNodeConfig struct { ELNodeConfig - RollupCfg *rollup.Config - ID stack.RollupBoostNodeID - FlashblocksWsClient stack.FlashblocksWSClient + RollupCfg *rollup.Config + ID stack.RollupBoostNodeID + FlashblocksClient *opclient.WSClient } type RollupBoostNode struct { @@ -21,7 +23,7 @@ type RollupBoostNode struct { id stack.RollupBoostNodeID - flashblocksWsClient stack.FlashblocksWSClient + flashblocksClient *opclient.WSClient } var _ stack.RollupBoostNode = (*RollupBoostNode)(nil) @@ -34,10 +36,10 @@ func NewRollupBoostNode(cfg RollupBoostNodeConfig) *RollupBoostNode { require.NoError(cfg.T, err) return &RollupBoostNode{ - rpcELNode: newRpcELNode(cfg.ELNodeConfig), - engineClient: l2EngineClient, - id: cfg.ID, - flashblocksWsClient: cfg.FlashblocksWsClient, + rpcELNode: newRpcELNode(cfg.ELNodeConfig), + engineClient: l2EngineClient, + id: cfg.ID, + flashblocksClient: cfg.FlashblocksClient, } } @@ -49,8 +51,8 @@ func (r *RollupBoostNode) L2EthClient() apis.L2EthClient { return r.engineClient.L2Client } -func (r *RollupBoostNode) FlashblocksClient() stack.FlashblocksWSClient { - return r.flashblocksWsClient +func (r *RollupBoostNode) FlashblocksClient() *opclient.WSClient { + return r.flashblocksClient } func (r *RollupBoostNode) L2EngineClient() apis.EngineClient { diff --git a/op-devstack/stack/op_rbuilder.go b/op-devstack/stack/op_rbuilder.go index 7c655e32237..03926167f40 100644 --- a/op-devstack/stack/op_rbuilder.go +++ b/op-devstack/stack/op_rbuilder.go @@ -4,6 +4,7 @@ import ( "log/slog" "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/eth" ) @@ -72,7 +73,7 @@ type OPRBuilderNode interface { ID() OPRBuilderNodeID L2EthClient() apis.L2EthClient L2EngineClient() apis.EngineClient - FlashblocksClient() FlashblocksWSClient + FlashblocksClient() *client.WSClient ELNode } diff --git a/op-devstack/stack/rollup_boost.go b/op-devstack/stack/rollup_boost.go index 92fdd6e760f..8c44e3d79d3 100644 --- a/op-devstack/stack/rollup_boost.go +++ b/op-devstack/stack/rollup_boost.go @@ -4,6 +4,7 @@ import ( "log/slog" "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/eth" ) @@ -72,7 +73,7 @@ type RollupBoostNode interface { ID() RollupBoostNodeID L2EthClient() apis.L2EthClient L2EngineClient() apis.EngineClient - FlashblocksClient() FlashblocksWSClient + FlashblocksClient() *client.WSClient ELNode } diff --git a/op-devstack/sysext/l2.go b/op-devstack/sysext/l2.go index 05a12fb0bd7..5ec2be41558 100644 --- a/op-devstack/sysext/l2.go +++ b/op-devstack/sysext/l2.go @@ -206,6 +206,13 @@ func (o *Orchestrator) hydrateRollupBoostNodeMaybe(node *descriptors.Node, l2Net flashblocksWsUrl, flashblocksWsHeaders, err := o.findProtocolService(rollupBoostService, WebsocketFlashblocksProtocol) require.NoError(err, "failed to find websocket service for rollup-boost") + wsClient, err := client.DialWS(l2Net.T().Ctx(), client.WSConfig{ + URL: flashblocksWsUrl, + Headers: flashblocksWsHeaders, + Log: l2Net.Logger(), + }) + require.NoError(err, "failed to create rollup-boost websocket client") + rollupBoost := shim.NewRollupBoostNode(shim.RollupBoostNodeConfig{ ID: stack.NewRollupBoostNodeID(rollupBoostService.Name, l2ID.ChainID()), ELNodeConfig: shim.ELNodeConfig{ @@ -213,13 +220,8 @@ func (o *Orchestrator) hydrateRollupBoostNodeMaybe(node *descriptors.Node, l2Net Client: o.rpcClient(l2Net.T(), rollupBoostService, RPCProtocol, "/", opts...), ChainID: l2ID.ChainID(), }, - RollupCfg: l2Net.RollupConfig(), - FlashblocksWsClient: shim.NewFlashblocksWSClient(shim.FlashblocksWSClientConfig{ - CommonConfig: shim.NewCommonConfig(l2Net.T()), - ID: stack.NewFlashblocksWSClientID(rollupBoostService.Name, l2ID.ChainID()), - WsUrl: flashblocksWsUrl, - WsHeaders: flashblocksWsHeaders, - }), + RollupCfg: l2Net.RollupConfig(), + FlashblocksClient: wsClient, }) l2Net.AddRollupBoostNode(rollupBoost) @@ -264,6 +266,13 @@ func (o *Orchestrator) hydrateOPRBuilderMaybe(node *descriptors.Node, l2Net stac flashblocksWsUrl, flashblocksWsHeaders, err := o.findProtocolService(rbuilderService, WebsocketFlashblocksProtocol) require.NoError(err, "failed to find websocket service for rbuilder") + wsClient, err := client.DialWS(l2Net.T().Ctx(), client.WSConfig{ + URL: flashblocksWsUrl, + Headers: flashblocksWsHeaders, + Log: l2Net.Logger(), + }) + require.NoError(err, "failed to create rbuilder websocket client") + flashblocksBuilder := shim.NewOPRBuilderNode(shim.OPRBuilderNodeConfig{ ID: stack.NewOPRBuilderNodeID(rbuilderService.Name, l2ID.ChainID()), ELNodeConfig: shim.ELNodeConfig{ @@ -271,12 +280,7 @@ func (o *Orchestrator) hydrateOPRBuilderMaybe(node *descriptors.Node, l2Net stac Client: o.rpcClient(l2Net.T(), rbuilderService, RPCProtocol, "/", opts...), ChainID: l2ID.ChainID(), }, - FlashblocksWsClient: shim.NewFlashblocksWSClient(shim.FlashblocksWSClientConfig{ - CommonConfig: shim.NewCommonConfig(l2Net.T()), - ID: stack.NewFlashblocksWSClientID(rbuilderService.Name, l2ID.ChainID()), - WsUrl: flashblocksWsUrl, - WsHeaders: flashblocksWsHeaders, - }), + FlashblocksClient: wsClient, }) l2Net.AddOPRBuilderNode(flashblocksBuilder) diff --git a/op-devstack/sysgo/l2_batcher.go b/op-devstack/sysgo/l2_batcher.go index 643858c18d1..4001ec30582 100644 --- a/op-devstack/sysgo/l2_batcher.go +++ b/op-devstack/sysgo/l2_batcher.go @@ -89,7 +89,7 @@ func WithBatcher(batcherID stack.L2BatcherID, l1ELID stack.L1ELNodeID, l2CLID st L1EthRpc: l1EL.UserRPC(), L2EthRpc: []string{l2EL.UserRPC()}, RollupRpc: []string{l2CL.UserRPC()}, - MaxPendingTransactions: 1, + MaxPendingTransactions: 7, MaxChannelDuration: 1, MaxL1TxSize: 120_000, TestUseMaxTxSizeForBlobs: false, @@ -115,8 +115,13 @@ func WithBatcher(batcherID stack.L2BatcherID, l1ELID stack.L1ELNodeID, l2CLID st opt(batcherID, batcherCLIConfig) } + batcherContext, cancelBatcherCtx := context.WithCancel(p.Ctx()) + var closeAppFn context.CancelCauseFunc = func(cause error) { + p.Errorf("closeAppFn called, batcher hit a critical error: %v", cause) + cancelBatcherCtx() + } batcher, err := bss.BatcherServiceFromCLIConfig( - p.Ctx(), "0.0.1", batcherCLIConfig, + batcherContext, closeAppFn, "0.0.1", batcherCLIConfig, logger) require.NoError(err) require.NoError(batcher.Start(p.Ctx())) diff --git a/op-devstack/sysgo/l2_cl_opnode.go b/op-devstack/sysgo/l2_cl_opnode.go index ddc037f3a7d..2831a4666e2 100644 --- a/op-devstack/sysgo/l2_cl_opnode.go +++ b/op-devstack/sysgo/l2_cl_opnode.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup/interop" nodeSync "github.com/ethereum-optimism/optimism/op-node/rollup/sync" "github.com/ethereum-optimism/optimism/op-service/client" + "github.com/ethereum-optimism/optimism/op-service/clock" "github.com/ethereum-optimism/optimism/op-service/eth" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" "github.com/ethereum-optimism/optimism/op-service/oppprof" @@ -51,6 +52,7 @@ type OpNode struct { el *stack.L2ELNodeID // Optional: nil when using SyncTester userProxy *tcpproxy.Proxy interopProxy *tcpproxy.Proxy + clock clock.Clock } var _ L2CLNode = (*OpNode)(nil) @@ -130,7 +132,7 @@ func (n *OpNode) Start() { n.interopEndpoint = "ws://" + n.interopProxy.Addr() } n.logger.Info("Starting op-node") - opNode, err := opnode.NewOpnode(n.logger, n.cfg, func(err error) { + opNode, err := opnode.NewOpnode(n.logger, n.cfg, n.clock, func(err error) { n.p.Require().NoError(err, "op-node critical error") }) n.p.Require().NoError(err, "op-node failed to start") @@ -339,6 +341,10 @@ func WithOpNode(l2CLID stack.L2CLNodeID, l1CLID stack.L1CLNodeID, l1ELID stack.L p: p, } + if orch.timeTravelClock != nil { + l2CLNode.clock = orch.timeTravelClock + } + // Set the EL field to link to the L2EL node l2CLNode.el = &l2ELID require.True(orch.l2CLs.SetIfMissing(l2CLID, l2CLNode), fmt.Sprintf("must not already exist: %s", l2CLID)) diff --git a/op-devstack/sysgo/op_rbuilder.go b/op-devstack/sysgo/op_rbuilder.go index 4bab3d0002e..fc92a5aba2d 100644 --- a/op-devstack/sysgo/op_rbuilder.go +++ b/op-devstack/sysgo/op_rbuilder.go @@ -312,6 +312,13 @@ func (b *OPRBuilderNode) hydrate(system stack.ExtensibleSystem) { system.T().Require().NoError(err) system.T().Cleanup(elRPC.Close) + // Create a shared websocket client for flashblocks traffic over the proxy. + wsClient, err := client.DialWS(system.T().Ctx(), client.WSConfig{ + URL: b.wsProxyURL, + Log: system.Logger(), + }) + system.T().Require().NoError(err) + node := shim.NewOPRBuilderNode(shim.OPRBuilderNodeConfig{ ID: b.id, ELNodeConfig: shim.ELNodeConfig{ @@ -319,13 +326,8 @@ func (b *OPRBuilderNode) hydrate(system stack.ExtensibleSystem) { Client: elRPC, ChainID: b.id.ChainID(), }, - RollupCfg: b.rollupCfg, - FlashblocksWsClient: shim.NewFlashblocksWSClient(shim.FlashblocksWSClientConfig{ - CommonConfig: shim.NewCommonConfig(system.T()), - ID: stack.NewFlashblocksWSClientID(b.id.Key(), b.id.ChainID()), - WsUrl: b.wsProxyURL, - WsHeaders: nil, - }), + RollupCfg: b.rollupCfg, + FlashblocksClient: wsClient, }) system.L2Network(stack.L2NetworkID(b.id.ChainID())).(stack.ExtensibleL2Network).AddOPRBuilderNode(node) } diff --git a/op-devstack/sysgo/rollup_boost.go b/op-devstack/sysgo/rollup_boost.go index 90258e40a21..ece089e00e4 100644 --- a/op-devstack/sysgo/rollup_boost.go +++ b/op-devstack/sysgo/rollup_boost.go @@ -21,7 +21,7 @@ import ( // RollupBoostNode is a lightweight sysgo-managed process wrapper around a rollup-boost // WebSocket stream source. It exposes a stable proxied ws URL and hydrates the L2 -// network with a FlashblocksWSClient shim that points at it. +// network with a shared WSClient that points at it. type RollupBoostNode struct { mu sync.Mutex @@ -51,6 +51,14 @@ func (r *RollupBoostNode) hydrate(system stack.ExtensibleSystem) { system.T().Require().NoError(err) system.T().Cleanup(elRPC.Close) + // Create a shared websocket client for flashblocks traffic over the proxy. + wsClient, err := client.DialWS(system.T().Ctx(), client.WSConfig{ + URL: r.wsProxyURL, + Headers: r.header, + Log: system.Logger(), + }) + system.T().Require().NoError(err) + node := shim.NewRollupBoostNode(shim.RollupBoostNodeConfig{ ID: r.id, ELNodeConfig: shim.ELNodeConfig{ @@ -58,13 +66,8 @@ func (r *RollupBoostNode) hydrate(system stack.ExtensibleSystem) { Client: elRPC, ChainID: r.id.ChainID(), }, - RollupCfg: system.L2Network(stack.L2NetworkID(r.id.ChainID())).RollupConfig(), - FlashblocksWsClient: shim.NewFlashblocksWSClient(shim.FlashblocksWSClientConfig{ - CommonConfig: shim.NewCommonConfig(system.T()), - ID: stack.NewFlashblocksWSClientID(r.id.Key(), r.id.ChainID()), - WsUrl: r.wsProxyURL, - WsHeaders: r.header, - }), + RollupCfg: system.L2Network(stack.L2NetworkID(r.id.ChainID())).RollupConfig(), + FlashblocksClient: wsClient, }) system.L2Network(stack.L2NetworkID(r.id.ChainID())).(stack.ExtensibleL2Network).AddRollupBoostNode(node) } @@ -149,7 +152,7 @@ func (r *RollupBoostNode) Stop() { } // WithRollupBoost starts a rollup-boost process using the provided options -// and registers a FlashblocksWSClient on the target L2 chain. +// and registers a WSClient on the target L2 chain. // l2ELID is required to link the proxy to the L2 EL it serves. func WithRollupBoost(id stack.RollupBoostNodeID, l2ELID stack.L2ELNodeID, opts ...RollupBoostOption) stack.Option[*Orchestrator] { return stack.AfterDeploy(func(orch *Orchestrator) { diff --git a/op-devstack/sysgo/system.go b/op-devstack/sysgo/system.go index ef5e17a8a91..a925d4703e2 100644 --- a/op-devstack/sysgo/system.go +++ b/op-devstack/sysgo/system.go @@ -623,7 +623,7 @@ func singleChainSystemWithFlashblocksOpts(ids *SingleChainSystemWithFlashblocksI opt := stack.Combine[*Orchestrator]() // Precompute deterministic P2P identity and peering between sequencer EL and op-rbuilder EL. seqID := NewELNodeIdentity("127.0.0.1", 0) - builderID := NewELNodeIdentity("127.0.0.1", 30303) // use default reth p2p port + builderID := NewELNodeIdentity("127.0.0.1", 0) // allocate dynamic port for builder var missingEnv []string if os.Getenv("OP_RBUILDER_EXEC_PATH") == "" { diff --git a/op-devstack/sysgo/util.go b/op-devstack/sysgo/util.go index 68fb9d0eedd..893d9f17051 100644 --- a/op-devstack/sysgo/util.go +++ b/op-devstack/sysgo/util.go @@ -11,8 +11,8 @@ import ( "sync" "time" - "github.com/coder/websocket" "github.com/ethereum-optimism/optimism/op-devstack/devtest" + opclient "github.com/ethereum-optimism/optimism/op-service/client" "github.com/stretchr/testify/assert" ) @@ -79,14 +79,8 @@ func waitWSReady(p devtest.P, rawURL string, timeout time.Duration) { waitWSMsg := fmt.Sprintf("WebSocket endpoint %s not ready within %v", rawURL, timeout) p.Require().EventuallyWithT(func(c *assert.CollectT) { ctx, cancel := context.WithTimeout(context.Background(), 750*time.Millisecond) - conn, resp, err := websocket.Dial(ctx, rawURL, nil) + err := opclient.ProbeWS(ctx, rawURL) cancel() - if resp != nil && resp.Body != nil { - _ = resp.Body.Close() - } - if conn != nil { - _ = conn.Close(websocket.StatusNormalClosure, "") - } assert.NoError(c, err, "WebSocket handshake to %s should succeed", rawURL) }, timeout, 100*time.Millisecond, waitWSMsg) } diff --git a/op-dispute-mon/mon/extract/output_agreement_enricher_test.go b/op-dispute-mon/mon/extract/output_agreement_enricher_test.go index d0f665383c5..420f15d5754 100644 --- a/op-dispute-mon/mon/extract/output_agreement_enricher_test.go +++ b/op-dispute-mon/mon/extract/output_agreement_enricher_test.go @@ -40,7 +40,7 @@ func TestOutputAgreementEnricher(t *testing.T) { }) t.Run("SkipNonOutputRootGameTypes", func(t *testing.T) { - gameTypes := []uint32{4, 5, 7, 8, 10, 49812} + gameTypes := []uint32{4, 5, 7, 9, 11, 49812} for _, gameType := range gameTypes { gameType := gameType t.Run(fmt.Sprintf("GameType_%d", gameType), func(t *testing.T) { diff --git a/op-dispute-mon/mon/extract/super_agreement_enricher_test.go b/op-dispute-mon/mon/extract/super_agreement_enricher_test.go index 5ad80217a89..7fa06027862 100644 --- a/op-dispute-mon/mon/extract/super_agreement_enricher_test.go +++ b/op-dispute-mon/mon/extract/super_agreement_enricher_test.go @@ -61,7 +61,7 @@ func TestDetector_CheckSuperRootAgreement(t *testing.T) { }) t.Run("FetchAllNonOutputRootGameTypes", func(t *testing.T) { - gameTypes := []uint32{4, 5, 7, 8, 10, 49812} // Treat unknown game types as using super roots + gameTypes := []uint32{4, 5, 7, 9, 11, 49812} // Treat unknown game types as using super roots for _, gameType := range gameTypes { gameType := gameType t.Run(fmt.Sprintf("GameType_%d", gameType), func(t *testing.T) { diff --git a/op-dispute-mon/mon/types/types.go b/op-dispute-mon/mon/types/types.go index e05d2fd509b..7b1874516c8 100644 --- a/op-dispute-mon/mon/types/types.go +++ b/op-dispute-mon/mon/types/types.go @@ -13,7 +13,25 @@ import ( // outputRootGameTypes lists the set of legacy game types that use output roots // It is assumed that all other game types use super roots -var outputRootGameTypes = []uint32{0, 1, 2, 3, 6, 254, 255, 1337} +var outputRootGameTypes = []types.GameType{ + types.CannonGameType, + types.PermissionedGameType, + types.AsteriscGameType, + types.AsteriscKonaGameType, + types.OPSuccinctGameType, + types.CannonKonaGameType, + types.OptimisticZKGameType, + types.FastGameType, + types.AlphabetGameType, + types.KailuaGameType, +} + +var superRootGameTypes = []types.GameType{ + types.SuperCannonGameType, + types.SuperPermissionedGameType, + types.SuperAsteriscKonaGameType, + types.SuperCannonKonaGameType, +} // EnrichedClaim extends the faultTypes.Claim with additional context. type EnrichedClaim struct { @@ -87,7 +105,7 @@ type EnrichedGameData struct { // UsesOutputRoots returns true if the game type is one of the known types that use output roots as proposals. func (g EnrichedGameData) UsesOutputRoots() bool { - return slices.Contains(outputRootGameTypes, g.GameType) + return slices.Contains(outputRootGameTypes, types.GameType(g.GameType)) } // HasMixedAvailability returns true if some rollup endpoints returned "not found" while others succeeded diff --git a/op-dispute-mon/mon/types/types_test.go b/op-dispute-mon/mon/types/types_test.go index 99df937ea8e..1addef9c9a0 100644 --- a/op-dispute-mon/mon/types/types_test.go +++ b/op-dispute-mon/mon/types/types_test.go @@ -13,7 +13,7 @@ func TestEnrichedGameData_UsesOutputRoots(t *testing.T) { gameType := gameType t.Run(fmt.Sprintf("GameType-%v", gameType), func(t *testing.T) { data := EnrichedGameData{ - GameMetadata: types.GameMetadata{GameType: gameType}, + GameMetadata: types.GameMetadata{GameType: uint32(gameType)}, } require.True(t, data.UsesOutputRoots()) }) @@ -177,3 +177,22 @@ func TestEnrichedGameData_HasMixedSafety(t *testing.T) { }) } } + +func TestAllSupportedGameTypesAreOutputOrSuperRootType(t *testing.T) { + for _, gameType := range types.SupportedGameTypes { + t.Run(gameType.String(), func(t *testing.T) { + data := EnrichedGameData{ + GameMetadata: types.GameMetadata{ + GameType: uint32(gameType), + }, + } + if data.UsesOutputRoots() { + require.Contains(t, outputRootGameTypes, gameType) + require.NotContains(t, superRootGameTypes, gameType) + } else { + require.Contains(t, superRootGameTypes, gameType) + require.NotContains(t, outputRootGameTypes, gameType) + } + }) + } +} diff --git a/op-e2e/actions/proofs/jovian_minbasefee_test.go b/op-e2e/actions/proofs/jovian_minbasefee_test.go index b7e5c7c5a77..b046f3d581e 100644 --- a/op-e2e/actions/proofs/jovian_minbasefee_test.go +++ b/op-e2e/actions/proofs/jovian_minbasefee_test.go @@ -66,7 +66,7 @@ func Test_ProgramAction_JovianMinBaseFee(gt *testing.T) { require.True(t, isJovian, "GPO should report that Jovian is active") activationBlock := env.Engine.L2Chain().GetBlockByHash(env.Sequencer.L2Unsafe().Hash) - require.Equal(t, eip1559.EncodeMinBaseFeeExtraData(250, 6, 0), activationBlock.Extra(), "activation block should have Jovian extraData") + require.Equal(t, eip1559.EncodeJovianExtraData(250, 6, 0), activationBlock.Extra(), "activation block should have Jovian extraData") // Set the minimum base fee setMinBaseFeeViaSystemConfig(t, env, minBaseFee) @@ -87,7 +87,7 @@ func Test_ProgramAction_JovianMinBaseFee(gt *testing.T) { // Block after the SystemConfig change env.Sequencer.ActL2EmptyBlock(t) blockAfterSystemConfigChange := env.Engine.L2Chain().GetBlockByHash(env.Sequencer.L2Unsafe().Hash) - expectedJovianExtraDataWithMinFee := eip1559.EncodeMinBaseFeeExtraData(250, 6, minBaseFee) + expectedJovianExtraDataWithMinFee := eip1559.EncodeJovianExtraData(250, 6, minBaseFee) require.Equal(t, expectedJovianExtraDataWithMinFee, blockAfterSystemConfigChange.Extra(), "block should have updated Jovian extraData with min base fee") // Verify base fee is clamped diff --git a/op-e2e/actions/proofs/precompile_test.go b/op-e2e/actions/proofs/precompile_test.go index d3f14a9b526..04a4d0987dc 100644 --- a/op-e2e/actions/proofs/precompile_test.go +++ b/op-e2e/actions/proofs/precompile_test.go @@ -214,12 +214,15 @@ func runPrecompileTest(gt *testing.T, testCfg *helpers.TestCfg[PrecompileTestFix require.Equal(t, receipt.Logs[0].Address, invokerContract) require.Len(t, receipt.Logs[0].Topics, 2) precompileAddress := receipt.Logs[0].Topics[1] - var out struct{ Result []byte } + var out struct { + Result []byte + DelegateCallResult []byte + } err = abi.UnpackIntoInterface(&out, "PrecompileInvoked", receipt.Logs[0].Data) - precompileResult := out.Result require.NoError(t, err) require.Equal(t, common.HexToAddress(precompileAddress.Hex()), testCase.Address) - require.Equal(t, expectedResult, precompileResult) + require.Equal(t, expectedResult, out.Result) + require.Equal(t, expectedResult, out.DelegateCallResult) // instruct the batcher to submit the Invoker precompile tx to l1, and include the transaction. env.Batcher.ActSubmitAll(t) diff --git a/op-e2e/e2eutils/contracts/bindings/invoker/invoker.go b/op-e2e/e2eutils/contracts/bindings/invoker/invoker.go index f98cc82da62..3462c03289c 100644 --- a/op-e2e/e2eutils/contracts/bindings/invoker/invoker.go +++ b/op-e2e/e2eutils/contracts/bindings/invoker/invoker.go @@ -31,8 +31,8 @@ var ( // InvokerMetaData contains all meta data concerning the Invoker contract. var InvokerMetaData = &bind.MetaData{ - ABI: "[{\"type\":\"function\",\"name\":\"invokePrecompile\",\"inputs\":[{\"name\":\"_precompile\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_input\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"PrecompileInvoked\",\"inputs\":[{\"name\":\"precompile\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"result\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"PrecompileCallFailed\",\"inputs\":[]}]", - Bin: "0x6080604052348015600e575f5ffd5b506102f58061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063051f3bdf1461002d575b5f5ffd5b61004061003b366004610164565b610042565b005b5f5f8373ffffffffffffffffffffffffffffffffffffffff1683604051610069919061027f565b5f604051808303815f865af19150503d805f81146100a2576040519150601f19603f3d011682016040523d82523d5f602084013e6100a7565b606091505b5091509150816100e3576040517ffd23ff6400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8373ffffffffffffffffffffffffffffffffffffffff167fde9caeb04cbecadc4b3b08dd3b026ff047428c7c681a368b2b48d1097bf465a8826040516101299190610295565b60405180910390a250505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f5f60408385031215610175575f5ffd5b823573ffffffffffffffffffffffffffffffffffffffff81168114610198575f5ffd5b9150602083013567ffffffffffffffff8111156101b3575f5ffd5b8301601f810185136101c3575f5ffd5b803567ffffffffffffffff8111156101dd576101dd610137565b6040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8501160116810181811067ffffffffffffffff8211171561024957610249610137565b604052818152828201602001871015610260575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f6040828501015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168401019150509291505056fea164736f6c634300081c000a", + ABI: "[{\"type\":\"function\",\"name\":\"invokePrecompile\",\"inputs\":[{\"name\":\"_precompile\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_input\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"PrecompileInvoked\",\"inputs\":[{\"name\":\"precompile\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"result\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"},{\"name\":\"delegateCallResult\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"PrecompileCallFailed\",\"inputs\":[]}]", + Bin: "0x6080604052348015600e575f5ffd5b506103bf8061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063051f3bdf1461002d575b5f5ffd5b61004061003b366004610208565b610042565b005b5f5f8373ffffffffffffffffffffffffffffffffffffffff16836040516100699190610323565b5f604051808303815f865af19150503d805f81146100a2576040519150601f19603f3d011682016040523d82523d5f602084013e6100a7565b606091505b5091509150816100e3576040517ffd23ff6400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60608473ffffffffffffffffffffffffffffffffffffffff168460405161010a9190610323565b5f60405180830381855af49150503d805f8114610142576040519150601f19603f3d011682016040523d82523d5f602084013e610147565b606091505b50909350905082610184576040517ffd23ff6400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff167fc331673664ab9732fd2c0b1a4aa0cd948da43af82aca20906b4c306c7228e07983836040516101cc929190610385565b60405180910390a25050505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f5f60408385031215610219575f5ffd5b823573ffffffffffffffffffffffffffffffffffffffff8116811461023c575f5ffd5b9150602083013567ffffffffffffffff811115610257575f5ffd5b8301601f81018513610267575f5ffd5b803567ffffffffffffffff811115610281576102816101db565b6040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8501160116810181811067ffffffffffffffff821117156102ed576102ed6101db565b604052818152828201602001871015610304575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f82518060208501845e5f920191825250919050565b5f81518084528060208401602086015e5f6020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b604081525f6103976040830185610339565b82810360208401526103a98185610339565b9594505050505056fea164736f6c634300081c000a", } // InvokerABI is the input ABI used to generate the binding from. @@ -292,14 +292,15 @@ func (it *InvokerPrecompileInvokedIterator) Close() error { // InvokerPrecompileInvoked represents a PrecompileInvoked event raised by the Invoker contract. type InvokerPrecompileInvoked struct { - Precompile common.Address - Result []byte - Raw types.Log // Blockchain specific contextual infos + Precompile common.Address + Result []byte + DelegateCallResult []byte + Raw types.Log // Blockchain specific contextual infos } -// FilterPrecompileInvoked is a free log retrieval operation binding the contract event 0xde9caeb04cbecadc4b3b08dd3b026ff047428c7c681a368b2b48d1097bf465a8. +// FilterPrecompileInvoked is a free log retrieval operation binding the contract event 0xc331673664ab9732fd2c0b1a4aa0cd948da43af82aca20906b4c306c7228e079. // -// Solidity: event PrecompileInvoked(address indexed precompile, bytes result) +// Solidity: event PrecompileInvoked(address indexed precompile, bytes result, bytes delegateCallResult) func (_Invoker *InvokerFilterer) FilterPrecompileInvoked(opts *bind.FilterOpts, precompile []common.Address) (*InvokerPrecompileInvokedIterator, error) { var precompileRule []interface{} @@ -314,9 +315,9 @@ func (_Invoker *InvokerFilterer) FilterPrecompileInvoked(opts *bind.FilterOpts, return &InvokerPrecompileInvokedIterator{contract: _Invoker.contract, event: "PrecompileInvoked", logs: logs, sub: sub}, nil } -// WatchPrecompileInvoked is a free log subscription operation binding the contract event 0xde9caeb04cbecadc4b3b08dd3b026ff047428c7c681a368b2b48d1097bf465a8. +// WatchPrecompileInvoked is a free log subscription operation binding the contract event 0xc331673664ab9732fd2c0b1a4aa0cd948da43af82aca20906b4c306c7228e079. // -// Solidity: event PrecompileInvoked(address indexed precompile, bytes result) +// Solidity: event PrecompileInvoked(address indexed precompile, bytes result, bytes delegateCallResult) func (_Invoker *InvokerFilterer) WatchPrecompileInvoked(opts *bind.WatchOpts, sink chan<- *InvokerPrecompileInvoked, precompile []common.Address) (event.Subscription, error) { var precompileRule []interface{} @@ -356,9 +357,9 @@ func (_Invoker *InvokerFilterer) WatchPrecompileInvoked(opts *bind.WatchOpts, si }), nil } -// ParsePrecompileInvoked is a log parse operation binding the contract event 0xde9caeb04cbecadc4b3b08dd3b026ff047428c7c681a368b2b48d1097bf465a8. +// ParsePrecompileInvoked is a log parse operation binding the contract event 0xc331673664ab9732fd2c0b1a4aa0cd948da43af82aca20906b4c306c7228e079. // -// Solidity: event PrecompileInvoked(address indexed precompile, bytes result) +// Solidity: event PrecompileInvoked(address indexed precompile, bytes result, bytes delegateCallResult) func (_Invoker *InvokerFilterer) ParsePrecompileInvoked(log types.Log) (*InvokerPrecompileInvoked, error) { event := new(InvokerPrecompileInvoked) if err := _Invoker.contract.UnpackLog(event, "PrecompileInvoked", log); err != nil { diff --git a/op-e2e/e2eutils/contracts/build/DelegateCallProxy.sol/DelegateCallProxy.json b/op-e2e/e2eutils/contracts/build/DelegateCallProxy.sol/DelegateCallProxy.json index b46c051880b..e3f9886fc21 100644 --- a/op-e2e/e2eutils/contracts/build/DelegateCallProxy.sol/DelegateCallProxy.json +++ b/op-e2e/e2eutils/contracts/build/DelegateCallProxy.sol/DelegateCallProxy.json @@ -1 +1 @@ -{"abi":[{"type":"constructor","inputs":[{"name":"_owner","type":"address","internalType":"address"}],"stateMutability":"nonpayable"},{"type":"function","name":"executeDelegateCall","inputs":[{"name":"_target","type":"address","internalType":"address"},{"name":"_data","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"","type":"bytes","internalType":"bytes"}],"stateMutability":"nonpayable"},{"type":"function","name":"owner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"transferOwnership","inputs":[{"name":"_proxyAdmin","type":"address","internalType":"address"},{"name":"_newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"error","name":"NotOwner","inputs":[]}],"bytecode":{"object":"0x60a0604052348015600e575f5ffd5b506040516105ae3803806105ae833981016040819052602b91603b565b6001600160a01b03166080526066565b5f60208284031215604a575f5ffd5b81516001600160a01b0381168114605f575f5ffd5b9392505050565b60805161052b6100835f395f8181605d015260e1015261052b5ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80636d435421146100435780638da5cb5b14610058578063b68df16d146100a9575b5f5ffd5b610056610051366004610356565b6100c9565b005b61007f7f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6100bc6100b73660046103b4565b6102ae565b6040516100a091906104b5565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610138576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405173ffffffffffffffffffffffffffffffffffffffff821660248201525f90604401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167ff2fde38b00000000000000000000000000000000000000000000000000000000179052519091505f9073ffffffffffffffffffffffffffffffffffffffff8516906101fb908490610508565b5f604051808303815f865af19150503d805f8114610234576040519150601f19603f3d011682016040523d82523d5f602084013e610239565b606091505b50509050806102a8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f5472616e736665724f776e6572736869703a206661696c656400000000000000604482015260640160405180910390fd5b50505050565b60605f5f8473ffffffffffffffffffffffffffffffffffffffff16846040516102d79190610508565b5f60405180830381855af49150503d805f811461030f576040519150601f19603f3d011682016040523d82523d5f602084013e610314565b606091505b50915091508161032657805160208201fd5b949350505050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610351575f5ffd5b919050565b5f5f60408385031215610367575f5ffd5b6103708361032e565b915061037e6020840161032e565b90509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f5f604083850312156103c5575f5ffd5b6103ce8361032e565b9150602083013567ffffffffffffffff8111156103e9575f5ffd5b8301601f810185136103f9575f5ffd5b803567ffffffffffffffff81111561041357610413610387565b6040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8501160116810181811067ffffffffffffffff8211171561047f5761047f610387565b604052818152828201602001871015610496575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b602081525f82518060208401528060208501604085015e5f6040828501015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011684010191505092915050565b5f82518060208501845e5f92019182525091905056fea164736f6c634300081b000a","sourceMap":"57:904:0:-:0;;;149:59;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;187:14:0;;;57:904;;14:290:1;84:6;137:2;125:9;116:7;112:23;108:32;105:52;;;153:1;150;143:12;105:52;179:16;;-1:-1:-1;;;;;224:31:1;;214:42;;204:70;;270:1;267;260:12;204:70;293:5;14:290;-1:-1:-1;;;14:290:1:o;:::-;57:904:0;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80636d435421146100435780638da5cb5b14610058578063b68df16d146100a9575b5f5ffd5b610056610051366004610356565b6100c9565b005b61007f7f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6100bc6100b73660046103b4565b6102ae565b6040516100a091906104b5565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610138576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405173ffffffffffffffffffffffffffffffffffffffff821660248201525f90604401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167ff2fde38b00000000000000000000000000000000000000000000000000000000179052519091505f9073ffffffffffffffffffffffffffffffffffffffff8516906101fb908490610508565b5f604051808303815f865af19150503d805f8114610234576040519150601f19603f3d011682016040523d82523d5f602084013e610239565b606091505b50509050806102a8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f5472616e736665724f776e6572736869703a206661696c656400000000000000604482015260640160405180910390fd5b50505050565b60605f5f8473ffffffffffffffffffffffffffffffffffffffff16846040516102d79190610508565b5f60405180830381855af49150503d805f811461030f576040519150601f19603f3d011682016040523d82523d5f602084013e610314565b606091505b50915091508161032657805160208201fd5b949350505050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610351575f5ffd5b919050565b5f5f60408385031215610367575f5ffd5b6103708361032e565b915061037e6020840161032e565b90509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f5f604083850312156103c5575f5ffd5b6103ce8361032e565b9150602083013567ffffffffffffffff8111156103e9575f5ffd5b8301601f810185136103f9575f5ffd5b803567ffffffffffffffff81111561041357610413610387565b6040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8501160116810181811067ffffffffffffffff8211171561047f5761047f610387565b604052818152828201602001871015610496575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b602081525f82518060208401528060208501604085015e5f6040828501015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011684010191505092915050565b5f82518060208501845e5f92019182525091905056fea164736f6c634300081b000a","sourceMap":"57:904:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;549:410;;;;;;:::i;:::-;;:::i;:::-;;90:30;;;;;;;;656:42:1;644:55;;;626:74;;614:2;599:18;90:30:0;;;;;;;;214:329;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;549:410::-;639:10;:19;653:5;639:19;;635:67;;681:10;;;;;;;;;;;;;;635:67;782:64;;656:42:1;644:55;;782:64:0;;;626:74:1;762:17:0;;599:18:1;;782:64:0;;;;;;;;;;;;;;;;;;;;;;;;875:22;782:64;;-1:-1:-1;;;875:16:0;;;;:22;;782:64;;875:22;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;856:41;;;915:7;907:45;;;;;;;3029:2:1;907:45:0;;;3011:21:1;3068:2;3048:18;;;3041:30;3107:27;3087:18;;;3080:55;3152:18;;907:45:0;;;;;;;;625:334;;549:410;;:::o;214:329::-;298:12;323;337:19;360:7;:20;;381:5;360:27;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;322:65;;;;402:7;397:117;;482:6;476:13;471:2;463:6;459:15;452:38;397:117;530:6;214:329;-1:-1:-1;;;;214:329:0:o;14:196:1:-;82:20;;142:42;131:54;;121:65;;111:93;;200:1;197;190:12;111:93;14:196;;;:::o;215:260::-;283:6;291;344:2;332:9;323:7;319:23;315:32;312:52;;;360:1;357;350:12;312:52;383:29;402:9;383:29;:::i;:::-;373:39;;431:38;465:2;454:9;450:18;431:38;:::i;:::-;421:48;;215:260;;;;;:::o;711:184::-;763:77;760:1;753:88;860:4;857:1;850:15;884:4;881:1;874:15;900:1136;977:6;985;1038:2;1026:9;1017:7;1013:23;1009:32;1006:52;;;1054:1;1051;1044:12;1006:52;1077:29;1096:9;1077:29;:::i;:::-;1067:39;;1157:2;1146:9;1142:18;1129:32;1184:18;1176:6;1173:30;1170:50;;;1216:1;1213;1206:12;1170:50;1239:22;;1292:4;1284:13;;1280:27;-1:-1:-1;1270:55:1;;1321:1;1318;1311:12;1270:55;1361:2;1348:16;1387:18;1379:6;1376:30;1373:56;;;1409:18;;:::i;:::-;1458:2;1452:9;1605:66;1600:2;1531:66;1524:4;1516:6;1512:17;1508:90;1504:99;1500:172;1492:6;1488:185;1739:6;1727:10;1724:22;1703:18;1691:10;1688:34;1685:62;1682:88;;;1750:18;;:::i;:::-;1786:2;1779:22;1810;;;1851:15;;;1868:2;1847:24;1844:37;-1:-1:-1;1841:57:1;;;1894:1;1891;1884:12;1841:57;1950:6;1945:2;1941;1937:11;1932:2;1924:6;1920:15;1907:50;2003:1;1998:2;1989:6;1981;1977:19;1973:28;1966:39;2024:6;2014:16;;;;;900:1136;;;;;:::o;2041:475::-;2188:2;2177:9;2170:21;2151:4;2220:6;2214:13;2263:6;2258:2;2247:9;2243:18;2236:34;2322:6;2317:2;2309:6;2305:15;2300:2;2289:9;2285:18;2279:50;2378:1;2373:2;2364:6;2353:9;2349:22;2345:31;2338:42;2507:2;2437:66;2432:2;2424:6;2420:15;2416:88;2405:9;2401:104;2397:113;2389:121;;;2041:475;;;;:::o;2521:301::-;2650:3;2688:6;2682:13;2734:6;2727:4;2719:6;2715:17;2710:3;2704:37;2796:1;2760:16;;2785:13;;;-1:-1:-1;2760:16:1;2521:301;-1:-1:-1;2521:301:1:o","linkReferences":{},"immutableReferences":{"3":[{"start":93,"length":32},{"start":225,"length":32}]}},"methodIdentifiers":{"executeDelegateCall(address,bytes)":"b68df16d","owner()":"8da5cb5b","transferOwnership(address,address)":"6d435421"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.27+commit.40a35a09\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"NotOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_target\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"executeDelegateCall\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_proxyAdmin\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/DelegateCallProxy.sol\":\"DelegateCallProxy\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[]},\"sources\":{\"src/DelegateCallProxy.sol\":{\"keccak256\":\"0xc59415cd917768bf92d0405afd43fc36e2c80330ae766c9d5cfb34b5fb431e8a\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://6fe4a3a47fb430ccaa416c56d40cd63aa8be17cdb116ab1b06a69dc252fbe316\",\"dweb:/ipfs/QmNVhngA2AKKmF7E9iMmqsPpUQ57QEvbXKoLdY873TBHLQ\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.27+commit.40a35a09"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"type":"error","name":"NotOwner"},{"inputs":[{"internalType":"address","name":"_target","type":"address"},{"internalType":"bytes","name":"_data","type":"bytes"}],"stateMutability":"nonpayable","type":"function","name":"executeDelegateCall","outputs":[{"internalType":"bytes","name":"","type":"bytes"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}]},{"inputs":[{"internalType":"address","name":"_proxyAdmin","type":"address"},{"internalType":"address","name":"_newOwner","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"transferOwnership"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"src/DelegateCallProxy.sol":"DelegateCallProxy"},"evmVersion":"cancun","libraries":{}},"sources":{"src/DelegateCallProxy.sol":{"keccak256":"0xc59415cd917768bf92d0405afd43fc36e2c80330ae766c9d5cfb34b5fb431e8a","urls":["bzz-raw://6fe4a3a47fb430ccaa416c56d40cd63aa8be17cdb116ab1b06a69dc252fbe316","dweb:/ipfs/QmNVhngA2AKKmF7E9iMmqsPpUQ57QEvbXKoLdY873TBHLQ"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"userdoc":{"version":1,"kind":"user"},"devdoc":{"version":1,"kind":"dev"},"ast":{"absolutePath":"src/DelegateCallProxy.sol","id":80,"exportedSymbols":{"DelegateCallProxy":[79]},"nodeType":"SourceUnit","src":"32:930:0","nodes":[{"id":1,"nodeType":"PragmaDirective","src":"32:23:0","nodes":[],"literals":["solidity","^","0.8",".0"]},{"id":79,"nodeType":"ContractDefinition","src":"57:904:0","nodes":[{"id":3,"nodeType":"VariableDeclaration","src":"90:30:0","nodes":[],"constant":false,"functionSelector":"8da5cb5b","mutability":"immutable","name":"owner","nameLocation":"115:5:0","scope":79,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":2,"name":"address","nodeType":"ElementaryTypeName","src":"90:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"public"},{"id":5,"nodeType":"ErrorDefinition","src":"126:17:0","nodes":[],"errorSelector":"30cd7471","name":"NotOwner","nameLocation":"132:8:0","parameters":{"id":4,"nodeType":"ParameterList","parameters":[],"src":"140:2:0"}},{"id":15,"nodeType":"FunctionDefinition","src":"149:59:0","nodes":[],"body":{"id":14,"nodeType":"Block","src":"177:31:0","nodes":[],"statements":[{"expression":{"id":12,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":10,"name":"owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":3,"src":"187:5:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":11,"name":"_owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":7,"src":"195:6:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"src":"187:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":13,"nodeType":"ExpressionStatement","src":"187:14:0"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":8,"nodeType":"ParameterList","parameters":[{"constant":false,"id":7,"mutability":"mutable","name":"_owner","nameLocation":"169:6:0","nodeType":"VariableDeclaration","scope":15,"src":"161:14:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":6,"name":"address","nodeType":"ElementaryTypeName","src":"161:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"160:16:0"},"returnParameters":{"id":9,"nodeType":"ParameterList","parameters":[],"src":"177:0:0"},"scope":79,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":41,"nodeType":"FunctionDefinition","src":"214:329:0","nodes":[],"body":{"id":40,"nodeType":"Block","src":"312:231:0","nodes":[],"statements":[{"assignments":[25,27],"declarations":[{"constant":false,"id":25,"mutability":"mutable","name":"success","nameLocation":"328:7:0","nodeType":"VariableDeclaration","scope":40,"src":"323:12:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":24,"name":"bool","nodeType":"ElementaryTypeName","src":"323:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"},{"constant":false,"id":27,"mutability":"mutable","name":"result","nameLocation":"350:6:0","nodeType":"VariableDeclaration","scope":40,"src":"337:19:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":26,"name":"bytes","nodeType":"ElementaryTypeName","src":"337:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"id":32,"initialValue":{"arguments":[{"id":30,"name":"_data","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":19,"src":"381:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"expression":{"id":28,"name":"_target","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":17,"src":"360:7:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":29,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"368:12:0","memberName":"delegatecall","nodeType":"MemberAccess","src":"360:20:0","typeDescriptions":{"typeIdentifier":"t_function_baredelegatecall_nonpayable$_t_bytes_memory_ptr_$returns$_t_bool_$_t_bytes_memory_ptr_$","typeString":"function (bytes memory) returns (bool,bytes memory)"}},"id":31,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"360:27:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$_t_bool_$_t_bytes_memory_ptr_$","typeString":"tuple(bool,bytes memory)"}},"nodeType":"VariableDeclarationStatement","src":"322:65:0"},{"condition":{"id":34,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"!","prefix":true,"src":"401:8:0","subExpression":{"id":33,"name":"success","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":25,"src":"402:7:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"id":37,"nodeType":"IfStatement","src":"397:117:0","trueBody":{"id":36,"nodeType":"Block","src":"411:103:0","statements":[{"AST":{"nativeSrc":"434:70:0","nodeType":"YulBlock","src":"434:70:0","statements":[{"expression":{"arguments":[{"arguments":[{"name":"result","nativeSrc":"463:6:0","nodeType":"YulIdentifier","src":"463:6:0"},{"kind":"number","nativeSrc":"471:2:0","nodeType":"YulLiteral","src":"471:2:0","type":"","value":"32"}],"functionName":{"name":"add","nativeSrc":"459:3:0","nodeType":"YulIdentifier","src":"459:3:0"},"nativeSrc":"459:15:0","nodeType":"YulFunctionCall","src":"459:15:0"},{"arguments":[{"name":"result","nativeSrc":"482:6:0","nodeType":"YulIdentifier","src":"482:6:0"}],"functionName":{"name":"mload","nativeSrc":"476:5:0","nodeType":"YulIdentifier","src":"476:5:0"},"nativeSrc":"476:13:0","nodeType":"YulFunctionCall","src":"476:13:0"}],"functionName":{"name":"revert","nativeSrc":"452:6:0","nodeType":"YulIdentifier","src":"452:6:0"},"nativeSrc":"452:38:0","nodeType":"YulFunctionCall","src":"452:38:0"},"nativeSrc":"452:38:0","nodeType":"YulExpressionStatement","src":"452:38:0"}]},"evmVersion":"cancun","externalReferences":[{"declaration":27,"isOffset":false,"isSlot":false,"src":"463:6:0","valueSize":1},{"declaration":27,"isOffset":false,"isSlot":false,"src":"482:6:0","valueSize":1}],"id":35,"nodeType":"InlineAssembly","src":"425:79:0"}]}},{"expression":{"id":38,"name":"result","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":27,"src":"530:6:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}},"functionReturnParameters":23,"id":39,"nodeType":"Return","src":"523:13:0"}]},"functionSelector":"b68df16d","implemented":true,"kind":"function","modifiers":[],"name":"executeDelegateCall","nameLocation":"223:19:0","parameters":{"id":20,"nodeType":"ParameterList","parameters":[{"constant":false,"id":17,"mutability":"mutable","name":"_target","nameLocation":"251:7:0","nodeType":"VariableDeclaration","scope":41,"src":"243:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":16,"name":"address","nodeType":"ElementaryTypeName","src":"243:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":19,"mutability":"mutable","name":"_data","nameLocation":"273:5:0","nodeType":"VariableDeclaration","scope":41,"src":"260:18:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":18,"name":"bytes","nodeType":"ElementaryTypeName","src":"260:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"242:37:0"},"returnParameters":{"id":23,"nodeType":"ParameterList","parameters":[{"constant":false,"id":22,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":41,"src":"298:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":21,"name":"bytes","nodeType":"ElementaryTypeName","src":"298:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"297:14:0"},"scope":79,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":78,"nodeType":"FunctionDefinition","src":"549:410:0","nodes":[],"body":{"id":77,"nodeType":"Block","src":"625:334:0","nodes":[],"statements":[{"condition":{"commonType":{"typeIdentifier":"t_address","typeString":"address"},"id":51,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":48,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"639:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":49,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"643:6:0","memberName":"sender","nodeType":"MemberAccess","src":"639:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"BinaryOperation","operator":"!=","rightExpression":{"id":50,"name":"owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":3,"src":"653:5:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"src":"639:19:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"id":56,"nodeType":"IfStatement","src":"635:67:0","trueBody":{"id":55,"nodeType":"Block","src":"660:42:0","statements":[{"errorCall":{"arguments":[],"expression":{"argumentTypes":[],"id":52,"name":"NotOwner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":5,"src":"681:8:0","typeDescriptions":{"typeIdentifier":"t_function_error_pure$__$returns$_t_error_$","typeString":"function () pure returns (error)"}},"id":53,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"681:10:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_error","typeString":"error"}},"id":54,"nodeType":"RevertStatement","src":"674:17:0"}]}},{"assignments":[58],"declarations":[{"constant":false,"id":58,"mutability":"mutable","name":"data","nameLocation":"775:4:0","nodeType":"VariableDeclaration","scope":77,"src":"762:17:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":57,"name":"bytes","nodeType":"ElementaryTypeName","src":"762:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"id":64,"initialValue":{"arguments":[{"hexValue":"7472616e736665724f776e657273686970286164647265737329","id":61,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"806:28:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f2fde38b092330466c661fc723d5289b90272a3e580e3187d1d7ef788506c557","typeString":"literal_string \"transferOwnership(address)\""},"value":"transferOwnership(address)"},{"id":62,"name":"_newOwner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":45,"src":"836:9:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f2fde38b092330466c661fc723d5289b90272a3e580e3187d1d7ef788506c557","typeString":"literal_string \"transferOwnership(address)\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":59,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"782:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":60,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberLocation":"786:19:0","memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"782:23:0","typeDescriptions":{"typeIdentifier":"t_function_abiencodewithsignature_pure$_t_string_memory_ptr_$returns$_t_bytes_memory_ptr_$","typeString":"function (string memory) pure returns (bytes memory)"}},"id":63,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"782:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}},"nodeType":"VariableDeclarationStatement","src":"762:84:0"},{"assignments":[66,null],"declarations":[{"constant":false,"id":66,"mutability":"mutable","name":"success","nameLocation":"862:7:0","nodeType":"VariableDeclaration","scope":77,"src":"857:12:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":65,"name":"bool","nodeType":"ElementaryTypeName","src":"857:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"},null],"id":71,"initialValue":{"arguments":[{"id":69,"name":"data","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":58,"src":"892:4:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"expression":{"id":67,"name":"_proxyAdmin","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":43,"src":"875:11:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":68,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"887:4:0","memberName":"call","nodeType":"MemberAccess","src":"875:16:0","typeDescriptions":{"typeIdentifier":"t_function_barecall_payable$_t_bytes_memory_ptr_$returns$_t_bool_$_t_bytes_memory_ptr_$","typeString":"function (bytes memory) payable returns (bool,bytes memory)"}},"id":70,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"875:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$_t_bool_$_t_bytes_memory_ptr_$","typeString":"tuple(bool,bytes memory)"}},"nodeType":"VariableDeclarationStatement","src":"856:41:0"},{"expression":{"arguments":[{"id":73,"name":"success","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":66,"src":"915:7:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"5472616e736665724f776e6572736869703a206661696c6564","id":74,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"924:27:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_73db9f9416f5527e05767c00d66cf341ab0abe8bc7d2bf6b5bc085ce1317a4ad","typeString":"literal_string \"TransferOwnership: failed\""},"value":"TransferOwnership: failed"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_73db9f9416f5527e05767c00d66cf341ab0abe8bc7d2bf6b5bc085ce1317a4ad","typeString":"literal_string \"TransferOwnership: failed\""}],"id":72,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18,-18],"referencedDeclaration":-18,"src":"907:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":75,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"907:45:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":76,"nodeType":"ExpressionStatement","src":"907:45:0"}]},"functionSelector":"6d435421","implemented":true,"kind":"function","modifiers":[],"name":"transferOwnership","nameLocation":"558:17:0","parameters":{"id":46,"nodeType":"ParameterList","parameters":[{"constant":false,"id":43,"mutability":"mutable","name":"_proxyAdmin","nameLocation":"584:11:0","nodeType":"VariableDeclaration","scope":78,"src":"576:19:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":42,"name":"address","nodeType":"ElementaryTypeName","src":"576:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":45,"mutability":"mutable","name":"_newOwner","nameLocation":"605:9:0","nodeType":"VariableDeclaration","scope":78,"src":"597:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":44,"name":"address","nodeType":"ElementaryTypeName","src":"597:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"575:40:0"},"returnParameters":{"id":47,"nodeType":"ParameterList","parameters":[],"src":"625:0:0"},"scope":79,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"DelegateCallProxy","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[79],"name":"DelegateCallProxy","nameLocation":"66:17:0","scope":80,"usedErrors":[5],"usedEvents":[]}],"license":"MIT"},"id":0} \ No newline at end of file +{"abi":[{"type":"constructor","inputs":[{"name":"_owner","type":"address","internalType":"address"}],"stateMutability":"nonpayable"},{"type":"function","name":"executeDelegateCall","inputs":[{"name":"_target","type":"address","internalType":"address"},{"name":"_data","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"","type":"bytes","internalType":"bytes"}],"stateMutability":"nonpayable"},{"type":"function","name":"owner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"transferOwnership","inputs":[{"name":"_proxyAdmin","type":"address","internalType":"address"},{"name":"_newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"error","name":"NotOwner","inputs":[]}],"bytecode":{"object":"0x60a0604052348015600e575f5ffd5b506040516105ae3803806105ae833981016040819052602b91603b565b6001600160a01b03166080526066565b5f60208284031215604a575f5ffd5b81516001600160a01b0381168114605f575f5ffd5b9392505050565b60805161052b6100835f395f8181605d015260e1015261052b5ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80636d435421146100435780638da5cb5b14610058578063b68df16d146100a9575b5f5ffd5b610056610051366004610356565b6100c9565b005b61007f7f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6100bc6100b73660046103b4565b6102ae565b6040516100a091906104b5565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610138576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405173ffffffffffffffffffffffffffffffffffffffff821660248201525f90604401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167ff2fde38b00000000000000000000000000000000000000000000000000000000179052519091505f9073ffffffffffffffffffffffffffffffffffffffff8516906101fb908490610508565b5f604051808303815f865af19150503d805f8114610234576040519150601f19603f3d011682016040523d82523d5f602084013e610239565b606091505b50509050806102a8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f5472616e736665724f776e6572736869703a206661696c656400000000000000604482015260640160405180910390fd5b50505050565b60605f5f8473ffffffffffffffffffffffffffffffffffffffff16846040516102d79190610508565b5f60405180830381855af49150503d805f811461030f576040519150601f19603f3d011682016040523d82523d5f602084013e610314565b606091505b50915091508161032657805160208201fd5b949350505050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610351575f5ffd5b919050565b5f5f60408385031215610367575f5ffd5b6103708361032e565b915061037e6020840161032e565b90509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f5f604083850312156103c5575f5ffd5b6103ce8361032e565b9150602083013567ffffffffffffffff8111156103e9575f5ffd5b8301601f810185136103f9575f5ffd5b803567ffffffffffffffff81111561041357610413610387565b6040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8501160116810181811067ffffffffffffffff8211171561047f5761047f610387565b604052818152828201602001871015610496575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b602081525f82518060208401528060208501604085015e5f6040828501015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011684010191505092915050565b5f82518060208501845e5f92019182525091905056fea164736f6c634300081c000a","sourceMap":"57:904:0:-:0;;;149:59;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;187:14:0;;;57:904;;14:290:4;84:6;137:2;125:9;116:7;112:23;108:32;105:52;;;153:1;150;143:12;105:52;179:16;;-1:-1:-1;;;;;224:31:4;;214:42;;204:70;;270:1;267;260:12;204:70;293:5;14:290;-1:-1:-1;;;14:290:4:o;:::-;57:904:0;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80636d435421146100435780638da5cb5b14610058578063b68df16d146100a9575b5f5ffd5b610056610051366004610356565b6100c9565b005b61007f7f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6100bc6100b73660046103b4565b6102ae565b6040516100a091906104b5565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610138576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405173ffffffffffffffffffffffffffffffffffffffff821660248201525f90604401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167ff2fde38b00000000000000000000000000000000000000000000000000000000179052519091505f9073ffffffffffffffffffffffffffffffffffffffff8516906101fb908490610508565b5f604051808303815f865af19150503d805f8114610234576040519150601f19603f3d011682016040523d82523d5f602084013e610239565b606091505b50509050806102a8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f5472616e736665724f776e6572736869703a206661696c656400000000000000604482015260640160405180910390fd5b50505050565b60605f5f8473ffffffffffffffffffffffffffffffffffffffff16846040516102d79190610508565b5f60405180830381855af49150503d805f811461030f576040519150601f19603f3d011682016040523d82523d5f602084013e610314565b606091505b50915091508161032657805160208201fd5b949350505050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610351575f5ffd5b919050565b5f5f60408385031215610367575f5ffd5b6103708361032e565b915061037e6020840161032e565b90509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f5f604083850312156103c5575f5ffd5b6103ce8361032e565b9150602083013567ffffffffffffffff8111156103e9575f5ffd5b8301601f810185136103f9575f5ffd5b803567ffffffffffffffff81111561041357610413610387565b6040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8501160116810181811067ffffffffffffffff8211171561047f5761047f610387565b604052818152828201602001871015610496575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b602081525f82518060208401528060208501604085015e5f6040828501015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011684010191505092915050565b5f82518060208501845e5f92019182525091905056fea164736f6c634300081c000a","sourceMap":"57:904:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;549:410;;;;;;:::i;:::-;;:::i;:::-;;90:30;;;;;;;;656:42:4;644:55;;;626:74;;614:2;599:18;90:30:0;;;;;;;;214:329;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;549:410::-;639:10;:19;653:5;639:19;;635:67;;681:10;;;;;;;;;;;;;;635:67;782:64;;656:42:4;644:55;;782:64:0;;;626:74:4;762:17:0;;599:18:4;;782:64:0;;;;;;;;;;;;;;;;;;;;;;;;875:22;782:64;;-1:-1:-1;;;875:16:0;;;;:22;;782:64;;875:22;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;856:41;;;915:7;907:45;;;;;;;3029:2:4;907:45:0;;;3011:21:4;3068:2;3048:18;;;3041:30;3107:27;3087:18;;;3080:55;3152:18;;907:45:0;;;;;;;;625:334;;549:410;;:::o;214:329::-;298:12;323;337:19;360:7;:20;;381:5;360:27;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;322:65;;;;402:7;397:117;;482:6;476:13;471:2;463:6;459:15;452:38;397:117;530:6;214:329;-1:-1:-1;;;;214:329:0:o;14:196:4:-;82:20;;142:42;131:54;;121:65;;111:93;;200:1;197;190:12;111:93;14:196;;;:::o;215:260::-;283:6;291;344:2;332:9;323:7;319:23;315:32;312:52;;;360:1;357;350:12;312:52;383:29;402:9;383:29;:::i;:::-;373:39;;431:38;465:2;454:9;450:18;431:38;:::i;:::-;421:48;;215:260;;;;;:::o;711:184::-;763:77;760:1;753:88;860:4;857:1;850:15;884:4;881:1;874:15;900:1136;977:6;985;1038:2;1026:9;1017:7;1013:23;1009:32;1006:52;;;1054:1;1051;1044:12;1006:52;1077:29;1096:9;1077:29;:::i;:::-;1067:39;;1157:2;1146:9;1142:18;1129:32;1184:18;1176:6;1173:30;1170:50;;;1216:1;1213;1206:12;1170:50;1239:22;;1292:4;1284:13;;1280:27;-1:-1:-1;1270:55:4;;1321:1;1318;1311:12;1270:55;1361:2;1348:16;1387:18;1379:6;1376:30;1373:56;;;1409:18;;:::i;:::-;1458:2;1452:9;1605:66;1600:2;1531:66;1524:4;1516:6;1512:17;1508:90;1504:99;1500:172;1492:6;1488:185;1739:6;1727:10;1724:22;1703:18;1691:10;1688:34;1685:62;1682:88;;;1750:18;;:::i;:::-;1786:2;1779:22;1810;;;1851:15;;;1868:2;1847:24;1844:37;-1:-1:-1;1841:57:4;;;1894:1;1891;1884:12;1841:57;1950:6;1945:2;1941;1937:11;1932:2;1924:6;1920:15;1907:50;2003:1;1998:2;1989:6;1981;1977:19;1973:28;1966:39;2024:6;2014:16;;;;;900:1136;;;;;:::o;2041:475::-;2188:2;2177:9;2170:21;2151:4;2220:6;2214:13;2263:6;2258:2;2247:9;2243:18;2236:34;2322:6;2317:2;2309:6;2305:15;2300:2;2289:9;2285:18;2279:50;2378:1;2373:2;2364:6;2353:9;2349:22;2345:31;2338:42;2507:2;2437:66;2432:2;2424:6;2420:15;2416:88;2405:9;2401:104;2397:113;2389:121;;;2041:475;;;;:::o;2521:301::-;2650:3;2688:6;2682:13;2734:6;2727:4;2719:6;2715:17;2710:3;2704:37;2796:1;2760:16;;2785:13;;;-1:-1:-1;2760:16:4;2521:301;-1:-1:-1;2521:301:4:o","linkReferences":{},"immutableReferences":{"3":[{"start":93,"length":32},{"start":225,"length":32}]}},"methodIdentifiers":{"executeDelegateCall(address,bytes)":"b68df16d","owner()":"8da5cb5b","transferOwnership(address,address)":"6d435421"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.28+commit.7893614a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"NotOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_target\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"executeDelegateCall\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_proxyAdmin\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/DelegateCallProxy.sol\":\"DelegateCallProxy\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[]},\"sources\":{\"src/DelegateCallProxy.sol\":{\"keccak256\":\"0xc59415cd917768bf92d0405afd43fc36e2c80330ae766c9d5cfb34b5fb431e8a\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://6fe4a3a47fb430ccaa416c56d40cd63aa8be17cdb116ab1b06a69dc252fbe316\",\"dweb:/ipfs/QmNVhngA2AKKmF7E9iMmqsPpUQ57QEvbXKoLdY873TBHLQ\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.28+commit.7893614a"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"type":"error","name":"NotOwner"},{"inputs":[{"internalType":"address","name":"_target","type":"address"},{"internalType":"bytes","name":"_data","type":"bytes"}],"stateMutability":"nonpayable","type":"function","name":"executeDelegateCall","outputs":[{"internalType":"bytes","name":"","type":"bytes"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}]},{"inputs":[{"internalType":"address","name":"_proxyAdmin","type":"address"},{"internalType":"address","name":"_newOwner","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"transferOwnership"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"src/DelegateCallProxy.sol":"DelegateCallProxy"},"evmVersion":"cancun","libraries":{}},"sources":{"src/DelegateCallProxy.sol":{"keccak256":"0xc59415cd917768bf92d0405afd43fc36e2c80330ae766c9d5cfb34b5fb431e8a","urls":["bzz-raw://6fe4a3a47fb430ccaa416c56d40cd63aa8be17cdb116ab1b06a69dc252fbe316","dweb:/ipfs/QmNVhngA2AKKmF7E9iMmqsPpUQ57QEvbXKoLdY873TBHLQ"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"userdoc":{"version":1,"kind":"user"},"devdoc":{"version":1,"kind":"dev"},"ast":{"absolutePath":"src/DelegateCallProxy.sol","id":80,"exportedSymbols":{"DelegateCallProxy":[79]},"nodeType":"SourceUnit","src":"32:930:0","nodes":[{"id":1,"nodeType":"PragmaDirective","src":"32:23:0","nodes":[],"literals":["solidity","^","0.8",".0"]},{"id":79,"nodeType":"ContractDefinition","src":"57:904:0","nodes":[{"id":3,"nodeType":"VariableDeclaration","src":"90:30:0","nodes":[],"constant":false,"functionSelector":"8da5cb5b","mutability":"immutable","name":"owner","nameLocation":"115:5:0","scope":79,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":2,"name":"address","nodeType":"ElementaryTypeName","src":"90:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"public"},{"id":5,"nodeType":"ErrorDefinition","src":"126:17:0","nodes":[],"errorSelector":"30cd7471","name":"NotOwner","nameLocation":"132:8:0","parameters":{"id":4,"nodeType":"ParameterList","parameters":[],"src":"140:2:0"}},{"id":15,"nodeType":"FunctionDefinition","src":"149:59:0","nodes":[],"body":{"id":14,"nodeType":"Block","src":"177:31:0","nodes":[],"statements":[{"expression":{"id":12,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":10,"name":"owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":3,"src":"187:5:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":11,"name":"_owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":7,"src":"195:6:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"src":"187:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":13,"nodeType":"ExpressionStatement","src":"187:14:0"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":8,"nodeType":"ParameterList","parameters":[{"constant":false,"id":7,"mutability":"mutable","name":"_owner","nameLocation":"169:6:0","nodeType":"VariableDeclaration","scope":15,"src":"161:14:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":6,"name":"address","nodeType":"ElementaryTypeName","src":"161:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"160:16:0"},"returnParameters":{"id":9,"nodeType":"ParameterList","parameters":[],"src":"177:0:0"},"scope":79,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":41,"nodeType":"FunctionDefinition","src":"214:329:0","nodes":[],"body":{"id":40,"nodeType":"Block","src":"312:231:0","nodes":[],"statements":[{"assignments":[25,27],"declarations":[{"constant":false,"id":25,"mutability":"mutable","name":"success","nameLocation":"328:7:0","nodeType":"VariableDeclaration","scope":40,"src":"323:12:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":24,"name":"bool","nodeType":"ElementaryTypeName","src":"323:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"},{"constant":false,"id":27,"mutability":"mutable","name":"result","nameLocation":"350:6:0","nodeType":"VariableDeclaration","scope":40,"src":"337:19:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":26,"name":"bytes","nodeType":"ElementaryTypeName","src":"337:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"id":32,"initialValue":{"arguments":[{"id":30,"name":"_data","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":19,"src":"381:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"expression":{"id":28,"name":"_target","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":17,"src":"360:7:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":29,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"368:12:0","memberName":"delegatecall","nodeType":"MemberAccess","src":"360:20:0","typeDescriptions":{"typeIdentifier":"t_function_baredelegatecall_nonpayable$_t_bytes_memory_ptr_$returns$_t_bool_$_t_bytes_memory_ptr_$","typeString":"function (bytes memory) returns (bool,bytes memory)"}},"id":31,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"360:27:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$_t_bool_$_t_bytes_memory_ptr_$","typeString":"tuple(bool,bytes memory)"}},"nodeType":"VariableDeclarationStatement","src":"322:65:0"},{"condition":{"id":34,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"!","prefix":true,"src":"401:8:0","subExpression":{"id":33,"name":"success","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":25,"src":"402:7:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"id":37,"nodeType":"IfStatement","src":"397:117:0","trueBody":{"id":36,"nodeType":"Block","src":"411:103:0","statements":[{"AST":{"nativeSrc":"434:70:0","nodeType":"YulBlock","src":"434:70:0","statements":[{"expression":{"arguments":[{"arguments":[{"name":"result","nativeSrc":"463:6:0","nodeType":"YulIdentifier","src":"463:6:0"},{"kind":"number","nativeSrc":"471:2:0","nodeType":"YulLiteral","src":"471:2:0","type":"","value":"32"}],"functionName":{"name":"add","nativeSrc":"459:3:0","nodeType":"YulIdentifier","src":"459:3:0"},"nativeSrc":"459:15:0","nodeType":"YulFunctionCall","src":"459:15:0"},{"arguments":[{"name":"result","nativeSrc":"482:6:0","nodeType":"YulIdentifier","src":"482:6:0"}],"functionName":{"name":"mload","nativeSrc":"476:5:0","nodeType":"YulIdentifier","src":"476:5:0"},"nativeSrc":"476:13:0","nodeType":"YulFunctionCall","src":"476:13:0"}],"functionName":{"name":"revert","nativeSrc":"452:6:0","nodeType":"YulIdentifier","src":"452:6:0"},"nativeSrc":"452:38:0","nodeType":"YulFunctionCall","src":"452:38:0"},"nativeSrc":"452:38:0","nodeType":"YulExpressionStatement","src":"452:38:0"}]},"evmVersion":"cancun","externalReferences":[{"declaration":27,"isOffset":false,"isSlot":false,"src":"463:6:0","valueSize":1},{"declaration":27,"isOffset":false,"isSlot":false,"src":"482:6:0","valueSize":1}],"id":35,"nodeType":"InlineAssembly","src":"425:79:0"}]}},{"expression":{"id":38,"name":"result","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":27,"src":"530:6:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}},"functionReturnParameters":23,"id":39,"nodeType":"Return","src":"523:13:0"}]},"functionSelector":"b68df16d","implemented":true,"kind":"function","modifiers":[],"name":"executeDelegateCall","nameLocation":"223:19:0","parameters":{"id":20,"nodeType":"ParameterList","parameters":[{"constant":false,"id":17,"mutability":"mutable","name":"_target","nameLocation":"251:7:0","nodeType":"VariableDeclaration","scope":41,"src":"243:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":16,"name":"address","nodeType":"ElementaryTypeName","src":"243:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":19,"mutability":"mutable","name":"_data","nameLocation":"273:5:0","nodeType":"VariableDeclaration","scope":41,"src":"260:18:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":18,"name":"bytes","nodeType":"ElementaryTypeName","src":"260:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"242:37:0"},"returnParameters":{"id":23,"nodeType":"ParameterList","parameters":[{"constant":false,"id":22,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":41,"src":"298:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":21,"name":"bytes","nodeType":"ElementaryTypeName","src":"298:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"297:14:0"},"scope":79,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":78,"nodeType":"FunctionDefinition","src":"549:410:0","nodes":[],"body":{"id":77,"nodeType":"Block","src":"625:334:0","nodes":[],"statements":[{"condition":{"commonType":{"typeIdentifier":"t_address","typeString":"address"},"id":51,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":48,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"639:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":49,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"643:6:0","memberName":"sender","nodeType":"MemberAccess","src":"639:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"BinaryOperation","operator":"!=","rightExpression":{"id":50,"name":"owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":3,"src":"653:5:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"src":"639:19:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"id":56,"nodeType":"IfStatement","src":"635:67:0","trueBody":{"id":55,"nodeType":"Block","src":"660:42:0","statements":[{"errorCall":{"arguments":[],"expression":{"argumentTypes":[],"id":52,"name":"NotOwner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":5,"src":"681:8:0","typeDescriptions":{"typeIdentifier":"t_function_error_pure$__$returns$_t_error_$","typeString":"function () pure returns (error)"}},"id":53,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"681:10:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_error","typeString":"error"}},"id":54,"nodeType":"RevertStatement","src":"674:17:0"}]}},{"assignments":[58],"declarations":[{"constant":false,"id":58,"mutability":"mutable","name":"data","nameLocation":"775:4:0","nodeType":"VariableDeclaration","scope":77,"src":"762:17:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":57,"name":"bytes","nodeType":"ElementaryTypeName","src":"762:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"id":64,"initialValue":{"arguments":[{"hexValue":"7472616e736665724f776e657273686970286164647265737329","id":61,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"806:28:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f2fde38b092330466c661fc723d5289b90272a3e580e3187d1d7ef788506c557","typeString":"literal_string \"transferOwnership(address)\""},"value":"transferOwnership(address)"},{"id":62,"name":"_newOwner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":45,"src":"836:9:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f2fde38b092330466c661fc723d5289b90272a3e580e3187d1d7ef788506c557","typeString":"literal_string \"transferOwnership(address)\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":59,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"782:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":60,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberLocation":"786:19:0","memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"782:23:0","typeDescriptions":{"typeIdentifier":"t_function_abiencodewithsignature_pure$_t_string_memory_ptr_$returns$_t_bytes_memory_ptr_$","typeString":"function (string memory) pure returns (bytes memory)"}},"id":63,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"782:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}},"nodeType":"VariableDeclarationStatement","src":"762:84:0"},{"assignments":[66,null],"declarations":[{"constant":false,"id":66,"mutability":"mutable","name":"success","nameLocation":"862:7:0","nodeType":"VariableDeclaration","scope":77,"src":"857:12:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":65,"name":"bool","nodeType":"ElementaryTypeName","src":"857:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"},null],"id":71,"initialValue":{"arguments":[{"id":69,"name":"data","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":58,"src":"892:4:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"expression":{"id":67,"name":"_proxyAdmin","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":43,"src":"875:11:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":68,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"887:4:0","memberName":"call","nodeType":"MemberAccess","src":"875:16:0","typeDescriptions":{"typeIdentifier":"t_function_barecall_payable$_t_bytes_memory_ptr_$returns$_t_bool_$_t_bytes_memory_ptr_$","typeString":"function (bytes memory) payable returns (bool,bytes memory)"}},"id":70,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"875:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$_t_bool_$_t_bytes_memory_ptr_$","typeString":"tuple(bool,bytes memory)"}},"nodeType":"VariableDeclarationStatement","src":"856:41:0"},{"expression":{"arguments":[{"id":73,"name":"success","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":66,"src":"915:7:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"5472616e736665724f776e6572736869703a206661696c6564","id":74,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"924:27:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_73db9f9416f5527e05767c00d66cf341ab0abe8bc7d2bf6b5bc085ce1317a4ad","typeString":"literal_string \"TransferOwnership: failed\""},"value":"TransferOwnership: failed"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_73db9f9416f5527e05767c00d66cf341ab0abe8bc7d2bf6b5bc085ce1317a4ad","typeString":"literal_string \"TransferOwnership: failed\""}],"id":72,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18,-18],"referencedDeclaration":-18,"src":"907:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":75,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"907:45:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":76,"nodeType":"ExpressionStatement","src":"907:45:0"}]},"functionSelector":"6d435421","implemented":true,"kind":"function","modifiers":[],"name":"transferOwnership","nameLocation":"558:17:0","parameters":{"id":46,"nodeType":"ParameterList","parameters":[{"constant":false,"id":43,"mutability":"mutable","name":"_proxyAdmin","nameLocation":"584:11:0","nodeType":"VariableDeclaration","scope":78,"src":"576:19:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":42,"name":"address","nodeType":"ElementaryTypeName","src":"576:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":45,"mutability":"mutable","name":"_newOwner","nameLocation":"605:9:0","nodeType":"VariableDeclaration","scope":78,"src":"597:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":44,"name":"address","nodeType":"ElementaryTypeName","src":"597:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"575:40:0"},"returnParameters":{"id":47,"nodeType":"ParameterList","parameters":[],"src":"625:0:0"},"scope":79,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"DelegateCallProxy","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[79],"name":"DelegateCallProxy","nameLocation":"66:17:0","scope":80,"usedErrors":[5],"usedEvents":[]}],"license":"MIT"},"id":0} \ No newline at end of file diff --git a/op-e2e/e2eutils/contracts/build/ICrossL2Inbox.sol/ICrossL2Inbox.json b/op-e2e/e2eutils/contracts/build/ICrossL2Inbox.sol/ICrossL2Inbox.json index 00d0c90cbda..f060aac2565 100644 --- a/op-e2e/e2eutils/contracts/build/ICrossL2Inbox.sol/ICrossL2Inbox.json +++ b/op-e2e/e2eutils/contracts/build/ICrossL2Inbox.sol/ICrossL2Inbox.json @@ -1 +1 @@ -{"abi":[{"type":"function","name":"validateMessage","inputs":[{"name":"_id","type":"tuple","internalType":"struct Identifier","components":[{"name":"origin","type":"address","internalType":"address"},{"name":"blockNumber","type":"uint256","internalType":"uint256"},{"name":"logIndex","type":"uint256","internalType":"uint256"},{"name":"timestamp","type":"uint256","internalType":"uint256"},{"name":"chainId","type":"uint256","internalType":"uint256"}]},{"name":"_msgHash","type":"bytes32","internalType":"bytes32"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"version","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"event","name":"ExecutingMessage","inputs":[{"name":"msgHash","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"id","type":"tuple","indexed":false,"internalType":"struct Identifier","components":[{"name":"origin","type":"address","internalType":"address"},{"name":"blockNumber","type":"uint256","internalType":"uint256"},{"name":"logIndex","type":"uint256","internalType":"uint256"},{"name":"timestamp","type":"uint256","internalType":"uint256"},{"name":"chainId","type":"uint256","internalType":"uint256"}]}],"anonymous":false},{"type":"error","name":"BlockNumberTooHigh","inputs":[]},{"type":"error","name":"LogIndexTooHigh","inputs":[]},{"type":"error","name":"NoExecutingDeposits","inputs":[]},{"type":"error","name":"NotInAccessList","inputs":[]},{"type":"error","name":"TimestampTooHigh","inputs":[]}],"bytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"deployedBytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"methodIdentifiers":{"validateMessage((address,uint256,uint256,uint256,uint256),bytes32)":"ab4d6f75","version()":"54fd4d50"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.25+commit.b61c2a91\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"BlockNumberTooHigh\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"LogIndexTooHigh\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NoExecutingDeposits\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotInAccessList\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"TimestampTooHigh\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"msgHash\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"origin\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"logIndex\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"indexed\":false,\"internalType\":\"struct Identifier\",\"name\":\"id\",\"type\":\"tuple\"}],\"name\":\"ExecutingMessage\",\"type\":\"event\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"origin\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"logIndex\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Identifier\",\"name\":\"_id\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"_msgHash\",\"type\":\"bytes32\"}],\"name\":\"validateMessage\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/ICrossL2Inbox.sol\":\"ICrossL2Inbox\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[]},\"sources\":{\"src/ICrossL2Inbox.sol\":{\"keccak256\":\"0x9d31923d67c620293adcc180d6f6219f196dcca4d81425b932113537801749de\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://10456b2586608b6336f58ba78fa481c70ca0da48a459b9be42b2b52da738adfe\",\"dweb:/ipfs/QmVNkgm9YFv2HCYNEipCUbP3jdkC3Ma1XWLvWKVnb3xi8t\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.25+commit.b61c2a91"},"language":"Solidity","output":{"abi":[{"inputs":[],"type":"error","name":"BlockNumberTooHigh"},{"inputs":[],"type":"error","name":"LogIndexTooHigh"},{"inputs":[],"type":"error","name":"NoExecutingDeposits"},{"inputs":[],"type":"error","name":"NotInAccessList"},{"inputs":[],"type":"error","name":"TimestampTooHigh"},{"inputs":[{"internalType":"bytes32","name":"msgHash","type":"bytes32","indexed":true},{"internalType":"struct Identifier","name":"id","type":"tuple","components":[{"internalType":"address","name":"origin","type":"address"},{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"uint256","name":"logIndex","type":"uint256"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"uint256","name":"chainId","type":"uint256"}],"indexed":false}],"type":"event","name":"ExecutingMessage","anonymous":false},{"inputs":[{"internalType":"struct Identifier","name":"_id","type":"tuple","components":[{"internalType":"address","name":"origin","type":"address"},{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"uint256","name":"logIndex","type":"uint256"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"uint256","name":"chainId","type":"uint256"}]},{"internalType":"bytes32","name":"_msgHash","type":"bytes32"}],"stateMutability":"nonpayable","type":"function","name":"validateMessage"},{"inputs":[],"stateMutability":"view","type":"function","name":"version","outputs":[{"internalType":"string","name":"","type":"string"}]}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"src/ICrossL2Inbox.sol":"ICrossL2Inbox"},"evmVersion":"cancun","libraries":{}},"sources":{"src/ICrossL2Inbox.sol":{"keccak256":"0x9d31923d67c620293adcc180d6f6219f196dcca4d81425b932113537801749de","urls":["bzz-raw://10456b2586608b6336f58ba78fa481c70ca0da48a459b9be42b2b52da738adfe","dweb:/ipfs/QmVNkgm9YFv2HCYNEipCUbP3jdkC3Ma1XWLvWKVnb3xi8t"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"userdoc":{"version":1,"kind":"user"},"devdoc":{"version":1,"kind":"dev"},"ast":{"absolutePath":"src/ICrossL2Inbox.sol","id":45,"exportedSymbols":{"ICrossL2Inbox":[44],"Identifier":[13]},"nodeType":"SourceUnit","src":"32:604:0","nodes":[{"id":1,"nodeType":"PragmaDirective","src":"32:23:0","nodes":[],"literals":["solidity","^","0.8",".0"]},{"id":13,"nodeType":"StructDefinition","src":"106:132:0","nodes":[],"canonicalName":"Identifier","documentation":{"id":2,"nodeType":"StructuredDocumentation","src":"57:49:0","text":"@notice Identifier of a cross chain message."},"members":[{"constant":false,"id":4,"mutability":"mutable","name":"origin","nameLocation":"138:6:0","nodeType":"VariableDeclaration","scope":13,"src":"130:14:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":3,"name":"address","nodeType":"ElementaryTypeName","src":"130:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":6,"mutability":"mutable","name":"blockNumber","nameLocation":"158:11:0","nodeType":"VariableDeclaration","scope":13,"src":"150:19:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":5,"name":"uint256","nodeType":"ElementaryTypeName","src":"150:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"},{"constant":false,"id":8,"mutability":"mutable","name":"logIndex","nameLocation":"183:8:0","nodeType":"VariableDeclaration","scope":13,"src":"175:16:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":7,"name":"uint256","nodeType":"ElementaryTypeName","src":"175:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"},{"constant":false,"id":10,"mutability":"mutable","name":"timestamp","nameLocation":"205:9:0","nodeType":"VariableDeclaration","scope":13,"src":"197:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":9,"name":"uint256","nodeType":"ElementaryTypeName","src":"197:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"},{"constant":false,"id":12,"mutability":"mutable","name":"chainId","nameLocation":"228:7:0","nodeType":"VariableDeclaration","scope":13,"src":"220:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":11,"name":"uint256","nodeType":"ElementaryTypeName","src":"220:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"name":"Identifier","nameLocation":"113:10:0","scope":45,"visibility":"public"},{"id":44,"nodeType":"ContractDefinition","src":"240:395:0","nodes":[{"id":15,"nodeType":"ErrorDefinition","src":"270:28:0","nodes":[],"errorSelector":"753f1072","name":"NoExecutingDeposits","nameLocation":"276:19:0","parameters":{"id":14,"nodeType":"ParameterList","parameters":[],"src":"295:2:0"}},{"id":17,"nodeType":"ErrorDefinition","src":"303:24:0","nodes":[],"errorSelector":"e3c00816","name":"NotInAccessList","nameLocation":"309:15:0","parameters":{"id":16,"nodeType":"ParameterList","parameters":[],"src":"324:2:0"}},{"id":19,"nodeType":"ErrorDefinition","src":"332:27:0","nodes":[],"errorSelector":"d1f79e82","name":"BlockNumberTooHigh","nameLocation":"338:18:0","parameters":{"id":18,"nodeType":"ParameterList","parameters":[],"src":"356:2:0"}},{"id":21,"nodeType":"ErrorDefinition","src":"364:25:0","nodes":[],"errorSelector":"596a19a9","name":"TimestampTooHigh","nameLocation":"370:16:0","parameters":{"id":20,"nodeType":"ParameterList","parameters":[],"src":"386:2:0"}},{"id":23,"nodeType":"ErrorDefinition","src":"394:24:0","nodes":[],"errorSelector":"94338eba","name":"LogIndexTooHigh","nameLocation":"400:15:0","parameters":{"id":22,"nodeType":"ParameterList","parameters":[],"src":"415:2:0"}},{"id":30,"nodeType":"EventDefinition","src":"424:63:0","nodes":[],"anonymous":false,"eventSelector":"5c37832d2e8d10e346e55ad62071a6a2f9fa5130614ef2ec6617555c6f467ba7","name":"ExecutingMessage","nameLocation":"430:16:0","parameters":{"id":29,"nodeType":"ParameterList","parameters":[{"constant":false,"id":25,"indexed":true,"mutability":"mutable","name":"msgHash","nameLocation":"463:7:0","nodeType":"VariableDeclaration","scope":30,"src":"447:23:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":24,"name":"bytes32","nodeType":"ElementaryTypeName","src":"447:7:0","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"},{"constant":false,"id":28,"indexed":false,"mutability":"mutable","name":"id","nameLocation":"483:2:0","nodeType":"VariableDeclaration","scope":30,"src":"472:13:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_struct$_Identifier_$13_memory_ptr","typeString":"struct Identifier"},"typeName":{"id":27,"nodeType":"UserDefinedTypeName","pathNode":{"id":26,"name":"Identifier","nameLocations":["472:10:0"],"nodeType":"IdentifierPath","referencedDeclaration":13,"src":"472:10:0"},"referencedDeclaration":13,"src":"472:10:0","typeDescriptions":{"typeIdentifier":"t_struct$_Identifier_$13_storage_ptr","typeString":"struct Identifier"}},"visibility":"internal"}],"src":"446:40:0"}},{"id":35,"nodeType":"FunctionDefinition","src":"493:57:0","nodes":[],"functionSelector":"54fd4d50","implemented":false,"kind":"function","modifiers":[],"name":"version","nameLocation":"502:7:0","parameters":{"id":31,"nodeType":"ParameterList","parameters":[],"src":"509:2:0"},"returnParameters":{"id":34,"nodeType":"ParameterList","parameters":[{"constant":false,"id":33,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":35,"src":"535:13:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":32,"name":"string","nodeType":"ElementaryTypeName","src":"535:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"534:15:0"},"scope":44,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":43,"nodeType":"FunctionDefinition","src":"556:77:0","nodes":[],"functionSelector":"ab4d6f75","implemented":false,"kind":"function","modifiers":[],"name":"validateMessage","nameLocation":"565:15:0","parameters":{"id":41,"nodeType":"ParameterList","parameters":[{"constant":false,"id":38,"mutability":"mutable","name":"_id","nameLocation":"601:3:0","nodeType":"VariableDeclaration","scope":43,"src":"581:23:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_struct$_Identifier_$13_calldata_ptr","typeString":"struct Identifier"},"typeName":{"id":37,"nodeType":"UserDefinedTypeName","pathNode":{"id":36,"name":"Identifier","nameLocations":["581:10:0"],"nodeType":"IdentifierPath","referencedDeclaration":13,"src":"581:10:0"},"referencedDeclaration":13,"src":"581:10:0","typeDescriptions":{"typeIdentifier":"t_struct$_Identifier_$13_storage_ptr","typeString":"struct Identifier"}},"visibility":"internal"},{"constant":false,"id":40,"mutability":"mutable","name":"_msgHash","nameLocation":"614:8:0","nodeType":"VariableDeclaration","scope":43,"src":"606:16:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":39,"name":"bytes32","nodeType":"ElementaryTypeName","src":"606:7:0","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"}],"src":"580:43:0"},"returnParameters":{"id":42,"nodeType":"ParameterList","parameters":[],"src":"632:0:0"},"scope":44,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"ICrossL2Inbox","contractDependencies":[],"contractKind":"interface","fullyImplemented":false,"linearizedBaseContracts":[44],"name":"ICrossL2Inbox","nameLocation":"250:13:0","scope":45,"usedErrors":[15,17,19,21,23],"usedEvents":[30]}],"license":"MIT"},"id":0} \ No newline at end of file +{"abi":[{"type":"function","name":"validateMessage","inputs":[{"name":"_id","type":"tuple","internalType":"struct Identifier","components":[{"name":"origin","type":"address","internalType":"address"},{"name":"blockNumber","type":"uint256","internalType":"uint256"},{"name":"logIndex","type":"uint256","internalType":"uint256"},{"name":"timestamp","type":"uint256","internalType":"uint256"},{"name":"chainId","type":"uint256","internalType":"uint256"}]},{"name":"_msgHash","type":"bytes32","internalType":"bytes32"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"version","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"event","name":"ExecutingMessage","inputs":[{"name":"msgHash","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"id","type":"tuple","indexed":false,"internalType":"struct Identifier","components":[{"name":"origin","type":"address","internalType":"address"},{"name":"blockNumber","type":"uint256","internalType":"uint256"},{"name":"logIndex","type":"uint256","internalType":"uint256"},{"name":"timestamp","type":"uint256","internalType":"uint256"},{"name":"chainId","type":"uint256","internalType":"uint256"}]}],"anonymous":false},{"type":"error","name":"BlockNumberTooHigh","inputs":[]},{"type":"error","name":"LogIndexTooHigh","inputs":[]},{"type":"error","name":"NotInAccessList","inputs":[]},{"type":"error","name":"TimestampTooHigh","inputs":[]}],"bytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"deployedBytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"methodIdentifiers":{"validateMessage((address,uint256,uint256,uint256,uint256),bytes32)":"ab4d6f75","version()":"54fd4d50"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.28+commit.7893614a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"BlockNumberTooHigh\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"LogIndexTooHigh\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotInAccessList\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"TimestampTooHigh\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"msgHash\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"origin\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"logIndex\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"indexed\":false,\"internalType\":\"struct Identifier\",\"name\":\"id\",\"type\":\"tuple\"}],\"name\":\"ExecutingMessage\",\"type\":\"event\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"origin\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"logIndex\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Identifier\",\"name\":\"_id\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"_msgHash\",\"type\":\"bytes32\"}],\"name\":\"validateMessage\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/ICrossL2Inbox.sol\":\"ICrossL2Inbox\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[]},\"sources\":{\"src/ICrossL2Inbox.sol\":{\"keccak256\":\"0x40ed00ca46f8b6ddbd0c919680903207358105ce11e17d889ec4693f8a3651d2\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://0b9058ab9aabfc2c3a672186a10f08d001d993aa309f14e1cf66a843de61be9a\",\"dweb:/ipfs/QmQMjNSzFjJqUMAhhxTgsh4njBjPkptBQDQfkuj2SsgxsG\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.28+commit.7893614a"},"language":"Solidity","output":{"abi":[{"inputs":[],"type":"error","name":"BlockNumberTooHigh"},{"inputs":[],"type":"error","name":"LogIndexTooHigh"},{"inputs":[],"type":"error","name":"NotInAccessList"},{"inputs":[],"type":"error","name":"TimestampTooHigh"},{"inputs":[{"internalType":"bytes32","name":"msgHash","type":"bytes32","indexed":true},{"internalType":"struct Identifier","name":"id","type":"tuple","components":[{"internalType":"address","name":"origin","type":"address"},{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"uint256","name":"logIndex","type":"uint256"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"uint256","name":"chainId","type":"uint256"}],"indexed":false}],"type":"event","name":"ExecutingMessage","anonymous":false},{"inputs":[{"internalType":"struct Identifier","name":"_id","type":"tuple","components":[{"internalType":"address","name":"origin","type":"address"},{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"uint256","name":"logIndex","type":"uint256"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"uint256","name":"chainId","type":"uint256"}]},{"internalType":"bytes32","name":"_msgHash","type":"bytes32"}],"stateMutability":"nonpayable","type":"function","name":"validateMessage"},{"inputs":[],"stateMutability":"view","type":"function","name":"version","outputs":[{"internalType":"string","name":"","type":"string"}]}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"src/ICrossL2Inbox.sol":"ICrossL2Inbox"},"evmVersion":"cancun","libraries":{}},"sources":{"src/ICrossL2Inbox.sol":{"keccak256":"0x40ed00ca46f8b6ddbd0c919680903207358105ce11e17d889ec4693f8a3651d2","urls":["bzz-raw://0b9058ab9aabfc2c3a672186a10f08d001d993aa309f14e1cf66a843de61be9a","dweb:/ipfs/QmQMjNSzFjJqUMAhhxTgsh4njBjPkptBQDQfkuj2SsgxsG"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"userdoc":{"version":1,"kind":"user"},"devdoc":{"version":1,"kind":"dev"},"ast":{"absolutePath":"src/ICrossL2Inbox.sol","id":123,"exportedSymbols":{"ICrossL2Inbox":[122],"Identifier":[93]},"nodeType":"SourceUnit","src":"32:571:1","nodes":[{"id":81,"nodeType":"PragmaDirective","src":"32:23:1","nodes":[],"literals":["solidity","^","0.8",".0"]},{"id":93,"nodeType":"StructDefinition","src":"106:132:1","nodes":[],"canonicalName":"Identifier","documentation":{"id":82,"nodeType":"StructuredDocumentation","src":"57:49:1","text":"@notice Identifier of a cross chain message."},"members":[{"constant":false,"id":84,"mutability":"mutable","name":"origin","nameLocation":"138:6:1","nodeType":"VariableDeclaration","scope":93,"src":"130:14:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":83,"name":"address","nodeType":"ElementaryTypeName","src":"130:7:1","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":86,"mutability":"mutable","name":"blockNumber","nameLocation":"158:11:1","nodeType":"VariableDeclaration","scope":93,"src":"150:19:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":85,"name":"uint256","nodeType":"ElementaryTypeName","src":"150:7:1","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"},{"constant":false,"id":88,"mutability":"mutable","name":"logIndex","nameLocation":"183:8:1","nodeType":"VariableDeclaration","scope":93,"src":"175:16:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":87,"name":"uint256","nodeType":"ElementaryTypeName","src":"175:7:1","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"},{"constant":false,"id":90,"mutability":"mutable","name":"timestamp","nameLocation":"205:9:1","nodeType":"VariableDeclaration","scope":93,"src":"197:17:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":89,"name":"uint256","nodeType":"ElementaryTypeName","src":"197:7:1","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"},{"constant":false,"id":92,"mutability":"mutable","name":"chainId","nameLocation":"228:7:1","nodeType":"VariableDeclaration","scope":93,"src":"220:15:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":91,"name":"uint256","nodeType":"ElementaryTypeName","src":"220:7:1","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"name":"Identifier","nameLocation":"113:10:1","scope":123,"visibility":"public"},{"id":122,"nodeType":"ContractDefinition","src":"240:362:1","nodes":[{"id":95,"nodeType":"ErrorDefinition","src":"270:24:1","nodes":[],"errorSelector":"e3c00816","name":"NotInAccessList","nameLocation":"276:15:1","parameters":{"id":94,"nodeType":"ParameterList","parameters":[],"src":"291:2:1"}},{"id":97,"nodeType":"ErrorDefinition","src":"299:27:1","nodes":[],"errorSelector":"d1f79e82","name":"BlockNumberTooHigh","nameLocation":"305:18:1","parameters":{"id":96,"nodeType":"ParameterList","parameters":[],"src":"323:2:1"}},{"id":99,"nodeType":"ErrorDefinition","src":"331:25:1","nodes":[],"errorSelector":"596a19a9","name":"TimestampTooHigh","nameLocation":"337:16:1","parameters":{"id":98,"nodeType":"ParameterList","parameters":[],"src":"353:2:1"}},{"id":101,"nodeType":"ErrorDefinition","src":"361:24:1","nodes":[],"errorSelector":"94338eba","name":"LogIndexTooHigh","nameLocation":"367:15:1","parameters":{"id":100,"nodeType":"ParameterList","parameters":[],"src":"382:2:1"}},{"id":108,"nodeType":"EventDefinition","src":"391:63:1","nodes":[],"anonymous":false,"eventSelector":"5c37832d2e8d10e346e55ad62071a6a2f9fa5130614ef2ec6617555c6f467ba7","name":"ExecutingMessage","nameLocation":"397:16:1","parameters":{"id":107,"nodeType":"ParameterList","parameters":[{"constant":false,"id":103,"indexed":true,"mutability":"mutable","name":"msgHash","nameLocation":"430:7:1","nodeType":"VariableDeclaration","scope":108,"src":"414:23:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":102,"name":"bytes32","nodeType":"ElementaryTypeName","src":"414:7:1","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"},{"constant":false,"id":106,"indexed":false,"mutability":"mutable","name":"id","nameLocation":"450:2:1","nodeType":"VariableDeclaration","scope":108,"src":"439:13:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_struct$_Identifier_$93_memory_ptr","typeString":"struct Identifier"},"typeName":{"id":105,"nodeType":"UserDefinedTypeName","pathNode":{"id":104,"name":"Identifier","nameLocations":["439:10:1"],"nodeType":"IdentifierPath","referencedDeclaration":93,"src":"439:10:1"},"referencedDeclaration":93,"src":"439:10:1","typeDescriptions":{"typeIdentifier":"t_struct$_Identifier_$93_storage_ptr","typeString":"struct Identifier"}},"visibility":"internal"}],"src":"413:40:1"}},{"id":113,"nodeType":"FunctionDefinition","src":"460:57:1","nodes":[],"functionSelector":"54fd4d50","implemented":false,"kind":"function","modifiers":[],"name":"version","nameLocation":"469:7:1","parameters":{"id":109,"nodeType":"ParameterList","parameters":[],"src":"476:2:1"},"returnParameters":{"id":112,"nodeType":"ParameterList","parameters":[{"constant":false,"id":111,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":113,"src":"502:13:1","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":110,"name":"string","nodeType":"ElementaryTypeName","src":"502:6:1","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"501:15:1"},"scope":122,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":121,"nodeType":"FunctionDefinition","src":"523:77:1","nodes":[],"functionSelector":"ab4d6f75","implemented":false,"kind":"function","modifiers":[],"name":"validateMessage","nameLocation":"532:15:1","parameters":{"id":119,"nodeType":"ParameterList","parameters":[{"constant":false,"id":116,"mutability":"mutable","name":"_id","nameLocation":"568:3:1","nodeType":"VariableDeclaration","scope":121,"src":"548:23:1","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_struct$_Identifier_$93_calldata_ptr","typeString":"struct Identifier"},"typeName":{"id":115,"nodeType":"UserDefinedTypeName","pathNode":{"id":114,"name":"Identifier","nameLocations":["548:10:1"],"nodeType":"IdentifierPath","referencedDeclaration":93,"src":"548:10:1"},"referencedDeclaration":93,"src":"548:10:1","typeDescriptions":{"typeIdentifier":"t_struct$_Identifier_$93_storage_ptr","typeString":"struct Identifier"}},"visibility":"internal"},{"constant":false,"id":118,"mutability":"mutable","name":"_msgHash","nameLocation":"581:8:1","nodeType":"VariableDeclaration","scope":121,"src":"573:16:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":117,"name":"bytes32","nodeType":"ElementaryTypeName","src":"573:7:1","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"}],"src":"547:43:1"},"returnParameters":{"id":120,"nodeType":"ParameterList","parameters":[],"src":"599:0:1"},"scope":122,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"ICrossL2Inbox","contractDependencies":[],"contractKind":"interface","fullyImplemented":false,"linearizedBaseContracts":[122],"name":"ICrossL2Inbox","nameLocation":"250:13:1","scope":123,"usedErrors":[95,97,99,101],"usedEvents":[108]}],"license":"MIT"},"id":1} \ No newline at end of file diff --git a/op-e2e/e2eutils/contracts/build/Invoker.sol/Invoker.abi b/op-e2e/e2eutils/contracts/build/Invoker.sol/Invoker.abi index 67051a1f48e..e035d5778e1 100644 --- a/op-e2e/e2eutils/contracts/build/Invoker.sol/Invoker.abi +++ b/op-e2e/e2eutils/contracts/build/Invoker.sol/Invoker.abi @@ -32,6 +32,12 @@ "type": "bytes", "indexed": false, "internalType": "bytes" + }, + { + "name": "delegateCallResult", + "type": "bytes", + "indexed": false, + "internalType": "bytes" } ], "anonymous": false diff --git a/op-e2e/e2eutils/contracts/build/Invoker.sol/Invoker.bin b/op-e2e/e2eutils/contracts/build/Invoker.sol/Invoker.bin index 5705b73fcbb..7342b9b2f69 100644 --- a/op-e2e/e2eutils/contracts/build/Invoker.sol/Invoker.bin +++ b/op-e2e/e2eutils/contracts/build/Invoker.sol/Invoker.bin @@ -1 +1 @@ -0x6080604052348015600e575f5ffd5b506102f58061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063051f3bdf1461002d575b5f5ffd5b61004061003b366004610164565b610042565b005b5f5f8373ffffffffffffffffffffffffffffffffffffffff1683604051610069919061027f565b5f604051808303815f865af19150503d805f81146100a2576040519150601f19603f3d011682016040523d82523d5f602084013e6100a7565b606091505b5091509150816100e3576040517ffd23ff6400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8373ffffffffffffffffffffffffffffffffffffffff167fde9caeb04cbecadc4b3b08dd3b026ff047428c7c681a368b2b48d1097bf465a8826040516101299190610295565b60405180910390a250505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f5f60408385031215610175575f5ffd5b823573ffffffffffffffffffffffffffffffffffffffff81168114610198575f5ffd5b9150602083013567ffffffffffffffff8111156101b3575f5ffd5b8301601f810185136101c3575f5ffd5b803567ffffffffffffffff8111156101dd576101dd610137565b6040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8501160116810181811067ffffffffffffffff8211171561024957610249610137565b604052818152828201602001871015610260575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f6040828501015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168401019150509291505056fea164736f6c634300081c000a +0x6080604052348015600e575f5ffd5b506103bf8061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063051f3bdf1461002d575b5f5ffd5b61004061003b366004610208565b610042565b005b5f5f8373ffffffffffffffffffffffffffffffffffffffff16836040516100699190610323565b5f604051808303815f865af19150503d805f81146100a2576040519150601f19603f3d011682016040523d82523d5f602084013e6100a7565b606091505b5091509150816100e3576040517ffd23ff6400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60608473ffffffffffffffffffffffffffffffffffffffff168460405161010a9190610323565b5f60405180830381855af49150503d805f8114610142576040519150601f19603f3d011682016040523d82523d5f602084013e610147565b606091505b50909350905082610184576040517ffd23ff6400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff167fc331673664ab9732fd2c0b1a4aa0cd948da43af82aca20906b4c306c7228e07983836040516101cc929190610385565b60405180910390a25050505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f5f60408385031215610219575f5ffd5b823573ffffffffffffffffffffffffffffffffffffffff8116811461023c575f5ffd5b9150602083013567ffffffffffffffff811115610257575f5ffd5b8301601f81018513610267575f5ffd5b803567ffffffffffffffff811115610281576102816101db565b6040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8501160116810181811067ffffffffffffffff821117156102ed576102ed6101db565b604052818152828201602001871015610304575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f82518060208501845e5f920191825250919050565b5f81518084528060208401602086015e5f6020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b604081525f6103976040830185610339565b82810360208401526103a98185610339565b9594505050505056fea164736f6c634300081c000a diff --git a/op-e2e/e2eutils/contracts/build/Invoker.sol/Invoker.json b/op-e2e/e2eutils/contracts/build/Invoker.sol/Invoker.json index be7e840ac88..84b53934e81 100644 --- a/op-e2e/e2eutils/contracts/build/Invoker.sol/Invoker.json +++ b/op-e2e/e2eutils/contracts/build/Invoker.sol/Invoker.json @@ -1 +1 @@ -{"abi":[{"type":"function","name":"invokePrecompile","inputs":[{"name":"_precompile","type":"address","internalType":"address"},{"name":"_input","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"PrecompileInvoked","inputs":[{"name":"precompile","type":"address","indexed":true,"internalType":"address"},{"name":"result","type":"bytes","indexed":false,"internalType":"bytes"}],"anonymous":false},{"type":"error","name":"PrecompileCallFailed","inputs":[]}],"bytecode":{"object":"0x6080604052348015600e575f5ffd5b506102f58061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063051f3bdf1461002d575b5f5ffd5b61004061003b366004610164565b610042565b005b5f5f8373ffffffffffffffffffffffffffffffffffffffff1683604051610069919061027f565b5f604051808303815f865af19150503d805f81146100a2576040519150601f19603f3d011682016040523d82523d5f602084013e6100a7565b606091505b5091509150816100e3576040517ffd23ff6400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8373ffffffffffffffffffffffffffffffffffffffff167fde9caeb04cbecadc4b3b08dd3b026ff047428c7c681a368b2b48d1097bf465a8826040516101299190610295565b60405180910390a250505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f5f60408385031215610175575f5ffd5b823573ffffffffffffffffffffffffffffffffffffffff81168114610198575f5ffd5b9150602083013567ffffffffffffffff8111156101b3575f5ffd5b8301601f810185136101c3575f5ffd5b803567ffffffffffffffff8111156101dd576101dd610137565b6040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8501160116810181811067ffffffffffffffff8211171561024957610249610137565b604052818152828201602001871015610260575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f6040828501015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168401019150509291505056fea164736f6c634300081c000a","sourceMap":"58:481:0:-:0;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063051f3bdf1461002d575b5f5ffd5b61004061003b366004610164565b610042565b005b5f5f8373ffffffffffffffffffffffffffffffffffffffff1683604051610069919061027f565b5f604051808303815f865af19150503d805f81146100a2576040519150601f19603f3d011682016040523d82523d5f602084013e6100a7565b606091505b5091509150816100e3576040517ffd23ff6400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8373ffffffffffffffffffffffffffffffffffffffff167fde9caeb04cbecadc4b3b08dd3b026ff047428c7c681a368b2b48d1097bf465a8826040516101299190610295565b60405180910390a250505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f5f60408385031215610175575f5ffd5b823573ffffffffffffffffffffffffffffffffffffffff81168114610198575f5ffd5b9150602083013567ffffffffffffffff8111156101b3575f5ffd5b8301601f810185136101c3575f5ffd5b803567ffffffffffffffff8111156101dd576101dd610137565b6040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8501160116810181811067ffffffffffffffff8211171561024957610249610137565b604052818152828201602001871015610260575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f6040828501015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168401019150509291505056fea164736f6c634300081c000a","sourceMap":"58:481:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;187:350;;;;;;:::i;:::-;;:::i;:::-;;;339:12;353:19;376:11;:16;;393:6;376:24;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;338:62;;;;415:7;410:68;;445:22;;;;;;;;;;;;;;410:68;510:11;492:38;;;523:6;492:38;;;;;;:::i;:::-;;;;;;;;264:273;;187:350;;:::o;14:184:1:-;66:77;63:1;56:88;163:4;160:1;153:15;187:4;184:1;177:15;203:1259;280:6;288;341:2;329:9;320:7;316:23;312:32;309:52;;;357:1;354;347:12;309:52;396:9;383:23;446:42;439:5;435:54;428:5;425:65;415:93;;504:1;501;494:12;415:93;527:5;-1:-1:-1;583:2:1;568:18;;555:32;610:18;599:30;;596:50;;;642:1;639;632:12;596:50;665:22;;718:4;710:13;;706:27;-1:-1:-1;696:55:1;;747:1;744;737:12;696:55;787:2;774:16;813:18;805:6;802:30;799:56;;;835:18;;:::i;:::-;884:2;878:9;1031:66;1026:2;957:66;950:4;942:6;938:17;934:90;930:99;926:172;918:6;914:185;1165:6;1153:10;1150:22;1129:18;1117:10;1114:34;1111:62;1108:88;;;1176:18;;:::i;:::-;1212:2;1205:22;1236;;;1277:15;;;1294:2;1273:24;1270:37;-1:-1:-1;1267:57:1;;;1320:1;1317;1310:12;1267:57;1376:6;1371:2;1367;1363:11;1358:2;1350:6;1346:15;1333:50;1429:1;1424:2;1415:6;1407;1403:19;1399:28;1392:39;1450:6;1440:16;;;;;203:1259;;;;;:::o;1467:301::-;1596:3;1634:6;1628:13;1680:6;1673:4;1665:6;1661:17;1656:3;1650:37;1742:1;1706:16;;1731:13;;;-1:-1:-1;1706:16:1;1467:301;-1:-1:-1;1467:301:1:o;1773:475::-;1920:2;1909:9;1902:21;1883:4;1952:6;1946:13;1995:6;1990:2;1979:9;1975:18;1968:34;2054:6;2049:2;2041:6;2037:15;2032:2;2021:9;2017:18;2011:50;2110:1;2105:2;2096:6;2085:9;2081:22;2077:31;2070:42;2239:2;2169:66;2164:2;2156:6;2152:15;2148:88;2137:9;2133:104;2129:113;2121:121;;;1773:475;;;;:::o","linkReferences":{}},"methodIdentifiers":{"invokePrecompile(address,bytes)":"051f3bdf"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.28+commit.7893614a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"PrecompileCallFailed\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"precompile\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"result\",\"type\":\"bytes\"}],\"name\":\"PrecompileInvoked\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_precompile\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_input\",\"type\":\"bytes\"}],\"name\":\"invokePrecompile\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/Invoker.sol\":\"Invoker\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[]},\"sources\":{\"src/Invoker.sol\":{\"keccak256\":\"0xddfb3915e14861712ef66f325e48d931c06bd6f98efcc3ae7e591779dcf942af\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://1b9f829106397fd5474e7ac870e18347d0762248c0c8698be25838a48d157d1a\",\"dweb:/ipfs/QmXKADnBTCewgGXyMGX9grdgyW3uz29AnLgniakKGouYBD\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.28+commit.7893614a"},"language":"Solidity","output":{"abi":[{"inputs":[],"type":"error","name":"PrecompileCallFailed"},{"inputs":[{"internalType":"address","name":"precompile","type":"address","indexed":true},{"internalType":"bytes","name":"result","type":"bytes","indexed":false}],"type":"event","name":"PrecompileInvoked","anonymous":false},{"inputs":[{"internalType":"address","name":"_precompile","type":"address"},{"internalType":"bytes","name":"_input","type":"bytes"}],"stateMutability":"nonpayable","type":"function","name":"invokePrecompile"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"src/Invoker.sol":"Invoker"},"evmVersion":"cancun","libraries":{}},"sources":{"src/Invoker.sol":{"keccak256":"0xddfb3915e14861712ef66f325e48d931c06bd6f98efcc3ae7e591779dcf942af","urls":["bzz-raw://1b9f829106397fd5474e7ac870e18347d0762248c0c8698be25838a48d157d1a","dweb:/ipfs/QmXKADnBTCewgGXyMGX9grdgyW3uz29AnLgniakKGouYBD"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"userdoc":{"version":1,"kind":"user"},"devdoc":{"version":1,"kind":"dev"},"ast":{"absolutePath":"src/Invoker.sol","id":40,"exportedSymbols":{"Invoker":[39]},"nodeType":"SourceUnit","src":"32:508:0","nodes":[{"id":1,"nodeType":"PragmaDirective","src":"32:24:0","nodes":[],"literals":["solidity","^","0.8",".15"]},{"id":39,"nodeType":"ContractDefinition","src":"58:481:0","nodes":[{"id":7,"nodeType":"EventDefinition","src":"81:66:0","nodes":[],"anonymous":false,"eventSelector":"de9caeb04cbecadc4b3b08dd3b026ff047428c7c681a368b2b48d1097bf465a8","name":"PrecompileInvoked","nameLocation":"87:17:0","parameters":{"id":6,"nodeType":"ParameterList","parameters":[{"constant":false,"id":3,"indexed":true,"mutability":"mutable","name":"precompile","nameLocation":"121:10:0","nodeType":"VariableDeclaration","scope":7,"src":"105:26:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":2,"name":"address","nodeType":"ElementaryTypeName","src":"105:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":5,"indexed":false,"mutability":"mutable","name":"result","nameLocation":"139:6:0","nodeType":"VariableDeclaration","scope":7,"src":"133:12:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":4,"name":"bytes","nodeType":"ElementaryTypeName","src":"133:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"104:42:0"}},{"id":9,"nodeType":"ErrorDefinition","src":"152:29:0","nodes":[],"errorSelector":"fd23ff64","name":"PrecompileCallFailed","nameLocation":"158:20:0","parameters":{"id":8,"nodeType":"ParameterList","parameters":[],"src":"178:2:0"}},{"id":38,"nodeType":"FunctionDefinition","src":"187:350:0","nodes":[],"body":{"id":37,"nodeType":"Block","src":"264:273:0","nodes":[],"statements":[{"assignments":[17,19],"declarations":[{"constant":false,"id":17,"mutability":"mutable","name":"success","nameLocation":"344:7:0","nodeType":"VariableDeclaration","scope":37,"src":"339:12:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":16,"name":"bool","nodeType":"ElementaryTypeName","src":"339:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"},{"constant":false,"id":19,"mutability":"mutable","name":"result","nameLocation":"366:6:0","nodeType":"VariableDeclaration","scope":37,"src":"353:19:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":18,"name":"bytes","nodeType":"ElementaryTypeName","src":"353:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"id":24,"initialValue":{"arguments":[{"id":22,"name":"_input","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":13,"src":"393:6:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"expression":{"id":20,"name":"_precompile","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":11,"src":"376:11:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":21,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"388:4:0","memberName":"call","nodeType":"MemberAccess","src":"376:16:0","typeDescriptions":{"typeIdentifier":"t_function_barecall_payable$_t_bytes_memory_ptr_$returns$_t_bool_$_t_bytes_memory_ptr_$","typeString":"function (bytes memory) payable returns (bool,bytes memory)"}},"id":23,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"376:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$_t_bool_$_t_bytes_memory_ptr_$","typeString":"tuple(bool,bytes memory)"}},"nodeType":"VariableDeclarationStatement","src":"338:62:0"},{"condition":{"id":26,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"!","prefix":true,"src":"414:8:0","subExpression":{"id":25,"name":"success","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":17,"src":"415:7:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"id":31,"nodeType":"IfStatement","src":"410:68:0","trueBody":{"id":30,"nodeType":"Block","src":"424:54:0","statements":[{"errorCall":{"arguments":[],"expression":{"argumentTypes":[],"id":27,"name":"PrecompileCallFailed","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":9,"src":"445:20:0","typeDescriptions":{"typeIdentifier":"t_function_error_pure$__$returns$_t_error_$","typeString":"function () pure returns (error)"}},"id":28,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"445:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_error","typeString":"error"}},"id":29,"nodeType":"RevertStatement","src":"438:29:0"}]}},{"eventCall":{"arguments":[{"id":33,"name":"_precompile","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":11,"src":"510:11:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":34,"name":"result","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":19,"src":"523:6:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":32,"name":"PrecompileInvoked","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":7,"src":"492:17:0","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_address_$_t_bytes_memory_ptr_$returns$__$","typeString":"function (address,bytes memory)"}},"id":35,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"492:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":36,"nodeType":"EmitStatement","src":"487:43:0"}]},"functionSelector":"051f3bdf","implemented":true,"kind":"function","modifiers":[],"name":"invokePrecompile","nameLocation":"196:16:0","parameters":{"id":14,"nodeType":"ParameterList","parameters":[{"constant":false,"id":11,"mutability":"mutable","name":"_precompile","nameLocation":"221:11:0","nodeType":"VariableDeclaration","scope":38,"src":"213:19:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":10,"name":"address","nodeType":"ElementaryTypeName","src":"213:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":13,"mutability":"mutable","name":"_input","nameLocation":"247:6:0","nodeType":"VariableDeclaration","scope":38,"src":"234:19:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":12,"name":"bytes","nodeType":"ElementaryTypeName","src":"234:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"212:42:0"},"returnParameters":{"id":15,"nodeType":"ParameterList","parameters":[],"src":"264:0:0"},"scope":39,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"Invoker","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[39],"name":"Invoker","nameLocation":"67:7:0","scope":40,"usedErrors":[9],"usedEvents":[7]}],"license":"MIT"},"id":0} \ No newline at end of file +{"abi":[{"type":"function","name":"invokePrecompile","inputs":[{"name":"_precompile","type":"address","internalType":"address"},{"name":"_input","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"PrecompileInvoked","inputs":[{"name":"precompile","type":"address","indexed":true,"internalType":"address"},{"name":"result","type":"bytes","indexed":false,"internalType":"bytes"},{"name":"delegateCallResult","type":"bytes","indexed":false,"internalType":"bytes"}],"anonymous":false},{"type":"error","name":"PrecompileCallFailed","inputs":[]}],"bytecode":{"object":"0x6080604052348015600e575f5ffd5b506103bf8061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063051f3bdf1461002d575b5f5ffd5b61004061003b366004610208565b610042565b005b5f5f8373ffffffffffffffffffffffffffffffffffffffff16836040516100699190610323565b5f604051808303815f865af19150503d805f81146100a2576040519150601f19603f3d011682016040523d82523d5f602084013e6100a7565b606091505b5091509150816100e3576040517ffd23ff6400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60608473ffffffffffffffffffffffffffffffffffffffff168460405161010a9190610323565b5f60405180830381855af49150503d805f8114610142576040519150601f19603f3d011682016040523d82523d5f602084013e610147565b606091505b50909350905082610184576040517ffd23ff6400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff167fc331673664ab9732fd2c0b1a4aa0cd948da43af82aca20906b4c306c7228e07983836040516101cc929190610385565b60405180910390a25050505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f5f60408385031215610219575f5ffd5b823573ffffffffffffffffffffffffffffffffffffffff8116811461023c575f5ffd5b9150602083013567ffffffffffffffff811115610257575f5ffd5b8301601f81018513610267575f5ffd5b803567ffffffffffffffff811115610281576102816101db565b6040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8501160116810181811067ffffffffffffffff821117156102ed576102ed6101db565b604052818152828201602001871015610304575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f82518060208501845e5f920191825250919050565b5f81518084528060208401602086015e5f6020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b604081525f6103976040830185610339565b82810360208401526103a98185610339565b9594505050505056fea164736f6c634300081c000a","sourceMap":"58:719:0:-:0;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063051f3bdf1461002d575b5f5ffd5b61004061003b366004610208565b610042565b005b5f5f8373ffffffffffffffffffffffffffffffffffffffff16836040516100699190610323565b5f604051808303815f865af19150503d805f81146100a2576040519150601f19603f3d011682016040523d82523d5f602084013e6100a7565b606091505b5091509150816100e3576040517ffd23ff6400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60608473ffffffffffffffffffffffffffffffffffffffff168460405161010a9190610323565b5f60405180830381855af49150503d805f8114610142576040519150601f19603f3d011682016040523d82523d5f602084013e610147565b606091505b50909350905082610184576040517ffd23ff6400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff167fc331673664ab9732fd2c0b1a4aa0cd948da43af82aca20906b4c306c7228e07983836040516101cc929190610385565b60405180910390a25050505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f5f60408385031215610219575f5ffd5b823573ffffffffffffffffffffffffffffffffffffffff8116811461023c575f5ffd5b9150602083013567ffffffffffffffff811115610257575f5ffd5b8301601f81018513610267575f5ffd5b803567ffffffffffffffff811115610281576102816101db565b6040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8501160116810181811067ffffffffffffffff821117156102ed576102ed6101db565b604052818152828201602001871015610304575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f82518060208501845e5f920191825250919050565b5f81518084528060208401602086015e5f6020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b604081525f6103976040830185610339565b82810360208401526103a98185610339565b9594505050505056fea164736f6c634300081c000a","sourceMap":"58:719:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;213:562;;;;;;:::i;:::-;;:::i;:::-;;;365:12;379:19;402:11;:16;;419:6;402:24;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;364:62;;;;441:7;436:68;;471:22;;;;;;;;;;;;;;436:68;513:31;586:11;:24;;611:6;586:32;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;554:64:0;;-1:-1:-1;554:64:0;-1:-1:-1;554:64:0;628:68;;663:22;;;;;;;;;;;;;;628:68;728:11;710:58;;;741:6;749:18;710:58;;;;;;;:::i;:::-;;;;;;;;290:485;;;213:562;;:::o;14:184:1:-;66:77;63:1;56:88;163:4;160:1;153:15;187:4;184:1;177:15;203:1259;280:6;288;341:2;329:9;320:7;316:23;312:32;309:52;;;357:1;354;347:12;309:52;396:9;383:23;446:42;439:5;435:54;428:5;425:65;415:93;;504:1;501;494:12;415:93;527:5;-1:-1:-1;583:2:1;568:18;;555:32;610:18;599:30;;596:50;;;642:1;639;632:12;596:50;665:22;;718:4;710:13;;706:27;-1:-1:-1;696:55:1;;747:1;744;737:12;696:55;787:2;774:16;813:18;805:6;802:30;799:56;;;835:18;;:::i;:::-;884:2;878:9;1031:66;1026:2;957:66;950:4;942:6;938:17;934:90;930:99;926:172;918:6;914:185;1165:6;1153:10;1150:22;1129:18;1117:10;1114:34;1111:62;1108:88;;;1176:18;;:::i;:::-;1212:2;1205:22;1236;;;1277:15;;;1294:2;1273:24;1270:37;-1:-1:-1;1267:57:1;;;1320:1;1317;1310:12;1267:57;1376:6;1371:2;1367;1363:11;1358:2;1350:6;1346:15;1333:50;1429:1;1424:2;1415:6;1407;1403:19;1399:28;1392:39;1450:6;1440:16;;;;;203:1259;;;;;:::o;1467:301::-;1596:3;1634:6;1628:13;1680:6;1673:4;1665:6;1661:17;1656:3;1650:37;1742:1;1706:16;;1731:13;;;-1:-1:-1;1706:16:1;1467:301;-1:-1:-1;1467:301:1:o;1773:347::-;1814:3;1852:5;1846:12;1879:6;1874:3;1867:19;1935:6;1928:4;1921:5;1917:16;1910:4;1905:3;1901:14;1895:47;1987:1;1980:4;1971:6;1966:3;1962:16;1958:27;1951:38;2109:4;2039:66;2034:2;2026:6;2022:15;2018:88;2013:3;2009:98;2005:109;1998:116;;;1773:347;;;;:::o;2125:377::-;2318:2;2307:9;2300:21;2281:4;2344:44;2384:2;2373:9;2369:18;2361:6;2344:44;:::i;:::-;2436:9;2428:6;2424:22;2419:2;2408:9;2404:18;2397:50;2464:32;2489:6;2481;2464:32;:::i;:::-;2456:40;2125:377;-1:-1:-1;;;;;2125:377:1:o","linkReferences":{}},"methodIdentifiers":{"invokePrecompile(address,bytes)":"051f3bdf"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.28+commit.7893614a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"PrecompileCallFailed\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"precompile\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"result\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"delegateCallResult\",\"type\":\"bytes\"}],\"name\":\"PrecompileInvoked\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_precompile\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_input\",\"type\":\"bytes\"}],\"name\":\"invokePrecompile\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/Invoker.sol\":\"Invoker\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[]},\"sources\":{\"src/Invoker.sol\":{\"keccak256\":\"0x88fbfd5d6b4685de3d3a07490cf9a7c74f2bf4f9ee3100b5d04ccd1110760135\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://d204eccb3f02bab15af9f5aa12ef62012ca712750c8646dcf00f5b250f6e2480\",\"dweb:/ipfs/QmVpopga7igWGJTYP5cZnNGwxLHGVHUpMJBRDyocM9Gtjf\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.28+commit.7893614a"},"language":"Solidity","output":{"abi":[{"inputs":[],"type":"error","name":"PrecompileCallFailed"},{"inputs":[{"internalType":"address","name":"precompile","type":"address","indexed":true},{"internalType":"bytes","name":"result","type":"bytes","indexed":false},{"internalType":"bytes","name":"delegateCallResult","type":"bytes","indexed":false}],"type":"event","name":"PrecompileInvoked","anonymous":false},{"inputs":[{"internalType":"address","name":"_precompile","type":"address"},{"internalType":"bytes","name":"_input","type":"bytes"}],"stateMutability":"nonpayable","type":"function","name":"invokePrecompile"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"src/Invoker.sol":"Invoker"},"evmVersion":"cancun","libraries":{}},"sources":{"src/Invoker.sol":{"keccak256":"0x88fbfd5d6b4685de3d3a07490cf9a7c74f2bf4f9ee3100b5d04ccd1110760135","urls":["bzz-raw://d204eccb3f02bab15af9f5aa12ef62012ca712750c8646dcf00f5b250f6e2480","dweb:/ipfs/QmVpopga7igWGJTYP5cZnNGwxLHGVHUpMJBRDyocM9Gtjf"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"userdoc":{"version":1,"kind":"user"},"devdoc":{"version":1,"kind":"dev"},"ast":{"absolutePath":"src/Invoker.sol","id":62,"exportedSymbols":{"Invoker":[61]},"nodeType":"SourceUnit","src":"32:746:0","nodes":[{"id":1,"nodeType":"PragmaDirective","src":"32:24:0","nodes":[],"literals":["solidity","^","0.8",".15"]},{"id":61,"nodeType":"ContractDefinition","src":"58:719:0","nodes":[{"id":9,"nodeType":"EventDefinition","src":"81:92:0","nodes":[],"anonymous":false,"eventSelector":"c331673664ab9732fd2c0b1a4aa0cd948da43af82aca20906b4c306c7228e079","name":"PrecompileInvoked","nameLocation":"87:17:0","parameters":{"id":8,"nodeType":"ParameterList","parameters":[{"constant":false,"id":3,"indexed":true,"mutability":"mutable","name":"precompile","nameLocation":"121:10:0","nodeType":"VariableDeclaration","scope":9,"src":"105:26:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":2,"name":"address","nodeType":"ElementaryTypeName","src":"105:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":5,"indexed":false,"mutability":"mutable","name":"result","nameLocation":"139:6:0","nodeType":"VariableDeclaration","scope":9,"src":"133:12:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":4,"name":"bytes","nodeType":"ElementaryTypeName","src":"133:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"},{"constant":false,"id":7,"indexed":false,"mutability":"mutable","name":"delegateCallResult","nameLocation":"153:18:0","nodeType":"VariableDeclaration","scope":9,"src":"147:24:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":6,"name":"bytes","nodeType":"ElementaryTypeName","src":"147:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"104:68:0"}},{"id":11,"nodeType":"ErrorDefinition","src":"178:29:0","nodes":[],"errorSelector":"fd23ff64","name":"PrecompileCallFailed","nameLocation":"184:20:0","parameters":{"id":10,"nodeType":"ParameterList","parameters":[],"src":"204:2:0"}},{"id":60,"nodeType":"FunctionDefinition","src":"213:562:0","nodes":[],"body":{"id":59,"nodeType":"Block","src":"290:485:0","nodes":[],"statements":[{"assignments":[19,21],"declarations":[{"constant":false,"id":19,"mutability":"mutable","name":"success","nameLocation":"370:7:0","nodeType":"VariableDeclaration","scope":59,"src":"365:12:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":18,"name":"bool","nodeType":"ElementaryTypeName","src":"365:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"},{"constant":false,"id":21,"mutability":"mutable","name":"result","nameLocation":"392:6:0","nodeType":"VariableDeclaration","scope":59,"src":"379:19:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":20,"name":"bytes","nodeType":"ElementaryTypeName","src":"379:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"id":26,"initialValue":{"arguments":[{"id":24,"name":"_input","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":15,"src":"419:6:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"expression":{"id":22,"name":"_precompile","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":13,"src":"402:11:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":23,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"414:4:0","memberName":"call","nodeType":"MemberAccess","src":"402:16:0","typeDescriptions":{"typeIdentifier":"t_function_barecall_payable$_t_bytes_memory_ptr_$returns$_t_bool_$_t_bytes_memory_ptr_$","typeString":"function (bytes memory) payable returns (bool,bytes memory)"}},"id":25,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"402:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$_t_bool_$_t_bytes_memory_ptr_$","typeString":"tuple(bool,bytes memory)"}},"nodeType":"VariableDeclarationStatement","src":"364:62:0"},{"condition":{"id":28,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"!","prefix":true,"src":"440:8:0","subExpression":{"id":27,"name":"success","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":19,"src":"441:7:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"id":33,"nodeType":"IfStatement","src":"436:68:0","trueBody":{"id":32,"nodeType":"Block","src":"450:54:0","statements":[{"errorCall":{"arguments":[],"expression":{"argumentTypes":[],"id":29,"name":"PrecompileCallFailed","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":11,"src":"471:20:0","typeDescriptions":{"typeIdentifier":"t_function_error_pure$__$returns$_t_error_$","typeString":"function () pure returns (error)"}},"id":30,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"471:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_error","typeString":"error"}},"id":31,"nodeType":"RevertStatement","src":"464:29:0"}]}},{"assignments":[35],"declarations":[{"constant":false,"id":35,"mutability":"mutable","name":"delegateCallResult","nameLocation":"526:18:0","nodeType":"VariableDeclaration","scope":59,"src":"513:31:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":34,"name":"bytes","nodeType":"ElementaryTypeName","src":"513:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"id":36,"nodeType":"VariableDeclarationStatement","src":"513:31:0"},{"expression":{"id":44,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"components":[{"id":37,"name":"success","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":19,"src":"555:7:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"id":38,"name":"delegateCallResult","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":35,"src":"564:18:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"id":39,"isConstant":false,"isInlineArray":false,"isLValue":true,"isPure":false,"lValueRequested":true,"nodeType":"TupleExpression","src":"554:29:0","typeDescriptions":{"typeIdentifier":"t_tuple$_t_bool_$_t_bytes_memory_ptr_$","typeString":"tuple(bool,bytes memory)"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"arguments":[{"id":42,"name":"_input","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":15,"src":"611:6:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"expression":{"id":40,"name":"_precompile","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":13,"src":"586:11:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":41,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"598:12:0","memberName":"delegatecall","nodeType":"MemberAccess","src":"586:24:0","typeDescriptions":{"typeIdentifier":"t_function_baredelegatecall_nonpayable$_t_bytes_memory_ptr_$returns$_t_bool_$_t_bytes_memory_ptr_$","typeString":"function (bytes memory) returns (bool,bytes memory)"}},"id":43,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"586:32:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$_t_bool_$_t_bytes_memory_ptr_$","typeString":"tuple(bool,bytes memory)"}},"src":"554:64:0","typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":45,"nodeType":"ExpressionStatement","src":"554:64:0"},{"condition":{"id":47,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"!","prefix":true,"src":"632:8:0","subExpression":{"id":46,"name":"success","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":19,"src":"633:7:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"id":52,"nodeType":"IfStatement","src":"628:68:0","trueBody":{"id":51,"nodeType":"Block","src":"642:54:0","statements":[{"errorCall":{"arguments":[],"expression":{"argumentTypes":[],"id":48,"name":"PrecompileCallFailed","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":11,"src":"663:20:0","typeDescriptions":{"typeIdentifier":"t_function_error_pure$__$returns$_t_error_$","typeString":"function () pure returns (error)"}},"id":49,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"663:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_error","typeString":"error"}},"id":50,"nodeType":"RevertStatement","src":"656:29:0"}]}},{"eventCall":{"arguments":[{"id":54,"name":"_precompile","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":13,"src":"728:11:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":55,"name":"result","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":21,"src":"741:6:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}},{"id":56,"name":"delegateCallResult","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":35,"src":"749:18:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"},{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":53,"name":"PrecompileInvoked","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":9,"src":"710:17:0","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_address_$_t_bytes_memory_ptr_$_t_bytes_memory_ptr_$returns$__$","typeString":"function (address,bytes memory,bytes memory)"}},"id":57,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"710:58:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":58,"nodeType":"EmitStatement","src":"705:63:0"}]},"functionSelector":"051f3bdf","implemented":true,"kind":"function","modifiers":[],"name":"invokePrecompile","nameLocation":"222:16:0","parameters":{"id":16,"nodeType":"ParameterList","parameters":[{"constant":false,"id":13,"mutability":"mutable","name":"_precompile","nameLocation":"247:11:0","nodeType":"VariableDeclaration","scope":60,"src":"239:19:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":12,"name":"address","nodeType":"ElementaryTypeName","src":"239:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":15,"mutability":"mutable","name":"_input","nameLocation":"273:6:0","nodeType":"VariableDeclaration","scope":60,"src":"260:19:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":14,"name":"bytes","nodeType":"ElementaryTypeName","src":"260:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"238:42:0"},"returnParameters":{"id":17,"nodeType":"ParameterList","parameters":[],"src":"290:0:0"},"scope":61,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"Invoker","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[61],"name":"Invoker","nameLocation":"67:7:0","scope":62,"usedErrors":[11],"usedEvents":[9]}],"license":"MIT"},"id":0} \ No newline at end of file diff --git a/op-e2e/e2eutils/contracts/build/emit.sol/EmitEvent.json b/op-e2e/e2eutils/contracts/build/emit.sol/EmitEvent.json index 2c4e3a26dab..94828b96192 100644 --- a/op-e2e/e2eutils/contracts/build/emit.sol/EmitEvent.json +++ b/op-e2e/e2eutils/contracts/build/emit.sol/EmitEvent.json @@ -1 +1 @@ -{"abi":[{"type":"function","name":"emitData","inputs":[{"name":"_data","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"DataEmitted","inputs":[{"name":"_data","type":"bytes","indexed":true,"internalType":"bytes"}],"anonymous":false}],"bytecode":{"object":"0x6080604052348015600e575f80fd5b5060ff8061001b5f395ff3fe6080604052348015600e575f80fd5b50600436106026575f3560e01c8063d836083e14602a575b5f80fd5b60396035366004607c565b603b565b005b8181604051604992919060e3565b604051908190038120907fe00bbfe6f6f8f1bbed2da38e3f5a139c6f9da594ab248a3cf8b44fc73627772c905f90a25050565b5f8060208385031215608c575f80fd5b823567ffffffffffffffff8082111560a2575f80fd5b818501915085601f83011260b4575f80fd5b81358181111560c1575f80fd5b86602082850101111560d1575f80fd5b60209290920196919550909350505050565b818382375f910190815291905056fea164736f6c6343000819000a","sourceMap":"58:278:1:-:0;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x6080604052348015600e575f80fd5b50600436106026575f3560e01c8063d836083e14602a575b5f80fd5b60396035366004607c565b603b565b005b8181604051604992919060e3565b604051908190038120907fe00bbfe6f6f8f1bbed2da38e3f5a139c6f9da594ab248a3cf8b44fc73627772c905f90a25050565b5f8060208385031215608c575f80fd5b823567ffffffffffffffff8082111560a2575f80fd5b818501915085601f83011260b4575f80fd5b81358181111560c1575f80fd5b86602082850101111560d1575f80fd5b60209290920196919550909350505050565b818382375f910190815291905056fea164736f6c6343000819000a","sourceMap":"58:278:1:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;245:89;;;;;;:::i;:::-;;:::i;:::-;;;321:5;;309:18;;;;;;;:::i;:::-;;;;;;;;;;;;;;;245:89;;:::o;14:591:2:-;84:6;92;145:2;133:9;124:7;120:23;116:32;113:52;;;161:1;158;151:12;113:52;201:9;188:23;230:18;271:2;263:6;260:14;257:34;;;287:1;284;277:12;257:34;325:6;314:9;310:22;300:32;;370:7;363:4;359:2;355:13;351:27;341:55;;392:1;389;382:12;341:55;432:2;419:16;458:2;450:6;447:14;444:34;;;474:1;471;464:12;444:34;519:7;514:2;505:6;501:2;497:15;493:24;490:37;487:57;;;540:1;537;530:12;487:57;571:2;563:11;;;;;593:6;;-1:-1:-1;14:591:2;;-1:-1:-1;;;;14:591:2:o;610:271::-;793:6;785;780:3;767:33;749:3;819:16;;844:13;;;819:16;610:271;-1:-1:-1;610:271:2:o","linkReferences":{}},"methodIdentifiers":{"emitData(bytes)":"d836083e"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.25+commit.b61c2a91\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"DataEmitted\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"emitData\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/emit.sol\":\"EmitEvent\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[]},\"sources\":{\"src/emit.sol\":{\"keccak256\":\"0xe078378fd445ed0cbbf1087c5013110412ab6a44850af24a15bcde945467accc\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://66c218de0059688c75903c2ba6d4066661dc6f5fa17a329dd7c385d151f3993a\",\"dweb:/ipfs/Qmby4S4N44naZ2miw3Tgdpq5Qbj4DwzJbcGapgqtd7qd26\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.25+commit.b61c2a91"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"bytes","name":"_data","type":"bytes","indexed":true}],"type":"event","name":"DataEmitted","anonymous":false},{"inputs":[{"internalType":"bytes","name":"_data","type":"bytes"}],"stateMutability":"nonpayable","type":"function","name":"emitData"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"src/emit.sol":"EmitEvent"},"evmVersion":"cancun","libraries":{}},"sources":{"src/emit.sol":{"keccak256":"0xe078378fd445ed0cbbf1087c5013110412ab6a44850af24a15bcde945467accc","urls":["bzz-raw://66c218de0059688c75903c2ba6d4066661dc6f5fa17a329dd7c385d151f3993a","dweb:/ipfs/Qmby4S4N44naZ2miw3Tgdpq5Qbj4DwzJbcGapgqtd7qd26"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"userdoc":{"version":1,"kind":"user"},"devdoc":{"version":1,"kind":"dev"},"ast":{"absolutePath":"src/emit.sol","id":62,"exportedSymbols":{"EmitEvent":[61]},"nodeType":"SourceUnit","src":"32:305:1","nodes":[{"id":46,"nodeType":"PragmaDirective","src":"32:24:1","nodes":[],"literals":["solidity","^","0.8",".15"]},{"id":61,"nodeType":"ContractDefinition","src":"58:278:1","nodes":[{"id":50,"nodeType":"EventDefinition","src":"133:39:1","nodes":[],"anonymous":false,"eventSelector":"e00bbfe6f6f8f1bbed2da38e3f5a139c6f9da594ab248a3cf8b44fc73627772c","name":"DataEmitted","nameLocation":"139:11:1","parameters":{"id":49,"nodeType":"ParameterList","parameters":[{"constant":false,"id":48,"indexed":true,"mutability":"mutable","name":"_data","nameLocation":"165:5:1","nodeType":"VariableDeclaration","scope":50,"src":"151:19:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":47,"name":"bytes","nodeType":"ElementaryTypeName","src":"151:5:1","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"150:21:1"}},{"id":60,"nodeType":"FunctionDefinition","src":"245:89:1","nodes":[],"body":{"id":59,"nodeType":"Block","src":"294:40:1","nodes":[],"statements":[{"eventCall":{"arguments":[{"id":56,"name":"_data","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":52,"src":"321:5:1","typeDescriptions":{"typeIdentifier":"t_bytes_calldata_ptr","typeString":"bytes calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_calldata_ptr","typeString":"bytes calldata"}],"id":55,"name":"DataEmitted","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":50,"src":"309:11:1","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory)"}},"id":57,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"309:18:1","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":58,"nodeType":"EmitStatement","src":"304:23:1"}]},"functionSelector":"d836083e","implemented":true,"kind":"function","modifiers":[],"name":"emitData","nameLocation":"254:8:1","parameters":{"id":53,"nodeType":"ParameterList","parameters":[{"constant":false,"id":52,"mutability":"mutable","name":"_data","nameLocation":"278:5:1","nodeType":"VariableDeclaration","scope":60,"src":"263:20:1","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_bytes_calldata_ptr","typeString":"bytes"},"typeName":{"id":51,"name":"bytes","nodeType":"ElementaryTypeName","src":"263:5:1","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"262:22:1"},"returnParameters":{"id":54,"nodeType":"ParameterList","parameters":[],"src":"294:0:1"},"scope":61,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"EmitEvent","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[61],"name":"EmitEvent","nameLocation":"67:9:1","scope":62,"usedErrors":[],"usedEvents":[50]}],"license":"MIT"},"id":1} \ No newline at end of file +{"abi":[{"type":"function","name":"emitData","inputs":[{"name":"_data","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"DataEmitted","inputs":[{"name":"data","type":"bytes","indexed":true,"internalType":"bytes"}],"anonymous":false}],"bytecode":{"object":"0x6080604052348015600e575f5ffd5b506101018061001c5f395ff3fe6080604052348015600e575f5ffd5b50600436106026575f3560e01c8063d836083e14602a575b5f5ffd5b60396035366004607c565b603b565b005b8181604051604992919060e5565b604051908190038120907fe00bbfe6f6f8f1bbed2da38e3f5a139c6f9da594ab248a3cf8b44fc73627772c905f90a25050565b5f5f60208385031215608c575f5ffd5b823567ffffffffffffffff81111560a1575f5ffd5b8301601f8101851360b0575f5ffd5b803567ffffffffffffffff81111560c5575f5ffd5b85602082840101111560d5575f5ffd5b6020919091019590945092505050565b818382375f910190815291905056fea164736f6c634300081c000a","sourceMap":"58:277:3:-:0;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x6080604052348015600e575f5ffd5b50600436106026575f3560e01c8063d836083e14602a575b5f5ffd5b60396035366004607c565b603b565b005b8181604051604992919060e5565b604051908190038120907fe00bbfe6f6f8f1bbed2da38e3f5a139c6f9da594ab248a3cf8b44fc73627772c905f90a25050565b5f5f60208385031215608c575f5ffd5b823567ffffffffffffffff81111560a1575f5ffd5b8301601f8101851360b0575f5ffd5b803567ffffffffffffffff81111560c5575f5ffd5b85602082840101111560d5575f5ffd5b6020919091019590945092505050565b818382375f910190815291905056fea164736f6c634300081c000a","sourceMap":"58:277:3:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;244:89;;;;;;:::i;:::-;;:::i;:::-;;;320:5;;308:18;;;;;;;:::i;:::-;;;;;;;;;;;;;;;244:89;;:::o;14:586:4:-;84:6;92;145:2;133:9;124:7;120:23;116:32;113:52;;;161:1;158;151:12;113:52;201:9;188:23;234:18;226:6;223:30;220:50;;;266:1;263;256:12;220:50;289:22;;342:4;334:13;;330:27;-1:-1:-1;320:55:4;;371:1;368;361:12;320:55;411:2;398:16;437:18;429:6;426:30;423:50;;;469:1;466;459:12;423:50;514:7;509:2;500:6;496:2;492:15;488:24;485:37;482:57;;;535:1;532;525:12;482:57;566:2;558:11;;;;;588:6;;-1:-1:-1;14:586:4;-1:-1:-1;;;14:586:4:o;605:271::-;788:6;780;775:3;762:33;744:3;814:16;;839:13;;;814:16;605:271;-1:-1:-1;605:271:4:o","linkReferences":{}},"methodIdentifiers":{"emitData(bytes)":"d836083e"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.28+commit.7893614a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"DataEmitted\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"emitData\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/emit.sol\":\"EmitEvent\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[]},\"sources\":{\"src/emit.sol\":{\"keccak256\":\"0x43f7daec3aae0105cf0e7cb8a2ecf6f53e7140601e08867d3b859d2350fd2b06\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://5de28fb969a4e6aee942e0558a34ec0fb572602384425d3c85744348e53e1e32\",\"dweb:/ipfs/QmTiug6PULK2Yzix44CxjdjSF75YrHRAUEKEAHHZFR7fPn\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.28+commit.7893614a"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"bytes","name":"data","type":"bytes","indexed":true}],"type":"event","name":"DataEmitted","anonymous":false},{"inputs":[{"internalType":"bytes","name":"_data","type":"bytes"}],"stateMutability":"nonpayable","type":"function","name":"emitData"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"src/emit.sol":"EmitEvent"},"evmVersion":"cancun","libraries":{}},"sources":{"src/emit.sol":{"keccak256":"0x43f7daec3aae0105cf0e7cb8a2ecf6f53e7140601e08867d3b859d2350fd2b06","urls":["bzz-raw://5de28fb969a4e6aee942e0558a34ec0fb572602384425d3c85744348e53e1e32","dweb:/ipfs/QmTiug6PULK2Yzix44CxjdjSF75YrHRAUEKEAHHZFR7fPn"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"userdoc":{"version":1,"kind":"user"},"devdoc":{"version":1,"kind":"dev"},"ast":{"absolutePath":"src/emit.sol","id":209,"exportedSymbols":{"EmitEvent":[208]},"nodeType":"SourceUnit","src":"32:304:3","nodes":[{"id":193,"nodeType":"PragmaDirective","src":"32:24:3","nodes":[],"literals":["solidity","^","0.8",".15"]},{"id":208,"nodeType":"ContractDefinition","src":"58:277:3","nodes":[{"id":197,"nodeType":"EventDefinition","src":"133:38:3","nodes":[],"anonymous":false,"eventSelector":"e00bbfe6f6f8f1bbed2da38e3f5a139c6f9da594ab248a3cf8b44fc73627772c","name":"DataEmitted","nameLocation":"139:11:3","parameters":{"id":196,"nodeType":"ParameterList","parameters":[{"constant":false,"id":195,"indexed":true,"mutability":"mutable","name":"data","nameLocation":"165:4:3","nodeType":"VariableDeclaration","scope":197,"src":"151:18:3","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":194,"name":"bytes","nodeType":"ElementaryTypeName","src":"151:5:3","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"150:20:3"}},{"id":207,"nodeType":"FunctionDefinition","src":"244:89:3","nodes":[],"body":{"id":206,"nodeType":"Block","src":"293:40:3","nodes":[],"statements":[{"eventCall":{"arguments":[{"id":203,"name":"_data","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":199,"src":"320:5:3","typeDescriptions":{"typeIdentifier":"t_bytes_calldata_ptr","typeString":"bytes calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_calldata_ptr","typeString":"bytes calldata"}],"id":202,"name":"DataEmitted","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":197,"src":"308:11:3","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory)"}},"id":204,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"308:18:3","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":205,"nodeType":"EmitStatement","src":"303:23:3"}]},"functionSelector":"d836083e","implemented":true,"kind":"function","modifiers":[],"name":"emitData","nameLocation":"253:8:3","parameters":{"id":200,"nodeType":"ParameterList","parameters":[{"constant":false,"id":199,"mutability":"mutable","name":"_data","nameLocation":"277:5:3","nodeType":"VariableDeclaration","scope":207,"src":"262:20:3","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_bytes_calldata_ptr","typeString":"bytes"},"typeName":{"id":198,"name":"bytes","nodeType":"ElementaryTypeName","src":"262:5:3","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"261:22:3"},"returnParameters":{"id":201,"nodeType":"ParameterList","parameters":[],"src":"293:0:3"},"scope":208,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"EmitEvent","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[208],"name":"EmitEvent","nameLocation":"67:9:3","scope":209,"usedErrors":[],"usedEvents":[197]}],"license":"MIT"},"id":3} \ No newline at end of file diff --git a/op-e2e/e2eutils/contracts/src/Invoker.sol b/op-e2e/e2eutils/contracts/src/Invoker.sol index 6a8118ddb22..2181fdbcb90 100644 --- a/op-e2e/e2eutils/contracts/src/Invoker.sol +++ b/op-e2e/e2eutils/contracts/src/Invoker.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.15; contract Invoker { - event PrecompileInvoked(address indexed precompile, bytes result); + event PrecompileInvoked(address indexed precompile, bytes result, bytes delegateCallResult); error PrecompileCallFailed(); function invokePrecompile(address _precompile, bytes memory _input) external { @@ -11,6 +11,11 @@ contract Invoker { if (!success) { revert PrecompileCallFailed(); } - emit PrecompileInvoked(_precompile, result); + bytes memory delegateCallResult; + (success, delegateCallResult) = _precompile.delegatecall(_input); + if (!success) { + revert PrecompileCallFailed(); + } + emit PrecompileInvoked(_precompile, result, delegateCallResult); } } diff --git a/op-e2e/e2eutils/opnode/opnode.go b/op-e2e/e2eutils/opnode/opnode.go index 3a8cf3b6963..db5b7f5695f 100644 --- a/op-e2e/e2eutils/opnode/opnode.go +++ b/op-e2e/e2eutils/opnode/opnode.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/node/runcfg" "github.com/ethereum-optimism/optimism/op-node/p2p" "github.com/ethereum-optimism/optimism/op-service/cliapp" + "github.com/ethereum-optimism/optimism/op-service/clock" "github.com/ethereum-optimism/optimism/op-service/endpoint" "github.com/ethereum-optimism/optimism/op-service/eth" ) @@ -54,7 +55,7 @@ func (o *Opnode) P2P() p2p.Node { var _ services.RollupNode = (*Opnode)(nil) -func NewOpnode(l log.Logger, c *config.Config, errFn func(error)) (*Opnode, error) { +func NewOpnode(l log.Logger, c *config.Config, clk clock.Clock, errFn func(error)) (*Opnode, error) { if err := c.Check(); err != nil { return nil, err } @@ -70,7 +71,7 @@ func NewOpnode(l log.Logger, c *config.Config, errFn func(error)) (*Opnode, erro l.Warn("closed op-node!") }() } - node, err := rollupNode.New(context.Background(), c, l, "", metrics.NewMetrics("")) + node, err := rollupNode.New(context.Background(), c, l, "", metrics.NewMetrics(""), clk) if err != nil { return nil, err } diff --git a/op-e2e/interop/interop_test.go b/op-e2e/interop/interop_test.go index ca03e511274..080b06fda1c 100644 --- a/op-e2e/interop/interop_test.go +++ b/op-e2e/interop/interop_test.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum-optimism/optimism/op-service/dial" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/sources/batching" + "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" @@ -442,7 +443,7 @@ func TestProposals(t *testing.T) { head, err := ethClient.BlockByNumber(context.Background(), nil) require.NoError(t, err) - game, err := factory.GetGame(context.Background(), 0, head.Hash()) + game, err := factory.GetGame(context.Background(), 0, rpcblock.ByHash(head.Hash())) require.NoError(t, err) require.Equal(t, uint32(4) /* super permissionless */, game.GameType) } diff --git a/op-e2e/interop/supersystem_l2.go b/op-e2e/interop/supersystem_l2.go index fc3fb282a58..f62f3924078 100644 --- a/op-e2e/interop/supersystem_l2.go +++ b/op-e2e/interop/supersystem_l2.go @@ -22,6 +22,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup/sync" l2os "github.com/ethereum-optimism/optimism/op-proposer/proposer" "github.com/ethereum-optimism/optimism/op-service/client" + "github.com/ethereum-optimism/optimism/op-service/clock" "github.com/ethereum-optimism/optimism/op-service/dial" "github.com/ethereum-optimism/optimism/op-service/endpoint" oplog "github.com/ethereum-optimism/optimism/op-service/log" @@ -198,7 +199,7 @@ func (s *interopE2ESystem) newNodeForL2( ConfigPersistence: config.DisabledConfigPersistence{}, } opNode, err := opnode.NewOpnode(logger.New("service", "op-node"), - nodeCfg, func(err error) { + nodeCfg, clock.SystemClock, func(err error) { s.t.Error(err) }) require.NoError(s.t, err) @@ -299,8 +300,15 @@ func (s *interopE2ESystem) newBatcherForL2( DataAvailabilityType: daType, CompressionAlgo: derive.Brotli, } + + batcherContext, batcherCancel := context.WithCancel(context.Background()) + var closeAppFn context.CancelCauseFunc = func(cause error) { + s.t.Fatalf("closeAppFn called, batcher hit a critical error: %v", cause) + batcherCancel() + } + batcher, err := bss.BatcherServiceFromCLIConfig( - context.Background(), "0.0.1", batcherCLIConfig, + batcherContext, closeAppFn, "0.0.1", batcherCLIConfig, logger.New("service", "batcher")) require.NoError(s.t, err) require.NoError(s.t, batcher.Start(context.Background())) diff --git a/op-e2e/system/conductor/sequencer_failover_setup.go b/op-e2e/system/conductor/sequencer_failover_setup.go index 8a0832f7be9..38a67f51c0f 100644 --- a/op-e2e/system/conductor/sequencer_failover_setup.go +++ b/op-e2e/system/conductor/sequencer_failover_setup.go @@ -308,7 +308,13 @@ func setupBatcher(t *testing.T, sys *e2esys.System, conductors map[string]*condu CompressionAlgo: derive.Zlib, } - batcher, err := bss.BatcherServiceFromCLIConfig(context.Background(), "0.0.1", batcherCLIConfig, sys.Cfg.Loggers["batcher"]) + batcherContext, batcherCancel := context.WithCancel(context.Background()) + var closeAppFn context.CancelCauseFunc = func(cause error) { + t.Fatalf("closeAppFn called, batcher hit a critical error: %v", cause) + batcherCancel() + } + + batcher, err := bss.BatcherServiceFromCLIConfig(batcherContext, closeAppFn, "0.0.1", batcherCLIConfig, sys.Cfg.Loggers["batcher"]) require.NoError(t, err) err = batcher.Start(context.Background()) require.NoError(t, err) diff --git a/op-e2e/system/e2esys/setup.go b/op-e2e/system/e2esys/setup.go index 3c19a379a91..265e14c6ed0 100644 --- a/op-e2e/system/e2esys/setup.go +++ b/op-e2e/system/e2esys/setup.go @@ -612,10 +612,10 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System, // Automatically stop the system at the end of the test t.Cleanup(sys.Close) - c := clock.SystemClock + clk := clock.SystemClock if cfg.SupportL1TimeTravel { sys.TimeTravelClock = clock.NewAdvancingClock(100 * time.Millisecond) - c = sys.TimeTravelClock + clk = sys.TimeTravelClock } if err := cfg.DeployConfig.Check(testlog.Logger(t, log.LevelInfo)); err != nil { @@ -747,7 +747,7 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System, // Initialize nodes l1Geth, _, err := geth.InitL1( - cfg.DeployConfig.L1BlockTime, cfg.L1FinalizedDistance, l1Genesis, c, + cfg.DeployConfig.L1BlockTime, cfg.L1FinalizedDistance, l1Genesis, clk, path.Join(cfg.BlobsPath, "l1_el"), bcn, cfg.GethOptions[RoleL1]...) if err != nil { return nil, err @@ -887,7 +887,7 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System, c.Rollup.LogDescription(cfg.Loggers[name], chaincfg.L2ChainIDToNetworkDisplayName) l := cfg.Loggers[name] - n, err := opnode.NewOpnode(l, &c, func(err error) { + n, err := opnode.NewOpnode(l, &c, clk, func(err error) { t.Error(err) }) require.NoError(t, err) @@ -1014,7 +1014,12 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System, } // Batch Submitter - batcher, err := bss.BatcherServiceFromCLIConfig(context.Background(), "0.0.1", batcherCLIConfig, sys.Cfg.Loggers["batcher"]) + batcherContext, batcherCancel := context.WithCancel(context.Background()) + var closeAppFn context.CancelCauseFunc = func(cause error) { + t.Fatalf("closeAppFn called, batcher hit a critical error: %v", cause) + batcherCancel() + } + batcher, err := bss.BatcherServiceFromCLIConfig(batcherContext, closeAppFn, "0.0.1", batcherCLIConfig, sys.Cfg.Loggers["batcher"]) if err != nil { return nil, fmt.Errorf("failed to setup batch submitter: %w", err) } diff --git a/op-e2e/system/p2p/reqresp_test.go b/op-e2e/system/p2p/reqresp_test.go index 2680f9fb910..068977c1722 100644 --- a/op-e2e/system/p2p/reqresp_test.go +++ b/op-e2e/system/p2p/reqresp_test.go @@ -157,7 +157,7 @@ func TestSystemP2PAltSync(t *testing.T) { // Ensure L1 chain configuration is provided for the sync node syncNodeCfg.L1ChainConfig = sys.L1GenesisCfg.Config - syncerNode, err := rollupNode.New(ctx, syncNodeCfg, cfg.Loggers["syncer"], "", metrics.NewMetrics("")) + syncerNode, err := rollupNode.New(ctx, syncNodeCfg, cfg.Loggers["syncer"], "", metrics.NewMetrics(""), nil) require.NoError(t, err) err = syncerNode.Start(ctx) require.NoError(t, err) diff --git a/op-node/cmd/main.go b/op-node/cmd/main.go index 68af603022f..deacab727fb 100644 --- a/op-node/cmd/main.go +++ b/op-node/cmd/main.go @@ -94,7 +94,7 @@ func RollupNodeMain(ctx *cli.Context, closeApp context.CancelCauseFunc) (cliapp. cfg.Rollup.LogDescription(log, chaincfg.L2ChainIDToNetworkDisplayName) } - n, err := node.New(ctx.Context, cfg, log, VersionWithMeta, m) + n, err := node.New(ctx.Context, cfg, log, VersionWithMeta, m, nil) if err != nil { return nil, fmt.Errorf("unable to create the rollup node: %w", err) } diff --git a/op-node/node/node.go b/op-node/node/node.go index c81085a4268..fadc303b727 100644 --- a/op-node/node/node.go +++ b/op-node/node/node.go @@ -34,6 +34,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup/sequencing" "github.com/ethereum-optimism/optimism/op-node/rollup/sync" "github.com/ethereum-optimism/optimism/op-service/client" + "github.com/ethereum-optimism/optimism/op-service/clock" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/event" "github.com/ethereum-optimism/optimism/op-service/httputil" @@ -103,6 +104,7 @@ type OpNode struct { // Retain the config to test for active features rather than test for runtime state. cfg *config.Config log log.Logger + clock clock.Clock appVersion string metrics *metrics.Metrics @@ -153,15 +155,15 @@ type OpNode struct { // New creates a new OpNode instance. // The provided ctx argument is for the span of initialization only; // the node will immediately Stop(ctx) before finishing initialization if the context is canceled during initialization. -func New(ctx context.Context, cfg *config.Config, log log.Logger, appVersion string, m *metrics.Metrics) (*OpNode, error) { - return NewWithOverride(ctx, cfg, log, appVersion, m, InitializationOverrides{}) +func New(ctx context.Context, cfg *config.Config, log log.Logger, appVersion string, m *metrics.Metrics, clk clock.Clock) (*OpNode, error) { + return NewWithOverride(ctx, cfg, log, appVersion, m, clk, InitializationOverrides{}) } // NewWithOverride creates a new OpNode instance with optional initialization overrides. // This allows callers to override specific initialization steps, enabling resource sharing // (e.g., shared L1Client across multiple nodes) without duplicating connections or caches. // If override is nil or any of its fields are nil, the default initialization is used for those steps. -func NewWithOverride(ctx context.Context, cfg *config.Config, log log.Logger, appVersion string, m *metrics.Metrics, override InitializationOverrides) (*OpNode, error) { +func NewWithOverride(ctx context.Context, cfg *config.Config, log log.Logger, appVersion string, m *metrics.Metrics, clk clock.Clock, override InitializationOverrides) (*OpNode, error) { if err := cfg.Check(); err != nil { return nil, err } @@ -169,6 +171,7 @@ func NewWithOverride(ctx context.Context, cfg *config.Config, log log.Logger, ap n := &OpNode{ cfg: cfg, log: log, + clock: clk, appVersion: appVersion, metrics: m, rollupHalt: cfg.RollupHalt, @@ -712,7 +715,7 @@ func initP2P(cfg *config.Config, node *OpNode) (*p2p.NodeP2P, error) { } // embed syncDeriver and tracer(optional) to the blockReceiver to handle unsafe payloads via p2p rec := p2p.NewBlockReceiver(node.log, node.metrics, node.l2Driver.SyncDeriver, node.cfg.Tracer) - p2pNode, err := p2p.NewNodeP2P(node.resourcesCtx, &cfg.Rollup, node.log, cfg.P2P, rec, node.l2Source, node.runCfg, node.metrics) + p2pNode, err := p2p.NewNodeP2P(node.resourcesCtx, &cfg.Rollup, node.log, cfg.P2P, rec, node.l2Source, node.runCfg, node.metrics, node.clock) if err != nil { return nil, err } diff --git a/op-node/p2p/gossip.go b/op-node/p2p/gossip.go index 2a8442a9d23..7b778561901 100644 --- a/op-node/p2p/gossip.go +++ b/op-node/p2p/gossip.go @@ -21,6 +21,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/clock" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/ptr" opsigner "github.com/ethereum-optimism/optimism/op-service/signer" @@ -264,7 +265,7 @@ func (sb *seenBlocks) markSeen(h common.Hash) { sb.blockHashes = append(sb.blockHashes, h) } -func BuildBlocksValidator(log log.Logger, cfg *rollup.Config, runCfg GossipRuntimeConfig, blockVersion eth.BlockVersion, gossipConf GossipSetupConfigurables) pubsub.ValidatorEx { +func BuildBlocksValidator(log log.Logger, cfg *rollup.Config, runCfg GossipRuntimeConfig, blockVersion eth.BlockVersion, gossipConf GossipSetupConfigurables, clk clock.Clock) pubsub.ValidatorEx { // Seen block hashes per block height // uint64 -> *seenBlocks blockHeightLRU, err := lru.New[uint64, *seenBlocks](1000) @@ -331,6 +332,9 @@ func BuildBlocksValidator(log log.Logger, cfg *rollup.Config, runCfg GossipRunti // rounding down to seconds is fine here. now := uint64(time.Now().Unix()) + if clk != nil { + now = uint64(clk.Now().Unix()) + } // [REJECT] if the `payload.timestamp` is older than the configured threshold threshold := uint64(gossipConf.GetGossipTimestampThreshold().Seconds()) @@ -387,7 +391,7 @@ func BuildBlocksValidator(log log.Logger, cfg *rollup.Config, runCfg GossipRunti log.Warn("payload is on v3 topic, but has nil blob gas used", "bad_hash", payload.BlockHash.String()) return pubsub.ValidationReject // [REJECT] if the block is on a topic >= V3 and has a non-zero blob gas used field pre-Jovian - } else if !cfg.IsDAFootprintBlockLimit(uint64(payload.Timestamp)) && *payload.BlobGasUsed != 0 { + } else if !cfg.IsJovian(uint64(payload.Timestamp)) && *payload.BlobGasUsed != 0 { log.Warn("payload is on v3 topic, but has non-zero blob gas used", "bad_hash", payload.BlockHash.String(), "blob_gas_used", *payload.BlobGasUsed) return pubsub.ValidationReject @@ -630,11 +634,11 @@ func (p *publisher) Close() error { return errors.Join(e1, e2) } -func JoinGossip(self peer.ID, ps *pubsub.PubSub, log log.Logger, cfg *rollup.Config, runCfg GossipRuntimeConfig, gossipIn GossipIn, gossipConf GossipSetupConfigurables) (GossipOut, error) { +func JoinGossip(self peer.ID, ps *pubsub.PubSub, log log.Logger, cfg *rollup.Config, runCfg GossipRuntimeConfig, gossipIn GossipIn, gossipConf GossipSetupConfigurables, clk clock.Clock) (GossipOut, error) { p2pCtx, p2pCancel := context.WithCancel(context.Background()) v1Logger := log.New("topic", "blocksV1") - blocksV1Validator := guardGossipValidator(log, logValidationResult(self, "validated blockv1", v1Logger, BuildBlocksValidator(v1Logger, cfg, runCfg, eth.BlockV1, gossipConf))) + blocksV1Validator := guardGossipValidator(log, logValidationResult(self, "validated blockv1", v1Logger, BuildBlocksValidator(v1Logger, cfg, runCfg, eth.BlockV1, gossipConf, clk))) blocksV1, err := newBlockTopic(p2pCtx, blocksTopicV1(cfg), ps, v1Logger, gossipIn, blocksV1Validator) if err != nil { p2pCancel() @@ -642,7 +646,7 @@ func JoinGossip(self peer.ID, ps *pubsub.PubSub, log log.Logger, cfg *rollup.Con } v2Logger := log.New("topic", "blocksV2") - blocksV2Validator := guardGossipValidator(log, logValidationResult(self, "validated blockv2", v2Logger, BuildBlocksValidator(v2Logger, cfg, runCfg, eth.BlockV2, gossipConf))) + blocksV2Validator := guardGossipValidator(log, logValidationResult(self, "validated blockv2", v2Logger, BuildBlocksValidator(v2Logger, cfg, runCfg, eth.BlockV2, gossipConf, clk))) blocksV2, err := newBlockTopic(p2pCtx, blocksTopicV2(cfg), ps, v2Logger, gossipIn, blocksV2Validator) if err != nil { p2pCancel() @@ -650,7 +654,7 @@ func JoinGossip(self peer.ID, ps *pubsub.PubSub, log log.Logger, cfg *rollup.Con } v3Logger := log.New("topic", "blocksV3") - blocksV3Validator := guardGossipValidator(log, logValidationResult(self, "validated blockv3", v3Logger, BuildBlocksValidator(v3Logger, cfg, runCfg, eth.BlockV3, gossipConf))) + blocksV3Validator := guardGossipValidator(log, logValidationResult(self, "validated blockv3", v3Logger, BuildBlocksValidator(v3Logger, cfg, runCfg, eth.BlockV3, gossipConf, clk))) blocksV3, err := newBlockTopic(p2pCtx, blocksTopicV3(cfg), ps, v3Logger, gossipIn, blocksV3Validator) if err != nil { p2pCancel() @@ -658,7 +662,7 @@ func JoinGossip(self peer.ID, ps *pubsub.PubSub, log log.Logger, cfg *rollup.Con } v4Logger := log.New("topic", "blocksV4") - blocksV4Validator := guardGossipValidator(log, logValidationResult(self, "validated blockv4", v4Logger, BuildBlocksValidator(v4Logger, cfg, runCfg, eth.BlockV4, gossipConf))) + blocksV4Validator := guardGossipValidator(log, logValidationResult(self, "validated blockv4", v4Logger, BuildBlocksValidator(v4Logger, cfg, runCfg, eth.BlockV4, gossipConf, clk))) blocksV4, err := newBlockTopic(p2pCtx, blocksTopicV4(cfg), ps, v4Logger, gossipIn, blocksV4Validator) if err != nil { p2pCancel() diff --git a/op-node/p2p/gossip_test.go b/op-node/p2p/gossip_test.go index c98cb2ae440..10eb6fe4983 100644 --- a/op-node/p2p/gossip_test.go +++ b/op-node/p2p/gossip_test.go @@ -12,6 +12,7 @@ import ( "github.com/golang/snappy" "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/clock" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/ptr" opsigner "github.com/ethereum-optimism/optimism/op-service/signer" @@ -156,14 +157,14 @@ func TestBlockValidator(t *testing.T) { // Create a mock gossip configuration for testing mockGossipConf := &mockGossipSetupConfigurablesWithThreshold{threshold: 60 * time.Second} - v2Validator := BuildBlocksValidator(testlog.Logger(t, log.LevelCrit), cfg, runCfg, eth.BlockV2, mockGossipConf) - v3Validator := BuildBlocksValidator(testlog.Logger(t, log.LevelCrit), cfg, runCfg, eth.BlockV3, mockGossipConf) - v4Validator := BuildBlocksValidator(testlog.Logger(t, log.LevelDebug), cfg, runCfg, eth.BlockV4, mockGossipConf) + v2Validator := BuildBlocksValidator(testlog.Logger(t, log.LevelCrit), cfg, runCfg, eth.BlockV2, mockGossipConf, clock.SystemClock) + v3Validator := BuildBlocksValidator(testlog.Logger(t, log.LevelCrit), cfg, runCfg, eth.BlockV3, mockGossipConf, clock.SystemClock) + v4Validator := BuildBlocksValidator(testlog.Logger(t, log.LevelDebug), cfg, runCfg, eth.BlockV4, mockGossipConf, clock.SystemClock) jovianCfg := &rollup.Config{ L2ChainID: big.NewInt(100), JovianTime: ptr.New(uint64(0)), } - v4JovianValidator := BuildBlocksValidator(testlog.Logger(t, log.LevelCrit), jovianCfg, runCfg, eth.BlockV4, mockGossipConf) + v4JovianValidator := BuildBlocksValidator(testlog.Logger(t, log.LevelCrit), jovianCfg, runCfg, eth.BlockV4, mockGossipConf, clock.SystemClock) zero, one := uint64(0), uint64(1) beaconHash, withdrawalsRoot := common.HexToHash("0x1234"), common.HexToHash("0x9876") @@ -263,7 +264,7 @@ func TestGossipTimestampThreshold(t *testing.T) { mockConfig := &mockGossipSetupConfigurablesWithThreshold{threshold: tc.threshold} // Create validator with the mock config - validator := BuildBlocksValidator(testlog.Logger(t, log.LevelCrit), cfg, runCfg, eth.BlockV2, mockConfig) + validator := BuildBlocksValidator(testlog.Logger(t, log.LevelCrit), cfg, runCfg, eth.BlockV2, mockConfig, clock.SystemClock) // Create payload with the specific timestamp payload := createExecutionPayload(types.Withdrawals{}, nil, nil, nil) diff --git a/op-node/p2p/host_test.go b/op-node/p2p/host_test.go index 9f461b2fed7..621fc20747d 100644 --- a/op-node/p2p/host_test.go +++ b/op-node/p2p/host_test.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/metrics" "github.com/ethereum-optimism/optimism/op-node/p2p/store" "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/clock" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testutils" @@ -120,7 +121,7 @@ func TestP2PFull(t *testing.T) { runCfgB := &testutils.MockRuntimeConfig{P2PSeqAddress: common.Address{0x42}} logA := testlog.Logger(t, log.LevelError).New("host", "A") - nodeA, err := NewNodeP2P(context.Background(), &rollup.Config{}, logA, &confA, &mockGossipIn{}, nil, runCfgA, metrics.NoopMetrics) + nodeA, err := NewNodeP2P(context.Background(), &rollup.Config{}, logA, &confA, &mockGossipIn{}, nil, runCfgA, metrics.NoopMetrics, clock.SystemClock) require.NoError(t, err) defer nodeA.Close() @@ -149,7 +150,7 @@ func TestP2PFull(t *testing.T) { logB := testlog.Logger(t, log.LevelError).New("host", "B") - nodeB, err := NewNodeP2P(context.Background(), &rollup.Config{}, logB, &confB, &mockGossipIn{}, nil, runCfgB, metrics.NoopMetrics) + nodeB, err := NewNodeP2P(context.Background(), &rollup.Config{}, logB, &confB, &mockGossipIn{}, nil, runCfgB, metrics.NoopMetrics, clock.SystemClock) require.NoError(t, err) defer nodeB.Close() hostB := nodeB.Host() @@ -321,7 +322,7 @@ func TestDiscovery(t *testing.T) { resourcesCtx, resourcesCancel := context.WithCancel(context.Background()) defer resourcesCancel() - nodeA, err := NewNodeP2P(context.Background(), rollupCfg, logA, &confA, &mockGossipIn{}, nil, runCfgA, metrics.NoopMetrics) + nodeA, err := NewNodeP2P(context.Background(), rollupCfg, logA, &confA, &mockGossipIn{}, nil, runCfgA, metrics.NoopMetrics, clock.SystemClock) require.NoError(t, err) defer nodeA.Close() hostA := nodeA.Host() @@ -336,7 +337,7 @@ func TestDiscovery(t *testing.T) { confB.DiscoveryDB = discDBC // Start B - nodeB, err := NewNodeP2P(context.Background(), rollupCfg, logB, &confB, &mockGossipIn{}, nil, runCfgB, metrics.NoopMetrics) + nodeB, err := NewNodeP2P(context.Background(), rollupCfg, logB, &confB, &mockGossipIn{}, nil, runCfgB, metrics.NoopMetrics, clock.SystemClock) require.NoError(t, err) defer nodeB.Close() hostB := nodeB.Host() @@ -351,7 +352,7 @@ func TestDiscovery(t *testing.T) { }}) // Start C - nodeC, err := NewNodeP2P(context.Background(), rollupCfg, logC, &confC, &mockGossipIn{}, nil, runCfgC, metrics.NoopMetrics) + nodeC, err := NewNodeP2P(context.Background(), rollupCfg, logC, &confC, &mockGossipIn{}, nil, runCfgC, metrics.NoopMetrics, clock.SystemClock) require.NoError(t, err) defer nodeC.Close() hostC := nodeC.Host() diff --git a/op-node/p2p/node.go b/op-node/p2p/node.go index 149eec5a8f8..4c60f8b46df 100644 --- a/op-node/p2p/node.go +++ b/op-node/p2p/node.go @@ -61,6 +61,7 @@ func NewNodeP2P( l2Chain L2Chain, runCfg GossipRuntimeConfig, metrics metrics.Metricer, + clock clock.Clock, ) (*NodeP2P, error) { if setup == nil { return nil, errors.New("p2p node cannot be created without setup") @@ -69,7 +70,7 @@ func NewNodeP2P( return nil, errors.New("SetupP2P.Disabled is true") } var n NodeP2P - if err := n.init(resourcesCtx, rollupCfg, log, setup, gossipIn, l2Chain, runCfg, metrics); err != nil { + if err := n.init(resourcesCtx, rollupCfg, log, setup, gossipIn, l2Chain, runCfg, metrics, clock); err != nil { closeErr := n.Close() if closeErr != nil { log.Error("failed to close p2p after starting with err", "closeErr", closeErr, "err", err) @@ -93,6 +94,7 @@ func (n *NodeP2P) init( l2Chain L2Chain, runCfg GossipRuntimeConfig, metrics metrics.Metricer, + clk clock.Clock, ) error { bwc := p2pmetrics.NewBandwidthCounter() @@ -162,7 +164,7 @@ func (n *NodeP2P) init( if err != nil { return fmt.Errorf("failed to start gossipsub router: %w", err) } - n.gsOut, err = JoinGossip(n.host.ID(), n.gs, log, rollupCfg, runCfg, gossipIn, setup) + n.gsOut, err = JoinGossip(n.host.ID(), n.gs, log, rollupCfg, runCfg, gossipIn, setup, clk) if err != nil { return fmt.Errorf("failed to join blocks gossip topic: %w", err) } diff --git a/op-node/rollup/attributes/engine_consolidate_test.go b/op-node/rollup/attributes/engine_consolidate_test.go index 47086c518e8..7c57ef3cb6d 100644 --- a/op-node/rollup/attributes/engine_consolidate_test.go +++ b/op-node/rollup/attributes/engine_consolidate_test.go @@ -49,7 +49,7 @@ func jovianArgs() matchArgs { validTxData, _ = validTx.MarshalBinary() minBaseFee = uint64(1e9) - validJovianExtraData = eth.BytesMax32(eip1559.EncodeMinBaseFeeExtraData( + validJovianExtraData = eth.BytesMax32(eip1559.EncodeJovianExtraData( *defaultOpConfig.EIP1559DenominatorCanyon, defaultOpConfig.EIP1559Elasticity, minBaseFee)) validJovianEIP1559Params = new(eth.Bytes8) ) @@ -347,7 +347,7 @@ func TestAttributesMatch(t *testing.T) { { args: jovianArgsMinBaseFeeMissingFromBlock(), rollupCfg: cfg(forks.Jovian), - err: "invalid block extraData: MinBaseFee extraData should be 17 bytes, got 9", + err: "invalid block extraData: Jovian extraData should be 17 bytes, got 9", desc: "missingMinBaseFee", }, { diff --git a/op-node/rollup/derive/attributes.go b/op-node/rollup/derive/attributes.go index 5d201f1ded5..22361aeb519 100644 --- a/op-node/rollup/derive/attributes.go +++ b/op-node/rollup/derive/attributes.go @@ -146,7 +146,7 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex } if ba.rollupCfg.IsJovianActivationBlock(nextL2Time) { - jovian, err := JovianNetworkUpgradeTransactions(ba.rollupCfg.IsDAFootprintBlockLimit(nextL2Time), ba.rollupCfg.IsOperatorFeeFix(nextL2Time)) + jovian, err := JovianNetworkUpgradeTransactions() if err != nil { return nil, NewCriticalError(fmt.Errorf("failed to build jovian network upgrade txs: %w", err)) } @@ -206,7 +206,7 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex r.EIP1559Params = new(eth.Bytes8) *r.EIP1559Params = sysConfig.EIP1559Params } - if ba.rollupCfg.IsMinBaseFee(nextL2Time) { + if ba.rollupCfg.IsJovian(nextL2Time) { r.MinBaseFee = &sysConfig.MinBaseFee } return r, nil diff --git a/op-node/rollup/derive/jovian_upgrade_transactions.go b/op-node/rollup/derive/jovian_upgrade_transactions.go index c7d544851d4..3feb45ca883 100644 --- a/op-node/rollup/derive/jovian_upgrade_transactions.go +++ b/op-node/rollup/derive/jovian_upgrade_transactions.go @@ -128,24 +128,20 @@ func OperatorFeeFixUpgradeTransactions() ([]hexutil.Bytes, error) { return upgradeTxns, nil } -func JovianNetworkUpgradeTransactions(IsDAFootprintBlockLimit, IsOperatorFeeFix bool) ([]hexutil.Bytes, error) { +func JovianNetworkUpgradeTransactions() ([]hexutil.Bytes, error) { upgradeTxs := make([]hexutil.Bytes, 0) - if IsDAFootprintBlockLimit { - txs, err := DAFootprintNetworkUpgradeTransactions() - if err != nil { - return nil, err - } - upgradeTxs = append(upgradeTxs, txs...) + txs, err := DAFootprintNetworkUpgradeTransactions() + if err != nil { + return nil, err } + upgradeTxs = append(upgradeTxs, txs...) - if IsOperatorFeeFix { - txs, err := OperatorFeeFixUpgradeTransactions() - if err != nil { - return nil, err - } - upgradeTxs = append(upgradeTxs, txs...) + txs, err = OperatorFeeFixUpgradeTransactions() + if err != nil { + return nil, err } + upgradeTxs = append(upgradeTxs, txs...) return upgradeTxs, nil } diff --git a/op-node/rollup/derive/jovian_upgrade_transactions_test.go b/op-node/rollup/derive/jovian_upgrade_transactions_test.go index eef2b2e001a..b7970bfd01d 100644 --- a/op-node/rollup/derive/jovian_upgrade_transactions_test.go +++ b/op-node/rollup/derive/jovian_upgrade_transactions_test.go @@ -9,7 +9,7 @@ import ( ) func TestJovianNetworkTransactions(t *testing.T) { - upgradeTxns, err := JovianNetworkUpgradeTransactions(true, true) + upgradeTxns, err := JovianNetworkUpgradeTransactions() require.NoError(t, err) require.Len(t, upgradeTxns, 5) diff --git a/op-node/rollup/derive/payload_util.go b/op-node/rollup/derive/payload_util.go index 6e7bf11f1ca..e745ef924b4 100644 --- a/op-node/rollup/derive/payload_util.go +++ b/op-node/rollup/derive/payload_util.go @@ -104,11 +104,9 @@ func PayloadToSystemConfig(rollupCfg *rollup.Config, payload *eth.ExecutionPaylo }) } - if rollupCfg.IsMinBaseFee(uint64(payload.Timestamp)) { + if rollupCfg.IsJovian(uint64(payload.Timestamp)) { // ValidateOptimismExtraData returning a nil error guarantees that m is not nil r.MinBaseFee = *m - } - if rollupCfg.IsDAFootprintBlockLimit(uint64(payload.Timestamp)) { r.DAFootprintGasScalar = info.DAFootprintGasScalar } return r, nil diff --git a/op-node/rollup/interop/indexing/attributes.go b/op-node/rollup/interop/indexing/attributes.go index 28d5782ef3f..b0feb434896 100644 --- a/op-node/rollup/interop/indexing/attributes.go +++ b/op-node/rollup/interop/indexing/attributes.go @@ -54,7 +54,7 @@ func AttributesToReplaceInvalidBlock(invalidatedBlock *eth.ExecutionPayloadEnvel // unfortunately, the engine API needs the inner value, not the extra-data. // So we translate it here. extraData := invalidatedBlock.ExecutionPayload.ExtraData - denominator, elasticity, minBaseFee := eip1559.DecodeMinBaseFeeExtraData(extraData) + denominator, elasticity, minBaseFee := eip1559.DecodeJovianExtraData(extraData) eip1559Params := eth.Bytes8(eip1559.EncodeHolocene1559Params(denominator, elasticity)) attrs := ð.PayloadAttributes{ diff --git a/op-node/rollup/sequencing/origin_selector.go b/op-node/rollup/sequencing/origin_selector.go index 255522d4644..c5f23407d76 100644 --- a/op-node/rollup/sequencing/origin_selector.go +++ b/op-node/rollup/sequencing/origin_selector.go @@ -73,6 +73,8 @@ func (los *L1OriginSelector) OnEvent(ctx context.Context, ev event.Event) bool { // FindL1Origin determines what the next L1 Origin should be. // The L1 Origin is either the L2 Head's Origin, or the following L1 block // if the next L2 block's time is greater than or equal to the L2 Head's Origin. +// The origin selection relies purely on block numbers and it is the caller's +// responsibility to detect and handle L1 reorgs. func (los *L1OriginSelector) FindL1Origin(ctx context.Context, l2Head eth.L2BlockRef) (eth.L1BlockRef, error) { currentOrigin, nextOrigin, err := los.CurrentAndNextOrigin(ctx, l2Head) if err != nil { @@ -170,8 +172,10 @@ func (los *L1OriginSelector) maybeSetNextOrigin(nextOrigin eth.L1BlockRef) { los.mu.Lock() defer los.mu.Unlock() - // Set the next origin if it is the immediate child of the current origin. - if nextOrigin.ParentHash == los.currentOrigin.Hash { + // Set the next origin if it is the subsequent block by number. + // On reorgs, this might not be the immediate child of the current origin + // since the hash is not checked. + if nextOrigin.Number == los.currentOrigin.Number+1 { los.nextOrigin = nextOrigin } } diff --git a/op-node/rollup/sequencing/origin_selector_test.go b/op-node/rollup/sequencing/origin_selector_test.go index 9ec91af0901..be8a0dc7625 100644 --- a/op-node/rollup/sequencing/origin_selector_test.go +++ b/op-node/rollup/sequencing/origin_selector_test.go @@ -124,7 +124,6 @@ func TestOriginSelectorFetchNextError(t *testing.T) { // is no conf depth to stop the origin selection so block `b` should // be the next L1 origin, and then block `c` is the subsequent L1 origin. func TestOriginSelectorAdvances(t *testing.T) { - testOriginSelectorAdvances := func(t *testing.T, recoverMode bool) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -340,6 +339,85 @@ func TestOriginSelectorFetchesNextOrigin(t *testing.T) { require.Equal(t, b, next) } +// TestOriginSelectorHandlesReorg ensures that the origin selector +// can handle the current origin being reorged out +// +// There are 3 blocks [a, b, c]. After advancing to b, a reorg is simulated +// where b is reorged and replaced by providing a `c` next that has a different parent hash. +// The origin should still provide c as the next origin so upstream services can detect the reorg. +func TestOriginSelectorHandlesReorg(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + log := testlog.Logger(t, log.LevelDebug) + cfg := &rollup.Config{ + MaxSequencerDrift: 500, + BlockTime: 2, + } + l1 := &testutils.MockL1Source{} + defer l1.AssertExpectations(t) + a := eth.L1BlockRef{ + Hash: common.Hash{'a'}, + Number: 10, + Time: 20, + } + b := eth.L1BlockRef{ + Hash: common.Hash{'b'}, + Number: 11, + Time: 22, + ParentHash: a.Hash, + } + l2Head := eth.L2BlockRef{ + L1Origin: a.ID(), + Time: 24, + } + + // This is called as part of the background prefetch job + l1.ExpectL1BlockRefByNumber(b.Number, b, nil) + + s := NewL1OriginSelector(ctx, log, cfg, l1) + s.currentOrigin = a + + requireFindl1OriginEqual := func(l1ref eth.L1BlockRef) { + next, err := s.FindL1Origin(ctx, l2Head) + require.NoError(t, err) + require.Equal(t, l1ref, next) + } + + requireFindl1OriginEqual(a) + + // Selection is stable until the next origin is fetched + requireFindl1OriginEqual(a) + + // Trigger the background fetch via a forkchoice update + handled := s.OnEvent(context.Background(), engine.ForkchoiceUpdateEvent{UnsafeL2Head: l2Head}) + require.True(t, handled) + + // The next origin should be `b` now. + requireFindl1OriginEqual(b) + + // A reorg happens and `b` is replaced by a block with a different hash + c := eth.L1BlockRef{ + Hash: common.Hash{'c'}, + Number: 12, + Time: 24, + ParentHash: common.Hash{'b', '2'}, + } + l1.ExpectL1BlockRefByNumber(c.Number, c, nil) + l2Head = eth.L2BlockRef{ + L1Origin: b.ID(), + Time: 26, + } + + // Trigger the background fetch via a forkchoice update + handled = s.OnEvent(context.Background(), engine.ForkchoiceUpdateEvent{UnsafeL2Head: l2Head}) + require.True(t, handled) + + // The next origin should be `c` now, otherwise an upstream service cannot detect the reorg + // and the origin will be stuck at `b` + requireFindl1OriginEqual(c) +} + // TestOriginSelectorRespectsOriginTiming ensures that the origin selector // does not pick an origin that is ahead of the next L2 block time // diff --git a/op-node/rollup/toggles.go b/op-node/rollup/toggles.go index d49f1c18362..04e4a9201c8 100644 --- a/op-node/rollup/toggles.go +++ b/op-node/rollup/toggles.go @@ -1,16 +1,10 @@ package rollup -// This file contains ephemeral feature toggles which should be removed +// This file contains ephemeral feature toggles for the next +// fork while it is in development. They should be removed // after the fork scope is locked. -func (c *Config) IsMinBaseFee(time uint64) bool { - return c.IsJovian(time) // Replace with return false to disable -} - -func (c *Config) IsDAFootprintBlockLimit(time uint64) bool { - return c.IsJovian(time) // Replace with return false to disable -} - -func (c *Config) IsOperatorFeeFix(time uint64) bool { - return c.IsJovian(time) // Replace with return false to disable -} +// Example: +// func (c *Config) IsMinBaseFee(time uint64) bool { +// return c.IsJovian(time) // Replace with return false to disable +// } diff --git a/op-program/client/l2/test/miner.go b/op-program/client/l2/test/miner.go index 3d35c996bf3..7c5247bc7f9 100644 --- a/op-program/client/l2/test/miner.go +++ b/op-program/client/l2/test/miner.go @@ -122,7 +122,7 @@ func (m *Miner) Fork(t *testing.T, blockNumber uint64, attrs *eth.PayloadAttribu GasLimit: &gasLimit, EIP1559Params: &eip1559Params, } - if m.backend.Config().IsMinBaseFee(head.Time) { + if m.backend.Config().IsJovian(head.Time) { stub := uint64(1e9) attrs.MinBaseFee = &stub } @@ -146,7 +146,7 @@ func (m *Miner) MineAt(t *testing.T, head *types.Header, attrs *eth.PayloadAttri GasLimit: &gasLimit, EIP1559Params: &eip1559Params, } - if m.backend.Config().IsMinBaseFee(head.Time) { + if m.backend.Config().IsJovian(head.Time) { stub := uint64(1e9) attrs.MinBaseFee = &stub } diff --git a/op-service/client/ws.go b/op-service/client/ws.go new file mode 100644 index 00000000000..1f875560330 --- /dev/null +++ b/op-service/client/ws.go @@ -0,0 +1,200 @@ +package client + +import ( + "context" + "errors" + "net/http" + "time" + + "github.com/coder/websocket" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/retry" +) + +// WSConfig configures a websocket connection. +// This is the shared configuration type for all outbound websocket clients in the codebase. +// Higher level users can build additional behavior on top, but should prefer DialWS / WSClient +// instead of constructing websocket.Dial calls directly. +type WSConfig struct { + // URL is the websocket endpoint, e.g. wss://example:8546/ws. + URL string + // Headers are optional HTTP headers included in the websocket handshake. + Headers http.Header + + // DialTimeout bounds the initial websocket dial. + DialTimeout time.Duration + // ReadTimeout bounds individual Read calls when a context without deadline is used. + ReadTimeout time.Duration + // WriteTimeout bounds individual Write calls when a context without deadline is used. + WriteTimeout time.Duration + + // MaxAttempts configures how many dial attempts are made with backoff. + // Defaults to 1 if zero. + MaxAttempts int + // Backoff is the backoff strategy used between dial attempts. + // Defaults to retry.Exponential() if nil. + Backoff retry.Strategy + + // Log is used for connection level logging. + // If nil, logging is disabled. + Log log.Logger +} + +// applyDefaults fills empty WSConfig fields with conservative defaults. +func (c *WSConfig) applyDefaults() { + if c.DialTimeout == 0 { + c.DialTimeout = 10 * time.Second + } + if c.ReadTimeout == 0 { + c.ReadTimeout = 30 * time.Second + } + if c.WriteTimeout == 0 { + c.WriteTimeout = 30 * time.Second + } + if c.MaxAttempts < 1 { + c.MaxAttempts = 1 + } + if c.Backoff == nil { + c.Backoff = retry.Exponential() + } +} + +// WSClient is the canonical outbound websocket client for the monorepo. +// It wraps a coder/websocket connection, handles dialing with backoff, and exposes +// context-aware read/write helpers. New outbound websocket integrations should go +// through this type (or helpers built on top of it) rather than using websocket.Dial +// directly. +type WSClient struct { + conn *websocket.Conn + config WSConfig +} + +// DialWS establishes a websocket connection using the given configuration. +// It performs MaxAttempts connection attempts with the configured backoff. +func DialWS(ctx context.Context, cfg WSConfig) (*WSClient, error) { + cfg.applyDefaults() + + if cfg.Log != nil { + cfg.Log.Info("Dialing websocket", "url", cfg.URL) + } + + conn, err := retry.Do(ctx, cfg.MaxAttempts, cfg.Backoff, func() (*websocket.Conn, error) { + dialCtx, cancel := context.WithTimeout(ctx, cfg.DialTimeout) + defer cancel() + conn, resp, err := websocket.Dial(dialCtx, cfg.URL, &websocket.DialOptions{ + HTTPHeader: cfg.Headers, + }) + if resp != nil && resp.Body != nil { + _ = resp.Body.Close() + } + return conn, err + }) + if err != nil { + if cfg.Log != nil { + cfg.Log.Warn("Failed to dial websocket", "url", cfg.URL, "err", err) + } + return nil, err + } + + if cfg.Log != nil { + cfg.Log.Info("Websocket connection established", "url", cfg.URL) + } + + return &WSClient{ + conn: conn, + config: cfg, + }, nil +} + +// Close closes the websocket connection with the given status and reason. +func (c *WSClient) Close(status websocket.StatusCode, reason string) error { + if c == nil || c.conn == nil { + return nil + } + return c.conn.Close(status, reason) +} + +// Read reads the next message from the websocket connection. +// If the context has no deadline, a default read timeout from the config is applied. +func (c *WSClient) Read(ctx context.Context) (websocket.MessageType, []byte, error) { + if deadline, ok := ctx.Deadline(); !ok || time.Until(deadline) <= 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, c.config.ReadTimeout) + defer cancel() + } + return c.conn.Read(ctx) +} + +// Write writes a message to the websocket connection. +// If the context has no deadline, a default write timeout from the config is applied. +func (c *WSClient) Write(ctx context.Context, msgType websocket.MessageType, data []byte) error { + if deadline, ok := ctx.Deadline(); !ok || time.Until(deadline) <= 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, c.config.WriteTimeout) + defer cancel() + } + return c.conn.Write(ctx, msgType, data) +} + +// ReadAll streams all websocket messages for the given duration into the provided output channel. +// It closes the done channel (when provided) after finishing the read loop. +func (c *WSClient) ReadAll(ctx context.Context, logger log.Logger, duration time.Duration, output chan<- []byte, done chan<- struct{}) error { + if c == nil { + return errors.New("ws client is nil") + } + if logger == nil { + return errors.New("logger is nil") + } + if done != nil { + defer close(done) + } + + logger.Info("Listening on WebSocket client", "duration", duration) + + ctx, cancel := context.WithTimeout(ctx, duration) + defer cancel() + + for { + _, message, err := c.Read(ctx) + if err != nil { + if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { + logger.Info("WebSocket read finished") + return nil + } + logger.Error("Error reading WebSocket message", "error", err) + return err + } + logger.Debug("Received WebSocket message", "message_length", len(message)) + + select { + case output <- message: + case <-ctx.Done(): + logger.Info("Context done while sending message") + return nil + } + } +} + +// ProbeWS performs a lightweight websocket handshake against the given URL and closes immediately. +// It can be used in readiness checks to verify that the endpoint accepts websocket connections. +func ProbeWS(ctx context.Context, url string) error { + cfg := WSConfig{ + URL: url, + DialTimeout: 5 * time.Second, + MaxAttempts: 1, + } + cfg.applyDefaults() + + conn, err := DialWS(ctx, cfg) + if err != nil { + return err + } + // Close without waiting for the peer's close frame. Some flashblocks endpoints + // immediately drop the connection after a successful handshake which makes the + // full close handshake fail spuriously even though the endpoint is healthy. + if conn.conn == nil { + return nil + } + return conn.conn.CloseNow() +} diff --git a/op-service/client/ws_test.go b/op-service/client/ws_test.go new file mode 100644 index 00000000000..697b0604746 --- /dev/null +++ b/op-service/client/ws_test.go @@ -0,0 +1,99 @@ +package client + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/coder/websocket" + "github.com/ethereum/go-ethereum/log" +) + +// newTestLogger returns a no-op logger suitable for tests. +func newTestLogger(t *testing.T) log.Logger { + t.Helper() + // Root logger with no handlers is effectively silent. + return log.New() +} + +// wsEchoServer starts a simple websocket echo server for tests. +func wsEchoServer(t *testing.T) (*httptest.Server, string) { + t.Helper() + ctx := t.Context() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + conn, err := websocket.Accept(w, r, &websocket.AcceptOptions{ + CompressionMode: websocket.CompressionDisabled, + }) + if err != nil { + return + } + defer conn.Close(websocket.StatusNormalClosure, "test complete") + + for { + msgType, data, err := conn.Read(ctx) + if err != nil { + return + } + if err := conn.Write(ctx, msgType, data); err != nil { + return + } + } + }) + + srv := httptest.NewServer(handler) + + // Convert http://127.0.0.1:port to ws://127.0.0.1:port. + wsURL := "ws" + srv.URL[len("http"):] + return srv, wsURL +} + +func TestDialWSAndEcho(t *testing.T) { + ctx := t.Context() + srv, wsURL := wsEchoServer(t) + defer srv.Close() + + opCtx, cancelOp := context.WithTimeout(ctx, 5*time.Second) + defer cancelOp() + + client, err := DialWS(opCtx, WSConfig{ + URL: wsURL, + Log: newTestLogger(t), + }) + if err != nil { + t.Fatalf("DialWS failed: %v", err) + } + defer client.Close(websocket.StatusNormalClosure, "test complete") + + const payload = "hello over websocket" + + if err := client.Write(opCtx, websocket.MessageText, []byte(payload)); err != nil { + t.Fatalf("Write failed: %v", err) + } + + msgType, data, err := client.Read(opCtx) + if err != nil { + t.Fatalf("Read failed: %v", err) + } + if msgType != websocket.MessageText { + t.Fatalf("unexpected message type: %v", msgType) + } + if string(data) != payload { + t.Fatalf("unexpected payload: got %q, want %q", string(data), payload) + } +} + +func TestProbeWS(t *testing.T) { + ctx := t.Context() + srv, wsURL := wsEchoServer(t) + defer srv.Close() + + opCtx, cancelOp := context.WithTimeout(ctx, 5*time.Second) + defer cancelOp() + + if err := ProbeWS(opCtx, wsURL); err != nil { + t.Fatalf("ProbeWS failed: %v", err) + } +} diff --git a/op-service/sources/l1_beacon_client.go b/op-service/sources/l1_beacon_client.go index c791adb4d9e..c193f1bd351 100644 --- a/op-service/sources/l1_beacon_client.go +++ b/op-service/sources/l1_beacon_client.go @@ -9,7 +9,6 @@ import ( "net/http" "net/url" "path" - "slices" "strconv" "sync" @@ -344,9 +343,13 @@ func (cl *L1BeaconClient) beaconBlobs(ctx context.Context, slot uint64, hashes [ return nil, fmt.Errorf("compute blob kzg commitment: %w", err) } got := eth.KZGToVersionedHash(commitment) - idx := slices.IndexFunc(hashes, func(indexedHash eth.IndexedBlobHash) bool { - return got == indexedHash.Hash - }) + idx := -1 + for i, indexedHash := range hashes { + if got == indexedHash.Hash && blobs[i] == nil { + idx = i + break + } + } if idx == -1 { return nil, fmt.Errorf("received a blob hash that does not match any expected hash: %s", got) } diff --git a/op-service/sources/l1_beacon_client_test.go b/op-service/sources/l1_beacon_client_test.go index 63b753805bd..10fdb3755d5 100644 --- a/op-service/sources/l1_beacon_client_test.go +++ b/op-service/sources/l1_beacon_client_test.go @@ -393,3 +393,39 @@ func TestGetBlobs(t *testing.T) { }) } } + +func TestRequestDuplicateBlobHashes(t *testing.T) { + ctx := context.Background() + p := mocks.NewBeaconClient(t) + p.EXPECT().BeaconGenesis(ctx).Return(eth.APIGenesisResponse{Data: eth.ReducedGenesisData{GenesisTime: 10}}, nil) + p.EXPECT().ConfigSpec(ctx).Return(eth.APIConfigResponse{Data: eth.ReducedConfigData{SecondsPerSlot: 2}}, nil) + client := NewL1BeaconClient(p, L1BeaconClientConfig{}) + ref := eth.L1BlockRef{Time: 12} + + hash0, sidecar0 := makeTestBlobSidecar(0) + hash1, sidecar1 := makeTestBlobSidecar(1) + hash2, sidecar2 := makeTestBlobSidecar(2) + sameHash := eth.IndexedBlobHash{ + Index: 3, + Hash: hash0.Hash, + } + sameHashSidecar := ð.BlobSidecar{ + Blob: sidecar0.Blob, + Index: eth.Uint64String(sameHash.Index), + KZGCommitment: sidecar0.KZGCommitment, + KZGProof: sidecar0.KZGProof, + } + hashes := []eth.IndexedBlobHash{hash0, hash2, hash1, sameHash} // Mix up the order. + beaconBlobs := []*eth.Blob{&sidecar0.Blob, &sidecar1.Blob, &sidecar2.Blob, &sameHashSidecar.Blob} + + // construct the mock response for the beacon blobs call + beaconBlobsResponse := eth.APIBeaconBlobsResponse{Data: beaconBlobs} + p.EXPECT().BeaconBlobs(ctx, uint64(1), hashes).Return(beaconBlobsResponse, nil) + + resp, err := client.GetBlobs(ctx, ref, hashes) + require.NoError(t, err) + for i, blob := range resp { + require.NotNil(t, blob, fmt.Sprintf("blob at index %d should not be nil", i)) + } + require.Equal(t, []*eth.Blob{&sidecar0.Blob, &sidecar2.Blob, &sidecar1.Blob, &sameHashSidecar.Blob}, resp) +} diff --git a/op-supernode/supernode/chain_container/virtual_node/virtual_node.go b/op-supernode/supernode/chain_container/virtual_node/virtual_node.go index af0cd88e637..f5677ec6425 100644 --- a/op-supernode/supernode/chain_container/virtual_node/virtual_node.go +++ b/op-supernode/supernode/chain_container/virtual_node/virtual_node.go @@ -20,7 +20,7 @@ func defaultInnerNodeFactory(ctx context.Context, cfg *opnodecfg.Config, log get if initOverload != nil { overrides = *initOverload } - return rollupNode.NewWithOverride(ctx, cfg, log, appVersion, m, overrides) + return rollupNode.NewWithOverride(ctx, cfg, log, appVersion, m, nil, overrides) } var ( diff --git a/op-sync-tester/synctester/backend/sync_tester.go b/op-sync-tester/synctester/backend/sync_tester.go index 5c97d2d7f70..45633843715 100644 --- a/op-sync-tester/synctester/backend/sync_tester.go +++ b/op-sync-tester/synctester/backend/sync_tester.go @@ -429,7 +429,7 @@ func (s *SyncTester) forkchoiceUpdated(ctx context.Context, session *eth.SyncTes } // https://github.com/ethereum-optimism/specs/blob/510377c586d0cbede2d40402d2371fcadd5656a0/specs/protocol/jovian/exec-engine.md#minimum-base-fee-in-block-header // Implicitly determine whether jovian is enabled by inspecting extraData from read only EL data - isJovian := eip1559.ValidateMinBaseFeeExtraData(newBlock.Header().Extra) == nil + isJovian := eip1559.ValidateJovianExtraData(newBlock.Header().Extra) == nil // https://github.com/ethereum-optimism/specs/blob/972dec7c7c967800513c354b2f8e5b79340de1c3/specs/protocol/holocene/exec-engine.md#eip-1559-parameters-in-block-header // Implicitly determine whether holocene is enabled by inspecting extraData from read only EL data isHolocene := true // holocene is always activated when jovian is activated diff --git a/ops/ai-eng/contracts-test-maintenance/VERSION b/ops/ai-eng/contracts-test-maintenance/VERSION index 23aa8390630..f0bb29e7638 100644 --- a/ops/ai-eng/contracts-test-maintenance/VERSION +++ b/ops/ai-eng/contracts-test-maintenance/VERSION @@ -1 +1 @@ -1.2.2 +1.3.0 diff --git a/ops/ai-eng/contracts-test-maintenance/components/devin-api/devin_client.py b/ops/ai-eng/contracts-test-maintenance/components/devin-api/devin_client.py index adcdbba7e3e..bad0ff0e3de 100644 --- a/ops/ai-eng/contracts-test-maintenance/components/devin-api/devin_client.py +++ b/ops/ai-eng/contracts-test-maintenance/components/devin-api/devin_client.py @@ -198,17 +198,28 @@ def monitor_session(session_id): print("Session stopped by user") return - # Blocked = PR created successfully (can't auto-merge due to review policy) + # Blocked = PR created or analysis completed without changes if current_status == "blocked": + structured_output = status.get("structured_output") or {} pr_data = status.get("pull_request") or {} + + # Check if analysis completed without changes + if structured_output.get("analysis_complete") and not structured_output.get("changes_needed"): + reason = structured_output.get("reason", "no changes needed") + print(f"Session completed - {reason}") + write_log(session_id, "finished_no_changes", status) + return + + # Check if PR was created if pr_data.get("url"): print("Session completed successfully - PR created") write_log(session_id, "finished", status) return - else: - print(f"Session blocked without PR - check Devin web interface") - # Don't write log.json so artifact won't be stored for failed sessions - sys.exit(1) # Exit with error code to mark job as failed + + # Blocked without completion signal + print(f"Session blocked without PR - check Devin web interface") + # Don't write log.json so artifact won't be stored for failed sessions + sys.exit(1) # Exit with error code to mark job as failed # Expired = session timed out if current_status == "expired": diff --git a/ops/ai-eng/contracts-test-maintenance/components/slack-notifier/prepare_notification.sh b/ops/ai-eng/contracts-test-maintenance/components/slack-notifier/prepare_notification.sh new file mode 100755 index 00000000000..1c366ad2962 --- /dev/null +++ b/ops/ai-eng/contracts-test-maintenance/components/slack-notifier/prepare_notification.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Prepare Slack notification for AI Contracts Test Maintenance System results +# Outputs a JSON message to be used with CircleCI Slack orb + +LOG_FILE="../../log.json" + +# Extract data from log file +PR_URL=$(jq -r '.pull_request_url // empty' "$LOG_FILE") +TEST_FILE=$(jq -r '.selected_files.test_path | split("/") | .[-1]' "$LOG_FILE") +STATUS=$(jq -r '.status // empty' "$LOG_FILE") + +# Prepare message based on outcome +if [ -n "$PR_URL" ]; then + # PR was created - notify team for review + MESSAGE=$' AI Contracts Test Maintenance System created a PR for '"${TEST_FILE}"$'\n<'"${PR_URL}"$'|View PR> | ' + SLACK_JSON=$(jq -n --arg msg "$MESSAGE" '{"text": $msg}') + echo "export AI_PR_SLACK_TEMPLATE='${SLACK_JSON}'" +elif [ "$STATUS" = "finished_no_changes" ]; then + # Analysis complete but no changes needed - informational only + MESSAGE=$'AI Contracts Test Maintenance System analyzed '"${TEST_FILE}"$' - no changes needed (test coverage is already comprehensive)' + SLACK_JSON=$(jq -n --arg msg "$MESSAGE" '{"text": $msg}') + echo "export AI_PR_SLACK_TEMPLATE='${SLACK_JSON}'" +else + # No notification needed + echo "No PR created, skipping notification" + echo "export AI_PR_SLACK_TEMPLATE=''" +fi diff --git a/ops/ai-eng/contracts-test-maintenance/docs/runbook.md b/ops/ai-eng/contracts-test-maintenance/docs/runbook.md index abc45624991..582d321c2f2 100644 --- a/ops/ai-eng/contracts-test-maintenance/docs/runbook.md +++ b/ops/ai-eng/contracts-test-maintenance/docs/runbook.md @@ -26,7 +26,7 @@ The system uses a **two-branch scoring algorithm**: contracts-test-maintenance/ ├── VERSION # System version ├── exclusion.toml # Static exclusions configuration -├── log.jsonl # Execution history and results +├── log.json # Latest execution log ├── prompt/ │ └── prompt.md # AI instruction template (~2000 lines) ├── components/ @@ -36,8 +36,10 @@ contracts-test-maintenance/ │ ├── prompt-renderer/ # Stage 2: Prompt generation │ │ ├── render.py │ │ └── output/{run_id}_prompt.md -│ └── devin-api/ # Stage 3: AI execution -│ └── devin_client.py +│ ├── devin-api/ # Stage 3: AI execution +│ │ └── devin_client.py +│ └── slack-notifier/ # Slack notification preparation +│ └── prepare_notification.sh └── docs/ └── runbook.md # This document ``` @@ -237,7 +239,7 @@ All Devin sessions are automatically logged to `log.json` with: - `run_time` - Human-readable timestamp of the run - `devin_session_id` - Unique Devin session identifier - `selected_files` - The test-contract pair that was worked on -- `status` - Final session status ("finished", "blocked", "expired", "failed") +- `status` - Final session status ("finished", "finished_no_changes", "blocked", "expired") - `pull_request_url` - GitHub PR URL (only present if status is "finished") #### Duplicate Prevention @@ -368,7 +370,8 @@ else: **Logged Status Values** (what gets written to `log.json`): - `finished`: Devin status was "blocked" AND PR was successfully created -- `blocked`: Devin status was "blocked" but no PR URL found +- `finished_no_changes`: Devin completed analysis and determined no changes needed (uses structured output) +- `blocked`: Devin status was "blocked" without PR URL or completion signal - `expired`: Session timed out - Note: User-stopped sessions are not logged diff --git a/ops/ai-eng/contracts-test-maintenance/exclusion.toml b/ops/ai-eng/contracts-test-maintenance/exclusion.toml index d049863db28..76a04639b1b 100644 --- a/ops/ai-eng/contracts-test-maintenance/exclusion.toml +++ b/ops/ai-eng/contracts-test-maintenance/exclusion.toml @@ -22,5 +22,6 @@ files = [ "test/universal/BenchmarkTest.t.sol", "test/universal/ExtendedPause.t.sol", "test/vendor/Initializable.t.sol", - "test/vendor/InitializableOZv5.t.sol" + "test/vendor/InitializableOZv5.t.sol", + "test/universal/ReinitializableBase.t.sol" ] diff --git a/ops/ai-eng/contracts-test-maintenance/prompt/prompt.md b/ops/ai-eng/contracts-test-maintenance/prompt/prompt.md index 779c34cd52f..38e350fe4c5 100644 --- a/ops/ai-eng/contracts-test-maintenance/prompt/prompt.md +++ b/ops/ai-eng/contracts-test-maintenance/prompt/prompt.md @@ -28,6 +28,46 @@ Only make changes you're confident about - analyze code behavior before testing. Don't guess or assume - if unsure, examine the source contract carefully. + +You MUST maintain a `structured_output` field to communicate task completion status. Please update the structured output immediately after completing your analysis (Phases 1-2) to indicate whether changes are needed. + +**Required Format:** +```json +{ + "analysis_complete": boolean, + "changes_needed": boolean, + "reason": string +} +``` + +**When to Update:** +- Update immediately after completing Phases 1-2 (Enhancement Analysis & Coverage Gap Analysis) +- Set `analysis_complete: true` as soon as you determine the outcome +- Set `changes_needed: true` if you will create or modify tests +- Set `changes_needed: false` if no changes are needed after thorough analysis +- Set `reason` with a brief explanation of the outcome + +**Examples:** + +No changes needed: +```json +{ + "analysis_complete": true, + "changes_needed": false, + "reason": "Test coverage is already comprehensive with all functions and code paths tested" +} +``` + +Changes needed: +```json +{ + "analysis_complete": true, + "changes_needed": true, + "reason": "Converting 3 tests to fuzz tests and adding coverage for 2 untested functions" +} +``` + + 1. NO creating NEW tests for inherited functions - only test functions declared in target contract 2. NO creating test contracts for constructor parameters - use Constructor_Test instead diff --git a/ops/ai-eng/graphite/rules.md b/ops/ai-eng/graphite/rules.md index 2cbb0fd07c7..4fe2a0a6761 100644 --- a/ops/ai-eng/graphite/rules.md +++ b/ops/ai-eng/graphite/rules.md @@ -6,6 +6,21 @@ This file explains the rules that you should use when reviewing a PR. You are ONLY to review changes to Solidity files (*.sol). Do NOT leave comments on any other file types. +## OPCM Version Bump Warnings + +If the PR modifies `OPContractsManagerV2.sol` and changes the `version` constant with a major or minor version bump, you MUST leave a prominent comment on the PR with the following message: + +> ⚠️ **OPCM Version Bump Detected** +> +> This PR includes a major or minor version bump to `OPContractsManagerV2.sol`. +> +> **Reminder of OPCM versioning rules:** +> - **Major bump**: Only for a new required sequential upgrade (e.g., U16 → U17) +> - **Minor bump**: Only for replacing an existing OPCM for the same upgrade (e.g., bug fixes, U16a) +> - **Patch bump**: Expected for normal development work +> +> Please confirm this version bump is intentional and follows the versioning policy. + ## Rules for Reviewing Solidity Files This section applies to Solidity files ONLY. diff --git a/ops/ai-eng/justfile b/ops/ai-eng/justfile index a4f4cdb89d2..33cdf858fbb 100644 --- a/ops/ai-eng/justfile +++ b/ops/ai-eng/justfile @@ -29,3 +29,7 @@ ai-contracts-test: # Step 3: Send to Devin just devin + +# Prepare Slack notification based on log.json results +prepare-slack-notification: + cd contracts-test-maintenance/components/slack-notifier && ./prepare_notification.sh diff --git a/ops/docker/op-stack-go/Dockerfile b/ops/docker/op-stack-go/Dockerfile index c0a77a6b22e..57052e7101b 100644 --- a/ops/docker/op-stack-go/Dockerfile +++ b/ops/docker/op-stack-go/Dockerfile @@ -16,10 +16,6 @@ ARG UBUNTU_TARGET_BASE_IMAGE=ubuntu:22.04 # The only build that uses this is `op-challenger-target`. ARG KONA_VERSION=none -# The version of asterisc to use. -# The only build that uses this is `op-challenger-target`. -ARG ASTERISC_VERSION=none - # builder_foundry does not need to be built on $BUILDPLATFORM, as foundry produces static binaries. FROM alpine:3.21 AS builder_foundry @@ -222,25 +218,20 @@ CMD ["op-node"] # Make the kona docker image published by upstream available as a source to copy kona from. FROM ghcr.io/op-rs/kona/kona-host:$KONA_VERSION AS kona -# Make the asterisc docker image published by upstream available as a source to copy asterisc from. -FROM us-docker.pkg.dev/oplabs-tools-artifacts/images/asterisc:$ASTERISC_VERSION AS asterisc -# Also produce an op-challenger loaded with kona and asterisc using ubuntu +# Also produce an op-challenger loaded with kona using ubuntu FROM $UBUNTU_TARGET_BASE_IMAGE AS op-challenger-target RUN apt-get update && apt-get install -y --no-install-recommends musl openssl ca-certificates COPY --from=op-challenger-builder /app/op-challenger/bin/op-challenger /usr/local/bin/ # Copy in op-program and cannon COPY --from=op-program-builder /app/op-program/bin/op-program /usr/local/bin/ -ENV OP_CHALLENGER_ASTERISC_SERVER=/usr/local/bin/op-program ENV OP_CHALLENGER_CANNON_SERVER=/usr/local/bin/op-program COPY --from=cannon-builder /app/cannon/bin/cannon /usr/local/bin/ ENV OP_CHALLENGER_CANNON_BIN=/usr/local/bin/cannon -# Copy in kona and asterisc +# Copy in kona COPY --from=kona /usr/local/bin/kona-host /usr/local/bin/ ENV OP_CHALLENGER_ASTERISC_KONA_SERVER=/usr/local/bin/kona-host ENV OP_CHALLENGER_CANNON_KONA_SERVER=/usr/local/bin/kona-host -COPY --from=asterisc /usr/local/bin/asterisc /usr/local/bin/ -ENV OP_CHALLENGER_ASTERISC_BIN=/usr/local/bin/asterisc CMD ["op-challenger"] FROM $TARGET_BASE_IMAGE AS op-dispute-mon-target diff --git a/packages/contracts-bedrock/book/src/policies/release-process.md b/packages/contracts-bedrock/book/src/policies/release-process.md index cde30e67d25..4cea8305e70 100644 --- a/packages/contracts-bedrock/book/src/policies/release-process.md +++ b/packages/contracts-bedrock/book/src/policies/release-process.md @@ -1,5 +1,19 @@ # Tagging and Release Process +This release process applies to all contract release namespaces: +- `op-contracts/vX.Y.Z`: Core protocol contracts +- `op-safe-contracts/vX.Y.Z`: Safe multisig extensions + +## OPCM Versioning + +The OPCM contract uses a specific versioning scheme: + +- **Major version bump**: New required sequential upgrade (e.g., U16 → U17 → U18). +- **Minor version bump**: Replacement OPCM for the same upgrade (e.g., bug fixes, U16a). +- **Patch version bump**: Development changes on `develop` branch. + +See [OPCM Semver Rules](./versioning.md#opcm-semver-rules) for more details. + ## Creating a tagged release First select a tag string based on the guidance in [Monorepo Contracts Release Versioning](./versioning.md#monorepo-contracts-release-versioning) @@ -44,10 +58,10 @@ efforts on the trunk branch. The process is as follows: 1. Make the fixes on `develop`. Increment the contracts semver as normal. -1. Create a new release branch, named `proposal/op-contracts/vX.Y.Z` off of the rc tag (all subsequent `-rc` tags - will be made from this branch). +1. Create a new release branch, named `proposal//vX.Y.Z` off of the rc tag (all subsequent `-rc` tags + will be made from this branch). For example: `proposal/op-contracts/vX.Y.Z` or `proposal/op-safe-contracts/vX.Y.Z`. 1. Cherry pick the fixes from `develop` into the release branch, and increment the semver as normal. If this increment results in any of the modified contracts' semver being equal to or greater than it is on `develop`, then the semver should immediately be increased on `develop` to be greater than on the release branch. This avoids a situation where a given contract has two different implementations with the same version. -1. After merging the changes into the new release branch, tag the resulting commit on the proposal branch as `op-contracts/vX.Y.Z-rc.n`. +1. After merging the changes into the new release branch, tag the resulting commit on the proposal branch as `/vX.Y.Z-rc.n` (e.g., `op-contracts/vX.Y.Z-rc.n` or `op-safe-contracts/vX.Y.Z-rc.n`). Create a new release for this tag per the instructions above. ## Finalizing a release diff --git a/packages/contracts-bedrock/book/src/policies/versioning.md b/packages/contracts-bedrock/book/src/policies/versioning.md index c40f16d79cf..546d164a7a0 100644 --- a/packages/contracts-bedrock/book/src/policies/versioning.md +++ b/packages/contracts-bedrock/book/src/policies/versioning.md @@ -64,7 +64,10 @@ Individual contract versioning could be deprecated when the following conditions Versioning for monorepo releases works as follows: -- Monorepo releases continue to follow the `op-contracts/vX.Y.Z` naming convention. +- Monorepo releases follow the `op-contracts/vX.Y.Z` naming convention for core protocol contracts. +- Additional release namespaces exist for specialized contract categories: + - `op-safe-contracts/vX.Y.Z`: Safe multisig extensions (SaferSafes, LivenessModule2, TimelockGuard, DeputyPauseModule, etc.) +- Each namespace maintains its own independent semver versioning. - The version used for the next release is determined by the highest version bump of any individual contract in the release. - Example 1: The monorepo is at `op-contracts/v1.5.0`. Clarifying comments are made in contracts, so all contracts only bump the patch version. The next monorepo release will be `op-contracts/v1.5.1`. - Example 2: The monorepo is at `op-contracts/v1.5.1`. Various tech debt and code is cleaned up in contracts, but no features are added, so at most, contracts bumped the minor version. The next monorepo release will be `op-contracts/v1.6.0`. @@ -79,3 +82,13 @@ Versioning for monorepo releases works as follows: The [OPCM](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L1/OPContractsManager.sol) is the contract that manages the deployment of all contracts on L1. The `OPCM` is the source of truth for the contracts that belong in a release, available as on-chain addresses by querying [the `getImplementations` function](https://github.com/ethereum-optimism/optimism/blob/4c8764f0453e141555846d8c9dd2af9edbc1d014/packages/contracts-bedrock/src/L1/OPContractsManager.sol#L1061). + +### OPCM Semver Rules + +OPCM follows a specific versioning scheme that differs from individual contract versioning: + +- **Major version bump**: Used when there is a new required sequential upgrade. Each new upgrade in the upgrade sequence (e.g., U16, U17, U18) requires a major version bump to the OPCM. +- **Minor version bump**: Used when an OPCM is replacing an existing OPCM for the same upgrade. This applies when: + - Fixing bugs in an OPCM for a given upgrade (e.g., fixes for the Superchain Config bug) + - Releasing an updated OPCM variant for the same upgrade (e.g., U16a replacing U16) +- **Patch version bump**: Used during active development on the `develop` branch. This is the expected behavior for day-to-day development work. diff --git a/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol b/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol index 837ca4e7cc1..881a0a5d298 100644 --- a/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol +++ b/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol @@ -24,6 +24,7 @@ interface ISystemConfig is IProxyAdminOwnedBase { address optimismPortal; address optimismMintableERC20Factory; address delayedWETH; + address opcm; } error ReinitializableBase_ZeroInitVersion(); @@ -43,6 +44,7 @@ interface ISystemConfig is IProxyAdminOwnedBase { function DELAYED_WETH_SLOT() external view returns (bytes32); function START_BLOCK_SLOT() external view returns (bytes32); function UNSAFE_BLOCK_SIGNER_SLOT() external view returns (bytes32); + function OPCM_SLOT() external view returns (bytes32); function VERSION() external view returns (uint256); function basefeeScalar() external view returns (uint32); function batchInbox() external view returns (address addr_); @@ -81,6 +83,8 @@ interface ISystemConfig is IProxyAdminOwnedBase { function optimismMintableERC20Factory() external view returns (address addr_); function optimismPortal() external view returns (address addr_); function delayedWETH() external view returns (address addr_); + function lastUsedOPCM() external view returns (address addr_); + function lastUsedOPCMVersion() external view returns (string memory version_); function overhead() external view returns (uint256); function owner() external view returns (address); function renounceOwnership() external; diff --git a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerUtils.sol b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerUtils.sol new file mode 100644 index 00000000000..02f1b506d61 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerUtils.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IOPContractsManagerContainer } from "interfaces/L1/opcm/IOPContractsManagerContainer.sol"; +import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; +import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; + +interface IOPContractsManagerUtils { + struct ProxyDeployArgs { + IProxyAdmin proxyAdmin; + IAddressManager addressManager; + uint256 l2ChainId; + string saltMixer; + } + + struct ExtraInstruction { + string key; + bytes data; + } + + event ProxyCreation(string name, address proxy); + + error OPContractsManagerUtils_DowngradeNotAllowed(address _contract); + error OPContractsManagerUtils_ConfigLoadFailed(string _name); + error OPContractsManagerUtils_ProxyMustLoad(string _name); + error ReservedBitsSet(); + error UnsupportedERCVersion(uint8 version); + error SemverComp_InvalidSemverParts(); + error DeploymentFailed(); + error UnexpectedPreambleData(bytes data); + error NotABlueprint(); + error EmptyInitcode(); + error BytesArrayTooLong(); + error IdentityPrecompileCallFailed(); + + function implementations() external view returns (IOPContractsManagerContainer.Implementations memory); + function blueprints() external view returns (IOPContractsManagerContainer.Blueprints memory); + function contractsContainer() external view returns (IOPContractsManagerContainer); + function chainIdToBatchInboxAddress(uint256 _l2ChainId) external pure returns (address); + function computeSalt( + uint256 _l2ChainId, + string memory _saltMixer, + string memory _contractName + ) + external + pure + returns (bytes32); + + function isMatchingInstruction( + ExtraInstruction memory _instruction, + string memory _key, + bytes memory _data + ) + external + pure + returns (bool); + + function hasInstruction( + ExtraInstruction[] memory _instructions, + string memory _key, + bytes memory _data + ) + external + pure + returns (bool); + + function getInstructionByKey( + ExtraInstruction[] memory _instructions, + string memory _key + ) + external + pure + returns (ExtraInstruction memory); + + function loadBytes( + address _source, + bytes4 _selector, + string memory _name, + ExtraInstruction[] memory _instructions + ) + external + view + returns (bytes memory); + + function loadOrDeployProxy( + address _source, + bytes4 _selector, + ProxyDeployArgs memory _args, + string memory _contractName, + ExtraInstruction[] memory _instructions + ) + external + returns (address payable); + + function upgrade( + IProxyAdmin _proxyAdmin, + address _target, + address _implementation, + bytes memory _data, + bytes32 _slot, + uint8 _offset + ) + external; + + function __constructor__(IOPContractsManagerContainer _contractsContainer) external; +} diff --git a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerUtilsCaller.sol b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerUtilsCaller.sol new file mode 100644 index 00000000000..f061d52afdc --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerUtilsCaller.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IOPContractsManagerUtils } from "interfaces/L1/opcm/IOPContractsManagerUtils.sol"; + +interface IOPContractsManagerUtilsCaller { + function __constructor__(IOPContractsManagerUtils _utils) external; + function utils() external view returns (IOPContractsManagerUtils); +} diff --git a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol index 48fc1aefa97..9d35fe23920 100644 --- a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol +++ b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol @@ -21,6 +21,7 @@ import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; import { IOPContractsManagerContainer } from "interfaces/L1/opcm/IOPContractsManagerContainer.sol"; import { IOPContractsManagerStandardValidator } from "interfaces/L1/IOPContractsManagerStandardValidator.sol"; +import { IOPContractsManagerUtils } from "interfaces/L1/opcm/IOPContractsManagerUtils.sol"; interface IOPContractsManagerV2 { /// @notice Configuration for the FaultDisputeGame. @@ -82,32 +83,22 @@ interface IOPContractsManagerV2 { DisputeGameConfig[] disputeGameConfigs; } - struct ExtraInstruction { - string key; - bytes data; - } - struct UpgradeInput { ISystemConfig systemConfig; DisputeGameConfig[] disputeGameConfigs; - ExtraInstruction[] extraInstructions; + IOPContractsManagerUtils.ExtraInstruction[] extraInstructions; } struct SuperchainUpgradeInput { ISuperchainConfig superchainConfig; - ExtraInstruction[] extraInstructions; + IOPContractsManagerUtils.ExtraInstruction[] extraInstructions; } - event ProxyCreation(string name, address proxy); - error OPContractsManagerV2_InvalidGameConfigs(); error OPContractsManagerV2_InvalidUpgradeInput(); error OPContractsManagerV2_SuperchainConfigNeedsUpgrade(); error OPContractsManagerV2_UnsupportedGameType(); - error OPContractsManagerV2_ProxyMustLoad(string _name); - error OPContractsManagerV2_DowngradeNotAllowed(address _contract); - error OPContractsManagerV2_InvalidUpgradeInstruction(); - error OPContractsManagerV2_ConfigLoadFailed(string _name); + error OPContractsManagerV2_InvalidUpgradeInstruction(string _key); error IdentityPrecompileCallFailed(); error ReservedBitsSet(); error BytesArrayTooLong(); @@ -120,7 +111,8 @@ interface IOPContractsManagerV2 { function __constructor__( IOPContractsManagerContainer _contractsContainer, - IOPContractsManagerStandardValidator _standardValidator + IOPContractsManagerStandardValidator _standardValidator, + IOPContractsManagerUtils _utils ) external; @@ -132,6 +124,10 @@ interface IOPContractsManagerV2 { function standardValidator() external view returns (IOPContractsManagerStandardValidator); + function thisOPCM() external view returns (IOPContractsManagerV2); + + function utils() external view returns (IOPContractsManagerUtils); + function version() external view returns (string memory); /// @notice Upgrades Superchain-wide contracts. diff --git a/packages/contracts-bedrock/interfaces/safe/ILivenessModule2.sol b/packages/contracts-bedrock/interfaces/safe/ILivenessModule2.sol index 921d1a794c7..6a40033d7ea 100644 --- a/packages/contracts-bedrock/interfaces/safe/ILivenessModule2.sol +++ b/packages/contracts-bedrock/interfaces/safe/ILivenessModule2.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +interface ISafe { } + /// @title ILivenessModule2 /// @notice Interface for LivenessModule2, a singleton module for challenge-based ownership transfer interface ILivenessModule2 { @@ -10,18 +12,34 @@ interface ILivenessModule2 { address fallbackOwner; } + event ChallengeCancelled(address indexed safe); + event ChallengeStarted(address indexed safe, uint256 challengeStartTime); + event ChallengeSucceeded(address indexed safe, address fallbackOwner); + event ModuleCleared(address indexed safe); + event ModuleConfigured(address indexed safe, uint256 livenessResponsePeriod, address fallbackOwner); + + error LivenessModule2_ChallengeAlreadyExists(); + error LivenessModule2_ChallengeDoesNotExist(); + error LivenessModule2_InvalidFallbackOwner(); + error LivenessModule2_InvalidResponsePeriod(); + error LivenessModule2_InvalidVersion(); + error LivenessModule2_ModuleNotConfigured(); + error LivenessModule2_ModuleNotEnabled(); + error LivenessModule2_ModuleStillEnabled(); + error LivenessModule2_OwnershipTransferFailed(); + error LivenessModule2_ResponsePeriodActive(); + error LivenessModule2_ResponsePeriodEnded(); + error LivenessModule2_UnauthorizedCaller(); + error SemverComp_InvalidSemverParts(); + /// @notice Returns the configuration for a Safe - /// @return livenessResponsePeriod The response period - /// @return fallbackOwner The fallback owner address - function livenessSafeConfiguration(address) external view returns (uint256 livenessResponsePeriod, address fallbackOwner); + /// @param _safe The Safe to query + /// @return The ModuleConfig for the Safe + function livenessSafeConfiguration(ISafe _safe) external view returns (ModuleConfig memory); /// @notice Returns the challenge start time for a Safe (0 if no challenge) /// @return The challenge start timestamp - function challengeStartTime(address) external view returns (uint256); - - /// @notice Semantic version - /// @return version The contract version - function version() external view returns (string memory); + function challengeStartTime(ISafe) external view returns (uint256); /// @notice Configures the module for a Safe that has already enabled it /// @param _config The configuration parameters for the module @@ -33,16 +51,16 @@ interface ILivenessModule2 { /// @notice Returns challenge_start_time + liveness_response_period if there is a challenge, or 0 if not /// @param _safe The Safe address to query /// @return The challenge end timestamp, or 0 if no challenge - function getChallengePeriodEnd(address _safe) external view returns (uint256); + function getChallengePeriodEnd(ISafe _safe) external view returns (uint256); /// @notice Challenges an enabled safe /// @param _safe The Safe to challenge - function challenge(address _safe) external; + function challenge(ISafe _safe) external; /// @notice Responds to a challenge for an enabled safe, canceling it function respond() external; /// @notice Removes all current owners from an enabled safe and appoints fallback as sole owner /// @param _safe The Safe to transfer ownership of - function changeOwnershipToFallback(address _safe) external; + function changeOwnershipToFallback(ISafe _safe) external; } diff --git a/packages/contracts-bedrock/interfaces/safe/ISaferSafes.sol b/packages/contracts-bedrock/interfaces/safe/ISaferSafes.sol index 7fc50d05eda..27044452cf3 100644 --- a/packages/contracts-bedrock/interfaces/safe/ISaferSafes.sol +++ b/packages/contracts-bedrock/interfaces/safe/ISaferSafes.sol @@ -1,68 +1,28 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity ^0.8.0; -import {GnosisSafe} from "safe-contracts/GnosisSafe.sol"; -import {Enum} from "safe-contracts/common/Enum.sol"; -import {ISemver} from "interfaces/universal/ISemver.sol"; +import { ITimelockGuard, IEnum, ISafe } from "interfaces/safe/ITimelockGuard.sol"; +import { ILivenessModule2 } from "interfaces/safe/ILivenessModule2.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; interface ISaferSafes is ISemver { - struct ModuleConfig { - uint256 livenessResponsePeriod; - address fallbackOwner; - } - - struct ExecTransactionParams { - address to; - uint256 value; - bytes data; - Enum.Operation operation; - uint256 safeTxGas; - uint256 baseGas; - uint256 gasPrice; - address gasToken; - address payable refundReceiver; - } - - enum TransactionState { - PENDING, - CANCELLED, - EXECUTED - } - - struct ScheduledTransaction { - uint256 executionTime; - TransactionState state; - ExecTransactionParams params; - } - - event CancellationThresholdUpdated( - GnosisSafe indexed safe, - uint256 oldThreshold, - uint256 newThreshold - ); + event CancellationThresholdUpdated(ISafe indexed safe, uint256 oldThreshold, uint256 newThreshold); event ChallengeCancelled(address indexed safe); event ChallengeStarted(address indexed safe, uint256 challengeStartTime); event ChallengeSucceeded(address indexed safe, address fallbackOwner); - event GuardConfigured(GnosisSafe indexed safe, uint256 timelockDelay); + event GuardConfigured(ISafe indexed safe, uint256 timelockDelay); event Message(string message); event ModuleCleared(address indexed safe); - event ModuleConfigured( - address indexed safe, - uint256 livenessResponsePeriod, - address fallbackOwner - ); - event TransactionCancelled(GnosisSafe indexed safe, bytes32 indexed txHash); - event TransactionExecuted(GnosisSafe indexed safe, bytes32 txHash); - event TransactionScheduled( - GnosisSafe indexed safe, - bytes32 indexed txHash, - uint256 executionTime - ); + event ModuleConfigured(address indexed safe, uint256 livenessResponsePeriod, address fallbackOwner); + event TransactionCancelled(ISafe indexed safe, bytes32 indexed txHash); + event TransactionExecuted(ISafe indexed safe, bytes32 indexed txHash); + event TransactionScheduled(ISafe indexed safe, bytes32 indexed txHash, uint256 executionTime); error LivenessModule2_ChallengeAlreadyExists(); error LivenessModule2_ChallengeDoesNotExist(); error LivenessModule2_InvalidFallbackOwner(); error LivenessModule2_InvalidResponsePeriod(); + error LivenessModule2_InvalidVersion(); error LivenessModule2_ModuleNotConfigured(); error LivenessModule2_ModuleNotEnabled(); error LivenessModule2_ModuleStillEnabled(); @@ -74,8 +34,10 @@ interface ISaferSafes is ISemver { error SemverComp_InvalidSemverParts(); error TimelockGuard_GuardNotConfigured(); error TimelockGuard_GuardNotEnabled(); + error TimelockGuard_GuardStillEnabled(); error TimelockGuard_InvalidTimelockDelay(); error TimelockGuard_InvalidVersion(); + error TimelockGuard_NotOwner(); error TimelockGuard_TransactionAlreadyCancelled(); error TimelockGuard_TransactionAlreadyExecuted(); error TimelockGuard_TransactionAlreadyScheduled(); @@ -83,21 +45,20 @@ interface ISaferSafes is ISemver { error TimelockGuard_TransactionNotScheduled(); function cancelTransaction( - GnosisSafe _safe, + ISafe _safe, bytes32 _txHash, uint256 _nonce, bytes calldata _signatures - ) external; + ) + external; - function cancellationThreshold( - GnosisSafe _safe - ) external view returns (uint256); + function cancellationThreshold(ISafe _safe) external view returns (uint256); - function challenge(address _safe) external; + function challenge(ISafe _safe) external; - function challengeStartTime(address _safe) external view returns (uint256); + function challengeStartTime(ISafe) external view returns (uint256); - function changeOwnershipToFallback(address _safe) external; + function changeOwnershipToFallback(ISafe _safe) external; function checkAfterExecution(bytes32 _txHash, bool _success) external; @@ -105,60 +66,56 @@ interface ISaferSafes is ISemver { address _to, uint256 _value, bytes calldata _data, - Enum.Operation _operation, + IEnum.Operation _operation, uint256 _safeTxGas, uint256 _baseGas, uint256 _gasPrice, address _gasToken, address payable _refundReceiver, bytes calldata, - address - ) external view; + address _msgSender + ) + external; function clearLivenessModule() external; - function configureLivenessModule(ModuleConfig calldata _config) external; + function clearTimelockGuard() external; + + function configureLivenessModule(ILivenessModule2.ModuleConfig calldata _config) external; function configureTimelockGuard(uint256 _timelockDelay) external; - function getChallengePeriodEnd( - address _safe - ) external view returns (uint256); + function getChallengePeriodEnd(ISafe _safe) external view returns (uint256); - function livenessSafeConfiguration( - address _safe - ) - external - view - returns (uint256 livenessResponsePeriod, address fallbackOwner); + function livenessSafeConfiguration(ISafe _safe) external view returns (ILivenessModule2.ModuleConfig memory); - function maxCancellationThreshold( - GnosisSafe _safe - ) external view returns (uint256); + function maxCancellationThreshold(ISafe _safe) external view returns (uint256); - function pendingTransactions( - GnosisSafe _safe - ) external view returns (ScheduledTransaction[] memory); + function pendingTransactions(ISafe _safe) external view returns (ITimelockGuard.ScheduledTransaction[] memory); function respond() external; function scheduleTransaction( - GnosisSafe _safe, + ISafe _safe, uint256 _nonce, - ExecTransactionParams calldata _params, + ITimelockGuard.ExecTransactionParams calldata _params, bytes calldata _signatures - ) external; + ) + external; function scheduledTransaction( - GnosisSafe _safe, + ISafe _safe, bytes32 _txHash - ) external view returns (ScheduledTransaction memory); + ) + external + view + returns (ITimelockGuard.ScheduledTransaction memory); + + function signCancellation(bytes32) external; - function signCancellation(bytes32 _txHash) external; + function supportsInterface(bytes4 interfaceId) external view returns (bool); - function timelockConfiguration( - GnosisSafe _safe - ) external view returns (uint256); + function timelockDelay(ISafe _safe) external view returns (uint256); function version() external pure returns (string memory); } diff --git a/packages/contracts-bedrock/interfaces/safe/ITimelockGuard.sol b/packages/contracts-bedrock/interfaces/safe/ITimelockGuard.sol index 6ab35526a74..86871be9ecc 100644 --- a/packages/contracts-bedrock/interfaces/safe/ITimelockGuard.sol +++ b/packages/contracts-bedrock/interfaces/safe/ITimelockGuard.sol @@ -1,8 +1,13 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.4; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; -library Enum { - type Operation is uint8; +interface ISafe { } + +interface IEnum { + enum Operation { + Call, + DelegateCall + } } interface ITimelockGuard { @@ -25,7 +30,7 @@ interface ITimelockGuard { address to; uint256 value; bytes data; - Enum.Operation operation; + IEnum.Operation operation; uint256 safeTxGas; uint256 baseGas; uint256 gasPrice; @@ -37,58 +42,60 @@ interface ITimelockGuard { error TimelockGuard_GuardNotEnabled(); error TimelockGuard_GuardStillEnabled(); error TimelockGuard_InvalidTimelockDelay(); + error TimelockGuard_NotOwner(); error TimelockGuard_TransactionAlreadyCancelled(); error TimelockGuard_TransactionAlreadyScheduled(); error TimelockGuard_TransactionNotScheduled(); error TimelockGuard_TransactionNotReady(); error TimelockGuard_TransactionAlreadyExecuted(); error TimelockGuard_InvalidVersion(); + error SemverComp_InvalidSemverParts(); - event CancellationThresholdUpdated(address indexed safe, uint256 oldThreshold, uint256 newThreshold); - event GuardConfigured(address indexed safe, uint256 timelockDelay); - event TransactionCancelled(address indexed safe, bytes32 indexed txHash); - event TransactionScheduled(address indexed safe, bytes32 indexed txHash, uint256 executionTime); - event TransactionExecuted(address indexed safe, bytes32 indexed txHash); + event CancellationThresholdUpdated(ISafe indexed safe, uint256 oldThreshold, uint256 newThreshold); + event GuardConfigured(ISafe indexed safe, uint256 timelockDelay); + event TransactionCancelled(ISafe indexed safe, bytes32 indexed txHash); + event TransactionScheduled(ISafe indexed safe, bytes32 indexed txHash, uint256 executionTime); + event TransactionExecuted(ISafe indexed safe, bytes32 indexed txHash); event Message(string message); - event TransactionsNotCancelled(address indexed safe, uint256 uncancelledCount); - function cancelTransaction(address _safe, bytes32 _txHash, uint256 _nonce, bytes memory _signatures) external; - function signCancellation(bytes32 _txHash) external; - function cancellationThreshold(address _safe) external view returns (uint256); + function cancelTransaction(ISafe _safe, bytes32 _txHash, uint256 _nonce, bytes memory _signatures) external; + function signCancellation(bytes32) external; + function cancellationThreshold(ISafe _safe) external view returns (uint256); + function supportsInterface(bytes4 interfaceId) external view returns (bool); function checkTransaction( address _to, uint256 _value, bytes memory _data, - Enum.Operation _operation, + IEnum.Operation _operation, uint256 _safeTxGas, uint256 _baseGas, uint256 _gasPrice, address _gasToken, address payable _refundReceiver, - bytes memory _signatures, + bytes memory, address _msgSender ) external; - function checkAfterExecution(bytes32, bool) external; + function checkAfterExecution(bytes32 _txHash, bool _success) external; function configureTimelockGuard(uint256 _timelockDelay) external; + function clearTimelockGuard() external; function scheduledTransaction( - address _safe, + ISafe _safe, bytes32 _txHash ) external view returns (ScheduledTransaction memory); - function safeConfigs(address) external view returns (uint256 timelockDelay); function scheduleTransaction( - address _safe, + ISafe _safe, uint256 _nonce, ExecTransactionParams memory _params, bytes memory _signatures ) external; - function timelockConfiguration(address _safe) external view returns (uint256 timelockDelay); - function maxCancellationThreshold(address _safe) external view returns (uint256); - function pendingTransactions(address _safe) + function timelockDelay(ISafe _safe) external view returns (uint256); + function maxCancellationThreshold(ISafe _safe) external view returns (uint256); + function pendingTransactions(ISafe _safe) external view returns (ScheduledTransaction[] memory); diff --git a/packages/contracts-bedrock/justfile b/packages/contracts-bedrock/justfile index c47751e7e45..d91ba5a502b 100644 --- a/packages/contracts-bedrock/justfile +++ b/packages/contracts-bedrock/justfile @@ -60,7 +60,7 @@ build-dev *ARGS: lint-fix-no-fail # Builds the go-ffi tool for contract tests. build-go-ffi: - cd ./scripts/go-ffi && go build + cd ./scripts/go-ffi && go build -buildvcs=false # Cleans build artifacts and deployments. clean: diff --git a/packages/contracts-bedrock/scripts/L2Genesis.s.sol b/packages/contracts-bedrock/scripts/L2Genesis.s.sol index 19d3ef5e122..c189309fddd 100644 --- a/packages/contracts-bedrock/scripts/L2Genesis.s.sol +++ b/packages/contracts-bedrock/scripts/L2Genesis.s.sol @@ -89,8 +89,8 @@ contract L2Genesis is Script { uint256 internal constant PRECOMPILE_COUNT = 256; uint80 internal constant DEV_ACCOUNT_FUND_AMT = 10_000 ether; - uint32 internal constant WITHDRAWAL_MIN_GAS_LIMIT = 1_000_000; - uint256 internal constant MIN_WITHDRAWAL_AMOUNT_THRESHOLD = 10 ether; + uint32 internal constant WITHDRAWAL_MIN_GAS_LIMIT = 800_000; + uint256 internal constant MIN_WITHDRAWAL_AMOUNT_THRESHOLD = 2 ether; /// @notice Default Anvil dev accounts. Only funded if `cfg.fundDevAccounts == true`. /// Also known as "test test test test test test test test test test test junk" mnemonic accounts, diff --git a/packages/contracts-bedrock/scripts/checks/opcm-upgrade-checks/main.go b/packages/contracts-bedrock/scripts/checks/opcm-upgrade-checks/main.go index 7afe2cf1019..da3d257c651 100644 --- a/packages/contracts-bedrock/scripts/checks/opcm-upgrade-checks/main.go +++ b/packages/contracts-bedrock/scripts/checks/opcm-upgrade-checks/main.go @@ -36,7 +36,7 @@ func main() { // Process. if _, err := common.ProcessFilesGlob( []string{"forge-artifacts/**/*.json"}, - []string{"forge-artifacts/OPContractsManager.sol/*.json", "forge-artifacts/OPContractsManagerV2.sol/*.json", "forge-artifacts/opcm/OPContractsManagerV2.sol/*.json"}, + []string{"forge-artifacts/OPContractsManager.sol/*.json", "forge-artifacts/OPContractsManagerV2.sol/*.json", "forge-artifacts/OPContractsManagerUtils.sol/*.json", "forge-artifacts/opcm/**/*.json"}, processFile, ); err != nil { fmt.Printf("error: %v\n", err) diff --git a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index 9e743b2fa4f..87c76b7855d 100644 --- a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol @@ -169,6 +169,11 @@ contract Deploy is Deployer { deploySuperchain(); } + // Override useCustomGasToken config if system feature flag is set + if (Config.sysFeatureCustomGasToken()) { + cfg.setUseCustomGasToken(true); + } + deployImplementations({ _isInterop: cfg.useInterop() }); // Deploy Current OPChain Contracts diff --git a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol index b5eed69563d..8634e721898 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol @@ -31,6 +31,7 @@ import { } from "interfaces/L1/IOPContractsManager.sol"; import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; import { IOPContractsManagerContainer } from "interfaces/L1/opcm/IOPContractsManagerContainer.sol"; +import { IOPContractsManagerUtils } from "interfaces/L1/opcm/IOPContractsManagerUtils.sol"; import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; import { IOptimismPortalInterop } from "interfaces/L1/IOptimismPortalInterop.sol"; import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; @@ -77,6 +78,7 @@ contract DeployImplementations is Script { IOPContractsManagerUpgrader opcmUpgrader; IOPContractsManagerInteropMigrator opcmInteropMigrator; IOPContractsManagerStandardValidator opcmStandardValidator; + IOPContractsManagerUtils opcmUtils; IOPContractsManagerV2 opcmV2; IOPContractsManagerContainer opcmContainer; // v2 container IDelayedWETH delayedWETHImpl; @@ -222,6 +224,7 @@ contract DeployImplementations is Script { deployOPCMUpgrader(_output); deployOPCMInteropMigrator(_output); deployOPCMStandardValidator(_input, _output, implementations); + deployOPCMUtils(_output); deployOPCMV2(_output); // Semgrep rule will fail because the arguments are encoded inside of a separate function. @@ -762,13 +765,28 @@ contract DeployImplementations is Script { _output.opcmStandardValidator = impl; } + function deployOPCMUtils(Output memory _output) private { + IOPContractsManagerUtils impl = IOPContractsManagerUtils( + DeployUtils.createDeterministic({ + _name: "OPContractsManagerUtils.sol:OPContractsManagerUtils", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IOPContractsManagerUtils.__constructor__, (_output.opcmContainer)) + ), + _salt: _salt + }) + ); + vm.label(address(impl), "OPContractsManagerUtilsImpl"); + _output.opcmUtils = impl; + } + function deployOPCMV2(Output memory _output) private { IOPContractsManagerV2 impl = IOPContractsManagerV2( DeployUtils.createDeterministic({ _name: "OPContractsManagerV2.sol:OPContractsManagerV2", _args: DeployUtils.encodeConstructor( abi.encodeCall( - IOPContractsManagerV2.__constructor__, (_output.opcmContainer, _output.opcmStandardValidator) + IOPContractsManagerV2.__constructor__, + (_output.opcmContainer, _output.opcmStandardValidator, _output.opcmUtils) ) ), _salt: _salt diff --git a/packages/contracts-bedrock/scripts/deploy/DeploySaferSafes.s.sol b/packages/contracts-bedrock/scripts/deploy/DeploySaferSafes.s.sol new file mode 100644 index 00000000000..3c411baed94 --- /dev/null +++ b/packages/contracts-bedrock/scripts/deploy/DeploySaferSafes.s.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +// Forge +import { Script } from "forge-std/Script.sol"; + +// Scripts +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; + +// Interfaces +import { ISaferSafes } from "interfaces/safe/ISaferSafes.sol"; + +// Libraries +import { SemverComp } from "src/libraries/SemverComp.sol"; + +/// @title DeploySaferSafes +/// @notice Deploys the SaferSafes singleton contract using CREATE2 with the default salt. +contract DeploySaferSafes is Script { + struct Output { + ISaferSafes saferSafesSingleton; + } + + /// @notice Deploys SaferSafes and returns the output struct. + function run() public returns (Output memory output_) { + output_ = _deploy(); + assertValidOutput(output_); + } + + /// @notice Deploys SaferSafes without broadcasting (for use by other scripts). + function _deploy() internal returns (Output memory output_) { + output_.saferSafesSingleton = ISaferSafes( + DeployUtils.createDeterministic({ + _name: "SaferSafes", + _args: DeployUtils.encodeConstructor(bytes("")), + _salt: DeployUtils.DEFAULT_SALT + }) + ); + vm.label(address(output_.saferSafesSingleton), "SaferSafesSingleton"); + } + + /// @notice Validates the deployment output. + function assertValidOutput(Output memory _output) public view { + DeployUtils.assertValidContractAddress(address(_output.saferSafesSingleton)); + + require(SemverComp.eq(_output.saferSafesSingleton.version(), "1.10.1"), "DeploySaferSafes: unexpected version"); + } +} diff --git a/packages/contracts-bedrock/scripts/libraries/Config.sol b/packages/contracts-bedrock/scripts/libraries/Config.sol index 60653866f7c..4b5a2ed6339 100644 --- a/packages/contracts-bedrock/scripts/libraries/Config.sol +++ b/packages/contracts-bedrock/scripts/libraries/Config.sol @@ -286,13 +286,13 @@ library Config { return vm.envOr("DEV_FEATURE__DEPLOY_V2_DISPUTE_GAMES", false); } - /// @notice Returns true if the development feature custom gas token is enabled. - function devFeatureCustomGasToken() internal view returns (bool) { - return vm.envOr("DEV_FEATURE__CUSTOM_GAS_TOKEN", false); - } - /// @notice Returns true if the development feature opcm_v2 is enabled. function devFeatureOpcmV2() internal view returns (bool) { return vm.envOr("DEV_FEATURE__OPCM_V2", false); } + + /// @notice Returns true if the system feature custom_gas_token is enabled. + function sysFeatureCustomGasToken() internal view returns (bool) { + return vm.envOr("SYS_FEATURE__CUSTOM_GAS_TOKEN", false); + } } diff --git a/packages/contracts-bedrock/scripts/ops/publish-artifacts.sh b/packages/contracts-bedrock/scripts/ops/publish-artifacts.sh index 26e55ea0775..84901321f42 100755 --- a/packages/contracts-bedrock/scripts/ops/publish-artifacts.sh +++ b/packages/contracts-bedrock/scripts/ops/publish-artifacts.sh @@ -5,14 +5,13 @@ set -euo pipefail usage() { echo "Usage: $0 [--force|-f]" echo "" - echo "Publish contract artifacts to GCS with optional zstd compression." + echo "Publish contract artifacts to GCS using op-deployer's build process." echo "" echo "Options:" echo " --force, -f Force upload even if artifacts already exist" echo " --help, -h Show this help message" echo "" - echo "If zstd is available, creates both .tar.gz and .tar.zst files." - echo "Otherwise, creates only .tar.gz with a warning about future zstd requirement." + echo "Uses 'just build-contracts' and 'just copy-contract-artifacts' from op-deployer." exit 0 } @@ -20,7 +19,6 @@ echoerr() { echo "$@" 1>&2 } -# Check for help flag if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then usage fi @@ -30,157 +28,68 @@ CONTRACTS_DIR="$SCRIPT_DIR/../.." DEPLOY_BUCKET="oplabs-contract-artifacts" BUCKET_URL="https://storage.googleapis.com/$DEPLOY_BUCKET" -cd "$CONTRACTS_DIR" +# Resolve paths +ROOT_DIR=$(cd -- "$CONTRACTS_DIR/../.." &> /dev/null && pwd) +OP_DEPLOYER_DIR="$ROOT_DIR/op-deployer" + +if [ ! -d "$OP_DEPLOYER_DIR" ]; then + echoerr "> ERROR: op-deployer not found at $OP_DEPLOYER_DIR" + exit 1 +fi -# Check for force flag FORCE=false if [ "${1:-}" = "--force" ] || [ "${1:-}" = "-f" ]; then FORCE=true echoerr "> Force mode enabled - will overwrite existing artifacts" fi -if command -v zstd > /dev/null 2>&1; then - HAS_ZSTD=true - echoerr "> zstd found, will create both .tar.gz and .tar.zst files" -else - HAS_ZSTD=false - echoerr "> zstd not found, will create only .tar.gz files" - echoerr "> WARNING: zstd not available. In the future, only zstd will be supported." -fi +checksum=$(bash "$CONTRACTS_DIR/scripts/ops/calculate-checksum.sh") -# ensure that artifacts exists and is non-empty -if [ ! -d "forge-artifacts" ] || [ -z "$(ls -A forge-artifacts)" ]; then - echoerr "> No forge-artifacts directory found." - exit 1 -fi - -if [ ! -d "artifacts" ] || [ -z "$(ls -A artifacts)" ]; then - echoerr "> No artifacts directory found." - exit 1 -fi - -checksum=$(bash scripts/ops/calculate-checksum.sh) - -archive_name_gz="artifacts-v1-$checksum.tar.gz" archive_name_zst="artifacts-v1-$checksum.tar.zst" - -upload_url_gz="$BUCKET_URL/$archive_name_gz" upload_url_zst="$BUCKET_URL/$archive_name_zst" echoerr "> Checksum: $checksum" echoerr "> Checking for existing artifacts..." -if [ "$HAS_ZSTD" = true ]; then - exists_zst=$(curl -s -o /dev/null --fail -LI "$upload_url_zst" || echo "fail") - if [ "$exists_zst" != "fail" ] && [ "$FORCE" = false ]; then - echoerr "> Existing artifacts found (.tar.zst), nothing to do. Use --force to overwrite." - exit 0 - fi -fi - -exists_gz=$(curl -s -o /dev/null --fail -LI "$upload_url_gz" || echo "fail") - -if [ "$exists_gz" != "fail" ] && [ "$FORCE" = false ]; then - echoerr "> Existing artifacts found (.tar.gz), nothing to do. Use --force to overwrite." +exists_zst=$(curl -s -o /dev/null --fail -LI "$upload_url_zst" || echo "fail") +if [ "$exists_zst" != "fail" ] && [ "$FORCE" = false ]; then + echoerr "> Existing artifacts found (.tar.zst), nothing to do. Use --force to overwrite." exit 0 fi -echoerr "> Archiving artifacts..." - -# use gtar on darwin -if [[ "$OSTYPE" == "darwin*" ]]; then - tar="gtar" -else - tar="tar" -fi - -rm -f COMMIT -commit=$(git rev-parse HEAD) -echo "$commit" > COMMIT - -tar_args=("artifacts" "forge-artifacts" "COMMIT") -if [ -d "cache" ]; then - tar_args+=("cache") - echoerr "> Including cache directory in archive" -else - echoerr "> Cache directory not found, excluding from archive" -fi - -if [ "$HAS_ZSTD" = true ]; then - echoerr "> Compressing artifacts (.tar.gz and .tar.zst)..." - - # Create intermediate tar file first for reliable compression - temp_tar="artifacts-v1-$checksum.tar" - "$tar" -cf "$temp_tar" "${tar_args[@]}" - - gzip -9 < "$temp_tar" > "$archive_name_gz" & - gz_pid=$! - - zstd --ultra -22 -f "$temp_tar" -o "$archive_name_zst" & - zst_pid=$! - - wait "$gz_pid" - wait "$zst_pid" - - rm "$temp_tar" +echoerr "> Building contracts and creating artifacts..." - du -sh "$archive_name_gz" | awk '{$1=$1};1' # trim leading whitespace - echoerr "> Created .tar.gz archive" - du -sh "$archive_name_zst" | awk '{$1=$1};1' # trim leading whitespace - echoerr "> Created .tar.zst archive" +cd "$OP_DEPLOYER_DIR" - # Compare file sizes in MB - gz_size=$(stat -f%z "$archive_name_gz" 2> /dev/null || stat -c%s "$archive_name_gz" 2> /dev/null || echo "0") - zst_size=$(stat -f%z "$archive_name_zst" 2> /dev/null || stat -c%s "$archive_name_zst" 2> /dev/null || echo "0") +echoerr "> Running 'just build-contracts'..." +just build-contracts - if [ "$gz_size" -gt 0 ] && [ "$zst_size" -gt 0 ]; then - gz_mb=$(awk "BEGIN {printf \"%.2f\", $gz_size / 1048576}") - zst_mb=$(awk "BEGIN {printf \"%.2f\", $zst_size / 1048576}") - savings=$((gz_size - zst_size)) - savings_percent=$((100 * savings / gz_size)) - echoerr "> Size comparison: .tar.gz=${gz_mb}MB, .tar.zst=${zst_mb}MB (${savings_percent}% smaller)" - fi -else - echoerr "> Compressing artifacts (.tar.gz)..." +echoerr "> Running 'just copy-contract-artifacts'..." +just copy-contract-artifacts - "$tar" -czf "$archive_name_gz" "${tar_args[@]}" - du -sh "$archive_name_gz" | awk '{$1=$1};1' # trim leading whitespace - echoerr "> Created .tar.gz archive" +ARTIFACTS_TZST="./pkg/deployer/artifacts/forge-artifacts/artifacts.tzst" +if [ ! -f "$ARTIFACTS_TZST" ]; then + echoerr "> ERROR: Failed to create artifacts.tzst" + exit 1 fi -echoerr "> Done." +TEMP_DIR=$(mktemp -d) +trap 'rm -rf "$TEMP_DIR"' EXIT + +cp "$ARTIFACTS_TZST" "$TEMP_DIR/$archive_name_zst" +du -sh "$TEMP_DIR/$archive_name_zst" | awk '{$1=$1};1' +echoerr "> Created .tar.zst archive" echoerr "> Uploading artifacts to $BUCKET_URL..." # Force single-stream upload to improve reliability gcloud config set storage/parallel_composite_upload_enabled False -if [ "$HAS_ZSTD" = true ]; then - # Upload with checksum-based names - gcloud --verbosity="info" storage cp "$archive_name_gz" "$archive_name_zst" "gs://$DEPLOY_BUCKET/" - echoerr "> Uploaded to: $upload_url_gz" - echoerr "> Uploaded to: $upload_url_zst" - - # Also upload as "latest" for PR fallback - echoerr "> Uploading as 'latest' for PR fallback..." - gcloud --verbosity="info" storage cp "$archive_name_gz" "gs://$DEPLOY_BUCKET/artifacts-v1-latest.tar.gz" - gcloud --verbosity="info" storage cp "$archive_name_zst" "gs://$DEPLOY_BUCKET/artifacts-v1-latest.tar.zst" - echoerr "> Uploaded to: https://storage.googleapis.com/$DEPLOY_BUCKET/artifacts-v1-latest.tar.gz" - echoerr "> Uploaded to: https://storage.googleapis.com/$DEPLOY_BUCKET/artifacts-v1-latest.tar.zst" -else - # Upload with checksum-based name - gcloud --verbosity="info" storage cp "$archive_name_gz" "gs://$DEPLOY_BUCKET/$archive_name_gz" - echoerr "> Uploaded to: $upload_url_gz" - - # Also upload as "latest" for PR fallback - echoerr "> Uploading as 'latest' for PR fallback..." - gcloud --verbosity="info" storage cp "$archive_name_gz" "gs://$DEPLOY_BUCKET/artifacts-v1-latest.tar.gz" - echoerr "> Uploaded to: https://storage.googleapis.com/$DEPLOY_BUCKET/artifacts-v1-latest.tar.gz" -fi -echoerr "> Done." +gcloud --verbosity="info" storage cp "$TEMP_DIR/$archive_name_zst" "gs://$DEPLOY_BUCKET/$archive_name_zst" +echoerr "> Uploaded to: $upload_url_zst" -rm "$archive_name_gz" -if [ "$HAS_ZSTD" = true ]; then - rm "$archive_name_zst" -fi -rm COMMIT +echoerr "> Uploading as 'latest' for PR fallback..." +gcloud --verbosity="info" storage cp "gs://$DEPLOY_BUCKET/$archive_name_zst" "gs://$DEPLOY_BUCKET/artifacts-v1-latest.tar.zst" +echoerr "> Uploaded to: https://storage.googleapis.com/$DEPLOY_BUCKET/artifacts-v1-latest.tar.zst" + +echoerr "> Done." diff --git a/packages/contracts-bedrock/scripts/ops/pull-artifacts.sh b/packages/contracts-bedrock/scripts/ops/pull-artifacts.sh index 7f79c46341b..05f1d8a3f1a 100755 --- a/packages/contracts-bedrock/scripts/ops/pull-artifacts.sh +++ b/packages/contracts-bedrock/scripts/ops/pull-artifacts.sh @@ -30,10 +30,11 @@ download_and_extract() { echoerr "> Done." echoerr "> Extracting artifacts..." + # Only extract artifacts, forge-artifacts, and cache folders (nothing else) if [[ "$archive_name" == *.tar.zst ]]; then - zstd -dc "$archive_name" | tar -xf - --exclude='*..*' + zstd -dc "$archive_name" | tar -xf - --exclude='*..*' artifacts forge-artifacts cache else - tar -xzvf "$archive_name" --exclude='*..*' + tar -xzvf "$archive_name" --exclude='*..*' artifacts forge-artifacts cache fi echoerr "> Done." diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUtils.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUtils.json new file mode 100644 index 00000000000..b862e0fc33f --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUtils.json @@ -0,0 +1,640 @@ +[ + { + "inputs": [ + { + "internalType": "contract IOPContractsManagerContainer", + "name": "_contractsContainer", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "blueprints", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "addressManager", + "type": "address" + }, + { + "internalType": "address", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ChugSplashProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "resolvedDelegateProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame2", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame2", + "type": "address" + } + ], + "internalType": "struct IOPContractsManagerContainer.Blueprints", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_l2ChainId", + "type": "uint256" + } + ], + "name": "chainIdToBatchInboxAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_l2ChainId", + "type": "uint256" + }, + { + "internalType": "string", + "name": "_saltMixer", + "type": "string" + }, + { + "internalType": "string", + "name": "_contractName", + "type": "string" + } + ], + "name": "computeSalt", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "contractsContainer", + "outputs": [ + { + "internalType": "contract IOPContractsManagerContainer", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct OPContractsManagerUtils.ExtraInstruction[]", + "name": "_instructions", + "type": "tuple[]" + }, + { + "internalType": "string", + "name": "_key", + "type": "string" + } + ], + "name": "getInstructionByKey", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct OPContractsManagerUtils.ExtraInstruction", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct OPContractsManagerUtils.ExtraInstruction[]", + "name": "_instructions", + "type": "tuple[]" + }, + { + "internalType": "string", + "name": "_key", + "type": "string" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "hasInstruction", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "implementations", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "superchainConfigImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "protocolVersionsImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ERC721BridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismPortalImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismPortalInteropImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "ethLockboxImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "systemConfigImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismMintableERC20FactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1CrossDomainMessengerImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1StandardBridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "disputeGameFactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "anchorStateRegistryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "delayedWETHImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "mipsImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "faultDisputeGameV2Impl", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGameV2Impl", + "type": "address" + }, + { + "internalType": "address", + "name": "superFaultDisputeGameImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "superPermissionedDisputeGameImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "storageSetterImpl", + "type": "address" + } + ], + "internalType": "struct IOPContractsManagerContainer.Implementations", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct OPContractsManagerUtils.ExtraInstruction", + "name": "_instruction", + "type": "tuple" + }, + { + "internalType": "string", + "name": "_key", + "type": "string" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "isMatchingInstruction", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_source", + "type": "address" + }, + { + "internalType": "bytes4", + "name": "_selector", + "type": "bytes4" + }, + { + "internalType": "string", + "name": "_name", + "type": "string" + }, + { + "components": [ + { + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct OPContractsManagerUtils.ExtraInstruction[]", + "name": "_instructions", + "type": "tuple[]" + } + ], + "name": "loadBytes", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_source", + "type": "address" + }, + { + "internalType": "bytes4", + "name": "_selector", + "type": "bytes4" + }, + { + "components": [ + { + "internalType": "contract IProxyAdmin", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "contract IAddressManager", + "name": "addressManager", + "type": "address" + }, + { + "internalType": "uint256", + "name": "l2ChainId", + "type": "uint256" + }, + { + "internalType": "string", + "name": "saltMixer", + "type": "string" + } + ], + "internalType": "struct OPContractsManagerUtils.ProxyDeployArgs", + "name": "_args", + "type": "tuple" + }, + { + "internalType": "string", + "name": "_contractName", + "type": "string" + }, + { + "components": [ + { + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct OPContractsManagerUtils.ExtraInstruction[]", + "name": "_instructions", + "type": "tuple[]" + } + ], + "name": "loadOrDeployProxy", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IProxyAdmin", + "name": "_proxyAdmin", + "type": "address" + }, + { + "internalType": "address", + "name": "_target", + "type": "address" + }, + { + "internalType": "address", + "name": "_implementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + }, + { + "internalType": "bytes32", + "name": "_slot", + "type": "bytes32" + }, + { + "internalType": "uint8", + "name": "_offset", + "type": "uint8" + } + ], + "name": "upgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "indexed": false, + "internalType": "address", + "name": "proxy", + "type": "address" + } + ], + "name": "ProxyCreation", + "type": "event" + }, + { + "inputs": [], + "name": "BytesArrayTooLong", + "type": "error" + }, + { + "inputs": [], + "name": "DeploymentFailed", + "type": "error" + }, + { + "inputs": [], + "name": "EmptyInitcode", + "type": "error" + }, + { + "inputs": [], + "name": "IdentityPrecompileCallFailed", + "type": "error" + }, + { + "inputs": [], + "name": "NotABlueprint", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_name", + "type": "string" + } + ], + "name": "OPContractsManagerUtils_ConfigLoadFailed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_contract", + "type": "address" + } + ], + "name": "OPContractsManagerUtils_DowngradeNotAllowed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_name", + "type": "string" + } + ], + "name": "OPContractsManagerUtils_ProxyMustLoad", + "type": "error" + }, + { + "inputs": [], + "name": "ReservedBitsSet", + "type": "error" + }, + { + "inputs": [], + "name": "SemverComp_InvalidSemverParts", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "UnexpectedPreambleData", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "UnsupportedERCVersion", + "type": "error" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json index 1f10dbcd2b8..55019089504 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json @@ -10,6 +10,11 @@ "internalType": "contract IOPContractsManagerStandardValidator", "name": "_standardValidator", "type": "address" + }, + { + "internalType": "contract IOPContractsManagerUtils", + "name": "_utils", + "type": "address" } ], "stateMutability": "nonpayable", @@ -449,6 +454,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "thisOPCM", + "outputs": [ + { + "internalType": "contract OPContractsManagerV2", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -498,7 +516,7 @@ "type": "bytes" } ], - "internalType": "struct OPContractsManagerV2.ExtraInstruction[]", + "internalType": "struct IOPContractsManagerUtils.ExtraInstruction[]", "name": "extraInstructions", "type": "tuple[]" } @@ -603,7 +621,7 @@ "type": "bytes" } ], - "internalType": "struct OPContractsManagerV2.ExtraInstruction[]", + "internalType": "struct IOPContractsManagerUtils.ExtraInstruction[]", "name": "extraInstructions", "type": "tuple[]" } @@ -633,35 +651,29 @@ }, { "inputs": [], - "name": "version", + "name": "utils", "outputs": [ { - "internalType": "string", + "internalType": "contract IOPContractsManagerUtils", "name": "", - "type": "string" + "type": "address" } ], "stateMutability": "view", "type": "function" }, { - "anonymous": false, - "inputs": [ + "inputs": [], + "name": "version", + "outputs": [ { - "indexed": false, "internalType": "string", - "name": "name", + "name": "", "type": "string" - }, - { - "indexed": false, - "internalType": "address", - "name": "proxy", - "type": "address" } ], - "name": "ProxyCreation", - "type": "event" + "stateMutability": "view", + "type": "function" }, { "inputs": [], @@ -688,28 +700,6 @@ "name": "NotABlueprint", "type": "error" }, - { - "inputs": [ - { - "internalType": "string", - "name": "_name", - "type": "string" - } - ], - "name": "OPContractsManagerV2_ConfigLoadFailed", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_contract", - "type": "address" - } - ], - "name": "OPContractsManagerV2_DowngradeNotAllowed", - "type": "error" - }, { "inputs": [], "name": "OPContractsManagerV2_InvalidGameConfigs", @@ -720,20 +710,15 @@ "name": "OPContractsManagerV2_InvalidUpgradeInput", "type": "error" }, - { - "inputs": [], - "name": "OPContractsManagerV2_InvalidUpgradeInstruction", - "type": "error" - }, { "inputs": [ { "internalType": "string", - "name": "_name", + "name": "_key", "type": "string" } ], - "name": "OPContractsManagerV2_ProxyMustLoad", + "name": "OPContractsManagerV2_InvalidUpgradeInstruction", "type": "error" }, { diff --git a/packages/contracts-bedrock/snapshots/abi/SystemConfig.json b/packages/contracts-bedrock/snapshots/abi/SystemConfig.json index 826bfbcd9cb..3eb8330144d 100644 --- a/packages/contracts-bedrock/snapshots/abi/SystemConfig.json +++ b/packages/contracts-bedrock/snapshots/abi/SystemConfig.json @@ -69,6 +69,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "OPCM_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT", @@ -299,6 +312,11 @@ "internalType": "address", "name": "delayedWETH", "type": "address" + }, + { + "internalType": "address", + "name": "opcm", + "type": "address" } ], "internalType": "struct SystemConfig.Addresses", @@ -440,6 +458,11 @@ "internalType": "address", "name": "delayedWETH", "type": "address" + }, + { + "internalType": "address", + "name": "opcm", + "type": "address" } ], "internalType": "struct SystemConfig.Addresses", @@ -546,6 +569,32 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "lastUsedOPCM", + "outputs": [ + { + "internalType": "address", + "name": "addr_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastUsedOPCMVersion", + "outputs": [ + { + "internalType": "string", + "name": "version_", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "maximumGasLimit", diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 638beb96a01..4b748b3cd1e 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -24,8 +24,8 @@ "sourceCodeHash": "0xfca613b5d055ffc4c3cbccb0773ddb9030abedc1aa6508c9e2e7727cc0cd617b" }, "src/L1/OPContractsManager.sol:OPContractsManager": { - "initCodeHash": "0x6fd82aaec43858b34bd093e29bbacb659a95817d506de948aebd3c84e6d2a00a", - "sourceCodeHash": "0x5f63555e12cb8e27ce5c1511e986550bf2f1b9769e314223a7324b8dcfa29523" + "initCodeHash": "0x0cb3dfc803e34cd2cae5798e17f29bebbbb861c82cbe8258d6677aedcebcda74", + "sourceCodeHash": "0xb1dffb96b3ba20aaaadaf1110be06540ab03be79b7c6603e0b1ff3486957c8fc" }, "src/L1/OPContractsManagerStandardValidator.sol:OPContractsManagerStandardValidator": { "initCodeHash": "0x0c8b15453d0f0bc5d9af07f104505e0bbb2b358f0df418289822fb73a8652b30", @@ -48,12 +48,12 @@ "sourceCodeHash": "0xbf344c4369b8cb00ec7a3108f72795747f3bc59ab5b37ac18cf21e72e2979dbf" }, "src/L1/SystemConfig.sol:SystemConfig": { - "initCodeHash": "0x5ff0f79914999b54daeb3e3f38a1e2275f286e005a9e52055d169282605fec81", - "sourceCodeHash": "0xc2b530bf529ac23d1ba1adf67eedcc5d95e25c2919e1d97f4f2bba4823303b17" + "initCodeHash": "0xd4ec112de4cf7173668374479b7405bab9c828e5b32c946ef8ab5cd021f9703b", + "sourceCodeHash": "0xb3184aa5d95a82109e7134d1f61941b30e25f655b9849a0e303d04bbce0cde0b" }, "src/L1/opcm/OPContractsManagerV2.sol:OPContractsManagerV2": { - "initCodeHash": "0x0db0819c7dcefafc5a0780b3a85c0b2dded2413cb5ee12306eecd6c81d91a404", - "sourceCodeHash": "0x41a838105c41756eccab3a2ef2e89ebd1ec4e04aabde1597ca847878cb87fe4f" + "initCodeHash": "0x9b6b84f8b87f60c5a38460e78aa6768205f741b17fa60c6d93a1094a8681bd79", + "sourceCodeHash": "0x846dca230392d8e378a2e208a708fc9be342715fd1ca3e3d7c1700f667e25fff" }, "src/L2/BaseFeeVault.sol:BaseFeeVault": { "initCodeHash": "0x838bbd7f381e84e21887f72bd1da605bfc4588b3c39aed96cbce67c09335b3ee", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerUtils.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerUtils.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerUtils.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index 31bfb0989e8..20ab0f10f79 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -1584,7 +1584,8 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { l1StandardBridge: address(_output.l1StandardBridgeProxy), optimismPortal: address(_output.optimismPortalProxy), optimismMintableERC20Factory: address(_output.optimismMintableERC20FactoryProxy), - delayedWETH: address(0) // Will be used in OPCMv2. + delayedWETH: address(0), // Will be used in OPCMv2. + opcm: address(0) // Unsupported for V1. }); assertValidContractAddress(opChainAddrs_.l1CrossDomainMessenger); @@ -2236,9 +2237,9 @@ contract OPContractsManager is ISemver { // -------- Constants and Variables -------- - /// @custom:semver 5.7.1 + /// @custom:semver 5.8.0 function version() public pure virtual returns (string memory) { - return "5.7.1"; + return "5.8.0"; } OPContractsManagerGameTypeAdder public immutable opcmGameTypeAdder; diff --git a/packages/contracts-bedrock/src/L1/SystemConfig.sol b/packages/contracts-bedrock/src/L1/SystemConfig.sol index 9dc554e4053..8aadc105fcd 100644 --- a/packages/contracts-bedrock/src/L1/SystemConfig.sol +++ b/packages/contracts-bedrock/src/L1/SystemConfig.sol @@ -53,6 +53,7 @@ contract SystemConfig is ProxyAdminOwnedBase, OwnableUpgradeable, Reinitializabl address optimismPortal; address optimismMintableERC20Factory; address delayedWETH; + address opcm; } /// @notice Version identifier, used for upgrades. @@ -88,6 +89,9 @@ contract SystemConfig is ProxyAdminOwnedBase, OwnableUpgradeable, Reinitializabl /// @notice Storage slot that the DelayedWETH address is stored at. bytes32 public constant DELAYED_WETH_SLOT = bytes32(uint256(keccak256("systemconfig.delayedweth")) - 1); + /// @notice Storage slot that the OPCM address is stored at. + bytes32 public constant OPCM_SLOT = bytes32(uint256(keccak256("systemconfig.opcm")) - 1); + /// @notice Storage slot that the batch inbox address is stored at. bytes32 public constant BATCH_INBOX_SLOT = bytes32(uint256(keccak256("systemconfig.batchinbox")) - 1); @@ -170,9 +174,9 @@ contract SystemConfig is ProxyAdminOwnedBase, OwnableUpgradeable, Reinitializabl error SystemConfig_InvalidFeatureState(); /// @notice Semantic version. - /// @custom:semver 3.13.1 + /// @custom:semver 3.14.0 function version() public pure virtual returns (string memory) { - return "3.13.1"; + return "3.14.0"; } /// @notice Constructs the SystemConfig contract. @@ -233,6 +237,7 @@ contract SystemConfig is ProxyAdminOwnedBase, OwnableUpgradeable, Reinitializabl Storage.setAddress(OPTIMISM_PORTAL_SLOT, _addresses.optimismPortal); Storage.setAddress(OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT, _addresses.optimismMintableERC20Factory); Storage.setAddress(DELAYED_WETH_SLOT, _addresses.delayedWETH); + Storage.setAddress(OPCM_SLOT, _addresses.opcm); _setStartBlock(); _setResourceConfig(_config); @@ -303,6 +308,16 @@ contract SystemConfig is ProxyAdminOwnedBase, OwnableUpgradeable, Reinitializabl addr_ = Storage.getAddress(DELAYED_WETH_SLOT); } + /// @notice Getter for the OPCM address. + function lastUsedOPCM() public view returns (address addr_) { + addr_ = Storage.getAddress(OPCM_SLOT); + } + + /// @notice Getter for the version of the last used OPCM. + function lastUsedOPCMVersion() public view returns (string memory version_) { + version_ = ISemver(lastUsedOPCM()).version(); + } + /// @notice Consolidated getter for the Addresses struct. function getAddresses() external view returns (Addresses memory) { return Addresses({ @@ -311,7 +326,8 @@ contract SystemConfig is ProxyAdminOwnedBase, OwnableUpgradeable, Reinitializabl l1StandardBridge: l1StandardBridge(), optimismPortal: optimismPortal(), optimismMintableERC20Factory: optimismMintableERC20Factory(), - delayedWETH: delayedWETH() + delayedWETH: delayedWETH(), + opcm: lastUsedOPCM() }); } diff --git a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerUtils.sol b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerUtils.sol new file mode 100644 index 00000000000..df7025086db --- /dev/null +++ b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerUtils.sol @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Libraries +import { LibString } from "@solady/utils/LibString.sol"; +import { SemverComp } from "src/libraries/SemverComp.sol"; +import { Blueprint } from "src/libraries/Blueprint.sol"; +import { Constants } from "src/libraries/Constants.sol"; + +// Interfaces +import { IOPContractsManagerContainer } from "interfaces/L1/opcm/IOPContractsManagerContainer.sol"; +import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; +import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; +import { IStorageSetter } from "interfaces/universal/IStorageSetter.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; + +/// @title OPContractsManagerUtils +/// @notice OPContractsManagerUtils is a contract that provides utility functions for the OPContractsManager. +contract OPContractsManagerUtils { + /// @notice Helper struct for deploying proxies, keeps code cleaner. + struct ProxyDeployArgs { + IProxyAdmin proxyAdmin; + IAddressManager addressManager; + uint256 l2ChainId; + string saltMixer; + } + + /// @notice Struct that represents an additional instruction for an upgrade. Each upgrade has + /// its own set of extra upgrade instructions that may or may not be required. We use + /// this struct to keep the upgrade interface the same each time. + struct ExtraInstruction { + string key; + bytes data; + } + + /// @notice Emitted when a proxy is created by this contract. + /// @param name The name of the proxy. + /// @param proxy The address of the proxy. + event ProxyCreation(string name, address proxy); + + /// @notice Thrown when user attempts to downgrade a contract. + /// @param _contract The address of the contract that was attempted to be downgraded. + error OPContractsManagerUtils_DowngradeNotAllowed(address _contract); + + /// @notice Thrown when a config load fails. + /// @param _name The name of the config that failed to load. + error OPContractsManagerUtils_ConfigLoadFailed(string _name); + + /// @notice Thrown when a proxy must be loaded but couldn't be. + /// @param _name The name of the proxy that couldn't be loaded. + error OPContractsManagerUtils_ProxyMustLoad(string _name); + + /// @notice Container of blueprint and implementation contract addresses. + IOPContractsManagerContainer public immutable contractsContainer; + + /// @param _contractsContainer The container of blueprint and implementation contract addresses. + constructor(IOPContractsManagerContainer _contractsContainer) { + contractsContainer = _contractsContainer; + } + + /// @notice Maps an L2 chain ID to an L1 batch inbox address as defined by the standard + /// configuration's convention. This convention is + /// `versionByte || keccak256(bytes32(chainId))[:19]`, where || denotes concatenation, + /// versionByte is 0x00, and chainId is a uint256. + /// https://specs.optimism.io/protocol/configurability.html#consensus-parameters + /// @param _l2ChainId The L2 chain ID to map to an L1 batch inbox address. + /// @return Chain ID mapped to an L1 batch inbox address. + function chainIdToBatchInboxAddress(uint256 _l2ChainId) external pure returns (address) { + bytes1 versionByte = 0x00; + bytes32 hashedChainId = keccak256(bytes.concat(bytes32(_l2ChainId))); + bytes19 first19Bytes = bytes19(hashedChainId); + return address(uint160(bytes20(bytes.concat(versionByte, first19Bytes)))); + } + + /// @notice Computes a unique salt for a contract deployment. + /// @param _l2ChainId The L2 chain ID of the chain being deployed to. + /// @param _saltMixer The salt mixer to use for the deployment. + /// @param _contractName The name of the contract to deploy. + /// @return The computed salt. + function computeSalt( + uint256 _l2ChainId, + string memory _saltMixer, + string memory _contractName + ) + public + pure + returns (bytes32) + { + return keccak256(abi.encode(_l2ChainId, _saltMixer, _contractName)); + } + + /// @notice Helper function to check if an instruction matches a given key and data. + /// @param _instruction The instruction to check. + /// @param _key The key of the instruction to check for. + /// @param _data The data of the instruction to check for. + /// @return True if the instruction matches, false otherwise. + function isMatchingInstruction( + ExtraInstruction memory _instruction, + string memory _key, + bytes memory _data + ) + public + pure + returns (bool) + { + return LibString.eq(_instruction.key, _key) && LibString.eq(string(_instruction.data), string(_data)); + } + + /// @notice Helper function to check if a given instruction is present in a list of extra + /// upgrade instructions. + /// @param _instructions The list of extra upgrade instructions. + /// @param _key The key of the instruction to check for. + /// @param _data The data of the instruction to check for. + /// @return True if the instruction is present, false otherwise. + function hasInstruction( + ExtraInstruction[] memory _instructions, + string memory _key, + bytes memory _data + ) + public + pure + returns (bool) + { + for (uint256 i = 0; i < _instructions.length; i++) { + if (isMatchingInstruction(_instructions[i], _key, _data)) { + return true; + } + } + return false; + } + + /// @notice Helper function to get an instruction by key. + /// @param _instructions The list of extra upgrade instructions. + /// @param _key The key of the instruction to get. + /// @return The instruction, or an empty instruction if the instruction is not found. + function getInstructionByKey( + ExtraInstruction[] memory _instructions, + string memory _key + ) + public + pure + returns (ExtraInstruction memory) + { + for (uint256 i = 0; i < _instructions.length; i++) { + if (LibString.eq(_instructions[i].key, _key)) { + return _instructions[i]; + } + } + return ExtraInstruction({ key: "", data: bytes("") }); + } + + /// @notice Helper function to load data from a source contract as bytes. + /// @param _source The source contract to load the data from. + /// @param _selector The selector of the function to call on the source contract. + /// @param _name The name of the field to load. + /// @param _instructions The extra upgrade instructions for the data load. + /// @return Data retrieved from the source contract. + function loadBytes( + address _source, + bytes4 _selector, + string memory _name, + ExtraInstruction[] memory _instructions + ) + external + view + returns (bytes memory) + { + // If an override exists for this load, return the override data. + ExtraInstruction memory overrideInstruction = getInstructionByKey(_instructions, _name); + if (bytes(overrideInstruction.key).length > 0) { + return overrideInstruction.data; + } + + // Otherwise, load the data from the source contract. + (bool success, bytes memory result) = address(_source).staticcall(abi.encodePacked(_selector)); + if (!success) { + revert OPContractsManagerUtils_ConfigLoadFailed(_name); + } + + // Return the loaded data. + return result; + } + + /// @notice Attempts to load a proxy from a source function where the proxy should be found. If + /// the proxy isn't found at the source, or the call to the source fails, we build a + /// new proxy instead. Calls to source contracts MUST NOT fail under any circumstances + /// other than the function not existing (which can happen in an upgrade scenario). + /// @param _source The source contract to load the proxy from. + /// @param _selector The selector of the function to call on the source contract. + /// @param _args The basic arguments for the proxy deployment. + /// @param _contractName The name of the contract to deploy. + /// @param _instructions The extra upgrade instructions for the proxy deployment. + /// @return The address of the loaded or built proxy. + function loadOrDeployProxy( + address _source, + bytes4 _selector, + ProxyDeployArgs memory _args, + string memory _contractName, + ExtraInstruction[] memory _instructions + ) + external + returns (address payable) + { + // Loads are allowed to fail ONLY if the user explicitly permitted it (or if this is a + // deployment and the "ALL" permission is set). + bool loadCanFail = hasInstruction(_instructions, Constants.PERMITTED_PROXY_DEPLOYMENT_KEY, bytes(_contractName)) + || hasInstruction( + _instructions, Constants.PERMITTED_PROXY_DEPLOYMENT_KEY, Constants.PERMIT_ALL_CONTRACTS_INSTRUCTION + ); + + // Try to load the proxy from the source. + (bool success, bytes memory result) = address(_source).staticcall(abi.encodePacked(_selector)); + + // If the load succeeded and the result is not a zero address, return the result. + if (success && abi.decode(result, (address)) != address(0)) { + return payable(abi.decode(result, (address))); + } else if (!loadCanFail) { + // Load not permitted to fail but did, revert. + revert OPContractsManagerUtils_ProxyMustLoad(_contractName); + } + + // We've failed to load, but we allowed that failure. + // Deploy the right proxy depending on the contract name. + address ret; + if (LibString.eq(_contractName, "L1StandardBridge")) { + // L1StandardBridge is a special case ChugSplashProxy (legacy). + ret = Blueprint.deployFrom( + blueprints().l1ChugSplashProxy, + computeSalt(_args.l2ChainId, _args.saltMixer, "L1StandardBridge"), + abi.encode(_args.proxyAdmin) + ); + + // ChugSplashProxy requires setting the proxy type on the ProxyAdmin. + _args.proxyAdmin.setProxyType(ret, IProxyAdmin.ProxyType.CHUGSPLASH); + } else if (LibString.eq(_contractName, "L1CrossDomainMessenger")) { + // L1CrossDomainMessenger is a special case ResolvedDelegateProxy (legacy). + string memory l1XdmName = "OVM_L1CrossDomainMessenger"; + ret = Blueprint.deployFrom( + blueprints().resolvedDelegateProxy, + computeSalt(_args.l2ChainId, _args.saltMixer, "L1CrossDomainMessenger"), + abi.encode(_args.addressManager, l1XdmName) + ); + + // ResolvedDelegateProxy requires setting the proxy type on the ProxyAdmin. + _args.proxyAdmin.setProxyType(ret, IProxyAdmin.ProxyType.RESOLVED); + _args.proxyAdmin.setImplementationName(ret, l1XdmName); + } else { + // Otherwise this is a normal proxy. + ret = Blueprint.deployFrom( + blueprints().proxy, + computeSalt(_args.l2ChainId, _args.saltMixer, _contractName), + abi.encode(_args.proxyAdmin) + ); + } + + // Emit the proxy creation event. + emit ProxyCreation(_contractName, ret); + + // Return the final deployment result. + return payable(ret); + } + + /// @notice Upgrades a contract by resetting the initialized slot and calling the initializer. + /// @param _proxyAdmin The proxy admin of the contract. + /// @param _target The target of the contract. + /// @param _implementation The implementation of the contract. + /// @param _data The data to call the initializer with. + /// @param _slot The slot where the initialized value is located. + /// @param _offset The offset of the initializer value in the slot. + function upgrade( + IProxyAdmin _proxyAdmin, + address _target, + address _implementation, + bytes memory _data, + bytes32 _slot, + uint8 _offset + ) + external + { + // Check to make sure that we're not downgrading. Downgrades aren't inherently dangerous + // but we also don't test for them so we don't really know if a specific downgrade will be + // dangerous or not. It's easier to just revert instead. + // NOTE: We DO allow upgrades to the same version, which makes it possible to use this + // function to both upgrade and then later perform management actions like changing + // the prestate for the fault dispute games. + if ( + _proxyAdmin.getProxyImplementation(payable(_target)) != address(0) + && SemverComp.gt(ISemver(_target).version(), ISemver(_implementation).version()) + ) { + revert OPContractsManagerUtils_DowngradeNotAllowed(address(_target)); + } + + // Upgrade to StorageSetter. + _proxyAdmin.upgrade(payable(_target), address(implementations().storageSetterImpl)); + + // Otherwise, we need to reset the initialized slot and call the initializer. + // Reset the initialized slot by zeroing the single byte at `_offset` (from the right). + bytes32 current = IStorageSetter(_target).getBytes32(_slot); + uint256 mask = ~(uint256(0xff) << (uint256(_offset) * 8)); + IStorageSetter(_target).setBytes32(_slot, bytes32(uint256(current) & mask)); + + // Upgrade to the implementation and call the initializer. + _proxyAdmin.upgradeAndCall(payable(address(_target)), _implementation, _data); + } + + /// @notice Returns the implementations for the contracts. + /// @return The implementations for the contracts. + function implementations() public view returns (IOPContractsManagerContainer.Implementations memory) { + return contractsContainer.implementations(); + } + + /// @notice Returns the blueprints for the contracts. + /// @return The blueprints for the contracts. + function blueprints() public view returns (IOPContractsManagerContainer.Blueprints memory) { + return contractsContainer.blueprints(); + } +} diff --git a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerUtilsCaller.sol b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerUtilsCaller.sol new file mode 100644 index 00000000000..9cdb91f908d --- /dev/null +++ b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerUtilsCaller.sol @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Interfaces +import { IOPContractsManagerUtils } from "interfaces/L1/opcm/IOPContractsManagerUtils.sol"; +import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; + +/// @title OPContractsManagerUtilsCaller +/// @notice OPContractsManagerUtilsCaller is an abstract contract that exists to hide all of the +/// complexity of cheaply calling the OPContractsManagerUtils contract. Most of this logic +/// could simply live inside of the OPContractsManagerV2 contract directly, but it helps to +/// keep the OPContractsManagerV2 contract cleaner and more readable by moving this here. +/// @dev OPContractsManagerUtilsCaller and OPContractsManagerUtils operate together in a way almost +/// identical to an "external library" contract. You could use a real external library, but +/// this is much easier for humans to read and for us to validate offchain. +abstract contract OPContractsManagerUtilsCaller { + /// @notice Address of the OPContractsManagerUtils contract. + IOPContractsManagerUtils public immutable utils; + + /// @param _utils Address of the OPContractsManagerUtils contract. + constructor(IOPContractsManagerUtils _utils) { + utils = _utils; + } + + /// @notice Maps an L2 chain ID to an L1 batch inbox address as defined by the standard + /// configuration's convention. This convention is + /// `versionByte || keccak256(bytes32(chainId))[:19]`, where || denotes concatenation, + /// versionByte is 0x00, and chainId is a uint256. + /// https://specs.optimism.io/protocol/configurability.html#consensus-parameters + /// @param _l2ChainId The L2 chain ID to map to an L1 batch inbox address. + /// @return Chain ID mapped to an L1 batch inbox address. + function _chainIdToBatchInboxAddress(uint256 _l2ChainId) internal view returns (address) { + return abi.decode( + _staticcall(abi.encodeCall(IOPContractsManagerUtils.chainIdToBatchInboxAddress, (_l2ChainId))), (address) + ); + } + + /// @notice Helper for computing a salt for a contract deployment. + /// @param _l2ChainId The L2 chain ID of the chain being deployed to. + /// @param _saltMixer The salt mixer to use for the deployment. + /// @param _contractName The name of the contract to deploy. + /// @return The computed salt. + function _computeSalt( + uint256 _l2ChainId, + string memory _saltMixer, + string memory _contractName + ) + internal + view + returns (bytes32) + { + return abi.decode( + _staticcall(abi.encodeCall(IOPContractsManagerUtils.computeSalt, (_l2ChainId, _saltMixer, _contractName))), + (bytes32) + ); + } + + /// @notice Helper function to check if an instruction matches a given key and data. + /// @param _instruction The instruction to check. + /// @param _key The key of the instruction to check for. + /// @param _data The data of the instruction to check for. + /// @return True if the instruction matches, false otherwise. + function _isMatchingInstruction( + IOPContractsManagerUtils.ExtraInstruction memory _instruction, + string memory _key, + bytes memory _data + ) + internal + view + returns (bool) + { + return abi.decode( + _staticcall(abi.encodeCall(IOPContractsManagerUtils.isMatchingInstruction, (_instruction, _key, _data))), + (bool) + ); + } + + /// @notice Helper function to load data from a source contract as bytes. + /// @param _source The source contract to load the data from. + /// @param _selector The selector of the function to call on the source contract. + /// @param _name The name of the field to load. + /// @param _instructions The extra upgrade instructions for the data load. + /// @return Data retrieved from the source contract. + function _loadBytes( + address _source, + bytes4 _selector, + string memory _name, + IOPContractsManagerUtils.ExtraInstruction[] memory _instructions + ) + internal + view + returns (bytes memory) + { + return abi.decode( + _staticcall(abi.encodeCall(IOPContractsManagerUtils.loadBytes, (_source, _selector, _name, _instructions))), + (bytes) + ); + } + + /// @notice Attempts to load a proxy from a source function where the proxy should be found. If + /// the proxy isn't found at the source, or the call to the source fails, we build a + /// new proxy instead. Calls to source contracts MUST NOT fail under any circumstances + /// other than the function not existing (which can happen in an upgrade scenario). + /// @param _source The source contract to load the proxy from. + /// @param _selector The selector of the function to call on the source contract. + /// @param _args The basic arguments for the proxy deployment. + /// @param _contractName The name of the contract to deploy. + /// @param _instructions The extra upgrade instructions for the proxy deployment. + /// @return The address of the loaded or built proxy. + function _loadOrDeployProxy( + address _source, + bytes4 _selector, + IOPContractsManagerUtils.ProxyDeployArgs memory _args, + string memory _contractName, + IOPContractsManagerUtils.ExtraInstruction[] memory _instructions + ) + internal + returns (address payable) + { + return payable( + abi.decode( + _delegatecall( + abi.encodeCall( + IOPContractsManagerUtils.loadOrDeployProxy, + (_source, _selector, _args, _contractName, _instructions) + ) + ), + (address) + ) + ); + } + + /// @notice Upgrades a contract by resetting the initialized slot and calling the initializer. + /// @param _proxyAdmin The proxy admin of the contract. + /// @param _target The target of the contract. + /// @param _implementation The implementation of the contract. + /// @param _data The data to call the initializer with. + function _upgrade(IProxyAdmin _proxyAdmin, address _target, address _implementation, bytes memory _data) internal { + _upgrade(_proxyAdmin, _target, _implementation, _data, bytes32(0), 0); + } + + /// @notice Upgrades a contract by resetting the initialized slot and calling the initializer. + /// @param _proxyAdmin The proxy admin of the contract. + /// @param _target The target of the contract. + /// @param _implementation The implementation of the contract. + /// @param _data The data to call the initializer with. + /// @param _slot The slot where the initialized value is located. + /// @param _offset The offset of the initializer value in the slot. + function _upgrade( + IProxyAdmin _proxyAdmin, + address _target, + address _implementation, + bytes memory _data, + bytes32 _slot, + uint8 _offset + ) + internal + { + _delegatecall( + abi.encodeCall( + IOPContractsManagerUtils.upgrade, (_proxyAdmin, _target, _implementation, _data, _slot, _offset) + ) + ); + } + + /// @notice Helper calling the utils contract (via delegatecall). + /// @param _data Calldata to send to the utils contract. + /// @return Result of the call. + function _delegatecall(bytes memory _data) internal returns (bytes memory) { + (bool success, bytes memory result) = address(utils).delegatecall(_data); + if (!success) { + assembly { + revert(add(result, 0x20), mload(result)) + } + } + return result; + } + + /// @notice Helper calling the utils contract (via staticcall). + /// @param _data Calldata to send to the utils contract. + /// @return Result of the call. + function _staticcall(bytes memory _data) internal view returns (bytes memory) { + (bool success, bytes memory result) = address(utils).staticcall(_data); + if (!success) { + assembly { + revert(add(result, 0x20), mload(result)) + } + } + return result; + } +} diff --git a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol index f5bc13c83c2..9bdb7daa69c 100644 --- a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol +++ b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol @@ -1,13 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Contracts +import { OPContractsManagerUtilsCaller } from "src/L1/opcm/OPContractsManagerUtilsCaller.sol"; + // Libraries -import { LibString } from "@solady/utils/LibString.sol"; import { Blueprint } from "src/libraries/Blueprint.sol"; import { Claim, GameType, GameTypes, Proposal } from "src/dispute/lib/Types.sol"; import { SemverComp } from "src/libraries/SemverComp.sol"; import { Features } from "src/libraries/Features.sol"; import { DevFeatures } from "src/libraries/DevFeatures.sol"; +import { Constants } from "src/libraries/Constants.sol"; // Interfaces import { ISemver } from "interfaces/universal/ISemver.sol"; @@ -27,9 +30,9 @@ import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; -import { IStorageSetter } from "interfaces/universal/IStorageSetter.sol"; import { IOPContractsManagerContainer } from "interfaces/L1/opcm/IOPContractsManagerContainer.sol"; import { IOPContractsManagerStandardValidator } from "interfaces/L1/IOPContractsManagerStandardValidator.sol"; +import { IOPContractsManagerUtils } from "interfaces/L1/opcm/IOPContractsManagerUtils.sol"; /// @title OPContractsManagerV2 /// @notice OPContractsManagerV2 is an enhanced version of OPContractsManager. OPContractsManagerV2 @@ -50,7 +53,7 @@ import { IOPContractsManagerStandardValidator } from "interfaces/L1/IOPContracts /// doesn't quite get there yet in an attempt to be a more incremental improvement over the V1 /// design. Look at _apply, squint, and imagine that it can output an upgrade plan rather than /// actually executing the upgrade, and then you'll see how it can be improved. -contract OPContractsManagerV2 is ISemver { +contract OPContractsManagerV2 is ISemver, OPContractsManagerUtilsCaller { /// @notice Configuration struct for the FaultDisputeGame. struct FaultDisputeGameConfig { Claim absolutePrestate; @@ -92,14 +95,6 @@ contract OPContractsManagerV2 is ISemver { IDelayedWETH delayedWETH; } - /// @notice Struct that represents an additional instruction for an upgrade. Each upgrade has - /// its own set of extra upgrade instructions that may or may not be required. We use - /// this struct to keep the upgrade interface the same each time. - struct ExtraInstruction { - string key; - bytes data; - } - /// @notice Full chain management configuration. struct FullConfig { // Basic deployment configuration. @@ -127,28 +122,15 @@ contract OPContractsManagerV2 is ISemver { struct UpgradeInput { ISystemConfig systemConfig; DisputeGameConfig[] disputeGameConfigs; - ExtraInstruction[] extraInstructions; + IOPContractsManagerUtils.ExtraInstruction[] extraInstructions; } /// @notice Input for upgrading Superchain contracts. struct SuperchainUpgradeInput { ISuperchainConfig superchainConfig; - ExtraInstruction[] extraInstructions; + IOPContractsManagerUtils.ExtraInstruction[] extraInstructions; } - /// @notice Helper struct for deploying proxies, keeps code cleaner. - struct ProxyDeployArgs { - IProxyAdmin proxyAdmin; - IAddressManager addressManager; - uint256 l2ChainId; - string saltMixer; - } - - /// @notice Emitted when a proxy is created by this contract. - /// @param name The name of the proxy. - /// @param proxy The address of the proxy. - event ProxyCreation(string name, address proxy); - /// @notice Thrown when the SuperchainConfig needs to be upgraded. error OPContractsManagerV2_SuperchainConfigNeedsUpgrade(); @@ -161,17 +143,8 @@ contract OPContractsManagerV2 is ISemver { /// @notice Thrown when an invalid upgrade input is provided. error OPContractsManagerV2_InvalidUpgradeInput(); - /// @notice Thrown when a proxy must be loaded but couldn't be. - error OPContractsManagerV2_ProxyMustLoad(string _name); - - /// @notice Thrown when user attempts to downgrade a contract. - error OPContractsManagerV2_DowngradeNotAllowed(address _contract); - /// @notice Thrown when an invalid upgrade instruction is provided. - error OPContractsManagerV2_InvalidUpgradeInstruction(); - - /// @notice Thrown when a config load fails. - error OPContractsManagerV2_ConfigLoadFailed(string _name); + error OPContractsManagerV2_InvalidUpgradeInstruction(string _key); /// @notice Container of blueprint and implementation contract addresses. IOPContractsManagerContainer public immutable contractsContainer; @@ -179,25 +152,31 @@ contract OPContractsManagerV2 is ISemver { /// @notice Address of the Standard Validator for this OPCM release. IOPContractsManagerStandardValidator public immutable standardValidator; - /// @notice The version of the OPCM contract. - /// @custom:semver 6.1.0 - string public constant version = "6.1.0"; - - /// @notice Special constant key for the PermittedProxyDeployment instruction. - string internal constant PERMITTED_PROXY_DEPLOYMENT_KEY = "PermittedProxyDeployment"; + /// @notice Immutable reference to this OPCM contract so that the address of this contract can + /// be used when this contract is DELEGATECALLed. + OPContractsManagerV2 public immutable thisOPCM; - /// @notice Special constant value for the PermittedProxyDeployment instruction to permit all - /// contracts to be deployed. Only to be used for deployments. - bytes internal constant PERMIT_ALL_CONTRACTS_INSTRUCTION = bytes("ALL"); + /// @notice The version of the OPCM contract. + /// WARNING: OPCM versioning rules differ from other contracts: + /// - Major bump: New required sequential upgrade + /// - Minor bump: Replacement OPCM for same upgrade + /// - Patch bump: Development changes (expected for normal dev work) + /// @custom:semver 6.0.4 + string public constant version = "6.0.4"; /// @param _contractsContainer The container of blueprint and implementation contract addresses. /// @param _standardValidator The standard validator for this OPCM release. + /// @param _utils The utility functions for the OPContractsManager. constructor( IOPContractsManagerContainer _contractsContainer, - IOPContractsManagerStandardValidator _standardValidator - ) { + IOPContractsManagerStandardValidator _standardValidator, + IOPContractsManagerUtils _utils + ) + OPContractsManagerUtilsCaller(_utils) + { contractsContainer = _contractsContainer; standardValidator = _standardValidator; + thisOPCM = this; } /////////////////////////////////////////////////////////////////////////// @@ -231,9 +210,12 @@ contract OPContractsManagerV2 is ISemver { /// @return The chain contracts. function deploy(FullConfig memory _cfg) external returns (ChainContracts memory) { // Deploy is the ONLY place where we allow the "ALL" permission for proxy deployment. - ExtraInstruction[] memory instructions = new ExtraInstruction[](1); - instructions[0] = - ExtraInstruction({ key: PERMITTED_PROXY_DEPLOYMENT_KEY, data: PERMIT_ALL_CONTRACTS_INSTRUCTION }); + IOPContractsManagerUtils.ExtraInstruction[] memory instructions = + new IOPContractsManagerUtils.ExtraInstruction[](1); + instructions[0] = IOPContractsManagerUtils.ExtraInstruction({ + key: Constants.PERMITTED_PROXY_DEPLOYMENT_KEY, + data: Constants.PERMIT_ALL_CONTRACTS_INSTRUCTION + }); // Load the chain contracts. ChainContracts memory cts = @@ -283,21 +265,45 @@ contract OPContractsManagerV2 is ISemver { /////////////////////////////////////////////////////////////////////////// /// @notice Asserts that the upgrade instructions array is valid. + /// @dev Developers don't need to touch this function, modify _isPermittedInstruction instead. /// @param _extraInstructions The extra upgrade instructions for the chain. - function _assertValidUpgradeInstructions(ExtraInstruction[] memory _extraInstructions) internal pure { + function _assertValidUpgradeInstructions(IOPContractsManagerUtils.ExtraInstruction[] memory _extraInstructions) + internal + view + { for (uint256 i = 0; i < _extraInstructions.length; i++) { - if ( - LibString.eq(_extraInstructions[i].key, PERMITTED_PROXY_DEPLOYMENT_KEY) - && LibString.eq(string(_extraInstructions[i].data), "DelayedWETH") - ) { - // Unified DelayedWETH is being deployed for the first time. - // TODO:(#?????): Remove this allowance after unified DelayedWETH is deployed. - } else { - revert OPContractsManagerV2_InvalidUpgradeInstruction(); + if (!_isPermittedInstruction(_extraInstructions[i])) { + revert OPContractsManagerV2_InvalidUpgradeInstruction(_extraInstructions[i].key); } } } + /// @notice Checks if an upgrade instruction is permitted. + /// @param _instruction The upgrade instruction to check. + /// @return True if the instruction is permitted, false otherwise. + function _isPermittedInstruction(IOPContractsManagerUtils.ExtraInstruction memory _instruction) + internal + view + returns (bool) + { + // NOTE (IMPORTANT FOR DEVELOPERS): You MAY need to allow permitted instructions here for + // your specific upgrade. For example, if you are adding a new contract that needs to be + // deployed you will need to add an allowance so that the proxy can be deployed. + // Allowances MUST always be restricted to one specific upgrade. Here we maintain this + // restriction by checking that the version is less than the NEXT release version. Once + // developers start working on the next release this will automatically become false so + // even if the code is somehow forgotten it will not actually apply to the deployment. Make + // sure to REMOVE the allowance once the upgrade is complete. + if (SemverComp.lt(version, "7.0.0")) { + // Unified DelayedWETH is being deployed for the first time. + // TODO:(#18382): Remove this allowance after unified DelayedWETH is deployed. + return _isMatchingInstruction(_instruction, Constants.PERMITTED_PROXY_DEPLOYMENT_KEY, "DelayedWETH"); + } + + // Always return false by default. + return false; + } + /// @notice Loads (or builds) the chain contracts from whatever exists. /// @param _systemConfig The SystemConfig contract. /// @param _l2ChainId The L2 chain ID. @@ -308,7 +314,7 @@ contract OPContractsManagerV2 is ISemver { ISystemConfig _systemConfig, uint256 _l2ChainId, string memory _saltMixer, - ExtraInstruction[] memory _extraInstructions + IOPContractsManagerUtils.ExtraInstruction[] memory _extraInstructions ) internal returns (ChainContracts memory) @@ -363,7 +369,7 @@ contract OPContractsManagerV2 is ISemver { } // Set up the deploy args once, keeps the code cleaner. - ProxyDeployArgs memory proxyDeployArgs = ProxyDeployArgs({ + IOPContractsManagerUtils.ProxyDeployArgs memory proxyDeployArgs = IOPContractsManagerUtils.ProxyDeployArgs({ proxyAdmin: proxyAdmin, addressManager: addressManager, l2ChainId: _l2ChainId, @@ -839,7 +845,7 @@ contract OPContractsManagerV2 is ISemver { ChainContracts memory _cts ) internal - pure + view returns (bytes memory) { // Generate the SystemConfig addresses input. @@ -849,7 +855,8 @@ contract OPContractsManagerV2 is ISemver { l1StandardBridge: address(_cts.l1StandardBridge), optimismPortal: address(_cts.optimismPortal), optimismMintableERC20Factory: address(_cts.optimismMintableERC20Factory), - delayedWETH: address(_cts.delayedWETH) + delayedWETH: address(_cts.delayedWETH), + opcm: address(thisOPCM) }); // Generate the initializer arguments. @@ -952,244 +959,4 @@ contract OPContractsManagerV2 is ISemver { function isDevFeatureEnabled(bytes32 _feature) public view returns (bool) { return contractsContainer.isDevFeatureEnabled(_feature); } - - /////////////////////////////////////////////////////////////////////////// - // INTERNAL UTILITY FUNCTIONS // - /////////////////////////////////////////////////////////////////////////// - - /// @notice Maps an L2 chain ID to an L1 batch inbox address as defined by the standard - /// configuration's convention. This convention is - /// `versionByte || keccak256(bytes32(chainId))[:19]`, where || denotes concatenation, - /// versionByte is 0x00, and chainId is a uint256. - /// https://specs.optimism.io/protocol/configurability.html#consensus-parameters - /// @param _l2ChainId The L2 chain ID to map to an L1 batch inbox address. - /// @return Chain ID mapped to an L1 batch inbox address. - function _chainIdToBatchInboxAddress(uint256 _l2ChainId) internal pure returns (address) { - bytes1 versionByte = 0x00; - bytes32 hashedChainId = keccak256(bytes.concat(bytes32(_l2ChainId))); - bytes19 first19Bytes = bytes19(hashedChainId); - return address(uint160(bytes20(bytes.concat(versionByte, first19Bytes)))); - } - - /// @notice Computes a unique salt for a contract deployment. - /// @param _l2ChainId The L2 chain ID of the chain being deployed to. - /// @param _saltMixer The salt mixer to use for the deployment. - /// @param _contractName The name of the contract to deploy. - /// @return The computed salt. - function _computeSalt( - uint256 _l2ChainId, - string memory _saltMixer, - string memory _contractName - ) - internal - pure - returns (bytes32) - { - return keccak256(abi.encode(_l2ChainId, _saltMixer, _contractName)); - } - - /// @notice Helper function to check if a given instruction is present in a list of extra - /// upgrade instructions. - /// @param _instructions The list of extra upgrade instructions. - /// @param _key The key of the instruction to check for. - /// @param _data The data of the instruction to check for. - /// @return True if the instruction is present, false otherwise. - function _hasInstruction( - ExtraInstruction[] memory _instructions, - string memory _key, - bytes memory _data - ) - internal - pure - returns (bool) - { - for (uint256 i = 0; i < _instructions.length; i++) { - if (LibString.eq(_instructions[i].key, _key) && LibString.eq(string(_instructions[i].data), string(_data))) - { - return true; - } - } - return false; - } - - /// @notice Helper function to get an instruction by key. - /// @param _instructions The list of extra upgrade instructions. - /// @param _key The key of the instruction to get. - /// @return The instruction, or an empty instruction if the instruction is not found. - function _getInstructionByKey( - ExtraInstruction[] memory _instructions, - string memory _key - ) - internal - pure - returns (ExtraInstruction memory) - { - for (uint256 i = 0; i < _instructions.length; i++) { - if (LibString.eq(_instructions[i].key, _key)) { - return _instructions[i]; - } - } - return ExtraInstruction({ key: "", data: bytes("") }); - } - - /// @notice Helper function to load data from a source contract as bytes. - /// @param _source The source contract to load the data from. - /// @param _selector The selector of the function to call on the source contract. - /// @param _name The name of the field to load. - /// @param _instructions The extra upgrade instructions for the data load. - /// @return Data retrieved from the source contract. - function _loadBytes( - address _source, - bytes4 _selector, - string memory _name, - ExtraInstruction[] memory _instructions - ) - internal - view - returns (bytes memory) - { - // If an override exists for this load, return the override data. - ExtraInstruction memory overrideInstruction = _getInstructionByKey(_instructions, _name); - if (bytes(overrideInstruction.key).length > 0) { - return overrideInstruction.data; - } - - // Otherwise, load the data from the source contract. - (bool success, bytes memory result) = address(_source).staticcall(abi.encodePacked(_selector)); - if (!success) { - revert OPContractsManagerV2_ConfigLoadFailed(_name); - } - - // Return the loaded data. - return result; - } - - /// @notice Attempts to load a proxy from a source function where the proxy should be found. If - /// the proxy isn't found at the source, or the call to the source fails, we build a - /// new proxy instead. Calls to source contracts MUST NOT fail under any circumstances - /// other than the function not existing (which can happen in an upgrade scenario). - /// @param _source The source contract to load the proxy from. - /// @param _selector The selector of the function to call on the source contract. - /// @param _args The basic arguments for the proxy deployment. - /// @param _contractName The name of the contract to deploy. - /// @param _instructions The extra upgrade instructions for the proxy deployment. - /// @return The address of the loaded or built proxy. - function _loadOrDeployProxy( - address _source, - bytes4 _selector, - ProxyDeployArgs memory _args, - string memory _contractName, - ExtraInstruction[] memory _instructions - ) - internal - returns (address payable) - { - // Loads are allowed to fail ONLY if the user explicitly permitted it (or if this is a - // deployment and the "ALL" permission is set). - bool loadCanFail = _hasInstruction(_instructions, PERMITTED_PROXY_DEPLOYMENT_KEY, bytes(_contractName)) - || _hasInstruction(_instructions, PERMITTED_PROXY_DEPLOYMENT_KEY, PERMIT_ALL_CONTRACTS_INSTRUCTION); - - // Try to load the proxy from the source. - (bool success, bytes memory result) = address(_source).staticcall(abi.encodePacked(_selector)); - - // If the load succeeded and the result is not a zero address, return the result. - if (success && abi.decode(result, (address)) != address(0)) { - return payable(abi.decode(result, (address))); - } else if (!loadCanFail) { - // Load not permitted to fail but did, revert. - revert OPContractsManagerV2_ProxyMustLoad(_contractName); - } - - // We've failed to load, but we allowed that failure. - // Deploy the right proxy depending on the contract name. - address ret; - if (LibString.eq(_contractName, "L1StandardBridge")) { - // L1StandardBridge is a special case ChugSplashProxy (legacy). - ret = Blueprint.deployFrom( - blueprints().l1ChugSplashProxy, - _computeSalt(_args.l2ChainId, _args.saltMixer, "L1StandardBridge"), - abi.encode(_args.proxyAdmin) - ); - - // ChugSplashProxy requires setting the proxy type on the ProxyAdmin. - _args.proxyAdmin.setProxyType(ret, IProxyAdmin.ProxyType.CHUGSPLASH); - } else if (LibString.eq(_contractName, "L1CrossDomainMessenger")) { - // L1CrossDomainMessenger is a special case ResolvedDelegateProxy (legacy). - string memory l1XdmName = "OVM_L1CrossDomainMessenger"; - ret = Blueprint.deployFrom( - blueprints().resolvedDelegateProxy, - _computeSalt(_args.l2ChainId, _args.saltMixer, "L1CrossDomainMessenger"), - abi.encode(_args.addressManager, l1XdmName) - ); - - // ResolvedDelegateProxy requires setting the proxy type on the ProxyAdmin. - _args.proxyAdmin.setProxyType(ret, IProxyAdmin.ProxyType.RESOLVED); - _args.proxyAdmin.setImplementationName(ret, l1XdmName); - } else { - // Otherwise this is a normal proxy. - ret = Blueprint.deployFrom( - blueprints().proxy, - _computeSalt(_args.l2ChainId, _args.saltMixer, _contractName), - abi.encode(_args.proxyAdmin) - ); - } - - // Emit the proxy creation event. - emit ProxyCreation(_contractName, ret); - - // Return the final deployment result. - return payable(ret); - } - - /// @notice Upgrades a contract by resetting the initialized slot and calling the initializer. - /// @param _proxyAdmin The proxy admin of the contract. - /// @param _target The target of the contract. - /// @param _implementation The implementation of the contract. - /// @param _data The data to call the initializer with. - function _upgrade(IProxyAdmin _proxyAdmin, address _target, address _implementation, bytes memory _data) internal { - _upgrade(_proxyAdmin, _target, _implementation, _data, bytes32(0), 0); - } - - /// @notice Upgrades a contract by resetting the initialized slot and calling the initializer. - /// @param _proxyAdmin The proxy admin of the contract. - /// @param _target The target of the contract. - /// @param _implementation The implementation of the contract. - /// @param _data The data to call the initializer with. - /// @param _slot The slot where the initialized value is located. - /// @param _offset The offset of the initializer value in the slot. - function _upgrade( - IProxyAdmin _proxyAdmin, - address _target, - address _implementation, - bytes memory _data, - bytes32 _slot, - uint8 _offset - ) - internal - { - // Check to make sure that we're not downgrading. Downgrades aren't inherently dangerous - // but we also don't test for them so we don't really know if a specific downgrade will be - // dangerous or not. It's easier to just revert instead. - // NOTE: We DO allow upgrades to the same version, which makes it possible to use this - // function to both upgrade and then later perform management actions like changing - // the prestate for the fault dispute games. - if ( - _proxyAdmin.getProxyImplementation(payable(_target)) != address(0) - && SemverComp.gt(ISemver(_target).version(), ISemver(_implementation).version()) - ) { - revert OPContractsManagerV2_DowngradeNotAllowed(address(_target)); - } - - // Upgrade to StorageSetter. - _proxyAdmin.upgrade(payable(_target), address(implementations().storageSetterImpl)); - - // Otherwise, we need to reset the initialized slot and call the initializer. - // Reset the initialized slot by zeroing the single byte at `_offset` (from the right). - bytes32 current = IStorageSetter(_target).getBytes32(_slot); - uint256 mask = ~(uint256(0xff) << (uint256(_offset) * 8)); - IStorageSetter(_target).setBytes32(_slot, bytes32(uint256(current) & mask)); - - // Upgrade to the implementation and call the initializer. - _proxyAdmin.upgradeAndCall(payable(address(_target)), _implementation, _data); - } } diff --git a/packages/contracts-bedrock/src/libraries/Constants.sol b/packages/contracts-bedrock/src/libraries/Constants.sol index 012618f6ae9..ad20a7f3c3f 100644 --- a/packages/contracts-bedrock/src/libraries/Constants.sol +++ b/packages/contracts-bedrock/src/libraries/Constants.sol @@ -47,6 +47,13 @@ library Constants { /// made to have code in tests with cheatcodes. address internal constant TESTING_ENVIRONMENT_ADDRESS = address(0xbeefcafe); + /// @notice Special constant key for the PermittedProxyDeployment instruction. + string internal constant PERMITTED_PROXY_DEPLOYMENT_KEY = "PermittedProxyDeployment"; + + /// @notice Special constant value for the PermittedProxyDeployment instruction to permit all + /// contracts to be deployed. Only to be used for deployments. + bytes internal constant PERMIT_ALL_CONTRACTS_INSTRUCTION = bytes("ALL"); + /// @notice Returns the default values for the ResourceConfig. These are the recommended values /// for a production network. function DEFAULT_RESOURCE_CONFIG() internal pure returns (IResourceMetering.ResourceConfig memory) { diff --git a/packages/contracts-bedrock/src/libraries/DevFeatures.sol b/packages/contracts-bedrock/src/libraries/DevFeatures.sol index 33a372c63b0..ed46b8c8d2b 100644 --- a/packages/contracts-bedrock/src/libraries/DevFeatures.sol +++ b/packages/contracts-bedrock/src/libraries/DevFeatures.sol @@ -22,10 +22,6 @@ library DevFeatures { bytes32 public constant DEPLOY_V2_DISPUTE_GAMES = bytes32(0x0000000000000000000000000000000000000000000000000000000000000100); - /// @notice The feature that enables the custom gas token. - bytes32 public constant CUSTOM_GAS_TOKEN = - bytes32(0x0000000000000000000000000000000000000000000000000000000000001000); - /// @notice The feature that enables the OPContractsManagerV2 contract. bytes32 public constant OPCM_V2 = bytes32(0x0000000000000000000000000000000000000000000000000000000000010000); diff --git a/packages/contracts-bedrock/test/L1/FeesDepositor.t.sol b/packages/contracts-bedrock/test/L1/FeesDepositor.t.sol index 43b28d21150..e329f243869 100644 --- a/packages/contracts-bedrock/test/L1/FeesDepositor.t.sol +++ b/packages/contracts-bedrock/test/L1/FeesDepositor.t.sol @@ -9,7 +9,6 @@ import { FeesDepositor } from "src/L1/FeesDepositor.sol"; import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; import { Proxy } from "src/universal/Proxy.sol"; import { Features } from "src/libraries/Features.sol"; -import { DevFeatures } from "src/libraries/DevFeatures.sol"; /// @title FeesDepositor_TestInit /// @notice Base test contract with initialization for `FeesDepositor` tests. @@ -98,7 +97,7 @@ contract FeesDepositor_Receive_Test is FeesDepositor_TestInit { } function testFuzz_receive_atOrAboveThreshold_succeeds(uint256 _sendAmount) external { - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); // Handling the fork tests scenario case for the fork tests uint256 depositFeesRecipientBalanceBefore = depositFeesRecipient.balance; @@ -126,7 +125,7 @@ contract FeesDepositor_Receive_Test is FeesDepositor_TestInit { } function testFuzz_receive_multipleDeposits_succeeds(uint256 _firstAmount, uint256 _secondAmount) external { - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); // Handling the fork tests scenario uint256 depositFeesRecipientBalanceBefore = depositFeesRecipient.balance; diff --git a/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol b/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol index e26b938a9c2..abec06bf78b 100644 --- a/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol +++ b/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol @@ -15,7 +15,6 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { Features } from "src/libraries/Features.sol"; -import { DevFeatures } from "src/libraries/DevFeatures.sol"; // Interfaces import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; @@ -333,7 +332,7 @@ contract L1StandardBridge_Paused_Test is CommonTest { contract L1StandardBridge_Receive_Test is CommonTest { /// @notice Tests receive bridges ETH successfully. function test_receive_succeeds() external { - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); uint256 portalBalanceBefore = address(optimismPortal2).balance; uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; @@ -377,6 +376,20 @@ contract L1StandardBridge_Receive_Test is CommonTest { (bool revertsAsExpected,) = address(l1StandardBridge).call{ value: 100 }(hex""); assertTrue(revertsAsExpected, "expectRevert: call did not revert"); } + + /// @notice Tests that receive reverts when custom gas token is enabled and value is sent. + function testFuzz_receive_withCustomGasToken_reverts(uint256 _value) external { + skipIfSysFeatureDisabled(Features.CUSTOM_GAS_TOKEN); + + _value = bound(_value, 1, type(uint128).max); + vm.deal(alice, _value); + + vm.prank(alice, alice); + vm.expectRevert(IOptimismPortal2.OptimismPortal_NotAllowedOnCGTMode.selector); + + (bool revertsAsExpected,) = address(l1StandardBridge).call{ value: _value }(hex""); + assertTrue(revertsAsExpected, "expectRevert: call did not revert"); + } } /// @title L1StandardBridge_DepositETH_Test @@ -388,7 +401,7 @@ contract L1StandardBridge_DepositETH_Test is L1StandardBridge_TestInit { /// Only EOA can call depositETH. /// ETH ends up in the optimismPortal. function test_depositETH_fromEOA_succeeds() external { - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); _preBridgeETH({ isLegacy: true, value: 500 }); uint256 portalBalanceBefore = address(optimismPortal2).balance; uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; @@ -404,7 +417,7 @@ contract L1StandardBridge_DepositETH_Test is L1StandardBridge_TestInit { /// @notice Tests that depositing ETH succeeds for an EOA using 7702 delegation. function test_depositETH_fromEOA7702_succeeds() external { - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); // Set alice to have 7702 code. vm.etch(alice, abi.encodePacked(hex"EF0100", address(0))); @@ -428,6 +441,32 @@ contract L1StandardBridge_DepositETH_Test is L1StandardBridge_TestInit { vm.prank(alice); l1StandardBridge.depositETH{ value: 1 }(300, hex""); } + + /// @notice Tests that depositETH reverts when custom gas token is enabled and value is sent. + function testFuzz_depositETH_withCustomGasToken_reverts(uint256 _value, uint32 _minGasLimit) external { + skipIfSysFeatureDisabled(Features.CUSTOM_GAS_TOKEN); + + _value = bound(_value, 1, type(uint128).max); + vm.deal(alice, _value); + + vm.prank(alice, alice); + vm.expectRevert(IOptimismPortal2.OptimismPortal_NotAllowedOnCGTMode.selector); + l1StandardBridge.depositETH{ value: _value }(_minGasLimit, hex"dead"); + } + + /// @notice Tests that depositETH reverts when custom gas token is enabled for EOA with 7702 delegation. + function testFuzz_depositETH_fromEOA7702WithCustomGasToken_reverts(uint256 _value, uint32 _minGasLimit) external { + skipIfSysFeatureDisabled(Features.CUSTOM_GAS_TOKEN); + _value = bound(_value, 1, type(uint128).max); + + // Set alice to have 7702 code. + vm.etch(alice, abi.encodePacked(hex"EF0100", address(0))); + + vm.deal(alice, _value); + vm.prank(alice, alice); + vm.expectRevert(IOptimismPortal2.OptimismPortal_NotAllowedOnCGTMode.selector); + l1StandardBridge.depositETH{ value: _value }(_minGasLimit, hex"dead"); + } } /// @title L1StandardBridge_DepositETHTo_Test @@ -439,7 +478,7 @@ contract L1StandardBridge_DepositETHTo_Test is L1StandardBridge_TestInit { /// EOA or contract can call depositETHTo. /// ETH ends up in the optimismPortal. function test_depositETHTo_succeeds() external { - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); _preBridgeETHTo({ isLegacy: true, value: 600 }); uint256 portalBalanceBefore = address(optimismPortal2).balance; uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; @@ -457,7 +496,7 @@ contract L1StandardBridge_DepositETHTo_Test is L1StandardBridge_TestInit { /// @param _to Random recipient address /// @param _amount Random ETH amount to deposit function testFuzz_depositETHTo_randomRecipient_succeeds(address _to, uint256 _amount) external { - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); vm.assume(_to != address(0)); _amount = bound(_amount, 1, 10 ether); @@ -475,6 +514,23 @@ contract L1StandardBridge_DepositETHTo_Test is L1StandardBridge_TestInit { assertEq(address(optimismPortal2).balance, portalBalanceBefore + _amount); } } + + /// @notice Tests that depositETHTo reverts when custom gas token is enabled and value is sent. + function testFuzz_depositETHTo_withCustomGasToken_reverts( + address _to, + uint256 _value, + uint32 _minGasLimit + ) + external + { + skipIfSysFeatureDisabled(Features.CUSTOM_GAS_TOKEN); + vm.assume(_to != address(0)); + _value = bound(_value, 1, type(uint128).max); + vm.deal(alice, _value); + vm.prank(alice); + vm.expectRevert(IOptimismPortal2.OptimismPortal_NotAllowedOnCGTMode.selector); + l1StandardBridge.depositETHTo{ value: _value }(_to, _minGasLimit, hex"dead"); + } } /// @title L1StandardBridge_DepositERC20_Test @@ -786,7 +842,7 @@ contract L1StandardBridge_Uncategorized_Test is L1StandardBridge_TestInit { /// Only EOA can call bridgeETH. /// ETH ends up in the optimismPortal. function test_bridgeETH_succeeds() external { - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); _preBridgeETH({ isLegacy: false, value: 500 }); uint256 portalBalanceBefore = address(optimismPortal2).balance; uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; @@ -806,7 +862,7 @@ contract L1StandardBridge_Uncategorized_Test is L1StandardBridge_TestInit { /// Only EOA can call bridgeETHTo. /// ETH ends up in the optimismPortal. function test_bridgeETHTo_succeeds() external { - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); _preBridgeETHTo({ isLegacy: false, value: 600 }); uint256 portalBalanceBefore = address(optimismPortal2).balance; uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; @@ -878,4 +934,35 @@ contract L1StandardBridge_Uncategorized_Test is L1StandardBridge_TestInit { vm.expectRevert("StandardBridge: cannot send to messenger"); l1StandardBridge.finalizeBridgeETH{ value: 100 }(alice, messenger, 100, hex""); } + + /// @notice Tests that bridgeETH reverts when custom gas token is enabled and value is sent. + function testFuzz_bridgeETH_withCustomGasToken_reverts(uint256 _value, uint32 _minGasLimit) external { + skipIfSysFeatureDisabled(Features.CUSTOM_GAS_TOKEN); + + _value = bound(_value, 1, type(uint128).max); + vm.deal(alice, _value); + + vm.prank(alice, alice); + vm.expectRevert(IOptimismPortal2.OptimismPortal_NotAllowedOnCGTMode.selector); + l1StandardBridge.bridgeETH{ value: _value }(_minGasLimit, hex"dead"); + } + + /// @notice Tests that bridgeETHTo reverts when custom gas token is enabled and value is sent. + function testFuzz_bridgeETHTo_withCustomGasToken_reverts( + address _to, + uint256 _value, + uint32 _minGasLimit + ) + external + { + skipIfSysFeatureDisabled(Features.CUSTOM_GAS_TOKEN); + + vm.assume(_to != address(0)); + _value = bound(_value, 1, type(uint128).max); + vm.deal(alice, _value); + + vm.prank(alice); + vm.expectRevert(IOptimismPortal2.OptimismPortal_NotAllowedOnCGTMode.selector); + l1StandardBridge.bridgeETHTo{ value: _value }(_to, _minGasLimit, hex"dead"); + } } diff --git a/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol index dde4824c9fb..be961a98f37 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol @@ -45,6 +45,7 @@ import { DisputeGames } from "../setup/DisputeGames.sol"; import { IStaticERC1967Proxy } from "interfaces/universal/IStaticERC1967Proxy.sol"; import { IDelayedWETH } from "../../interfaces/dispute/IDelayedWETH.sol"; import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; +import { IOPContractsManagerUtils } from "interfaces/L1/opcm/IOPContractsManagerUtils.sol"; /// @title BadDisputeGameFactoryReturner /// @notice Used to return a bad DisputeGameFactory address to the OPContractsManagerStandardValidator. Far easier @@ -248,7 +249,7 @@ abstract contract OPContractsManagerStandardValidator_TestInit is CommonTest, Di IOPContractsManagerV2.UpgradeInput({ systemConfig: systemConfig, disputeGameConfigs: disputeGameConfigs, - extraInstructions: new IOPContractsManagerV2.ExtraInstruction[](0) + extraInstructions: new IOPContractsManagerUtils.ExtraInstruction[](0) }) ) ) diff --git a/packages/contracts-bedrock/test/L1/SystemConfig.t.sol b/packages/contracts-bedrock/test/L1/SystemConfig.t.sol index b3787cea943..69e1fcf6ea1 100644 --- a/packages/contracts-bedrock/test/L1/SystemConfig.t.sol +++ b/packages/contracts-bedrock/test/L1/SystemConfig.t.sol @@ -11,6 +11,7 @@ import { ForgeArtifacts, StorageSlot } from "scripts/libraries/ForgeArtifacts.so import { Constants } from "src/libraries/Constants.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { Features } from "src/libraries/Features.sol"; +import { DevFeatures } from "src/libraries/DevFeatures.sol"; // Interfaces import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; @@ -165,7 +166,8 @@ contract SystemConfig_Initialize_Test is SystemConfig_TestInit { l1StandardBridge: address(0), optimismPortal: address(0), optimismMintableERC20Factory: address(0), - delayedWETH: address(0) + delayedWETH: address(0), + opcm: address(0) }), _l2ChainId: 1234, _superchainConfig: ISuperchainConfig(address(0)) @@ -222,7 +224,8 @@ contract SystemConfig_Initialize_Test is SystemConfig_TestInit { l1StandardBridge: address(0), optimismPortal: address(0), optimismMintableERC20Factory: address(0), - delayedWETH: address(0) + delayedWETH: address(0), + opcm: address(0) }), _l2ChainId: 1234, _superchainConfig: ISuperchainConfig(address(0)) @@ -257,7 +260,8 @@ contract SystemConfig_StartBlock_Test is SystemConfig_TestInit { l1StandardBridge: address(0), optimismPortal: address(0), optimismMintableERC20Factory: address(0), - delayedWETH: address(0) + delayedWETH: address(0), + opcm: address(0) }), _l2ChainId: 1234, _superchainConfig: ISuperchainConfig(address(0)) @@ -289,7 +293,8 @@ contract SystemConfig_StartBlock_Test is SystemConfig_TestInit { l1StandardBridge: address(0), optimismPortal: address(0), optimismMintableERC20Factory: address(0), - delayedWETH: address(0) + delayedWETH: address(0), + opcm: address(0) }), _l2ChainId: 1234, _superchainConfig: ISuperchainConfig(address(0)) @@ -605,7 +610,8 @@ contract SystemConfig_SetResourceConfig_Test is SystemConfig_TestInit { l1StandardBridge: address(0), optimismPortal: address(0), optimismMintableERC20Factory: address(0), - delayedWETH: address(0) + delayedWETH: address(0), + opcm: address(0) }), _l2ChainId: 1234, _superchainConfig: ISuperchainConfig(address(0)) @@ -866,7 +872,18 @@ contract SystemConfig_SetFeature_Test is SystemConfig_TestInit { contract SystemConfig_IsFeatureEnabled_Test is SystemConfig_TestInit { /// @notice Tests that `isFeatureEnabled` returns false for unset features. /// @param _feature The feature to check. - function testFuzz_isFeatureEnabled_unsetFeature_succeeds(bytes32 _feature) external view { + function testFuzz_isFeatureEnabled_unsetFeature_succeeds(bytes32 _feature) external { + if (_feature == Features.ETH_LOCKBOX && systemConfig.isFeatureEnabled(Features.ETH_LOCKBOX)) { + // Needs to be anything but ETH_LOCKBOX because we can't turn that feature off if it's on. + vm.skip(true); + } + + // Normalize CUSTOM_GAS_TOKEN to avoid environment-dependent state + if (systemConfig.isFeatureEnabled(Features.CUSTOM_GAS_TOKEN)) { + vm.prank(address(systemConfig.proxyAdmin())); + systemConfig.setFeature(Features.CUSTOM_GAS_TOKEN, false); + } + assertFalse(systemConfig.isFeatureEnabled(_feature)); } @@ -966,3 +983,19 @@ contract SystemConfig_IsCustomGasToken_Test is SystemConfig_TestInit { assertFalse(systemConfig.isCustomGasToken()); } } + +/// @title SystemConfig_LastUsedOPCM_Test +/// @notice Test contract for SystemConfig `lastUsedOPCM` and `lastUsedOPCMVersion` functions. +contract SystemConfig_LastUsedOPCM_Test is SystemConfig_TestInit { + /// @notice Tests that `lastUsedOPCM` returns the correct OPCM V2 address and that + /// `lastUsedOPCMVersion` matches the OPCM V2 version. + function test_lastUsedOPCM_opcmV2_succeeds() external { + skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); + + // Verify that the lastUsedOPCM address matches the deployed OPCM V2 address + assertEq(systemConfig.lastUsedOPCM(), address(opcmV2)); + + // Verify that the lastUsedOPCMVersion matches the OPCM V2 version + assertEq(systemConfig.lastUsedOPCMVersion(), opcmV2.version()); + } +} diff --git a/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerUtils.t.sol b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerUtils.t.sol new file mode 100644 index 00000000000..48085b3d6a6 --- /dev/null +++ b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerUtils.t.sol @@ -0,0 +1,610 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Testing +import { Test } from "forge-std/Test.sol"; + +// Contracts +import { OPContractsManagerUtils } from "src/L1/opcm/OPContractsManagerUtils.sol"; +import { OPContractsManagerContainer } from "src/L1/opcm/OPContractsManagerContainer.sol"; + +// Libraries +import { Constants } from "src/libraries/Constants.sol"; +import { Blueprint } from "src/libraries/Blueprint.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; + +// Interfaces +import { IOPContractsManagerContainer } from "interfaces/L1/opcm/IOPContractsManagerContainer.sol"; +import { IOPContractsManagerUtils } from "interfaces/L1/opcm/IOPContractsManagerUtils.sol"; +import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; +import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IStorageSetter } from "interfaces/universal/IStorageSetter.sol"; + +/// @title ImplV1_Harness +/// @notice Implementation contract with version 1.0.0 for testing upgrades. +contract OPContractsManagerUtils_ImplV1_Harness is ISemver { + /// @custom:semver 1.0.0 + string public constant version = "1.0.0"; + + function initialize() external { } +} + +/// @title ImplV1b_Harness +/// @notice Another v1 implementation for testing same-version upgrades. +contract OPContractsManagerUtils_ImplV1b_Harness is ISemver { + /// @custom:semver 1.0.0 + string public constant version = "1.0.0"; + + function initialize() external { } +} + +/// @title ImplV2_Harness +/// @notice Implementation contract with version 2.0.0 for testing upgrades. +contract OPContractsManagerUtils_ImplV2_Harness is ISemver { + /// @custom:semver 2.0.0 + string public constant version = "2.0.0"; + + function initialize() external { } +} + +/// @title OPContractsManagerUtils_TestInit +/// @notice Shared setup for OPContractsManagerUtils tests. +contract OPContractsManagerUtils_TestInit is Test { + OPContractsManagerUtils internal utils; + OPContractsManagerContainer internal container; + OPContractsManagerContainer.Blueprints internal blueprints; + OPContractsManagerContainer.Implementations internal implementations; + + /// @notice Real StorageSetter used by utils.upgrade(). + IStorageSetter internal storageSetter; + + function setUp() public virtual { + // Etch code into the magic testing address so we're recognized as a test env. + vm.etch(Constants.TESTING_ENVIRONMENT_ADDRESS, hex"01"); + + // Deploy real StorageSetter using DeployUtils. + storageSetter = IStorageSetter( + DeployUtils.create1({ + _name: "StorageSetter", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IStorageSetter.__constructor__, ())) + }) + ); + + // Set up mock blueprints. + blueprints = OPContractsManagerContainer.Blueprints({ + addressManager: makeAddr("addressManager"), + proxy: makeAddr("proxy"), + proxyAdmin: makeAddr("proxyAdmin"), + l1ChugSplashProxy: makeAddr("l1ChugSplashProxy"), + resolvedDelegateProxy: makeAddr("resolvedDelegateProxy"), + permissionedDisputeGame1: makeAddr("permissionedDisputeGame1"), + permissionedDisputeGame2: makeAddr("permissionedDisputeGame2"), + permissionlessDisputeGame1: makeAddr("permissionlessDisputeGame1"), + permissionlessDisputeGame2: makeAddr("permissionlessDisputeGame2") + }); + + // Set up implementations - use real StorageSetter, mocks for the rest. + implementations = OPContractsManagerContainer.Implementations({ + superchainConfigImpl: makeAddr("superchainConfigImpl"), + protocolVersionsImpl: makeAddr("protocolVersionsImpl"), + l1ERC721BridgeImpl: makeAddr("l1ERC721BridgeImpl"), + optimismPortalImpl: makeAddr("optimismPortalImpl"), + optimismPortalInteropImpl: makeAddr("optimismPortalInteropImpl"), + ethLockboxImpl: makeAddr("ethLockboxImpl"), + systemConfigImpl: makeAddr("systemConfigImpl"), + optimismMintableERC20FactoryImpl: makeAddr("optimismMintableERC20FactoryImpl"), + l1CrossDomainMessengerImpl: makeAddr("l1CrossDomainMessengerImpl"), + l1StandardBridgeImpl: makeAddr("l1StandardBridgeImpl"), + disputeGameFactoryImpl: makeAddr("disputeGameFactoryImpl"), + anchorStateRegistryImpl: makeAddr("anchorStateRegistryImpl"), + delayedWETHImpl: makeAddr("delayedWETHImpl"), + mipsImpl: makeAddr("mipsImpl"), + faultDisputeGameV2Impl: makeAddr("faultDisputeGameV2Impl"), + permissionedDisputeGameV2Impl: makeAddr("permissionedDisputeGameV2Impl"), + superFaultDisputeGameImpl: makeAddr("superFaultDisputeGameImpl"), + superPermissionedDisputeGameImpl: makeAddr("superPermissionedDisputeGameImpl"), + storageSetterImpl: address(storageSetter) + }); + + // Deploy the container and utils. + container = new OPContractsManagerContainer(blueprints, implementations, bytes32(0)); + utils = new OPContractsManagerUtils(IOPContractsManagerContainer(address(container))); + } + + /// @notice Helper to create an array of ExtraInstructions. + /// @param _key The key of the instruction. + /// @param _data The data of the instruction. + /// @return The array of extra instructions. + function _createInstructions( + string memory _key, + bytes memory _data + ) + internal + pure + returns (OPContractsManagerUtils.ExtraInstruction[] memory) + { + OPContractsManagerUtils.ExtraInstruction[] memory instructions = + new OPContractsManagerUtils.ExtraInstruction[](1); + instructions[0] = OPContractsManagerUtils.ExtraInstruction({ key: _key, data: _data }); + return instructions; + } + + /// @notice Helper to create an empty array of ExtraInstructions. + /// @return The empty array of extra instructions. + function _emptyInstructions() internal pure returns (OPContractsManagerUtils.ExtraInstruction[] memory) { + return new OPContractsManagerUtils.ExtraInstruction[](0); + } +} + +/// @title OPContractsManagerUtils_ChainIdToBatchInboxAddress_Test +/// @notice Tests the chainIdToBatchInboxAddress function. +contract OPContractsManagerUtils_ChainIdToBatchInboxAddress_Test is OPContractsManagerUtils_TestInit { + /// @notice Tests that chainIdToBatchInboxAddress produces deterministic, correctly formatted addresses. + /// @param _chainId The chain ID to test. + function testFuzz_chainIdToBatchInboxAddress_succeeds(uint256 _chainId) public view { + address inbox = utils.chainIdToBatchInboxAddress(_chainId); + + // The version byte (first byte) should be 0x00. + bytes20 inboxBytes = bytes20(inbox); + assertEq(inboxBytes[0], 0x00, "First byte should be version byte 0x00"); + + // Verify determinism by calling again. + assertEq(utils.chainIdToBatchInboxAddress(_chainId), inbox, "Result should be deterministic"); + } + + /// @notice Tests that different chain IDs produce different batch inbox addresses. + /// @param _chainId1 The first chain ID. + /// @param _chainId2 The second chain ID. + function testFuzz_chainIdToBatchInboxAddress_differentInputs_succeeds( + uint256 _chainId1, + uint256 _chainId2 + ) + public + view + { + vm.assume(_chainId1 != _chainId2); + + address inbox1 = utils.chainIdToBatchInboxAddress(_chainId1); + address inbox2 = utils.chainIdToBatchInboxAddress(_chainId2); + + assertNotEq(inbox1, inbox2, "Different chain IDs should produce different addresses"); + } +} + +/// @title OPContractsManagerUtils_ComputeSalt_Test +/// @notice Tests the computeSalt function. +contract OPContractsManagerUtils_ComputeSalt_Test is OPContractsManagerUtils_TestInit { + /// @notice Tests that computeSalt produces deterministic output matching keccak256 encoding. + /// @param _chainId The chain ID. + /// @param _mixer The salt mixer. + /// @param _name The contract name. + function testFuzz_computeSalt_succeeds( + uint256 _chainId, + string calldata _mixer, + string calldata _name + ) + public + view + { + bytes32 expected = keccak256(abi.encode(_chainId, _mixer, _name)); + bytes32 actual = utils.computeSalt(_chainId, _mixer, _name); + + assertEq(actual, expected, "Salt should match keccak256(abi.encode(...))"); + + // Verify determinism by calling again. + assertEq(utils.computeSalt(_chainId, _mixer, _name), actual, "Salt should be deterministic"); + } +} + +/// @title OPContractsManagerUtils_HasInstruction_Test +/// @notice Tests the hasInstruction function. +contract OPContractsManagerUtils_HasInstruction_Test is OPContractsManagerUtils_TestInit { + /// @notice Tests that hasInstruction returns false for empty instructions array. + function test_hasInstruction_emptyArray_succeeds() public view { + OPContractsManagerUtils.ExtraInstruction[] memory instructions = _emptyInstructions(); + + assertFalse(utils.hasInstruction(instructions, "AnyKey", "AnyData"), "Empty array should return false"); + } + + /// @notice Tests that hasInstruction returns true when the instruction exists, false otherwise. + /// @param _key The key to search for. + /// @param _data The data to search for. + function testFuzz_hasInstruction_exists_succeeds(string calldata _key, bytes calldata _data) public view { + OPContractsManagerUtils.ExtraInstruction[] memory instructions = + new OPContractsManagerUtils.ExtraInstruction[](1); + instructions[0] = OPContractsManagerUtils.ExtraInstruction({ key: _key, data: _data }); + + assertTrue(utils.hasInstruction(instructions, _key, _data), "Should find matching instruction"); + assertFalse(utils.hasInstruction(instructions, "nonexistent", _data), "Wrong key returns false"); + assertFalse(utils.hasInstruction(instructions, _key, "nonexistent"), "Wrong data returns false"); + } + + /// @notice Tests hasInstruction finds correct instruction among multiple entries. + function test_hasInstruction_multipleInstructions_succeeds() public view { + OPContractsManagerUtils.ExtraInstruction[] memory instructions = + new OPContractsManagerUtils.ExtraInstruction[](3); + instructions[0] = OPContractsManagerUtils.ExtraInstruction({ key: "Key1", data: bytes("Data1") }); + instructions[1] = OPContractsManagerUtils.ExtraInstruction({ key: "Key2", data: bytes("Data2") }); + instructions[2] = OPContractsManagerUtils.ExtraInstruction({ key: "Key3", data: bytes("Data3") }); + + assertTrue(utils.hasInstruction(instructions, "Key1", "Data1"), "First instruction should be found"); + assertTrue(utils.hasInstruction(instructions, "Key2", "Data2"), "Second instruction should be found"); + assertTrue(utils.hasInstruction(instructions, "Key3", "Data3"), "Third instruction should be found"); + assertFalse(utils.hasInstruction(instructions, "Key4", "Data4"), "Non-existent should not be found"); + } +} + +/// @title OPContractsManagerUtils_GetInstructionByKey_Test +/// @notice Tests the getInstructionByKey function. +contract OPContractsManagerUtils_GetInstructionByKey_Test is OPContractsManagerUtils_TestInit { + /// @notice Tests that getInstructionByKey returns empty for empty array. + function test_getInstructionByKey_emptyArray_succeeds() public view { + OPContractsManagerUtils.ExtraInstruction[] memory instructions = _emptyInstructions(); + + OPContractsManagerUtils.ExtraInstruction memory result = utils.getInstructionByKey(instructions, "AnyKey"); + + assertEq(result.key, "", "Key should be empty"); + assertEq(result.data, bytes(""), "Data should be empty"); + } + + /// @notice Tests getInstructionByKey returns correct result when exists or empty when not. + /// @param _key The key to search for. + /// @param _data The data to associate with the key. + function testFuzz_getInstructionByKey_succeeds(string calldata _key, bytes calldata _data) public view { + OPContractsManagerUtils.ExtraInstruction[] memory instructions = + new OPContractsManagerUtils.ExtraInstruction[](1); + instructions[0] = OPContractsManagerUtils.ExtraInstruction({ key: _key, data: _data }); + + // Should find the instruction. + OPContractsManagerUtils.ExtraInstruction memory found = utils.getInstructionByKey(instructions, _key); + assertEq(found.key, _key, "Key should match"); + assertEq(found.data, _data, "Data should match"); + + // Should not find a non-existent instruction. + OPContractsManagerUtils.ExtraInstruction memory notFound = + utils.getInstructionByKey(instructions, "nonexistent"); + assertEq(notFound.key, "", "Key should be empty for not found"); + } + + /// @notice Tests that getInstructionByKey returns the first matching instruction for dupes. + function test_getInstructionByKey_duplicateKeys_succeeds() public view { + OPContractsManagerUtils.ExtraInstruction[] memory instructions = + new OPContractsManagerUtils.ExtraInstruction[](2); + instructions[0] = OPContractsManagerUtils.ExtraInstruction({ key: "DupeKey", data: bytes("FirstData") }); + instructions[1] = OPContractsManagerUtils.ExtraInstruction({ key: "DupeKey", data: bytes("SecondData") }); + + OPContractsManagerUtils.ExtraInstruction memory result = utils.getInstructionByKey(instructions, "DupeKey"); + + assertEq(result.data, bytes("FirstData"), "Should return first matching instruction"); + } +} + +/// @title OPContractsManagerUtils_LoadBytes_Test +/// @notice Tests the loadBytes function. +contract OPContractsManagerUtils_LoadBytes_Test is OPContractsManagerUtils_TestInit { + /// @notice Mock source contract for testing loadBytes. + address internal mockSource; + + /// @notice Selector for the mock function. + bytes4 internal constant MOCK_SELECTOR = bytes4(keccak256("getData()")); + + function setUp() public override { + super.setUp(); + mockSource = makeAddr("mockSource"); + } + + /// @notice Tests that loadBytes returns data from the source when no override exists. + function test_loadBytes_fromSource_succeeds() public { + bytes memory expectedData = abi.encode("test data"); + + // Mock the source to return expected data. + vm.mockCall(mockSource, abi.encodePacked(MOCK_SELECTOR), expectedData); + + bytes memory result = utils.loadBytes(mockSource, MOCK_SELECTOR, "testField", _emptyInstructions()); + + assertEq(result, expectedData, "Should return data from source"); + } + + /// @notice Tests that loadBytes returns override data when an override instruction exists. + /// @param _overrideData Fuzzed override data to test with. + function testFuzz_loadBytes_withOverride_succeeds(bytes calldata _overrideData) public view { + OPContractsManagerUtils.ExtraInstruction[] memory instructions = _createInstructions("testField", _overrideData); + + bytes memory result = utils.loadBytes(mockSource, MOCK_SELECTOR, "testField", instructions); + + assertEq(result, _overrideData, "Should return override data"); + } + + /// @notice Tests that loadBytes reverts when the source call fails. + function test_loadBytes_sourceCallFails_reverts() public { + // Mock the source to revert. + vm.mockCallRevert(mockSource, abi.encodePacked(MOCK_SELECTOR), "source error"); + + vm.expectRevert( + abi.encodeWithSelector( + IOPContractsManagerUtils.OPContractsManagerUtils_ConfigLoadFailed.selector, "testField" + ) + ); + utils.loadBytes(mockSource, MOCK_SELECTOR, "testField", _emptyInstructions()); + } +} + +/// @title OPContractsManagerUtils_LoadOrDeployProxy_Test +/// @notice Tests the loadOrDeployProxy function. +contract OPContractsManagerUtils_LoadOrDeployProxy_Test is OPContractsManagerUtils_TestInit { + /// @notice Mock source contract for testing load behavior. + address internal mockSource; + + /// @notice Real proxy admin for testing. + IProxyAdmin internal proxyAdmin; + + /// @notice Real address manager for testing. + IAddressManager internal addressManager; + + /// @notice Owner for ProxyAdmin. + address internal owner; + + /// @notice Selector for the mock proxy getter. + bytes4 internal constant MOCK_SELECTOR = bytes4(keccak256("getProxy()")); + + /// @notice ProxyDeployArgs for testing. + OPContractsManagerUtils.ProxyDeployArgs internal deployArgs; + + function setUp() public override { + super.setUp(); + + owner = makeAddr("owner"); + mockSource = makeAddr("mockSource"); + + // Deploy real ProxyAdmin. + proxyAdmin = IProxyAdmin( + DeployUtils.create1({ + _name: "ProxyAdmin", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxyAdmin.__constructor__, (owner))) + }) + ); + + // Deploy real AddressManager. + addressManager = IAddressManager( + DeployUtils.create1({ + _name: "AddressManager", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IAddressManager.__constructor__, ())) + }) + ); + + // Transfer AddressManager ownership to ProxyAdmin. + addressManager.transferOwnership(address(proxyAdmin)); + + // Set AddressManager on ProxyAdmin. + vm.prank(owner); + proxyAdmin.setAddressManager(addressManager); + + deployArgs = OPContractsManagerUtils.ProxyDeployArgs({ + proxyAdmin: proxyAdmin, + addressManager: addressManager, + l2ChainId: 42, + saltMixer: "testMixer" + }); + } + + /// @notice Tests that loadOrDeployProxy returns the proxy from the source when it exists. + /// @param _existingProxy Fuzzed address for the existing proxy. + function testFuzz_loadOrDeployProxy_loadsExisting_succeeds(address _existingProxy) public { + vm.assume(_existingProxy != address(0)); + + // Mock the source to return the existing proxy. + vm.mockCall(mockSource, abi.encodePacked(MOCK_SELECTOR), abi.encode(_existingProxy)); + + address result = + utils.loadOrDeployProxy(mockSource, MOCK_SELECTOR, deployArgs, "TestProxy", _emptyInstructions()); + + assertEq(result, _existingProxy, "Should return existing proxy"); + } + + /// @notice Tests that loadOrDeployProxy reverts when load fails and deployment is not permitted. + function test_loadOrDeployProxy_loadFailsNotPermitted_reverts() public { + // Mock the source to revert. + vm.mockCallRevert(mockSource, abi.encodePacked(MOCK_SELECTOR), "source error"); + + vm.expectRevert( + abi.encodeWithSelector(IOPContractsManagerUtils.OPContractsManagerUtils_ProxyMustLoad.selector, "TestProxy") + ); + utils.loadOrDeployProxy(mockSource, MOCK_SELECTOR, deployArgs, "TestProxy", _emptyInstructions()); + } + + /// @notice Tests that loadOrDeployProxy reverts when source returns zero address. + function test_loadOrDeployProxy_zeroAddressNotPermitted_reverts() public { + // Mock the source to return address(0). + vm.mockCall(mockSource, abi.encodePacked(MOCK_SELECTOR), abi.encode(address(0))); + + vm.expectRevert( + abi.encodeWithSelector(IOPContractsManagerUtils.OPContractsManagerUtils_ProxyMustLoad.selector, "TestProxy") + ); + utils.loadOrDeployProxy(mockSource, MOCK_SELECTOR, deployArgs, "TestProxy", _emptyInstructions()); + } + + /// @notice Tests that specific contract permission bypasses ProxyMustLoad when load fails. + function test_loadOrDeployProxy_specificPermission_succeeds() public { + vm.mockCall(mockSource, abi.encodePacked(MOCK_SELECTOR), abi.encode(address(0))); + + OPContractsManagerUtils.ExtraInstruction[] memory instructions = + _createInstructions(Constants.PERMITTED_PROXY_DEPLOYMENT_KEY, bytes("TestProxy")); + + // Permission check passes (no ProxyMustLoad error), but Blueprint deploy fails since mocked. + vm.expectRevert(Blueprint.NotABlueprint.selector); + utils.loadOrDeployProxy(mockSource, MOCK_SELECTOR, deployArgs, "TestProxy", instructions); + } + + /// @notice Tests that ALL permission bypasses ProxyMustLoad when load fails. + function test_loadOrDeployProxy_allPermission_succeeds() public { + vm.mockCall(mockSource, abi.encodePacked(MOCK_SELECTOR), abi.encode(address(0))); + + OPContractsManagerUtils.ExtraInstruction[] memory instructions = + _createInstructions(Constants.PERMITTED_PROXY_DEPLOYMENT_KEY, Constants.PERMIT_ALL_CONTRACTS_INSTRUCTION); + + // Permission check passes (no ProxyMustLoad error), but Blueprint deploy fails since mocked. + vm.expectRevert(Blueprint.NotABlueprint.selector); + utils.loadOrDeployProxy(mockSource, MOCK_SELECTOR, deployArgs, "TestProxy", instructions); + } +} + +/// @title OPContractsManagerUtils_Upgrade_Test +/// @notice Tests the upgrade function. +contract OPContractsManagerUtils_Upgrade_Test is OPContractsManagerUtils_TestInit { + /// @notice Real proxy admin for testing (owned by utils). + IProxyAdmin internal proxyAdmin; + + /// @notice Real proxy for testing. + IProxy internal proxy; + + /// @notice v1 implementation for testing. + OPContractsManagerUtils_ImplV1_Harness internal implV1; + + /// @notice Another v1 implementation for same-version testing. + OPContractsManagerUtils_ImplV1b_Harness internal implV1b; + + /// @notice v2 implementation for testing. + OPContractsManagerUtils_ImplV2_Harness internal implV2; + + /// @notice Storage slot to reset during upgrade (slot 0 for OZ Initializable). + bytes32 internal constant TEST_SLOT = bytes32(uint256(0)); + + /// @notice Byte offset within the slot for the initialized flag. + uint8 internal constant TEST_OFFSET = 0; + + function setUp() public override { + super.setUp(); + + // Deploy real ProxyAdmin with utils as owner so utils.upgrade() can call proxyAdmin. + proxyAdmin = IProxyAdmin( + DeployUtils.create1({ + _name: "ProxyAdmin", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxyAdmin.__constructor__, (address(utils)))) + }) + ); + + // Deploy real Proxy with ProxyAdmin as admin. + proxy = IProxy( + DeployUtils.create1({ + _name: "Proxy", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (address(proxyAdmin)))) + }) + ); + + // Set proxy type on ProxyAdmin (utils is owner). + vm.prank(address(utils)); + proxyAdmin.setProxyType(address(proxy), IProxyAdmin.ProxyType.ERC1967); + + // Deploy versioned implementations. + implV1 = new OPContractsManagerUtils_ImplV1_Harness(); + implV1b = new OPContractsManagerUtils_ImplV1b_Harness(); + implV2 = new OPContractsManagerUtils_ImplV2_Harness(); + } + + /// @notice Tests that upgrade reverts when attempting a downgrade. + function test_upgrade_downgradeNotAllowed_reverts() public { + // Set v2 as current implementation. + vm.prank(address(utils)); + proxyAdmin.upgrade(payable(address(proxy)), address(implV2)); + + // Try to downgrade to v1 - should revert. + vm.expectRevert( + abi.encodeWithSelector( + IOPContractsManagerUtils.OPContractsManagerUtils_DowngradeNotAllowed.selector, address(proxy) + ) + ); + utils.upgrade( + proxyAdmin, + address(proxy), + address(implV1), + abi.encodeCall(OPContractsManagerUtils_ImplV1_Harness.initialize, ()), + TEST_SLOT, + TEST_OFFSET + ); + } + + /// @notice Tests that upgrade allows upgrading to the same version. + function test_upgrade_sameVersion_succeeds() public { + // Set v1 as current implementation. + vm.prank(address(utils)); + proxyAdmin.upgrade(payable(address(proxy)), address(implV1)); + + // Upgrade to the same version (different contract) should succeed. + utils.upgrade( + proxyAdmin, + address(proxy), + address(implV1b), + abi.encodeCall(OPContractsManagerUtils_ImplV1b_Harness.initialize, ()), + TEST_SLOT, + TEST_OFFSET + ); + + // Verify the implementation changed. + assertEq(proxyAdmin.getProxyImplementation(payable(address(proxy))), address(implV1b)); + } + + /// @notice Tests that upgrade succeeds when upgrading to a newer version. + function test_upgrade_newerVersion_succeeds() public { + // Set v1 as current implementation. + vm.prank(address(utils)); + proxyAdmin.upgrade(payable(address(proxy)), address(implV1)); + + // Upgrade to v2 should succeed. + utils.upgrade( + proxyAdmin, + address(proxy), + address(implV2), + abi.encodeCall(OPContractsManagerUtils_ImplV2_Harness.initialize, ()), + TEST_SLOT, + TEST_OFFSET + ); + + // Verify the implementation changed. + assertEq(proxyAdmin.getProxyImplementation(payable(address(proxy))), address(implV2)); + } + + /// @notice Tests that upgrade succeeds when target has no implementation (fresh deploy). + function test_upgrade_noExistingImplementation_succeeds() public { + // Upgrade fresh proxy (no existing implementation) should succeed. + utils.upgrade( + proxyAdmin, + address(proxy), + address(implV1), + abi.encodeCall(OPContractsManagerUtils_ImplV1_Harness.initialize, ()), + TEST_SLOT, + TEST_OFFSET + ); + + // Verify the implementation was set. + assertEq(proxyAdmin.getProxyImplementation(payable(address(proxy))), address(implV1)); + } +} + +/// @title OPContractsManagerUtils_Blueprints_Test +/// @notice Tests the blueprints() getter. +contract OPContractsManagerUtils_Blueprints_Test is OPContractsManagerUtils_TestInit { + /// @notice Tests that blueprints() returns the struct from the container. + function test_blueprints_succeeds() public view { + assertEq(abi.encode(utils.blueprints()), abi.encode(blueprints)); + } +} + +/// @title OPContractsManagerUtils_Implementations_Test +/// @notice Tests the implementations() getter. +contract OPContractsManagerUtils_Implementations_Test is OPContractsManagerUtils_TestInit { + /// @notice Tests that implementations() returns the struct from the container. + function test_implementations_succeeds() public view { + assertEq(abi.encode(utils.implementations()), abi.encode(implementations)); + } +} + +/// @title OPContractsManagerUtils_ContractsContainer_Test +/// @notice Tests the contractsContainer() getter. +contract OPContractsManagerUtils_ContractsContainer_Test is OPContractsManagerUtils_TestInit { + /// @notice Tests that contractsContainer() returns the container provided at construction. + function test_contractsContainer_succeeds() public view { + assertEq(address(utils.contractsContainer()), address(container)); + } +} diff --git a/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol index 314edd4beba..53d4b8f3da4 100644 --- a/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol +++ b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol @@ -9,20 +9,201 @@ import { DisputeGames } from "test/setup/DisputeGames.sol"; // Libraries import { Config } from "scripts/libraries/Config.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; -import { Claim } from "src/dispute/lib/LibUDT.sol"; -import { GameType, GameTypes } from "src/dispute/lib/Types.sol"; +import { Claim, Hash } from "src/dispute/lib/LibUDT.sol"; +import { GameType, GameTypes, Proposal } from "src/dispute/lib/Types.sol"; import { DevFeatures } from "src/libraries/DevFeatures.sol"; // Interfaces +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IOPContractsManagerStandardValidator } from "interfaces/L1/IOPContractsManagerStandardValidator.sol"; import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; +import { IOPContractsManagerUtils } from "interfaces/L1/opcm/IOPContractsManagerUtils.sol"; + +/// @title OPContractsManagerV2_TestInit +/// @notice Base test initialization contract for OPContractsManagerV2. +contract OPContractsManagerV2_TestInit is CommonTest, DisputeGames { + /// @notice Fake prestate for Cannon games. + Claim cannonPrestate = Claim.wrap(bytes32(keccak256("cannonPrestate"))); + + /// @notice Fake prestate for Cannon Kona games. + Claim cannonKonaPrestate = Claim.wrap(bytes32(keccak256("cannonKonaPrestate"))); + + /// @notice Special string constant used to indicate that we expect a revert without any data. + bytes public constant EXPECT_REVERT_WITHOUT_DATA = bytes("EXPECT_REVERT_WITHOUT_DATA"); + + /// @notice Buffer percentage (relative to EIP-7825 gas limit) allowed for deployments. + uint256 public constant DEPLOY_GAS_BUFFER_PERCENTAGE = 80; // 80% + + /// @notice Sets up the test suite. + function setUp() public virtual override { + super.setUp(); + skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); + } + + /// @notice Helper function that runs an OPCM V2 deploy, asserts that the deploy was successful, + /// and runs post-deploy standard validator checks. + /// @param _opcm The OPCM contract to use for deployment. + /// @param _deployConfig The full config for the deployment. + /// @param _revertBytes The bytes of the revert to expect (empty if no revert expected). + /// @param _expectedValidatorErrors The StandardValidator errors to expect. + /// @return cts_ The deployed chain contracts. + function _runOpcmV2DeployAndChecks( + IOPContractsManagerV2 _opcm, + IOPContractsManagerV2.FullConfig memory _deployConfig, + bytes memory _revertBytes, + string memory _expectedValidatorErrors + ) + internal + returns (IOPContractsManagerV2.ChainContracts memory cts_) + { + // Grab the proposer and challenger from deploy config for validator. + address deployProposer; + address deployChallenger; + for (uint256 i = 0; i < _deployConfig.disputeGameConfigs.length; i++) { + if (_deployConfig.disputeGameConfigs[i].gameType.raw() == GameTypes.PERMISSIONED_CANNON.raw()) { + IOPContractsManagerV2.PermissionedDisputeGameConfig memory parsedArgs = abi.decode( + _deployConfig.disputeGameConfigs[i].gameArgs, (IOPContractsManagerV2.PermissionedDisputeGameConfig) + ); + deployProposer = parsedArgs.proposer; + deployChallenger = parsedArgs.challenger; + break; + } + } + + // Expect the revert if one is specified. + if (_revertBytes.length > 0) { + if (keccak256(_revertBytes) == keccak256(EXPECT_REVERT_WITHOUT_DATA)) { + // nosemgrep: sol-safety-expectrevert-no-args + vm.expectRevert(); + } else { + vm.expectRevert(_revertBytes); + } + } + + // Execute the V2 deploy. + cts_ = _opcm.deploy(_deployConfig); + + // Return early if a revert was expected. Otherwise we'll get errors below. + if (_revertBytes.length > 0) { + return cts_; + } + + // Less than the buffer percentage of the EIP-7825 gas limit to account for the gas used + // by using Safe. + uint256 fusakaLimit = 2 ** 24; + VmSafe.Gas memory gas = vm.lastCallGas(); + assertLt( + gas.gasTotalUsed, + fusakaLimit * DEPLOY_GAS_BUFFER_PERCENTAGE / 100, + string.concat( + "Deploy exceeds gas target of ", vm.toString(DEPLOY_GAS_BUFFER_PERCENTAGE), "% of 2**24 (EIP-7825)" + ) + ); + + // Coverage changes bytecode, so we get various errors. We can safely ignore the result of + // the standard validator in the coverage case. + if (vm.isContext(VmSafe.ForgeContext.Coverage)) { + return cts_; + } + + // Create validationOverrides for the newly deployed chain. + IOPContractsManagerStandardValidator.ValidationOverrides memory validationOverrides = + IOPContractsManagerStandardValidator.ValidationOverrides({ + l1PAOMultisig: _deployConfig.proxyAdminOwner, + challenger: deployChallenger + }); + + // Grab the validator before we do the error assertion. + IOPContractsManagerStandardValidator validator = _opcm.standardValidator(); + + // Expect validator errors if the user provides them. + if (bytes(_expectedValidatorErrors).length > 0) { + vm.expectRevert( + bytes( + string.concat( + "OPContractsManagerStandardValidator: OVERRIDES-L1PAOMULTISIG,OVERRIDES-CHALLENGER,", + _expectedValidatorErrors + ) + ) + ); + } + + // Run the StandardValidator checks on the newly deployed chain. + if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { + validator.validateWithOverrides( + IOPContractsManagerStandardValidator.ValidationInputDev({ + sysCfg: cts_.systemConfig, + cannonPrestate: cannonPrestate.raw(), + cannonKonaPrestate: cannonKonaPrestate.raw(), + l2ChainID: _deployConfig.l2ChainId, + proposer: deployProposer + }), + false, + validationOverrides + ); + } else { + validator.validateWithOverrides( + IOPContractsManagerStandardValidator.ValidationInput({ + sysCfg: cts_.systemConfig, + absolutePrestate: cannonPrestate.raw(), + l2ChainID: _deployConfig.l2ChainId, + proposer: deployProposer + }), + false, + validationOverrides + ); + } + + return cts_; + } + + /// @notice Executes a V2 deploy and checks the results. + /// @param _deployConfig The full config for the deployment. + /// @return The deployed chain contracts. + function runDeployV2(IOPContractsManagerV2.FullConfig memory _deployConfig) + public + returns (IOPContractsManagerV2.ChainContracts memory) + { + return _runOpcmV2DeployAndChecks(opcmV2, _deployConfig, bytes(""), ""); + } + + /// @notice Executes a V2 deploy and expects reverts. + /// @param _deployConfig The full config for the deployment. + /// @param _revertBytes The bytes of the revert to expect. + /// @return The deployed chain contracts. + function runDeployV2( + IOPContractsManagerV2.FullConfig memory _deployConfig, + bytes memory _revertBytes + ) + public + returns (IOPContractsManagerV2.ChainContracts memory) + { + return _runOpcmV2DeployAndChecks(opcmV2, _deployConfig, _revertBytes, ""); + } + + /// @notice Executes a V2 deploy and expects reverts with validator errors. + /// @param _deployConfig The full config for the deployment. + /// @param _revertBytes The bytes of the revert to expect. + /// @param _expectedValidatorErrors The StandardValidator errors to expect. + /// @return The deployed chain contracts. + function runDeployV2( + IOPContractsManagerV2.FullConfig memory _deployConfig, + bytes memory _revertBytes, + string memory _expectedValidatorErrors + ) + public + returns (IOPContractsManagerV2.ChainContracts memory) + { + return _runOpcmV2DeployAndChecks(opcmV2, _deployConfig, _revertBytes, _expectedValidatorErrors); + } +} /// @title OPContractsManagerV2_Upgrade_TestInit /// @notice Test initialization contract for OPContractsManagerV2 upgrade functions. -contract OPContractsManagerV2_Upgrade_TestInit is CommonTest, DisputeGames { +contract OPContractsManagerV2_Upgrade_TestInit is OPContractsManagerV2_TestInit { // The Upgraded event emitted by the Proxy contract. event Upgraded(address indexed implementation); @@ -35,20 +216,14 @@ contract OPContractsManagerV2_Upgrade_TestInit is CommonTest, DisputeGames { /// @notice Address of the Superchain ProxyAdmin owner. address superchainPAO; - /// @notice Fake prestate for Cannon games. - Claim cannonPrestate = Claim.wrap(bytes32(keccak256("cannonPrestate"))); - - /// @notice Fake prestate for Cannon Kona games. - Claim cannonKonaPrestate = Claim.wrap(bytes32(keccak256("cannonKonaPrestate"))); - /// @notice Name of the chain being forked. string public opChain = Config.forkOpChain(); /// @notice Default v2 upgrade input. IOPContractsManagerV2.UpgradeInput v2UpgradeInput; - /// @notice Special string constant used to indicate that we expect a revert without any data. - bytes public constant EXPECT_REVERT_WITHOUT_DATA = bytes("EXPECT_REVERT_WITHOUT_DATA"); + /// @notice Buffer percentage (relative to EIP-7825 gas limit) allowed for upgrades. + uint256 public constant UPGRADE_GAS_BUFFER_PERCENTAGE = 50; // 50% /// @notice Thrown when trying to run past upgrades on an unsupported chain. error UnsupportedChainId(); @@ -58,7 +233,6 @@ contract OPContractsManagerV2_Upgrade_TestInit is CommonTest, DisputeGames { super.disableUpgradedFork(); super.setUp(); - skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); skipIfNotForkTest("OPContractsManagerV2_Upgrade_TestInit: only runs in forked tests"); skipIfOpsRepoTest("OPContractsManagerV2_Upgrade_TestInit: skipped in superchain-ops"); @@ -110,7 +284,7 @@ contract OPContractsManagerV2_Upgrade_TestInit is CommonTest, DisputeGames { // Allow the DelayedWETH proxy to be (re)deployed during upgrades if it is missing. v2UpgradeInput.extraInstructions.push( - IOPContractsManagerV2.ExtraInstruction({ key: "PermittedProxyDeployment", data: bytes("DelayedWETH") }) + IOPContractsManagerUtils.ExtraInstruction({ key: "PermittedProxyDeployment", data: bytes("DelayedWETH") }) ); } @@ -140,7 +314,7 @@ contract OPContractsManagerV2_Upgrade_TestInit is CommonTest, DisputeGames { ( IOPContractsManagerV2.SuperchainUpgradeInput({ superchainConfig: superchainConfig, - extraInstructions: new IOPContractsManagerV2.ExtraInstruction[](0) + extraInstructions: new IOPContractsManagerUtils.ExtraInstruction[](0) }) ) ) @@ -151,7 +325,7 @@ contract OPContractsManagerV2_Upgrade_TestInit is CommonTest, DisputeGames { // the implementations struct interface can change between OPCM versions which would // cause the test to break and be a pain to resolve. assertTrue( - bytes4(reason) == IOPContractsManagerV2.OPContractsManagerV2_DowngradeNotAllowed.selector, + bytes4(reason) == IOPContractsManagerUtils.OPContractsManagerUtils_DowngradeNotAllowed.selector, "Revert reason other than DowngradeNotAllowed" ); } @@ -177,10 +351,17 @@ contract OPContractsManagerV2_Upgrade_TestInit is CommonTest, DisputeGames { return; } - // Less than 90% of the gas target of 2**24 (EIP-7825) to account for the gas used by using Safe. + // Less than the buffer percentage of the EIP-7825 gas limit to account for the gas used + // by using Safe. uint256 fusakaLimit = 2 ** 24; VmSafe.Gas memory gas = vm.lastCallGas(); - assertLt(gas.gasTotalUsed, fusakaLimit * 9 / 10, "Upgrade exceeds gas target of 90% of 2**24 (EIP-7825)"); + assertLt( + gas.gasTotalUsed, + fusakaLimit * UPGRADE_GAS_BUFFER_PERCENTAGE / 100, + string.concat( + "Upgrade exceeds gas target of ", vm.toString(UPGRADE_GAS_BUFFER_PERCENTAGE), "% of 2**24 (EIP-7825)" + ) + ); // Coverage changes bytecode, so we get various errors. We can safely ignore the result of // the standard validator in the coverage case, if the validator is failing in coverage @@ -387,14 +568,16 @@ contract OPContractsManagerV2_Upgrade_Test is OPContractsManagerV2_Upgrade_TestI function test_upgrade_allPermittedProxyDeployments_reverts() public { delete v2UpgradeInput.extraInstructions; v2UpgradeInput.extraInstructions.push( - IOPContractsManagerV2.ExtraInstruction({ key: "PermitProxyDeployment", data: abi.encode("ALL") }) + IOPContractsManagerUtils.ExtraInstruction({ key: "PermitProxyDeployment", data: abi.encode("ALL") }) ); // Expect upgrade to revert due to invalid upgrade input. // nosemgrep: sol-style-use-abi-encodecall runCurrentUpgradeV2( chainPAO, - abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_InvalidUpgradeInstruction.selector) + abi.encodeWithSelector( + IOPContractsManagerV2.OPContractsManagerV2_InvalidUpgradeInstruction.selector, "PermitProxyDeployment" + ) ); } @@ -412,7 +595,9 @@ contract OPContractsManagerV2_Upgrade_Test is OPContractsManagerV2_Upgrade_TestI // nosemgrep: sol-style-use-abi-encodecall runCurrentUpgradeV2( chainPAO, - abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_ProxyMustLoad.selector, "DelayedWETH") + abi.encodeWithSelector( + IOPContractsManagerUtils.OPContractsManagerUtils_ProxyMustLoad.selector, "DelayedWETH" + ) ); } @@ -431,7 +616,7 @@ contract OPContractsManagerV2_Upgrade_Test is OPContractsManagerV2_Upgrade_TestI } // Mock the first proxy load source call to succeed but return a payload with a length - // not equal to 32 bytes, triggering OPContractsManagerV2_ProxyLoadMustLoad. + // not equal to 32 bytes, triggering OPContractsManagerUtils_ProxyLoadMustLoad. vm.mockCall(address(systemConfig), abi.encodeCall(ISystemConfig.l1CrossDomainMessenger, ()), bad); // Expect a revert without any data (due to abi decoding failure). @@ -442,7 +627,7 @@ contract OPContractsManagerV2_Upgrade_Test is OPContractsManagerV2_Upgrade_TestI /// an existing proxy returns the zero address but we asked it to load. function test_upgrade_proxyMustLoadButZeroAddress_reverts() public { // Mock the first proxy load to succeed and return address(0) with 32 bytes, - // which triggers OPContractsManagerV2_ProxyMustLoad since _mustLoad is true in upgrade. + // which triggers OPContractsManagerUtils_ProxyMustLoad since _mustLoad is true in upgrade. vm.mockCall( address(systemConfig), abi.encodeCall(ISystemConfig.l1CrossDomainMessenger, ()), abi.encode(address(0)) ); @@ -451,7 +636,7 @@ contract OPContractsManagerV2_Upgrade_Test is OPContractsManagerV2_Upgrade_TestI runCurrentUpgradeV2( chainPAO, abi.encodeWithSelector( - IOPContractsManagerV2.OPContractsManagerV2_ProxyMustLoad.selector, "L1CrossDomainMessenger" + IOPContractsManagerUtils.OPContractsManagerUtils_ProxyMustLoad.selector, "L1CrossDomainMessenger" ) ); } @@ -460,7 +645,7 @@ contract OPContractsManagerV2_Upgrade_Test is OPContractsManagerV2_Upgrade_TestI /// an existing proxy returns an error but we asked it to load. function test_upgrade_proxyMustLoadButReverts_reverts() public { // Mock the first proxy load source to revert, which with _mustLoad=true triggers - // OPContractsManagerV2_ProxyMustLoad. + // OPContractsManagerUtils_ProxyMustLoad. // nosemgrep: sol-style-use-abi-encodecall vm.mockCallRevert(address(systemConfig), abi.encodeCall(ISystemConfig.l1CrossDomainMessenger, ()), bytes("")); @@ -468,7 +653,7 @@ contract OPContractsManagerV2_Upgrade_Test is OPContractsManagerV2_Upgrade_TestI runCurrentUpgradeV2( chainPAO, abi.encodeWithSelector( - IOPContractsManagerV2.OPContractsManagerV2_ProxyMustLoad.selector, "L1CrossDomainMessenger" + IOPContractsManagerUtils.OPContractsManagerUtils_ProxyMustLoad.selector, "L1CrossDomainMessenger" ) ); } @@ -641,7 +826,7 @@ contract OPContractsManagerV2_UpgradeSuperchain_Test is OPContractsManagerV2_Upg // nosemgrep: sol-style-use-abi-encodecall vm.expectRevert( abi.encodeWithSelector( - IOPContractsManagerV2.OPContractsManagerV2_DowngradeNotAllowed.selector, address(superchainConfig) + IOPContractsManagerUtils.OPContractsManagerUtils_DowngradeNotAllowed.selector, address(superchainConfig) ) ); prankDelegateCall(superchainPAO); @@ -651,3 +836,149 @@ contract OPContractsManagerV2_UpgradeSuperchain_Test is OPContractsManagerV2_Upg assertTrue(success, "upgradeSuperchain failed"); } } + +/// @title OPContractsManagerV2_Deploy_Test +/// @notice Tests OPContractsManagerV2.deploy +contract OPContractsManagerV2_Deploy_Test is OPContractsManagerV2_TestInit { + /// @notice Default deploy config. + IOPContractsManagerV2.FullConfig deployConfig; + + /// @notice Sets up the test. + function setUp() public override { + super.setUp(); + + // Set up default deploy config. + // We can't set storage structs directly, so we need to set each field individually. + deployConfig.saltMixer = "test-salt-mixer"; + deployConfig.superchainConfig = superchainConfig; + deployConfig.proxyAdminOwner = makeAddr("proxyAdminOwner"); + deployConfig.systemConfigOwner = makeAddr("systemConfigOwner"); + deployConfig.unsafeBlockSigner = makeAddr("unsafeBlockSigner"); + deployConfig.batcher = makeAddr("batcher"); + deployConfig.startingAnchorRoot = Proposal({ root: Hash.wrap(bytes32(hex"1234")), l2SequenceNumber: 123 }); + deployConfig.startingRespectedGameType = GameTypes.PERMISSIONED_CANNON; + deployConfig.basefeeScalar = 1368; + deployConfig.blobBasefeeScalar = 801949; + deployConfig.gasLimit = 60_000_000; + deployConfig.l2ChainId = 999_999_999; + deployConfig.resourceConfig = IResourceMetering.ResourceConfig({ + maxResourceLimit: 20_000_000, + elasticityMultiplier: 10, + baseFeeMaxChangeDenominator: 8, + minimumBaseFee: 1 gwei, + systemTxMaxGas: 1_000_000, + maximumBaseFee: type(uint128).max + }); + + // Set up dispute game configs using the same pattern as upgrade tests. + address initialChallenger = permissionedGameChallenger(disputeGameFactory); + address initialProposer = permissionedGameProposer(disputeGameFactory); + deployConfig.disputeGameConfigs.push( + IOPContractsManagerV2.DisputeGameConfig({ + enabled: true, + initBond: 0.08 ether, // Standard init bond + gameType: GameTypes.CANNON, + gameArgs: abi.encode(IOPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: cannonPrestate })) + }) + ); + deployConfig.disputeGameConfigs.push( + IOPContractsManagerV2.DisputeGameConfig({ + enabled: true, + initBond: 0.08 ether, // Standard init bond + gameType: GameTypes.PERMISSIONED_CANNON, + gameArgs: abi.encode( + IOPContractsManagerV2.PermissionedDisputeGameConfig({ + absolutePrestate: cannonPrestate, + proposer: initialProposer, + challenger: initialChallenger + }) + ) + }) + ); + deployConfig.disputeGameConfigs.push( + IOPContractsManagerV2.DisputeGameConfig({ + enabled: isDevFeatureEnabled(DevFeatures.CANNON_KONA), + initBond: isDevFeatureEnabled(DevFeatures.CANNON_KONA) ? 0.08 ether : 0, // Standard init bond + gameType: GameTypes.CANNON_KONA, + gameArgs: abi.encode(IOPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: cannonKonaPrestate })) + }) + ); + } + + /// @notice Tests that the deploy function succeeds and passes standard validation. + function test_deploy_succeeds() public { + // Run the deploy and standard validator checks. + IOPContractsManagerV2.ChainContracts memory cts = runDeployV2(deployConfig); + + // Verify key contracts are deployed. + assertTrue(address(cts.systemConfig) != address(0), "systemConfig not deployed"); + assertTrue(address(cts.proxyAdmin) != address(0), "proxyAdmin not deployed"); + assertTrue(address(cts.optimismPortal) != address(0), "optimismPortal not deployed"); + assertTrue(address(cts.disputeGameFactory) != address(0), "disputeGameFactory not deployed"); + assertTrue(address(cts.anchorStateRegistry) != address(0), "anchorStateRegistry not deployed"); + assertTrue(address(cts.delayedWETH) != address(0), "delayedWETH not deployed"); + + // Verify ownership is transferred to proxyAdminOwner. + assertEq(cts.proxyAdmin.owner(), deployConfig.proxyAdminOwner, "proxyAdmin owner mismatch"); + assertEq(cts.disputeGameFactory.owner(), deployConfig.proxyAdminOwner, "disputeGameFactory owner mismatch"); + } + + /// @notice Tests that deploy reverts when the superchainConfig needs upgrade. + function test_deploy_superchainConfigNeedsUpgrade_reverts() public { + // Force the SuperchainConfig to return an obviously outdated version. + vm.mockCall(address(superchainConfig), abi.encodeCall(ISuperchainConfig.version, ()), abi.encode("0.0.0")); + + // nosemgrep: sol-style-use-abi-encodecall + runDeployV2( + deployConfig, + abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_SuperchainConfigNeedsUpgrade.selector) + ); + } + + /// @notice Tests that deploy reverts when missing game configs. + function test_deploy_missingGameConfigs_reverts() public { + // Delete the Cannon Kona game configuration. + delete deployConfig.disputeGameConfigs[2]; + + // nosemgrep: sol-style-use-abi-encodecall + runDeployV2( + deployConfig, abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_InvalidGameConfigs.selector) + ); + } + + /// @notice Tests that deploy reverts when game configs are in wrong order. + function test_deploy_wrongGameConfigOrder_reverts() public { + // Swap the game config order. + IOPContractsManagerV2.DisputeGameConfig memory temp = deployConfig.disputeGameConfigs[0]; + deployConfig.disputeGameConfigs[0] = deployConfig.disputeGameConfigs[1]; + deployConfig.disputeGameConfigs[1] = temp; + + // nosemgrep: sol-style-use-abi-encodecall + runDeployV2( + deployConfig, abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_InvalidGameConfigs.selector) + ); + } + + /// @notice Tests that deploy reverts when the PermissionedDisputeGame is disabled. + function test_deploy_disabledPermissionedGame_reverts() public { + // Disable the PermissionedDisputeGame. + deployConfig.disputeGameConfigs[1].enabled = false; + + // nosemgrep: sol-style-use-abi-encodecall + runDeployV2( + deployConfig, abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_InvalidGameConfigs.selector) + ); + } + + /// @notice Tests that deploy reverts when a disabled game has non-zero init bond. + function test_deploy_disabledGameNonZeroBond_reverts() public { + // Disable Cannon but keep a non-zero init bond. + deployConfig.disputeGameConfigs[0].enabled = false; + deployConfig.disputeGameConfigs[0].initBond = 1 ether; + + // nosemgrep: sol-style-use-abi-encodecall + runDeployV2( + deployConfig, abi.encodeWithSelector(IOPContractsManagerV2.OPContractsManagerV2_InvalidGameConfigs.selector) + ); + } +} diff --git a/packages/contracts-bedrock/test/L2/FeeSplitter.t.sol b/packages/contracts-bedrock/test/L2/FeeSplitter.t.sol index 0ed14418f63..35edca9a278 100644 --- a/packages/contracts-bedrock/test/L2/FeeSplitter.t.sol +++ b/packages/contracts-bedrock/test/L2/FeeSplitter.t.sol @@ -13,7 +13,6 @@ import { ReentrantMockFeeVault } from "test/mocks/ReentrantMockFeeVault.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; import { Types } from "src/libraries/Types.sol"; -import { DevFeatures } from "src/libraries/DevFeatures.sol"; // Interfaces import { IFeeSplitter } from "interfaces/L2/IFeeSplitter.sol"; @@ -42,10 +41,6 @@ contract FeeSplitter_TestInit is CommonTest { /// @notice Test setup. function setUp() public virtual override { - // Resolve features and skip whole test suite if custom gas token is enabled - resolveFeaturesFromEnv(); - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); - // Enable revenue sharing before calling parent setUp super.enableRevenueShare(); super.setUp(); diff --git a/packages/contracts-bedrock/test/L2/FeeVault.t.sol b/packages/contracts-bedrock/test/L2/FeeVault.t.sol index fcdb1e464a9..aa8a572335b 100644 --- a/packages/contracts-bedrock/test/L2/FeeVault.t.sol +++ b/packages/contracts-bedrock/test/L2/FeeVault.t.sol @@ -8,12 +8,13 @@ import { CommonTest } from "test/setup/CommonTest.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { IFeeVault } from "interfaces/L2/IFeeVault.sol"; import { IL2ToL1MessagePasser } from "interfaces/L2/IL2ToL1MessagePasser.sol"; +import { IL2ToL1MessagePasserCGT } from "interfaces/L2/IL2ToL1MessagePasserCGT.sol"; // Libraries import { Hashing } from "src/libraries/Hashing.sol"; import { Types } from "src/libraries/Types.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; -import { DevFeatures } from "src/libraries/DevFeatures.sol"; +import { Features } from "src/libraries/Features.sol"; /// @title FeeVault_Uncategorized_Test /// @notice Abstract test contract for fee feeVault testing. @@ -83,7 +84,7 @@ abstract contract FeeVault_Uncategorized_Test is CommonTest { /// @notice Tests that `withdraw` successfully initiates a withdrawal to L1. function test_withdraw_toL1_succeeds() external { - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); // Setup L1 withdrawal vm.prank(IProxyAdmin(Predeploys.PROXY_ADMIN).owner()); @@ -145,6 +146,31 @@ abstract contract FeeVault_Uncategorized_Test is CommonTest { assertEq(Predeploys.L2_TO_L1_MESSAGE_PASSER.balance, amount); } + /// @notice Tests that withdraw to L1 reverts when custom gas token is enabled and value is sent. + function testFuzz_withdraw_toL1WithCustomGasToken_reverts(uint256 _amount) external { + skipIfSysFeatureDisabled(Features.CUSTOM_GAS_TOKEN); + + // Setup L1 withdrawal + vm.prank(IProxyAdmin(Predeploys.PROXY_ADMIN).owner()); + feeVault.setWithdrawalNetwork(Types.WithdrawalNetwork.L1); + + // Set recipient + vm.prank(IProxyAdmin(Predeploys.PROXY_ADMIN).owner()); + feeVault.setRecipient(recipient); + + // Set minimum withdrawal amount + vm.prank(IProxyAdmin(Predeploys.PROXY_ADMIN).owner()); + feeVault.setMinWithdrawalAmount(minWithdrawalAmount); + + // Set the balance to be greater than the minimum withdrawal amount + _amount = bound(_amount, feeVault.minWithdrawalAmount() + 1, type(uint128).max); + vm.deal(address(feeVault), _amount); + + // Withdrawal should revert due to CGT mode + vm.expectRevert(IL2ToL1MessagePasserCGT.L2ToL1MessagePasserCGT_NotAllowedOnCGTMode.selector); + feeVault.withdraw(); + } + /// @notice Tests that `withdraw` successfully initiates a withdrawal to L2. function test_withdraw_toL2_succeeds() public { _setupL2Withdrawal(); diff --git a/packages/contracts-bedrock/test/L2/L1Block.t.sol b/packages/contracts-bedrock/test/L2/L1Block.t.sol index 444587bb120..5d36bb88631 100644 --- a/packages/contracts-bedrock/test/L2/L1Block.t.sol +++ b/packages/contracts-bedrock/test/L2/L1Block.t.sol @@ -9,7 +9,7 @@ import { stdStorage, StdStorage } from "forge-std/Test.sol"; import { Encoding } from "src/libraries/Encoding.sol"; import { Constants } from "src/libraries/Constants.sol"; import "src/libraries/L1BlockErrors.sol"; -import { DevFeatures } from "src/libraries/DevFeatures.sol"; +import { Features } from "src/libraries/Features.sol"; // Interfaces import { IL1BlockCGT } from "interfaces/L2/IL1BlockCGT.sol"; @@ -54,7 +54,7 @@ contract L1Block_GasPayingToken_Test is L1Block_TestInit { /// @notice Tests that the `gasPayingToken` function returns the correct token address and /// decimals. function test_gasPayingToken_succeeds() external { - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); (address token, uint8 decimals) = l1Block.gasPayingToken(); assertEq(token, Constants.ETHER); assertEq(uint256(decimals), uint256(18)); @@ -62,7 +62,7 @@ contract L1Block_GasPayingToken_Test is L1Block_TestInit { /// @notice Tests that the `gasPayingToken` function reverts when custom gas token is enabled. function test_gasPayingToken_customGasToken_reverts() external { - skipIfDevFeatureDisabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureDisabled(Features.CUSTOM_GAS_TOKEN); vm.expectRevert("L1BlockCGT: deprecated"); l1Block.gasPayingToken(); } @@ -73,14 +73,14 @@ contract L1Block_GasPayingToken_Test is L1Block_TestInit { contract L1Block_GasPayingTokenName_Test is L1Block_TestInit { /// @notice Tests that the `gasPayingTokenName` function returns the correct token name. function test_gasPayingTokenName_succeeds() external { - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); assertEq("Ether", l1Block.gasPayingTokenName()); } /// @notice Tests that the `gasPayingTokenName` function returns the correct token name when custom gas token is /// enabled. function test_gasPayingTokenName_customGasToken_succeeds() external { - skipIfDevFeatureDisabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureDisabled(Features.CUSTOM_GAS_TOKEN); assertEq(liquidityController.gasPayingTokenName(), l1Block.gasPayingTokenName()); } } @@ -90,14 +90,14 @@ contract L1Block_GasPayingTokenName_Test is L1Block_TestInit { contract L1Block_GasPayingTokenSymbol_Test is L1Block_TestInit { /// @notice Tests that the `gasPayingTokenSymbol` function returns the correct token symbol. function test_gasPayingTokenSymbol_succeeds() external { - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); assertEq("ETH", l1Block.gasPayingTokenSymbol()); } /// @notice Tests that the `gasPayingTokenSymbol` function returns the correct token symbol when custom gas token is /// enabled. function test_gasPayingTokenSymbol_customGasToken_succeeds() external { - skipIfDevFeatureDisabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureDisabled(Features.CUSTOM_GAS_TOKEN); assertEq(liquidityController.gasPayingTokenSymbol(), l1Block.gasPayingTokenSymbol()); } } @@ -108,14 +108,14 @@ contract L1Block_IsCustomGasToken_Test is L1Block_TestInit { /// @notice Tests that the `isCustomGasToken` function returns false when no custom gas token /// is used. function test_isCustomGasToken_succeeds() external { - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); assertFalse(l1Block.isCustomGasToken()); } /// @notice Tests that the `isCustomGasToken` function returns true when custom gas token /// is used. function test_isCustomGasToken_customGasToken_succeeds() external { - skipIfDevFeatureDisabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureDisabled(Features.CUSTOM_GAS_TOKEN); assertTrue(l1Block.isCustomGasToken()); } } @@ -467,7 +467,7 @@ contract L1Block_SetCustomGasToken_Test is L1Block_TestInit { function setUp() public override { super.setUp(); - skipIfDevFeatureDisabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureDisabled(Features.CUSTOM_GAS_TOKEN); l1BlockCGT = IL1BlockCGT(address(l1Block)); } diff --git a/packages/contracts-bedrock/test/L2/L1Withdrawer.t.sol b/packages/contracts-bedrock/test/L2/L1Withdrawer.t.sol index 277959e30be..fa00468a039 100644 --- a/packages/contracts-bedrock/test/L2/L1Withdrawer.t.sol +++ b/packages/contracts-bedrock/test/L2/L1Withdrawer.t.sol @@ -6,7 +6,6 @@ import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenge import { Predeploys } from "src/libraries/Predeploys.sol"; import { IL1Withdrawer } from "interfaces/L2/IL1Withdrawer.sol"; import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; -import { DevFeatures } from "src/libraries/DevFeatures.sol"; /// @title L1Withdrawer_TestInit /// @notice Base test contract with initialization for `L1Withdrawer` tests. @@ -19,17 +18,13 @@ contract L1Withdrawer_TestInit is CommonTest { event WithdrawalGasLimitUpdated(uint32 oldWithdrawalGasLimit, uint32 newWithdrawalGasLimit); // Test state - uint256 minWithdrawalAmount = 10 ether; - uint32 withdrawalGasLimit = 1_000_000; + uint256 minWithdrawalAmount = 2 ether; + uint32 withdrawalGasLimit = 800_000; uint32 internal constant MIN_WITHDRAWAL_GAS_LIMIT = 800_000; /// @notice Test setup. function setUp() public virtual override { - // Resolve features and skip whole test suite if custom gas token is enabled - resolveFeaturesFromEnv(); - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); - // Enable revenue sharing before calling parent setUp super.enableRevenueShare(); super.setUp(); diff --git a/packages/contracts-bedrock/test/L2/L2StandardBridge.t.sol b/packages/contracts-bedrock/test/L2/L2StandardBridge.t.sol index 0f25b19caaa..a0f1c0aec8c 100644 --- a/packages/contracts-bedrock/test/L2/L2StandardBridge.t.sol +++ b/packages/contracts-bedrock/test/L2/L2StandardBridge.t.sol @@ -14,12 +14,13 @@ import { OptimismMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { Hashing } from "src/libraries/Hashing.sol"; import { Types } from "src/libraries/Types.sol"; -import { DevFeatures } from "src/libraries/DevFeatures.sol"; +import { Features } from "src/libraries/Features.sol"; // Interfaces import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; import { IStandardBridge } from "interfaces/universal/IStandardBridge.sol"; import { IL2ToL1MessagePasser } from "interfaces/L2/IL2ToL1MessagePasser.sol"; +import { IL2ToL1MessagePasserCGT } from "interfaces/L2/IL2ToL1MessagePasserCGT.sol"; import { IL2StandardBridge } from "interfaces/L2/IL2StandardBridge.sol"; /// @title L2StandardBridge_TestInit @@ -232,7 +233,7 @@ contract L2StandardBridge_Initialize_Test is L2StandardBridge_TestInit { contract L2StandardBridge_Receive_Test is L2StandardBridge_TestInit { /// @notice Tests that the bridge receives ETH and successfully initiates a withdrawal. function test_receive_succeeds() external { - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); assertEq(address(l2ToL1MessagePasser).balance, 0); uint256 nonce = l2CrossDomainMessenger.messageNonce(); @@ -299,6 +300,20 @@ contract L2StandardBridge_Receive_Test is L2StandardBridge_TestInit { assertEq(success, true); assertEq(address(l2ToL1MessagePasser).balance, 100); } + + /// @notice Tests that receive reverts when custom gas token is enabled and value is sent. + function testFuzz_receive_withCustomGasToken_reverts(uint256 _value) external { + skipIfSysFeatureDisabled(Features.CUSTOM_GAS_TOKEN); + + _value = bound(_value, 1, type(uint128).max); + vm.deal(alice, _value); + + vm.prank(alice, alice); + vm.expectRevert(IL2ToL1MessagePasserCGT.L2ToL1MessagePasserCGT_NotAllowedOnCGTMode.selector); + + (bool revertsAsExpected,) = address(l2StandardBridge).call{ value: _value }(hex""); + assertTrue(revertsAsExpected, "expectRevert: call did not revert"); + } } /// @title L2StandardBridge_Withdraw_Test @@ -325,7 +340,7 @@ contract L2StandardBridge_Withdraw_Test is L2StandardBridge_TestInit { /// @notice Tests that the legacy `withdraw` interface on the L2StandardBridge sucessfully /// initiates a withdrawal. function test_withdraw_ether_succeeds() external { - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); assertTrue(alice.balance >= 100); assertEq(Predeploys.L2_TO_L1_MESSAGE_PASSER.balance, 0); @@ -376,6 +391,18 @@ contract L2StandardBridge_Withdraw_Test is L2StandardBridge_TestInit { vm.expectRevert("StandardBridge: function can only be called from an EOA"); l2StandardBridge.withdraw(address(L2Token), 100, 1000, hex""); } + + /// @notice Tests that withdraw reverts when custom gas token is enabled and value is sent. + function testFuzz_withdraw_withCustomGasToken_reverts(uint256 _value, uint32 _minGasLimit) external { + skipIfSysFeatureDisabled(Features.CUSTOM_GAS_TOKEN); + + _value = bound(_value, 1, type(uint128).max); + vm.deal(alice, _value); + + vm.prank(alice, alice); + vm.expectRevert(IL2ToL1MessagePasserCGT.L2ToL1MessagePasserCGT_NotAllowedOnCGTMode.selector); + l2StandardBridge.withdraw{ value: _value }(Predeploys.LEGACY_ERC20_ETH, _value, _minGasLimit, hex""); + } } /// @title L2StandardBridge_WithdrawTo_Test @@ -468,7 +495,7 @@ contract L2StandardBridge_Uncategorized_Test is L2StandardBridge_TestInit { /// @notice Tests that bridging ETH succeeds. function testFuzz_bridgeETH_succeeds(uint256 _value, uint32 _minGasLimit, bytes calldata _extraData) external { - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); uint256 nonce = l2CrossDomainMessenger.messageNonce(); bytes memory message = abi.encodeCall(IStandardBridge.finalizeBridgeETH, (alice, alice, _value, _extraData)); @@ -502,7 +529,7 @@ contract L2StandardBridge_Uncategorized_Test is L2StandardBridge_TestInit { /// @notice Tests that bridging ETH to a different address succeeds. function testFuzz_bridgeETHTo_succeeds(uint256 _value, uint32 _minGasLimit, bytes calldata _extraData) external { - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); uint256 nonce = l2CrossDomainMessenger.messageNonce(); vm.expectCall( @@ -595,4 +622,42 @@ contract L2StandardBridge_Uncategorized_Test is L2StandardBridge_TestInit { vm.expectRevert("StandardBridge: wrong remote token for Optimism Mintable ERC20 local token"); l2StandardBridge.finalizeBridgeERC20(localToken, remoteToken, alice, alice, 100, hex""); } + + /// @notice Tests that bridgeETH reverts when custom gas token is enabled and value is sent. + function testFuzz_bridgeETH_withCustomGasToken_reverts( + uint256 _value, + uint32 _minGasLimit, + bytes calldata _extraData + ) + external + { + skipIfSysFeatureDisabled(Features.CUSTOM_GAS_TOKEN); + + _value = bound(_value, 1, type(uint128).max); + vm.deal(alice, _value); + + vm.prank(alice, alice); + vm.expectRevert(IL2ToL1MessagePasserCGT.L2ToL1MessagePasserCGT_NotAllowedOnCGTMode.selector); + l2StandardBridge.bridgeETH{ value: _value }(_minGasLimit, _extraData); + } + + /// @notice Tests that bridgeETHTo reverts when custom gas token is enabled and value is sent. + function testFuzz_bridgeETHTo_withCustomGasToken_reverts( + address _to, + uint256 _value, + uint32 _minGasLimit, + bytes calldata _extraData + ) + external + { + skipIfSysFeatureDisabled(Features.CUSTOM_GAS_TOKEN); + + vm.assume(_to != address(0)); + _value = bound(_value, 1, type(uint128).max); + vm.deal(alice, _value); + + vm.prank(alice, alice); + vm.expectRevert(IL2ToL1MessagePasserCGT.L2ToL1MessagePasserCGT_NotAllowedOnCGTMode.selector); + l2StandardBridge.bridgeETHTo{ value: _value }(_to, _minGasLimit, _extraData); + } } diff --git a/packages/contracts-bedrock/test/L2/L2ToL1MessagePasser.t.sol b/packages/contracts-bedrock/test/L2/L2ToL1MessagePasser.t.sol index c79f3af797e..e3ddd2497db 100644 --- a/packages/contracts-bedrock/test/L2/L2ToL1MessagePasser.t.sol +++ b/packages/contracts-bedrock/test/L2/L2ToL1MessagePasser.t.sol @@ -7,7 +7,7 @@ import { CommonTest } from "test/setup/CommonTest.sol"; // Libraries import { Types } from "src/libraries/Types.sol"; import { Hashing } from "src/libraries/Hashing.sol"; -import { DevFeatures } from "src/libraries/DevFeatures.sol"; +import { Features } from "src/libraries/Features.sol"; import { SemverComp } from "src/libraries/SemverComp.sol"; // Interfaces @@ -27,7 +27,7 @@ contract L2ToL1MessagePasser_Version_Test is CommonTest { contract L2ToL1MessagePasser_Receive_Test is CommonTest { /// @notice Tests that receive() initiates withdrawal with default gas limit. function testFuzz_receive_initiatesWithdrawal_succeeds(uint256 _value) external { - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); uint256 nonce = l2ToL1MessagePasser.messageNonce(); @@ -59,7 +59,7 @@ contract L2ToL1MessagePasser_Receive_Test is CommonTest { contract L2ToL1MessagePasser_Burn_Test is CommonTest { /// @notice Tests that `burn` succeeds and destroys the ETH held in the contract. function testFuzz_burn_succeeds(uint256 _value, address _target, uint256 _gasLimit, bytes memory _data) external { - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); vm.deal(address(this), _value); l2ToL1MessagePasser.initiateWithdrawal{ value: _value }({ _target: _target, _gasLimit: _gasLimit, _data: _data }); @@ -89,7 +89,7 @@ contract L2ToL1MessagePasser_InitiateWithdrawal_Test is CommonTest { ) external { - if (isDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN)) { + if (isSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN)) { _value = 0; } uint256 nonce = l2ToL1MessagePasser.messageNonce(); @@ -129,7 +129,7 @@ contract L2ToL1MessagePasser_InitiateWithdrawal_Test is CommonTest { ) external { - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); bytes32 withdrawalHash = Hashing.hashWithdrawal( Types.WithdrawalTransaction({ nonce: l2ToL1MessagePasser.messageNonce(), @@ -161,7 +161,7 @@ contract L2ToL1MessagePasser_InitiateWithdrawal_Test is CommonTest { ) external { - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); uint256 nonce = l2ToL1MessagePasser.messageNonce(); // Verify caller is an EOA (alice has no code) @@ -192,7 +192,7 @@ contract L2ToL1MessagePasser_InitiateWithdrawal_Test is CommonTest { ) external { - skipIfDevFeatureDisabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureDisabled(Features.CUSTOM_GAS_TOKEN); // Set initial state _value = bound(_value, 1, type(uint256).max); vm.deal(_randomAddress, _value); diff --git a/packages/contracts-bedrock/test/L2/LiquidityController.t.sol b/packages/contracts-bedrock/test/L2/LiquidityController.t.sol index 45375ce66ca..82208200fcc 100644 --- a/packages/contracts-bedrock/test/L2/LiquidityController.t.sol +++ b/packages/contracts-bedrock/test/L2/LiquidityController.t.sol @@ -6,7 +6,8 @@ import { CommonTest } from "test/setup/CommonTest.sol"; import { stdStorage, StdStorage } from "forge-std/Test.sol"; // Libraries -import { DevFeatures } from "src/libraries/DevFeatures.sol"; +import { Features } from "src/libraries/Features.sol"; +import { Predeploys } from "src/libraries/Predeploys.sol"; // Contracts import { LiquidityController } from "src/L2/LiquidityController.sol"; @@ -38,7 +39,7 @@ contract LiquidityController_TestInit is CommonTest { /// @notice Test setup. function setUp() public virtual override { super.setUp(); - skipIfDevFeatureDisabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureDisabled(Features.CUSTOM_GAS_TOKEN); } /// @notice Helper function to authorize a minter. @@ -215,6 +216,8 @@ contract LiquidityController_Mint_Test is LiquidityController_TestInit { contract LiquidityController_Burn_Test is LiquidityController_TestInit { /// @notice Tests that the burn function can be called by an authorized minter. function testFuzz_burn_fromAuthorizedMinter_succeeds(uint256 _amount, address _minter) public { + vm.assume(_minter != Predeploys.NATIVE_ASSET_LIQUIDITY); + _authorizeMinter(_minter); _amount = bound(_amount, 0, address(nativeAssetLiquidity).balance); diff --git a/packages/contracts-bedrock/test/L2/NativeAssetLiquidity.t.sol b/packages/contracts-bedrock/test/L2/NativeAssetLiquidity.t.sol index 2b5869b5565..db3a9c6eaca 100644 --- a/packages/contracts-bedrock/test/L2/NativeAssetLiquidity.t.sol +++ b/packages/contracts-bedrock/test/L2/NativeAssetLiquidity.t.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.15; import { CommonTest } from "test/setup/CommonTest.sol"; // Libraries -import { DevFeatures } from "src/libraries/DevFeatures.sol"; +import { Features } from "src/libraries/Features.sol"; import { NativeAssetLiquidity } from "src/L2/NativeAssetLiquidity.sol"; /// @title NativeAssetLiquidity_TestInit @@ -23,7 +23,7 @@ contract NativeAssetLiquidity_TestInit is CommonTest { /// @notice Test setup. function setUp() public virtual override { super.setUp(); - skipIfDevFeatureDisabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureDisabled(Features.CUSTOM_GAS_TOKEN); } } diff --git a/packages/contracts-bedrock/test/L2/RevenueSharingIntegration.t.sol b/packages/contracts-bedrock/test/L2/RevenueSharingIntegration.t.sol index 5484029d752..c8ec809265a 100644 --- a/packages/contracts-bedrock/test/L2/RevenueSharingIntegration.t.sol +++ b/packages/contracts-bedrock/test/L2/RevenueSharingIntegration.t.sol @@ -4,11 +4,8 @@ pragma solidity 0.8.15; import { CommonTest } from "test/setup/CommonTest.sol"; import { ISharesCalculator } from "interfaces/L2/ISharesCalculator.sol"; import { ISuperchainRevSharesCalculator } from "interfaces/L2/ISuperchainRevSharesCalculator.sol"; -import { IFeeVault } from "interfaces/L2/IFeeVault.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; -import { Types } from "src/libraries/Types.sol"; import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; -import { DevFeatures } from "src/libraries/DevFeatures.sol"; /// @title RevenueSharingIntegration_Test /// @notice Integration tests for the complete revenue sharing system including @@ -26,10 +23,6 @@ contract RevenueSharingIntegration_Test is CommonTest { event FundsReceived(address indexed sender, uint256 amount, uint256 newBalance); function setUp() public override { - // Resolve features and skip whole test suite if custom gas token is enabled - resolveFeaturesFromEnv(); - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); - // Enable revenue sharing before calling parent setUp super.enableRevenueShare(); super.setUp(); @@ -37,31 +30,6 @@ contract RevenueSharingIntegration_Test is CommonTest { disbursementInterval = feeSplitter.feeDisbursementInterval(); } - /// @notice Configure all vaults to withdraw to FeeSplitter on L2 - function _configureVaultsForFeeSplitter() private { - // Get the ProxyAdmin owner to configure vaults - address proxyAdminOwner = proxyAdmin.owner(); - - // Configure all vaults to withdraw to FeeSplitter on L2 - vm.startPrank(proxyAdminOwner); - IFeeVault(payable(address(sequencerFeeVault))).setRecipient(address(feeSplitter)); - IFeeVault(payable(address(sequencerFeeVault))).setWithdrawalNetwork(Types.WithdrawalNetwork.L2); - IFeeVault(payable(address(sequencerFeeVault))).setMinWithdrawalAmount(0); - - IFeeVault(payable(address(baseFeeVault))).setRecipient(address(feeSplitter)); - IFeeVault(payable(address(baseFeeVault))).setWithdrawalNetwork(Types.WithdrawalNetwork.L2); - IFeeVault(payable(address(baseFeeVault))).setMinWithdrawalAmount(0); - - IFeeVault(payable(address(l1FeeVault))).setRecipient(address(feeSplitter)); - IFeeVault(payable(address(l1FeeVault))).setWithdrawalNetwork(Types.WithdrawalNetwork.L2); - IFeeVault(payable(address(l1FeeVault))).setMinWithdrawalAmount(0); - - IFeeVault(payable(address(operatorFeeVault))).setRecipient(address(feeSplitter)); - IFeeVault(payable(address(operatorFeeVault))).setWithdrawalNetwork(Types.WithdrawalNetwork.L2); - IFeeVault(payable(address(operatorFeeVault))).setMinWithdrawalAmount(0); - vm.stopPrank(); - } - /// @notice Helper to fund vaults function _fundVaults(uint256 _sequencerFees, uint256 _baseFees, uint256 _l1Fees, uint256 _operatorFees) private { vm.deal(address(sequencerFeeVault), _sequencerFees); @@ -139,8 +107,9 @@ contract RevenueSharingIntegration_Test is CommonTest { // | 0/0/0/0 | 2.5 | 205.55 | Accumulating | // |__________________|______________|______________|________________________________| function test_revenueSharing_fullFlow_succeeds() public { - // Configure vaults to withdraw to FeeSplitter - _configureVaultsForFeeSplitter(); + // Use 10 ETH as the minimum withdrawal amount for this test's hardcoded math + vm.prank(proxyAdminOwner); + l1Withdrawer.setMinWithdrawalAmount(10 ether); // Get recipient addresses address shareRecipient = superchainRevSharesCalculator.shareRecipient(); @@ -277,9 +246,6 @@ contract RevenueSharingIntegration_Test is CommonTest { return; } - // Configure vaults for disbursement - _configureVaultsForFeeSplitter(); - { // Get share info from calculator first ISharesCalculator.ShareInfo[] memory shareInfo = diff --git a/packages/contracts-bedrock/test/L2/SuperchainRevSharesCalculator.t.sol b/packages/contracts-bedrock/test/L2/SuperchainRevSharesCalculator.t.sol index c801ae37308..9f08bdc33ac 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainRevSharesCalculator.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainRevSharesCalculator.t.sol @@ -8,9 +8,6 @@ import { CommonTest } from "test/setup/CommonTest.sol"; import { ISharesCalculator } from "interfaces/L2/ISharesCalculator.sol"; import { ISuperchainRevSharesCalculator } from "interfaces/L2/ISuperchainRevSharesCalculator.sol"; -// Libraries -import { DevFeatures } from "src/libraries/DevFeatures.sol"; - /// @notice Base setup contract for SuperchainRevSharesCalculator tests. contract SuperchainRevSharesCalculator_TestInit is CommonTest { uint256 internal constant BASIS_POINT_SCALE = 10_000; @@ -24,10 +21,6 @@ contract SuperchainRevSharesCalculator_TestInit is CommonTest { event RemainderRecipientUpdated(address indexed oldRemainderRecipient, address indexed newRemainderRecipient); function setUp() public virtual override { - // Resolve features and skip whole test suite if custom gas token is enabled - resolveFeaturesFromEnv(); - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); - // Enable revenue sharing before calling parent setUp super.enableRevenueShare(); super.setUp(); diff --git a/packages/contracts-bedrock/test/invariants/CustomGasToken.t.sol b/packages/contracts-bedrock/test/invariants/CustomGasToken.t.sol index fe96f009e30..764a3a0236c 100644 --- a/packages/contracts-bedrock/test/invariants/CustomGasToken.t.sol +++ b/packages/contracts-bedrock/test/invariants/CustomGasToken.t.sol @@ -8,7 +8,8 @@ import { CommonTest } from "test/setup/CommonTest.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; -import { DevFeatures } from "src/libraries/DevFeatures.sol"; +import { Features } from "src/libraries/Features.sol"; +import { SafeSend } from "src/universal/SafeSend.sol"; // Contracts import { ILiquidityController } from "interfaces/L2/ILiquidityController.sol"; @@ -103,7 +104,7 @@ contract NativeAssetLiquidity_Fundooor is StdUtils { _amount = bound(_amount, 0, address(this).balance); // action: fund _amount - vm.deal(address(nativeAssetLiquidity), _amount); + new SafeSend{ value: _amount }(payable(address(nativeAssetLiquidity))); // postcondition: nil here (in the invariant tests) // update ghost variables @@ -190,8 +191,8 @@ contract CustomGasToken_Invariants_Test is CommonTest { /// @notice Test setup. function setUp() public override { - skipIfDevFeatureDisabled(DevFeatures.CUSTOM_GAS_TOKEN); super.setUp(); + skipIfSysFeatureDisabled(Features.CUSTOM_GAS_TOKEN); randomActor = new RandomActor(); actor_funder = new NativeAssetLiquidity_Fundooor(vm); diff --git a/packages/contracts-bedrock/test/invariants/FeeSplit.t.sol b/packages/contracts-bedrock/test/invariants/FeeSplit.t.sol index 0ddacddd82b..f8d36eb6b19 100644 --- a/packages/contracts-bedrock/test/invariants/FeeSplit.t.sol +++ b/packages/contracts-bedrock/test/invariants/FeeSplit.t.sol @@ -10,7 +10,6 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { IFeeSplitter } from "interfaces/L2/IFeeSplitter.sol"; import { IL1Withdrawer } from "interfaces/L2/IL1Withdrawer.sol"; import { ISuperchainRevSharesCalculator } from "interfaces/L2/ISuperchainRevSharesCalculator.sol"; -import { DevFeatures } from "src/libraries/DevFeatures.sol"; /// @notice A struct to keep track of the state when a disburse call fails struct DisburseFailureState { @@ -215,10 +214,6 @@ contract FeeSplitter_Invariant is CommonTest { /// @notice Setup: enable the revenue share, deploy handlers and target them. function setUp() public override { - // Resolve features and skip whole test suite if custom gas token is enabled - resolveFeaturesFromEnv(); - skipIfDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN); - super.enableRevenueShare(); super.setUp(); diff --git a/packages/contracts-bedrock/test/invariants/SystemConfig.t.sol b/packages/contracts-bedrock/test/invariants/SystemConfig.t.sol index 281c01e2511..8dcd0da95a5 100644 --- a/packages/contracts-bedrock/test/invariants/SystemConfig.t.sol +++ b/packages/contracts-bedrock/test/invariants/SystemConfig.t.sol @@ -45,7 +45,8 @@ contract SystemConfig_GasLimitBoundaries_Invariant is Test { l1StandardBridge: address(0), optimismPortal: address(0), optimismMintableERC20Factory: address(0), - delayedWETH: address(0) + delayedWETH: address(0), + opcm: address(0) }), 1234, // _l2ChainId ISuperchainConfig(address(0)) // _superchainConfig diff --git a/packages/contracts-bedrock/test/libraries/Predeploys.t.sol b/packages/contracts-bedrock/test/libraries/Predeploys.t.sol index 590c10395cb..2a025cd5c09 100644 --- a/packages/contracts-bedrock/test/libraries/Predeploys.t.sol +++ b/packages/contracts-bedrock/test/libraries/Predeploys.t.sol @@ -9,7 +9,7 @@ import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { ForgeArtifacts } from "scripts/libraries/ForgeArtifacts.sol"; import { Fork } from "scripts/libraries/Config.sol"; -import { DevFeatures } from "src/libraries/DevFeatures.sol"; +import { Features } from "src/libraries/Features.sol"; /// @title Predeploys_TestInit /// @notice Reusable test initialization for `Predeploys` tests. @@ -165,7 +165,7 @@ contract Predeploys_Uncategorized_Test is Predeploys_TestInit { /// @notice Tests that the predeploy addresses are set correctly. They have code /// and the proxied accounts have the correct admin. Using custom gas token. function test_predeploys_customGasToken_succeeds() external { - skipIfDevFeatureDisabled(DevFeatures.CUSTOM_GAS_TOKEN); + skipIfSysFeatureDisabled(Features.CUSTOM_GAS_TOKEN); _test_predeploys(Fork.ISTHMUS, false, true); } } diff --git a/packages/contracts-bedrock/test/opcm/SetDisputeGameImpl.t.sol b/packages/contracts-bedrock/test/opcm/SetDisputeGameImpl.t.sol index 1de4796463e..ad266dccd79 100644 --- a/packages/contracts-bedrock/test/opcm/SetDisputeGameImpl.t.sol +++ b/packages/contracts-bedrock/test/opcm/SetDisputeGameImpl.t.sol @@ -193,7 +193,8 @@ contract SetDisputeGameImpl_Test is Test { l1StandardBridge: address(5), optimismPortal: address(6), optimismMintableERC20Factory: address(7), - delayedWETH: address(8) + delayedWETH: address(8), + opcm: address(0) }), 10, ISuperchainConfig(address(supConfigProxy)) diff --git a/packages/contracts-bedrock/test/scripts/L2Genesis.t.sol b/packages/contracts-bedrock/test/scripts/L2Genesis.t.sol index 52646aeb940..f4943f82ea8 100644 --- a/packages/contracts-bedrock/test/scripts/L2Genesis.t.sol +++ b/packages/contracts-bedrock/test/scripts/L2Genesis.t.sol @@ -194,9 +194,9 @@ abstract contract L2Genesis_TestInit is Test { // Check the L1Withdrawer is properly set IL1Withdrawer l1Withdrawer = IL1Withdrawer(superchainRevSharesCalculator.shareRecipient()); - assertEq(l1Withdrawer.minWithdrawalAmount(), 10 ether); + assertEq(l1Withdrawer.minWithdrawalAmount(), 2 ether); assertEq(l1Withdrawer.recipient(), input.l1FeesDepositor); - assertEq(l1Withdrawer.withdrawalGasLimit(), 1_000_000); + assertEq(l1Withdrawer.withdrawalGasLimit(), 800_000); } function testCGT() internal view { diff --git a/packages/contracts-bedrock/test/setup/CommonTest.sol b/packages/contracts-bedrock/test/setup/CommonTest.sol index 0be3c9cb7ae..529534b7ceb 100644 --- a/packages/contracts-bedrock/test/setup/CommonTest.sol +++ b/packages/contracts-bedrock/test/setup/CommonTest.sol @@ -14,9 +14,9 @@ import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; // Contracts import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { DevFeatures } from "src/libraries/DevFeatures.sol"; // Libraries +import { Config } from "scripts/libraries/Config.sol"; import { console } from "forge-std/console.sol"; // Interfaces @@ -79,6 +79,12 @@ abstract contract CommonTest is Test, Setup, Events { deploy.cfg().setUseInterop(true); } if (useRevenueShareOverride) { + // Revenue share is not supported when custom gas token is enabled + if (Config.sysFeatureCustomGasToken()) { + vm.skip(true); + } + + console.log("CommonTest: enabling revenue share"); deploy.cfg().setUseRevenueShare(true); deploy.cfg().setChainFeesRecipient(chainFeesRecipient); deploy.cfg().setL1FeesDepositor(l1FeesDepositor); @@ -86,7 +92,7 @@ abstract contract CommonTest is Test, Setup, Events { if (useUpgradedFork) { deploy.cfg().setUseUpgradedFork(true); } - if (isDevFeatureEnabled(DevFeatures.CUSTOM_GAS_TOKEN)) { + if (Config.sysFeatureCustomGasToken()) { console.log("CommonTest: enabling custom gas token"); deploy.cfg().setUseCustomGasToken(true); deploy.cfg().setGasPayingTokenName("Custom Gas Token"); diff --git a/packages/contracts-bedrock/test/setup/FeatureFlags.sol b/packages/contracts-bedrock/test/setup/FeatureFlags.sol index 2f71edac4f7..10f2d20ffa6 100644 --- a/packages/contracts-bedrock/test/setup/FeatureFlags.sol +++ b/packages/contracts-bedrock/test/setup/FeatureFlags.sol @@ -44,10 +44,6 @@ abstract contract FeatureFlags { console.log("Setup: DEV_FEATURE__DEPLOY_V2_DISPUTE_GAMES is enabled"); devFeatureBitmap |= DevFeatures.DEPLOY_V2_DISPUTE_GAMES; } - if (Config.devFeatureCustomGasToken()) { - console.log("Setup: DEV_FEATURE__CUSTOM_GAS_TOKEN is enabled"); - devFeatureBitmap |= DevFeatures.CUSTOM_GAS_TOKEN; - } if (Config.devFeatureOpcmV2()) { // WARNING: OPCMv2 also automatically implies DEPLOY_V2_DISPUTE_GAMES and CANNON_KONA. console.log("Setup: DEV_FEATURE__OPCM_V2 is enabled"); diff --git a/packages/contracts-bedrock/test/setup/ForkLive.s.sol b/packages/contracts-bedrock/test/setup/ForkLive.s.sol index c013ee70d46..3c69c17d39b 100644 --- a/packages/contracts-bedrock/test/setup/ForkLive.s.sol +++ b/packages/contracts-bedrock/test/setup/ForkLive.s.sol @@ -35,6 +35,7 @@ import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { IOPContractsManagerUpgrader } from "interfaces/L1/IOPContractsManager.sol"; import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol"; +import { IOPContractsManagerUtils } from "interfaces/L1/opcm/IOPContractsManagerUtils.sol"; /// @title ForkLive /// @notice This script is called by Setup.sol as a preparation step for the foundry test suite, and is run as an @@ -252,7 +253,7 @@ contract ForkLive is Deployer, StdAssertions, DisputeGames { ( IOPContractsManagerV2.SuperchainUpgradeInput({ superchainConfig: superchainConfig, - extraInstructions: new IOPContractsManagerV2.ExtraInstruction[](0) + extraInstructions: new IOPContractsManagerUtils.ExtraInstruction[](0) }) ) ) @@ -260,7 +261,7 @@ contract ForkLive is Deployer, StdAssertions, DisputeGames { if (success == false) { // Only acceptable revert reason is downgrade not allowed. assertTrue( - bytes4(reason) == IOPContractsManagerV2.OPContractsManagerV2_DowngradeNotAllowed.selector, + bytes4(reason) == IOPContractsManagerUtils.OPContractsManagerUtils_DowngradeNotAllowed.selector, "Revert reason other than DowngradeNotAllowed" ); } @@ -308,10 +309,10 @@ contract ForkLive is Deployer, StdAssertions, DisputeGames { }); // Add extra instructions to allow the DelayedWETH proxy to be deployed. - IOPContractsManagerV2.ExtraInstruction[] memory extraInstructions = - new IOPContractsManagerV2.ExtraInstruction[](1); + IOPContractsManagerUtils.ExtraInstruction[] memory extraInstructions = + new IOPContractsManagerUtils.ExtraInstruction[](1); extraInstructions[0] = - IOPContractsManagerV2.ExtraInstruction({ key: "PermittedProxyDeployment", data: bytes("DelayedWETH") }); + IOPContractsManagerUtils.ExtraInstruction({ key: "PermittedProxyDeployment", data: bytes("DelayedWETH") }); vm.prank(_delegateCaller, true); (bool upgradeSuccess,) = address(_opcm).delegatecall( diff --git a/packages/contracts-bedrock/test/vendor/AddressAliasHelper.t.sol b/packages/contracts-bedrock/test/vendor/AddressAliasHelper.t.sol index 5df2f4e0a25..b803abba9fa 100644 --- a/packages/contracts-bedrock/test/vendor/AddressAliasHelper.t.sol +++ b/packages/contracts-bedrock/test/vendor/AddressAliasHelper.t.sol @@ -4,6 +4,40 @@ pragma solidity 0.8.15; import { Test } from "forge-std/Test.sol"; import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; +/// @title AddressAliasHelper_ApplyL1ToL2Alias_Test +/// @notice Tests for the `applyL1ToL2Alias` function. +contract AddressAliasHelper_ApplyL1ToL2Alias_Test is Test { + uint160 constant OFFSET = uint160(0x1111000000000000000000000000000000001111); + + /// @notice Tests that applyL1ToL2Alias correctly adds the offset to L1 address. + /// @param _l1Address The L1 address to apply the alias to. + function testFuzz_applyL1ToL2Alias_addsOffset_succeeds(address _l1Address) external pure { + address l2Address = AddressAliasHelper.applyL1ToL2Alias(_l1Address); + uint160 expected; + unchecked { + expected = uint160(_l1Address) + OFFSET; + } + assertEq(uint160(l2Address), expected); + } +} + +/// @title AddressAliasHelper_UndoL1ToL2Alias_Test +/// @notice Tests for the `undoL1ToL2Alias` function. +contract AddressAliasHelper_UndoL1ToL2Alias_Test is Test { + uint160 constant OFFSET = uint160(0x1111000000000000000000000000000000001111); + + /// @notice Tests that undoL1ToL2Alias correctly subtracts offset from L2 address. + /// @param _l2Address The L2 address to undo the alias from. + function testFuzz_undoL1ToL2Alias_subtractsOffset_succeeds(address _l2Address) external pure { + address l1Address = AddressAliasHelper.undoL1ToL2Alias(_l2Address); + uint160 expected; + unchecked { + expected = uint160(_l2Address) - OFFSET; + } + assertEq(uint160(l1Address), expected); + } +} + /// @title AddressAliasHelper_Uncategorized_Test /// @notice General tests that are not testing any function directly of the `AddressAliasHelper` /// contract or are testing multiple functions at once. diff --git a/packages/contracts-bedrock/test/vendor/Initializable.t.sol b/packages/contracts-bedrock/test/vendor/Initializable.t.sol index 80398b34bab..6bde0b7ccbb 100644 --- a/packages/contracts-bedrock/test/vendor/Initializable.t.sol +++ b/packages/contracts-bedrock/test/vendor/Initializable.t.sol @@ -192,7 +192,8 @@ contract Initializer_Test is CommonTest { l1StandardBridge: address(0), optimismPortal: address(0), optimismMintableERC20Factory: address(0), - delayedWETH: address(0) + delayedWETH: address(0), + opcm: address(0) }), 0, ISuperchainConfig(address(0)) @@ -229,7 +230,8 @@ contract Initializer_Test is CommonTest { l1StandardBridge: address(0), optimismPortal: address(0), optimismMintableERC20Factory: address(0), - delayedWETH: address(0) + delayedWETH: address(0), + opcm: address(0) }), 0, ISuperchainConfig(address(0))